Skip to content

Commit

Permalink
Merge pull request #425 from JD557/cake-subsystems
Browse files Browse the repository at this point in the history
Make AllSubsystems a cake
  • Loading branch information
JD557 authored Oct 7, 2023
2 parents a7ece0e + 756b8a7 commit e2b8b86
Show file tree
Hide file tree
Showing 8 changed files with 85 additions and 90 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,6 @@ import eu.joaocosta.minart.input._

/** Internal object with an intersection of all subsystems.
*/
private[minart] class AllSubsystems(canvas: LowLevelCanvas, audioPlayer: LowLevelAudioPlayer)
extends LowLevelSubsystem.Composite[Canvas.Settings, AudioPlayer.Settings, LowLevelCanvas, LowLevelAudioPlayer](
canvas,
audioPlayer
)
with Canvas
with AudioPlayer {

private val _canvas: Canvas = canvas // Export only Canvas methods
export _canvas.*

private val _audioPlayer: AudioPlayer = audioPlayer // Export only AudioPlayer methods
export _audioPlayer.*
}
private[minart] class AllSubsystems(_canvas: LowLevelCanvas, _audioPlayer: LowLevelAudioPlayer)
extends CanvasSubsystem(_canvas)
with AudioPlayerSubsystem(_audioPlayer)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package eu.joaocosta.minart.backend.subsystem

import eu.joaocosta.minart.audio.AudioPlayer

/** Subsystem cake with an AudioPlayer
*/
trait AudioPlayerSubsystem(val audioPlayer: AudioPlayer)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package eu.joaocosta.minart.backend.subsystem

import eu.joaocosta.minart.graphics.Canvas

/** Subsystem cake with a Canvas
*/
trait CanvasSubsystem(val canvas: Canvas)
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package eu.joaocosta.minart.backend.subsystem

import eu.joaocosta.minart.audio._
import eu.joaocosta.minart.backend.defaults.DefaultBackend
import eu.joaocosta.minart.graphics._
import eu.joaocosta.minart.input._

/** Aggregation of all subsystems.
*/
case class LowLevelAllSubsystems(
lowLevelCanvas: LowLevelCanvas,
lowLevelAudioPlayer: LowLevelAudioPlayer
) extends AllSubsystems(lowLevelCanvas, lowLevelAudioPlayer)
with LowLevelSubsystem[(Canvas.Settings, AudioPlayer.Settings)] {
def settings: (Canvas.Settings, AudioPlayer.Settings) = (lowLevelCanvas.settings, lowLevelAudioPlayer.settings)
def isCreated(): Boolean = lowLevelCanvas.isCreated() && lowLevelAudioPlayer.isCreated()
def init(settings: (Canvas.Settings, AudioPlayer.Settings)): this.type = {
lowLevelCanvas.init(settings._1)
lowLevelAudioPlayer.init(settings._2)
this
}
def changeSettings(newSettings: (Canvas.Settings, AudioPlayer.Settings)): Unit = {
lowLevelCanvas.changeSettings(newSettings._1)
lowLevelAudioPlayer.changeSettings(newSettings._2)
}
def close(): Unit = {
lowLevelCanvas.close()
lowLevelAudioPlayer.close()
}
}

object LowLevelAllSubsystems {
implicit def defaultLowLevelAllSubsystems(implicit
c: DefaultBackend[Any, LowLevelCanvas],
a: DefaultBackend[Any, LowLevelAudioPlayer]
): DefaultBackend[Any, LowLevelAllSubsystems] =
DefaultBackend.fromFunction((_) => LowLevelAllSubsystems(c.defaultValue(), a.defaultValue()))
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package eu.joaocosta.minart.backend.subsystem

import eu.joaocosta.minart.backend.defaults.DefaultBackend

/** A low-level subsystem with init and close operations.
*/
trait LowLevelSubsystem[Settings] extends AutoCloseable {
Expand Down Expand Up @@ -35,13 +33,6 @@ trait LowLevelSubsystem[Settings] extends AutoCloseable {
* init() has an undefined behavior.
*/
def close(): Unit

/** Composes this subsystem with another subsystem
*/
def ++[SettingsB, SubsystemB <: LowLevelSubsystem[SettingsB]](
that: SubsystemB
): LowLevelSubsystem.Composite[Settings, SettingsB, this.type, that.type] =
LowLevelSubsystem.Composite(this, that)
}

object LowLevelSubsystem {
Expand Down Expand Up @@ -153,40 +144,4 @@ object LowLevelSubsystem {
_extendedSettings = defaultSettings
}
}

/** Subsystem that composes two subsystems.
*
* It is configured via a tuple containing the settings of both subsystems.
*/
case class Composite[SettingsA, SettingsB, +SubsystemA <: LowLevelSubsystem[
SettingsA
], +SubsystemB <: LowLevelSubsystem[SettingsB]](
subsystemA: SubsystemA,
subsystemB: SubsystemB
) extends LowLevelSubsystem[(SettingsA, SettingsB)] {
def settings: (SettingsA, SettingsB) = (subsystemA.settings, subsystemB.settings)
def isCreated(): Boolean = subsystemA.isCreated() && subsystemB.isCreated()
def init(settings: (SettingsA, SettingsB)): this.type = {
subsystemA.init(settings._1)
subsystemB.init(settings._2)
this
}
def changeSettings(newSettings: (SettingsA, SettingsB)): Unit = {
subsystemA.changeSettings(newSettings._1)
subsystemB.changeSettings(newSettings._2)
}
def close(): Unit = {
subsystemA.close()
subsystemB.close()
}
}
object Composite {
implicit def defaultComposite[SettingsA, SettingsB, SubsystemA <: LowLevelSubsystem[
SettingsA
], SubsystemB <: LowLevelSubsystem[SettingsB]](implicit
sa: DefaultBackend[Any, SubsystemA],
sb: DefaultBackend[Any, SubsystemB]
): DefaultBackend[Any, Composite[SettingsA, SettingsB, SubsystemA, SubsystemB]] =
DefaultBackend.fromFunction((_) => sa.defaultValue() ++ sb.defaultValue())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -164,23 +164,18 @@ object AppLoop {
renderFrame
)

type LowLevelAllSubsystems =
LowLevelSubsystem.Composite[Canvas.Settings, AudioPlayer.Settings, LowLevelCanvas, LowLevelAudioPlayer]

/** Creates an app loop with a canvas and an audio player that keeps and updates a state on every iteration,
* terminating when a certain condition is reached.
*
* @param renderFrame operation to render the frame and update the state
* @param terminateWhen loop termination check
*/
def statefulAppLoop[State](
renderFrame: State => (Canvas with AudioPlayer) => State,
renderFrame: State => (CanvasSubsystem with AudioPlayerSubsystem) => State,
terminateWhen: State => Boolean = (_: State) => false
): AppLoop.Definition[State, (Canvas.Settings, AudioPlayer.Settings), LowLevelAllSubsystems] =
statefulLoop[State, (Canvas.Settings, AudioPlayer.Settings), LowLevelAllSubsystems](
(state: State) => { case LowLevelSubsystem.Composite(canvas, audioPlayer) =>
renderFrame(state)(new AllSubsystems(canvas, audioPlayer))
},
renderFrame,
terminateWhen
)

Expand All @@ -189,10 +184,10 @@ object AppLoop {
* @param renderFrame operation to render the frame
*/
def statelessAppLoop(
renderFrame: (Canvas with AudioPlayer) => Unit
renderFrame: (CanvasSubsystem with AudioPlayerSubsystem) => Unit
): AppLoop.Definition[Unit, (Canvas.Settings, AudioPlayer.Settings), LowLevelAllSubsystems] =
statelessLoop[(Canvas.Settings, AudioPlayer.Settings), LowLevelAllSubsystems](
{ case LowLevelSubsystem.Composite(canvas, audioPlayer) => renderFrame(new AllSubsystems(canvas, audioPlayer)) }
renderFrame
)

}
24 changes: 13 additions & 11 deletions examples/snapshot/11-audio.sc
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
* Note: This is an experimental API, it might break in a future version
*/
import eu.joaocosta.minart.audio._
import eu.joaocosta.minart.backend.defaults._
import eu.joaocosta.minart.backend.subsystem._
import eu.joaocosta.minart.graphics._
import eu.joaocosta.minart.runtime._
import eu.joaocosta.minart.input._
import eu.joaocosta.minart.backend.defaults._
import eu.joaocosta.minart.runtime._

/** First, let's define a simple song
*/
Expand Down Expand Up @@ -44,18 +45,19 @@ object Audio {

// Here we use `statelessAppLoop` so that we get an object with a `canvas` and an `audioPlayer`
AppLoop
.statelessAppLoop((system: Canvas with AudioPlayer) => {
.statelessAppLoop((system: CanvasSubsystem with AudioPlayerSubsystem) => {
import system._
// When someone presses "Space", we send our sound wave to the queue
if (system.getKeyboardInput().keysPressed.contains(KeyboardInput.Key.Space))
system.play(Audio.testSample)
if (canvas.getKeyboardInput().keysPressed.contains(KeyboardInput.Key.Space))
audioPlayer.play(Audio.testSample)
// When someone presses "Backspace", we stop the audio player
if (system.getKeyboardInput().keysPressed.contains(KeyboardInput.Key.Backspace))
system.stop()
system.clear()
if (canvas.getKeyboardInput().keysPressed.contains(KeyboardInput.Key.Backspace))
audioPlayer.stop()
canvas.clear()
// Paint green when nothing is playing and red otherwise
if (!system.isPlaying()) system.fill(Color(0, 128, 0))
else system.fill(Color(128, 0, 0))
system.redraw()
if (!audioPlayer.isPlaying()) canvas.fill(Color(0, 128, 0))
else canvas.fill(Color(128, 0, 0))
canvas.redraw()
})
.configure((Canvas.Settings(width = 128, height = 128), AudioPlayer.Settings()), LoopFrequency.hz60)
.run()
24 changes: 13 additions & 11 deletions examples/snapshot/12-audio-clip.sc
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,28 @@
*/
import eu.joaocosta.minart.audio._
import eu.joaocosta.minart.audio.sound._
import eu.joaocosta.minart.backend.defaults._
import eu.joaocosta.minart.backend.subsystem._
import eu.joaocosta.minart.graphics._
import eu.joaocosta.minart.runtime._
import eu.joaocosta.minart.input._
import eu.joaocosta.minart.backend.defaults._
import eu.joaocosta.minart.runtime._

/** Just like loading an image, we can load sound resources
*/
val clip = Sound.loadAiffClip(Resource("assets/sample.aiff")).get

// Same logic as the audio example
AppLoop
.statelessAppLoop((system: Canvas with AudioPlayer) => {
if (system.getKeyboardInput().keysPressed.contains(KeyboardInput.Key.Space))
system.play(clip)
if (system.getKeyboardInput().keysPressed.contains(KeyboardInput.Key.Backspace))
system.stop()
system.clear()
if (!system.isPlaying()) system.fill(Color(0, 128, 0))
else system.fill(Color(128, 0, 0))
system.redraw()
.statelessAppLoop((system: CanvasSubsystem with AudioPlayerSubsystem) => {
import system._
if (canvas.getKeyboardInput().keysPressed.contains(KeyboardInput.Key.Space))
audioPlayer.play(clip)
if (canvas.getKeyboardInput().keysPressed.contains(KeyboardInput.Key.Backspace))
audioPlayer.stop()
canvas.clear()
if (!audioPlayer.isPlaying()) canvas.fill(Color(0, 128, 0))
else canvas.fill(Color(128, 0, 0))
canvas.redraw()
})
.configure((Canvas.Settings(width = 128, height = 128), AudioPlayer.Settings()), LoopFrequency.hz60)
.run()

0 comments on commit e2b8b86

Please sign in to comment.