Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make AllSubsystems a cake #425

Merged
merged 1 commit into from
Oct 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()
Loading