From 756b8a7834cf937e119de129d5d13275efa30c04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Costa?= Date: Sat, 7 Oct 2023 18:17:39 +0200 Subject: [PATCH] Make AllSubsystems a cake --- .../backend/subsystem/AllSubsystems.scala | 17 ++----- .../subsystem/AudioPlayerSubsystem.scala | 7 +++ .../backend/subsystem/CanvasSubsystem.scala | 7 +++ .../subsystem/LowLevelAllSubsystems.scala | 38 ++++++++++++++++ .../backend/subsystem/LowLevelSubsystem.scala | 45 ------------------- .../eu/joaocosta/minart/runtime/AppLoop.scala | 13 ++---- examples/snapshot/11-audio.sc | 24 +++++----- examples/snapshot/12-audio-clip.sc | 24 +++++----- 8 files changed, 85 insertions(+), 90 deletions(-) create mode 100644 core/shared/src/main/scala/eu/joaocosta/minart/backend/subsystem/AudioPlayerSubsystem.scala create mode 100644 core/shared/src/main/scala/eu/joaocosta/minart/backend/subsystem/CanvasSubsystem.scala create mode 100644 core/shared/src/main/scala/eu/joaocosta/minart/backend/subsystem/LowLevelAllSubsystems.scala diff --git a/core/shared/src/main/scala/eu/joaocosta/minart/backend/subsystem/AllSubsystems.scala b/core/shared/src/main/scala/eu/joaocosta/minart/backend/subsystem/AllSubsystems.scala index 9c0cd863..7cad5558 100644 --- a/core/shared/src/main/scala/eu/joaocosta/minart/backend/subsystem/AllSubsystems.scala +++ b/core/shared/src/main/scala/eu/joaocosta/minart/backend/subsystem/AllSubsystems.scala @@ -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) diff --git a/core/shared/src/main/scala/eu/joaocosta/minart/backend/subsystem/AudioPlayerSubsystem.scala b/core/shared/src/main/scala/eu/joaocosta/minart/backend/subsystem/AudioPlayerSubsystem.scala new file mode 100644 index 00000000..9752c29c --- /dev/null +++ b/core/shared/src/main/scala/eu/joaocosta/minart/backend/subsystem/AudioPlayerSubsystem.scala @@ -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) diff --git a/core/shared/src/main/scala/eu/joaocosta/minart/backend/subsystem/CanvasSubsystem.scala b/core/shared/src/main/scala/eu/joaocosta/minart/backend/subsystem/CanvasSubsystem.scala new file mode 100644 index 00000000..27de9be8 --- /dev/null +++ b/core/shared/src/main/scala/eu/joaocosta/minart/backend/subsystem/CanvasSubsystem.scala @@ -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) diff --git a/core/shared/src/main/scala/eu/joaocosta/minart/backend/subsystem/LowLevelAllSubsystems.scala b/core/shared/src/main/scala/eu/joaocosta/minart/backend/subsystem/LowLevelAllSubsystems.scala new file mode 100644 index 00000000..d61eb357 --- /dev/null +++ b/core/shared/src/main/scala/eu/joaocosta/minart/backend/subsystem/LowLevelAllSubsystems.scala @@ -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())) +} diff --git a/core/shared/src/main/scala/eu/joaocosta/minart/backend/subsystem/LowLevelSubsystem.scala b/core/shared/src/main/scala/eu/joaocosta/minart/backend/subsystem/LowLevelSubsystem.scala index 9ea66d35..c02ad7bd 100644 --- a/core/shared/src/main/scala/eu/joaocosta/minart/backend/subsystem/LowLevelSubsystem.scala +++ b/core/shared/src/main/scala/eu/joaocosta/minart/backend/subsystem/LowLevelSubsystem.scala @@ -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 { @@ -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 { @@ -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()) - } } diff --git a/core/shared/src/main/scala/eu/joaocosta/minart/runtime/AppLoop.scala b/core/shared/src/main/scala/eu/joaocosta/minart/runtime/AppLoop.scala index e4ded88e..c4ec5671 100644 --- a/core/shared/src/main/scala/eu/joaocosta/minart/runtime/AppLoop.scala +++ b/core/shared/src/main/scala/eu/joaocosta/minart/runtime/AppLoop.scala @@ -164,9 +164,6 @@ 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. * @@ -174,13 +171,11 @@ object AppLoop { * @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 ) @@ -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 ) } diff --git a/examples/snapshot/11-audio.sc b/examples/snapshot/11-audio.sc index fdc24696..31ec1a12 100644 --- a/examples/snapshot/11-audio.sc +++ b/examples/snapshot/11-audio.sc @@ -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 */ @@ -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() diff --git a/examples/snapshot/12-audio-clip.sc b/examples/snapshot/12-audio-clip.sc index 64fe8f85..a1426131 100644 --- a/examples/snapshot/12-audio-clip.sc +++ b/examples/snapshot/12-audio-clip.sc @@ -8,10 +8,11 @@ */ 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 */ @@ -19,15 +20,16 @@ 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()