Skip to content

Commit

Permalink
Merge pull request #306 from JD557/compose-managers
Browse files Browse the repository at this point in the history
Refactor runtime to support AudioPlayer
  • Loading branch information
JD557 authored Feb 5, 2023
2 parents f5c0e9e + baa56b0 commit e7362d9
Show file tree
Hide file tree
Showing 44 changed files with 899 additions and 723 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class HtmlCanvas(parentNode: => dom.Node = dom.document.body) extends SurfaceBac
// Rendering resources

private[this] var containerDiv: dom.HTMLDivElement = _
private[this] var canvas: JsCanvas = _
private[this] var jsCanvas: JsCanvas = _
private[this] var ctx: dom.CanvasRenderingContext2D = _
private[this] var childNode: dom.Node = _
protected var surface: ImageDataSurface = _
Expand All @@ -27,7 +27,7 @@ class HtmlCanvas(parentNode: => dom.Node = dom.document.body) extends SurfaceBac
private[this] var rawPointerPos: (Int, Int) = _
private[this] def cleanPointerPos: Option[PointerInput.Position] = Option(rawPointerPos).flatMap { case (x, y) =>
val (offsetX, offsetY) = {
val canvasRect = canvas.getBoundingClientRect()
val canvasRect = jsCanvas.getBoundingClientRect()
(canvasRect.left.toInt, canvasRect.top.toInt)
}
val xx = (x - offsetX) / settings.scale
Expand All @@ -44,13 +44,12 @@ class HtmlCanvas(parentNode: => dom.Node = dom.document.body) extends SurfaceBac
this.init(settings)
}

def unsafeInit(newSettings: Canvas.Settings): Unit = {
protected def unsafeInit(): Unit = {
containerDiv = dom.document.createElement("div").asInstanceOf[dom.HTMLDivElement]
canvas = dom.document.createElement("canvas").asInstanceOf[JsCanvas]
containerDiv.appendChild(canvas)
jsCanvas = dom.document.createElement("canvas").asInstanceOf[JsCanvas]
containerDiv.appendChild(jsCanvas)
childNode = parentNode.appendChild(containerDiv)
ctx = canvas.getContext("2d").asInstanceOf[dom.CanvasRenderingContext2D]
changeSettings(newSettings)
ctx = jsCanvas.getContext("2d").asInstanceOf[dom.CanvasRenderingContext2D]
dom.document.addEventListener[Event](
"fullscreenchange",
(_: Event) => if (dom.document.fullscreenElement == null) changeSettings(settings.copy(fullScreen = false))
Expand Down Expand Up @@ -86,22 +85,22 @@ class HtmlCanvas(parentNode: => dom.Node = dom.document.body) extends SurfaceBac
}
)
dom.document.addEventListener[PointerEvent]("pointercancel", (_: PointerEvent) => handleRelease())
canvas.addEventListener[PointerEvent](
jsCanvas.addEventListener[PointerEvent](
"pointermove",
(ev: PointerEvent) => {
handleMove(ev.clientX.toInt, ev.clientY.toInt)
}
)
}

def changeSettings(newSettings: Canvas.Settings) = if (extendedSettings == null || newSettings != settings) {
protected def unsafeApplySettings(newSettings: Canvas.Settings): LowLevelCanvas.ExtendedSettings = {
val oldSettings = settings
val clearColorStr = s"rgb(${newSettings.clearColor.r},${newSettings.clearColor.g},${newSettings.clearColor.b})"
extendedSettings =
val extendedSettings =
LowLevelCanvas.ExtendedSettings(newSettings, dom.window.screen.width.toInt, dom.window.screen.height.toInt)
canvas.width = newSettings.width
canvas.height = newSettings.height
canvas.style =
jsCanvas.width = newSettings.width
jsCanvas.height = newSettings.height
jsCanvas.style =
s"width:${extendedSettings.scaledWidth}px;height:${extendedSettings.scaledHeight}px;image-rendering:pixelated;"
ctx.imageSmoothingEnabled = false

Expand Down Expand Up @@ -129,11 +128,12 @@ class HtmlCanvas(parentNode: => dom.Node = dom.document.body) extends SurfaceBac
ctx.fillStyle = clearColorStr
ctx.fillRect(0, 0, newSettings.width, newSettings.height)
clear(Set(Canvas.Buffer.Backbuffer))
extendedSettings
}

// Cleanup

def unsafeDestroy(): Unit = if (childNode != null) {
protected def unsafeDestroy(): Unit = if (childNode != null) {
parentNode.removeChild(childNode)
childNode = null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@ class JsAudioPlayer() extends LowLevelAudioPlayer {
private var playQueue: AudioPlayer.MultiChannelAudioQueue = _
private var callbackRegistered = false

protected def unsafeInit(settings: AudioPlayer.Settings): Unit = {
protected def unsafeInit() = {}

protected def unsafeApplySettings(settings: AudioPlayer.Settings): AudioPlayer.Settings = {
// TODO this should probably stop the running audio
playQueue = new AudioPlayer.MultiChannelAudioQueue(settings.sampleRate)
settings
}

protected def unsafeDestroy(): Unit = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,33 @@ import eu.joaocosta.minart.runtime._
object JsLoopRunner extends LoopRunner {
lazy val hasWindow = Try(!isUndefined(dom.window)).getOrElse(false)

final class NeverRenderLoop[S](operation: S => S) extends Loop[S] {
def finiteLoop[S](
initialState: S,
operation: S => S,
terminateWhen: S => Boolean,
cleanup: () => Unit,
frequency: LoopFrequency
): Future[S] = {
frequency match {
case LoopFrequency.Never =>
new NeverLoop(operation).run(initialState)
case LoopFrequency.Uncapped =>
new UncappedLoop(operation, terminateWhen, cleanup).run(initialState)
case LoopFrequency.LoopDuration(iterationMillis) =>
new CappedLoop(operation, terminateWhen, iterationMillis, cleanup).run(initialState)
}
}

final class NeverLoop[S](operation: S => S) {
def run(initialState: S): Future[S] =
Future.fromTry(Try(operation(initialState)))
}

final class UncappedRenderLoop[S](
final class UncappedLoop[S](
operation: S => S,
terminateWhen: S => Boolean,
cleanup: () => Unit
) extends Loop[S] {
) {
def finiteLoopAsyncAux(state: S, promise: Promise[S]): Unit = try {
val newState = operation(state)
if (!terminateWhen(newState)) {
Expand All @@ -43,12 +60,12 @@ object JsLoopRunner extends LoopRunner {
}
}

final class CappedRenderLoop[S](
final class CappedLoop[S](
operation: S => S,
terminateWhen: S => Boolean,
iterationMillis: Long,
cleanup: () => Unit
) extends Loop[S] {
) {
def finiteLoopAux(state: S, promise: Promise[S]): Unit = try {
val startTime = System.currentTimeMillis()
val newState = operation(state)
Expand All @@ -71,20 +88,4 @@ object JsLoopRunner extends LoopRunner {
promise.future
}
}

def finiteLoop[S](
operation: S => S,
terminateWhen: S => Boolean,
frequency: LoopFrequency,
cleanup: () => Unit
): Loop[S] = {
frequency match {
case LoopFrequency.Never =>
new NeverRenderLoop(operation)
case LoopFrequency.Uncapped =>
new UncappedRenderLoop(operation, terminateWhen, cleanup)
case LoopFrequency.LoopDuration(iterationMillis) =>
new CappedRenderLoop(operation, terminateWhen, iterationMillis, cleanup)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,40 +60,37 @@ class AwtCanvas() extends SurfaceBackedCanvas {
this.init(settings)
}

def unsafeInit(newSettings: Canvas.Settings): Unit = {
changeSettings(newSettings)
}

def changeSettings(newSettings: Canvas.Settings) = {
if (extendedSettings == null || newSettings != settings) {
extendedSettings = LowLevelCanvas.ExtendedSettings(newSettings)
val image = new BufferedImage(newSettings.width, newSettings.height, BufferedImage.TYPE_INT_ARGB)
surface = new BufferedImageSurface(image)
if (javaCanvas != null) javaCanvas.frame.dispose()
javaCanvas = new AwtCanvas.InnerCanvas(
extendedSettings.scaledWidth,
extendedSettings.scaledHeight,
newSettings.fullScreen,
newSettings.title,
this
)
extendedSettings = extendedSettings.copy(
windowWidth = javaCanvas.getWidth,
windowHeight = javaCanvas.getHeight
)
keyListener = new AwtCanvas.KeyListener()
mouseListener = new AwtCanvas.MouseListener(javaCanvas, extendedSettings)
javaCanvas.addKeyListener(keyListener)
javaCanvas.frame.addKeyListener(keyListener)
javaCanvas.addMouseListener(mouseListener)
javaCanvas.frame.addMouseListener(mouseListener)
clear(Set(Canvas.Buffer.Backbuffer))
}
protected def unsafeInit(): Unit = {}

protected def unsafeApplySettings(newSettings: Canvas.Settings): LowLevelCanvas.ExtendedSettings = {
val extendedSettings = LowLevelCanvas.ExtendedSettings(newSettings)
val image = new BufferedImage(newSettings.width, newSettings.height, BufferedImage.TYPE_INT_ARGB)
surface = new BufferedImageSurface(image)
if (javaCanvas != null) javaCanvas.frame.dispose()
javaCanvas = new AwtCanvas.InnerCanvas(
extendedSettings.scaledWidth,
extendedSettings.scaledHeight,
newSettings.fullScreen,
newSettings.title,
this
)
val fullExtendedSettings = extendedSettings.copy(
windowWidth = javaCanvas.getWidth,
windowHeight = javaCanvas.getHeight
)
keyListener = new AwtCanvas.KeyListener()
mouseListener = new AwtCanvas.MouseListener(javaCanvas, fullExtendedSettings)
javaCanvas.addKeyListener(keyListener)
javaCanvas.frame.addKeyListener(keyListener)
javaCanvas.addMouseListener(mouseListener)
javaCanvas.frame.addMouseListener(mouseListener)
clear(Set(Canvas.Buffer.Backbuffer))
fullExtendedSettings
}

// Cleanup

def unsafeDestroy(): Unit = if (javaCanvas != null) {
protected def unsafeDestroy(): Unit = if (javaCanvas != null) {
javaCanvas.frame.dispose()
javaCanvas = null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,16 @@ class JavaAudioPlayer() extends LowLevelAudioPlayer {

private implicit val ec: ExecutionContext = ExecutionContext.global

protected def unsafeInit(settings: AudioPlayer.Settings): Unit = {
protected def unsafeInit(): Unit = {}

protected def unsafeApplySettings(settings: AudioPlayer.Settings): AudioPlayer.Settings = {
// TODO this should probably stop the running audio
val format = new AudioFormat(settings.sampleRate.toFloat, 8, 1, true, false)
playQueue = new AudioPlayer.MultiChannelAudioQueue(settings.sampleRate)
sourceDataLine = AudioSystem.getSourceDataLine(format)
sourceDataLine.open(format, settings.bufferSize)
sourceDataLine.start()
settings
}

protected def unsafeDestroy(): Unit = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,32 @@ import eu.joaocosta.minart.runtime._
*/
object JavaLoopRunner extends LoopRunner {
def finiteLoop[S](
initialState: S,
operation: S => S,
terminateWhen: S => Boolean,
frequency: LoopFrequency,
cleanup: () => Unit
): Loop[S] = {
cleanup: () => Unit,
frequency: LoopFrequency
): Future[S] = {
frequency match {
case LoopFrequency.Never =>
new NeverRenderLoop(operation)
new NeverLoop(operation).run(initialState)
case LoopFrequency.Uncapped =>
new UncappedRenderLoop(operation, terminateWhen, cleanup)
new UncappedLoop(operation, terminateWhen, cleanup).run(initialState)
case LoopFrequency.LoopDuration(iterationMillis) =>
new CappedRenderLoop(operation, terminateWhen, iterationMillis, cleanup)
new CappedLoop(operation, terminateWhen, iterationMillis, cleanup).run(initialState)
}
}

final class NeverRenderLoop[S](operation: S => S) extends Loop[S] {
final class NeverLoop[S](operation: S => S) {
def run(initialState: S) =
Future(operation(initialState))(ExecutionContext.global)
}

final class UncappedRenderLoop[S](
final class UncappedLoop[S](
operation: S => S,
terminateWhen: S => Boolean,
cleanup: () => Unit
) extends Loop[S] {
) {
@tailrec
def finiteLoopAux(state: S): S = {
val newState = operation(state)
Expand All @@ -47,12 +48,12 @@ object JavaLoopRunner extends LoopRunner {
}(ExecutionContext.global)
}

final class CappedRenderLoop[S](
final class CappedLoop[S](
operation: S => S,
terminateWhen: S => Boolean,
iterationMillis: Long,
cleanup: () => Unit
) extends Loop[S] {
) {
@tailrec
def finiteLoopAux(state: S): S = {
val startTime = System.currentTimeMillis()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@ class SdlAudioPlayer() extends LowLevelAudioPlayer {
private var playQueue: AudioPlayer.MultiChannelAudioQueue = _
private var callbackRegistered = false

protected def unsafeInit(settings: AudioPlayer.Settings): Unit = {
playQueue = new AudioPlayer.MultiChannelAudioQueue(settings.sampleRate)
protected def unsafeInit(): Unit = {
SDL_InitSubSystem(SDL_INIT_AUDIO)
}

protected def unsafeApplySettings(settings: AudioPlayer.Settings): AudioPlayer.Settings = {
// TODO this should probably stop the running audio
playQueue = new AudioPlayer.MultiChannelAudioQueue(settings.sampleRate)
val want = stackalloc[SDL_AudioSpec]()
val have = stackalloc[SDL_AudioSpec]()
want.freq = settings.sampleRate
Expand All @@ -31,6 +35,7 @@ class SdlAudioPlayer() extends LowLevelAudioPlayer {
want.samples = settings.bufferSize.toUShort
want.callback = null // Ideally this should use a SDL callback
device = SDL_OpenAudioDevice(null, 0, want, have, 0)
settings
}

protected def unsafeDestroy(): Unit = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,12 @@ class SdlCanvas() extends SurfaceBackedCanvas {
this.init(settings)
}

def unsafeInit(newSettings: Canvas.Settings) = {
protected def unsafeInit(): Unit = {
SDL_InitSubSystem(SDL_INIT_VIDEO)
changeSettings(newSettings)
}

def changeSettings(newSettings: Canvas.Settings) = if (extendedSettings == null || newSettings != settings) {
extendedSettings = LowLevelCanvas.ExtendedSettings(newSettings)
protected def unsafeApplySettings(newSettings: Canvas.Settings): LowLevelCanvas.ExtendedSettings = {
val extendedSettings = LowLevelCanvas.ExtendedSettings(newSettings)
SDL_DestroyWindow(window)
ubyteClearR = newSettings.clearColor.r.toUByte
ubyteClearG = newSettings.clearColor.g.toUByte
Expand All @@ -94,23 +93,24 @@ class SdlCanvas() extends SurfaceBackedCanvas {
SDL_CreateRGBSurface(0.toUInt, newSettings.width, newSettings.height, 32, 0.toUInt, 0.toUInt, 0.toUInt, 0.toUInt)
)
keyboardInput = KeyboardInput(Set(), Set(), Set())
extendedSettings = extendedSettings.copy(
val fullExtendedSettings = extendedSettings.copy(
windowWidth = windowSurface.w,
windowHeight = windowSurface.h
)
(0 until extendedSettings.windowHeight * extendedSettings.windowWidth).foreach { i =>
(0 until fullExtendedSettings.windowHeight * fullExtendedSettings.windowWidth).foreach { i =>
val baseAddr = i * 4
windowSurface.pixels(baseAddr + 0) = ubyteClearB.toByte
windowSurface.pixels(baseAddr + 1) = ubyteClearG.toByte
windowSurface.pixels(baseAddr + 2) = ubyteClearR.toByte
windowSurface.pixels(baseAddr + 3) = 255.toByte
}
clear(Set(Canvas.Buffer.Backbuffer))
fullExtendedSettings
}

// Cleanup

def unsafeDestroy() = {
protected def unsafeDestroy() = {
SDL_Quit()
}

Expand Down
Loading

0 comments on commit e7362d9

Please sign in to comment.