diff --git a/indigo/benchmarks/src/main/scala/indigo/benchmarks/BatchBenchmarks.scala b/indigo/benchmarks/src/main/scala/indigo/benchmarks/BatchBenchmarks.scala index dcc5f7c18..2a7133656 100644 --- a/indigo/benchmarks/src/main/scala/indigo/benchmarks/BatchBenchmarks.scala +++ b/indigo/benchmarks/src/main/scala/indigo/benchmarks/BatchBenchmarks.scala @@ -1,7 +1,6 @@ package indigo.benchmarks import indigo.* - import japgolly.scalajs.benchmark._ import japgolly.scalajs.benchmark.gui._ diff --git a/indigo/benchmarks/src/main/scala/indigo/benchmarks/BoundaryLocatorBenchmarks.scala b/indigo/benchmarks/src/main/scala/indigo/benchmarks/BoundaryLocatorBenchmarks.scala index f7585f422..6f35c0171 100644 --- a/indigo/benchmarks/src/main/scala/indigo/benchmarks/BoundaryLocatorBenchmarks.scala +++ b/indigo/benchmarks/src/main/scala/indigo/benchmarks/BoundaryLocatorBenchmarks.scala @@ -1,12 +1,11 @@ package indigo.benchmarks import indigo.* - +import indigo.platform.assets.DynamicText +import indigo.shared.AnimationsRegister +import indigo.shared.FontRegister import japgolly.scalajs.benchmark._ import japgolly.scalajs.benchmark.gui._ -import indigo.shared.FontRegister -import indigo.shared.AnimationsRegister -import indigo.platform.assets.DynamicText object BoundaryLocatorBenchmarks: diff --git a/indigo/benchmarks/src/main/scala/indigo/benchmarks/Caching.scala b/indigo/benchmarks/src/main/scala/indigo/benchmarks/Caching.scala index 76901d1bc..95c3f83f3 100644 --- a/indigo/benchmarks/src/main/scala/indigo/benchmarks/Caching.scala +++ b/indigo/benchmarks/src/main/scala/indigo/benchmarks/Caching.scala @@ -1,11 +1,11 @@ package indigo.benchmarks import indigo.* - +import indigo.facades.WeakMap +import indigo.shared.QuickCache import japgolly.scalajs.benchmark._ import japgolly.scalajs.benchmark.gui._ -import indigo.shared.QuickCache -import indigo.facades.WeakMap + import scala.scalajs.js object Caching: diff --git a/indigo/benchmarks/src/main/scala/indigo/benchmarks/Collisions.scala b/indigo/benchmarks/src/main/scala/indigo/benchmarks/Collisions.scala index e7a8dc459..bc7b0bd19 100644 --- a/indigo/benchmarks/src/main/scala/indigo/benchmarks/Collisions.scala +++ b/indigo/benchmarks/src/main/scala/indigo/benchmarks/Collisions.scala @@ -1,7 +1,6 @@ package indigo.benchmarks import indigo.* - import japgolly.scalajs.benchmark._ import japgolly.scalajs.benchmark.gui._ diff --git a/indigo/benchmarks/src/main/scala/indigo/benchmarks/LineBenchmarks.scala b/indigo/benchmarks/src/main/scala/indigo/benchmarks/LineBenchmarks.scala index 11fe6ae03..3064ec432 100644 --- a/indigo/benchmarks/src/main/scala/indigo/benchmarks/LineBenchmarks.scala +++ b/indigo/benchmarks/src/main/scala/indigo/benchmarks/LineBenchmarks.scala @@ -1,7 +1,6 @@ package indigo.benchmarks import indigo.* - import japgolly.scalajs.benchmark._ import japgolly.scalajs.benchmark.gui._ diff --git a/indigo/benchmarks/src/main/scala/indigo/benchmarks/Main.scala b/indigo/benchmarks/src/main/scala/indigo/benchmarks/Main.scala index b2c7d6e77..17476804e 100644 --- a/indigo/benchmarks/src/main/scala/indigo/benchmarks/Main.scala +++ b/indigo/benchmarks/src/main/scala/indigo/benchmarks/Main.scala @@ -1,7 +1,7 @@ package indigo.benchmarks -import org.scalajs.dom.document import japgolly.scalajs.benchmark.gui.BenchmarkGUI +import org.scalajs.dom.document import scala.scalajs.js.annotation.JSExportTopLevel diff --git a/indigo/benchmarks/src/main/scala/indigo/benchmarks/PhysicsWorldBenchmarks.scala b/indigo/benchmarks/src/main/scala/indigo/benchmarks/PhysicsWorldBenchmarks.scala index 22b901648..6f94a692c 100644 --- a/indigo/benchmarks/src/main/scala/indigo/benchmarks/PhysicsWorldBenchmarks.scala +++ b/indigo/benchmarks/src/main/scala/indigo/benchmarks/PhysicsWorldBenchmarks.scala @@ -1,9 +1,8 @@ package indigo.benchmarks import indigo.* -import indigo.syntax.* import indigo.physics.* - +import indigo.syntax.* import japgolly.scalajs.benchmark._ import japgolly.scalajs.benchmark.gui._ @@ -51,7 +50,7 @@ object PhysicsWorldBenchmarks: object TestWorlds: - val dice: Dice = Dice.fromSeed(0) + val dice: Dice = Dice.default val basicWorld: World[MyTag] = val circles = diff --git a/indigo/benchmarks/src/main/scala/indigo/benchmarks/QuadTreeBenchmarks.scala b/indigo/benchmarks/src/main/scala/indigo/benchmarks/QuadTreeBenchmarks.scala index db7926228..906ddf75e 100644 --- a/indigo/benchmarks/src/main/scala/indigo/benchmarks/QuadTreeBenchmarks.scala +++ b/indigo/benchmarks/src/main/scala/indigo/benchmarks/QuadTreeBenchmarks.scala @@ -1,7 +1,6 @@ package indigo.benchmarks import indigo.* - import japgolly.scalajs.benchmark._ import japgolly.scalajs.benchmark.gui._ diff --git a/indigo/benchmarks/src/main/scala/indigo/benchmarks/SignalFunctionBenchmarks.scala b/indigo/benchmarks/src/main/scala/indigo/benchmarks/SignalFunctionBenchmarks.scala index 30be73b19..33bcc7506 100644 --- a/indigo/benchmarks/src/main/scala/indigo/benchmarks/SignalFunctionBenchmarks.scala +++ b/indigo/benchmarks/src/main/scala/indigo/benchmarks/SignalFunctionBenchmarks.scala @@ -2,7 +2,6 @@ package indigo.benchmarks import indigo.* import indigo.syntax.* - import japgolly.scalajs.benchmark._ import japgolly.scalajs.benchmark.gui._ diff --git a/indigo/benchmarks/src/main/scala/indigo/benchmarks/SimpleComparisons.scala b/indigo/benchmarks/src/main/scala/indigo/benchmarks/SimpleComparisons.scala index 0e30ec59d..bdf282af8 100644 --- a/indigo/benchmarks/src/main/scala/indigo/benchmarks/SimpleComparisons.scala +++ b/indigo/benchmarks/src/main/scala/indigo/benchmarks/SimpleComparisons.scala @@ -1,12 +1,11 @@ package indigo.benchmarks import indigo.* - +import indigo.platform.assets.DynamicText +import indigo.shared.AnimationsRegister +import indigo.shared.FontRegister import japgolly.scalajs.benchmark._ import japgolly.scalajs.benchmark.gui._ -import indigo.shared.FontRegister -import indigo.shared.AnimationsRegister -import indigo.platform.assets.DynamicText object SimpleComparisons: diff --git a/indigo/indigo-extras/src/main/scala/indigoextras/jobs/JobMarket.scala b/indigo/indigo-extras/src/main/scala/indigoextras/jobs/JobMarket.scala index 40be8b4a1..7f2b6d8e6 100644 --- a/indigo/indigo-extras/src/main/scala/indigoextras/jobs/JobMarket.scala +++ b/indigo/indigo-extras/src/main/scala/indigoextras/jobs/JobMarket.scala @@ -5,7 +5,7 @@ import indigo.shared.datatypes.BindingKey import indigo.shared.events.GlobalEvent import indigo.shared.scenegraph.SceneUpdateFragment import indigo.shared.subsystems.SubSystem -import indigo.shared.subsystems.SubSystemFrameContext +import indigo.shared.subsystems.SubSystemContext import indigo.shared.subsystems.SubSystemId /** The JobMarket is a subsystem that manages a global pool of available jobs. @@ -36,7 +36,7 @@ final case class JobMarket[Model](id: SubSystemId, availableJobs: List[Job]) ext private given CanEqual[Option[Job], Option[Job]] = CanEqual.derived def update( - frameContext: SubSystemFrameContext[ReferenceData], + context: SubSystemContext[ReferenceData], jobs: List[Job] ): JobMarketEvent => Outcome[List[Job]] = { case JobMarketEvent.Post(job) => @@ -57,7 +57,7 @@ final case class JobMarket[Model](id: SubSystemId, availableJobs: List[Job]) ext Outcome(jobs) } - def present(frameContext: SubSystemFrameContext[ReferenceData], jobs: List[Job]): Outcome[SceneUpdateFragment] = + def present(context: SubSystemContext[ReferenceData], jobs: List[Job]): Outcome[SceneUpdateFragment] = Outcome(SceneUpdateFragment.empty) } diff --git a/indigo/indigo-extras/src/main/scala/indigoextras/subsystems/AssetBundleLoader.scala b/indigo/indigo-extras/src/main/scala/indigoextras/subsystems/AssetBundleLoader.scala index 376d2c041..37eecae59 100644 --- a/indigo/indigo-extras/src/main/scala/indigoextras/subsystems/AssetBundleLoader.scala +++ b/indigo/indigo-extras/src/main/scala/indigoextras/subsystems/AssetBundleLoader.scala @@ -11,7 +11,7 @@ import indigo.shared.events.GlobalEvent import indigo.shared.events.SubSystemEvent import indigo.shared.scenegraph.SceneUpdateFragment import indigo.shared.subsystems.SubSystem -import indigo.shared.subsystems.SubSystemFrameContext +import indigo.shared.subsystems.SubSystemContext import indigo.shared.subsystems.SubSystemId // Provides "at least once" message delivery for updates on a bundle's loading status. @@ -37,7 +37,7 @@ final class AssetBundleLoader[Model] extends SubSystem[Model]: private given CanEqual[Option[Set[AssetType]], Option[Set[AssetType]]] = CanEqual.derived def update( - frameContext: SubSystemFrameContext[ReferenceData], + context: SubSystemContext[ReferenceData], tracker: AssetBundleTracker ): GlobalEvent => Outcome[AssetBundleTracker] = // Asset Bundle Loader Commands @@ -76,7 +76,7 @@ final class AssetBundleLoader[Model] extends SubSystem[Model]: Outcome(tracker) def present( - frameContext: SubSystemFrameContext[ReferenceData], + context: SubSystemContext[ReferenceData], model: AssetBundleTracker ): Outcome[SceneUpdateFragment] = Outcome(SceneUpdateFragment.empty) diff --git a/indigo/indigo-extras/src/main/scala/indigoextras/subsystems/Automata.scala b/indigo/indigo-extras/src/main/scala/indigoextras/subsystems/Automata.scala index 6bb725c26..267f2d1e0 100644 --- a/indigo/indigo-extras/src/main/scala/indigoextras/subsystems/Automata.scala +++ b/indigo/indigo-extras/src/main/scala/indigoextras/subsystems/Automata.scala @@ -12,7 +12,7 @@ import indigo.shared.events.SubSystemEvent import indigo.shared.scenegraph.SceneNode import indigo.shared.scenegraph._ import indigo.shared.subsystems.SubSystem -import indigo.shared.subsystems.SubSystemFrameContext +import indigo.shared.subsystems.SubSystemContext import indigo.shared.subsystems.SubSystemId import indigo.shared.temporal.Signal import indigo.shared.temporal.SignalReader @@ -57,20 +57,20 @@ final case class Automata[Model]( private given CanEqual[Option[Int], Option[Int]] = CanEqual.derived def update( - frameContext: SubSystemFrameContext[ReferenceData], + context: SubSystemContext[ReferenceData], state: AutomataState ): AutomataEvent => Outcome[AutomataState] = case Spawn(key, position, lifeSpan, payload) if key == poolKey => val spawned = SpawnedAutomaton( - automaton.node.giveNode(state.totalSpawned, frameContext.dice), + automaton.node.giveNode(state.totalSpawned, context.frame.dice), automaton.modifier, automaton.onCull, new AutomatonSeedValues( position, - frameContext.gameTime.running, + context.frame.time.running, lifeSpan.getOrElse(automaton.lifespan), - frameContext.dice.roll, + context.frame.dice.roll, payload ) ) @@ -108,12 +108,12 @@ final case class Automata[Model]( case Update(key) if key == poolKey => val cullEvents = state.pool - .filterNot(_.isAlive(frameContext.gameTime.running)) + .filterNot(_.isAlive(context.frame.time.running)) .flatMap(sa => sa.onCull(sa.seedValues)) Outcome( state.copy( - pool = state.pool.filter(_.isAlive(frameContext.gameTime.running)) + pool = state.pool.filter(_.isAlive(context.frame.time.running)) ), Batch(cullEvents) ) @@ -121,8 +121,8 @@ final case class Automata[Model]( case _ => Outcome(state) - def present(frameContext: SubSystemFrameContext[ReferenceData], state: AutomataState): Outcome[SceneUpdateFragment] = - val updated = Automata.renderNoLayer(state.pool, frameContext.gameTime) + def present(context: SubSystemContext[ReferenceData], state: AutomataState): Outcome[SceneUpdateFragment] = + val updated = Automata.renderNoLayer(state.pool, context.frame.time) Outcome( SceneUpdateFragment( diff --git a/indigo/indigo-extras/src/main/scala/indigoextras/subsystems/FPSCounter.scala b/indigo/indigo-extras/src/main/scala/indigoextras/subsystems/FPSCounter.scala index 700799b18..223925e6c 100644 --- a/indigo/indigo-extras/src/main/scala/indigoextras/subsystems/FPSCounter.scala +++ b/indigo/indigo-extras/src/main/scala/indigoextras/subsystems/FPSCounter.scala @@ -17,7 +17,7 @@ import indigo.shared.scenegraph.SceneUpdateFragment import indigo.shared.scenegraph.Shape import indigo.shared.scenegraph.TextBox import indigo.shared.subsystems.SubSystem -import indigo.shared.subsystems.SubSystemFrameContext +import indigo.shared.subsystems.SubSystemContext import indigo.shared.subsystems.SubSystemId import indigo.shared.time.FPS import indigo.shared.time.Seconds @@ -56,15 +56,15 @@ final case class FPSCounter[Model]( Outcome(FPSCounterState.initial(startPosition)) def update( - context: SubSystemFrameContext[ReferenceData], + context: SubSystemContext[ReferenceData], model: FPSCounterState ): GlobalEvent => Outcome[FPSCounterState] = { case FrameTick => - if (context.gameTime.running >= (model.lastInterval + Seconds(1))) + if (context.frame.time.running >= (model.lastInterval + Seconds(1))) Outcome( model.copy( fps = decideNextFps(model.frameCountSinceInterval), - lastInterval = context.gameTime.running, + lastInterval = context.frame.time.running, frameCountSinceInterval = 0 ) ) @@ -82,7 +82,7 @@ final case class FPSCounter[Model]( .withFontFamily(fontFamily) .withFontSize(fontSize) - def present(context: SubSystemFrameContext[ReferenceData], model: FPSCounterState): Outcome[SceneUpdateFragment] = + def present(context: SubSystemContext[ReferenceData], model: FPSCounterState): Outcome[SceneUpdateFragment] = val text: TextBox = textBox .withText(s"""FPS ${model.fps.toString}""") @@ -90,8 +90,7 @@ final case class FPSCounter[Model]( .moveTo(model.position + 2) val size: Rectangle = - context.boundaryLocator - .measureText(text) + context.services.bounds.measureText(text) val boxSize = ({ (s: Size) => diff --git a/indigo/indigo-extras/src/main/scala/indigoextras/ui/InputField.scala b/indigo/indigo-extras/src/main/scala/indigoextras/ui/InputField.scala index 79b771598..e2a8bfe46 100644 --- a/indigo/indigo-extras/src/main/scala/indigoextras/ui/InputField.scala +++ b/indigo/indigo-extras/src/main/scala/indigoextras/ui/InputField.scala @@ -1,7 +1,7 @@ package indigoextras.ui import indigo.shared.BoundaryLocator -import indigo.shared.FrameContext +import indigo.shared.Context import indigo.shared.Outcome import indigo.shared.collections.Batch import indigo.shared.constants.Key @@ -35,8 +35,8 @@ final case class InputField( onLoseFocus: () => Batch[GlobalEvent] ) derives CanEqual: - def bounds(boundaryLocator: BoundaryLocator): Option[Rectangle] = - boundaryLocator.findBounds(assets.text.withText(text).moveTo(position)) + def bounds(_bounds: Context.Services.Bounds): Option[Rectangle] = + _bounds.find(assets.text.withText(text).moveTo(position)) def withText(newText: String): InputField = this.copy( @@ -166,7 +166,7 @@ final case class InputField( def withLoseFocusActions(actions: => Batch[GlobalEvent]): InputField = this.copy(onLoseFocus = () => actions) - def update(frameContext: FrameContext[?]): Outcome[InputField] = { + def update(context: Context[?]): Outcome[InputField] = { @tailrec def rec( keysReleased: List[Key], @@ -177,7 +177,7 @@ final case class InputField( keysReleased match { case Nil => if (touched) - Outcome(acc.copy(lastCursorMove = frameContext.gameTime.running), Batch.fromOption(changeEvent)) + Outcome(acc.copy(lastCursorMove = context.frame.time.running), Batch.fromOption(changeEvent)) else Outcome(acc, Batch.fromOption(changeEvent)) @@ -215,13 +215,13 @@ final case class InputField( val updated: Outcome[InputField] = if (hasFocus) - rec(frameContext.inputState.keyboard.keysReleased.toList, this, false, None) + rec(context.frame.input.keyboard.keysReleased.toList, this, false, None) else Outcome(this) - if (frameContext.inputState.pointers.isReleased) - bounds(frameContext.boundaryLocator) match + if (context.frame.input.pointers.isReleased) + bounds(context.services.bounds) match case Some(bounds) => - if frameContext.inputState.pointers.wasUpWithin(bounds, MouseButton.LeftMouseButton) then + if context.frame.input.pointers.wasUpWithin(bounds, MouseButton.LeftMouseButton) then updated.flatMap(_.giveFocus) else updated.flatMap(_.loseFocus) case _ => @@ -231,7 +231,7 @@ final case class InputField( def draw( gameTime: GameTime, - boundaryLocator: BoundaryLocator + boundaryLocator: Context.Services.Bounds ): Batch[SceneNode] = { val field = assets.text diff --git a/indigo/indigo-extras/src/test/scala/indigoextras/jobs/JobMarketTests.scala b/indigo/indigo-extras/src/test/scala/indigoextras/jobs/JobMarketTests.scala index cf715979a..a722bc117 100644 --- a/indigo/indigo-extras/src/test/scala/indigoextras/jobs/JobMarketTests.scala +++ b/indigo/indigo-extras/src/test/scala/indigoextras/jobs/JobMarketTests.scala @@ -3,6 +3,7 @@ package indigoextras.jobs import indigo.platform.assets.DynamicText import indigo.shared.AnimationsRegister import indigo.shared.BoundaryLocator +import indigo.shared.Context import indigo.shared.FontRegister import indigo.shared.collections.Batch import indigo.shared.datatypes.BindingKey @@ -11,19 +12,18 @@ import indigo.shared.events.FrameTick import indigo.shared.events.InputState import indigo.shared.scenegraph.SceneAudio import indigo.shared.subsystems -import indigo.shared.subsystems.SubSystemFrameContext +import indigo.shared.subsystems.SubSystemContext import indigo.shared.subsystems.SubSystemId import indigo.shared.time.GameTime class JobMarketTests extends munit.FunSuite { val context = - SubSystemFrameContext[Unit]( - GameTime.zero, - Dice.loaded(6), - InputState.default, - new BoundaryLocator(new AnimationsRegister, new FontRegister, new DynamicText), - () + SubSystemContext.fromContext( + Context.initial + .modifyFrame( + _.withDice(Dice.loaded(6)) + ) ) val workContext = diff --git a/indigo/indigo-extras/src/test/scala/indigoextras/pathfinding/PathFindingTests.scala b/indigo/indigo-extras/src/test/scala/indigoextras/pathfinding/PathFindingTests.scala index d1bf4ef16..d2f95d5b6 100644 --- a/indigo/indigo-extras/src/test/scala/indigoextras/pathfinding/PathFindingTests.scala +++ b/indigo/indigo-extras/src/test/scala/indigoextras/pathfinding/PathFindingTests.scala @@ -21,7 +21,7 @@ class PathFindingTests extends munit.FunSuite { val searchGrid = SearchGrid.generate(start, end, List(impassable), 3, 3) - val path: List[Coords] = searchGrid.locatePath(Dice.fromSeed(0)) + val path: List[Coords] = searchGrid.locatePath(Dice.default) val possiblePaths: List[List[Coords]] = List( List(start, Coords(2, 2), Coords(1, 2), end), @@ -45,7 +45,7 @@ class PathFindingTests extends munit.FunSuite { val searchGrid = SearchGrid.generate(start, end, List(impassable), 3, 3) - val path: List[Coords] = searchGrid.locatePath(Dice.fromSeed(0)) + val path: List[Coords] = searchGrid.locatePath(Dice.default) val possiblePaths: List[List[Coords]] = List( List(start, Coords(0, 2), Coords(1, 2), Coords(2, 2), end), @@ -67,7 +67,7 @@ class PathFindingTests extends munit.FunSuite { val searchGrid = SearchGrid.generate(start, end, List(impassable), 3, 3) - val path: List[Coords] = searchGrid.locatePath(Dice.fromSeed(0)) + val path: List[Coords] = searchGrid.locatePath(Dice.default) val possiblePaths: List[List[Coords]] = List( List(start, Coords(2, 0), Coords(2, 1), Coords(2, 2), end), @@ -89,7 +89,7 @@ class PathFindingTests extends munit.FunSuite { val searchGrid = SearchGrid.generate(start, end, List(impassable), 3, 3) - val path: List[Coords] = searchGrid.locatePath(Dice.fromSeed(0)) + val path: List[Coords] = searchGrid.locatePath(Dice.default) val possiblePaths: List[List[Coords]] = List( List(start, Coords(2, 2), Coords(2, 1), Coords(2, 0), end), @@ -111,7 +111,7 @@ class PathFindingTests extends munit.FunSuite { val searchGrid = SearchGrid.generate(start, end, List(impassable), 3, 3) - val path: List[Coords] = searchGrid.locatePath(Dice.fromSeed(0)) + val path: List[Coords] = searchGrid.locatePath(Dice.default) val possiblePaths: List[List[Coords]] = List( List(start, Coords(2, 2), Coords(1, 2), Coords(0, 2), end), diff --git a/indigo/indigo-extras/src/test/scala/indigoextras/subsystems/FakeSubSystemFrameContext.scala b/indigo/indigo-extras/src/test/scala/indigoextras/subsystems/FakeSubSystemFrameContext.scala index 25a0f161c..dd8837708 100644 --- a/indigo/indigo-extras/src/test/scala/indigoextras/subsystems/FakeSubSystemFrameContext.scala +++ b/indigo/indigo-extras/src/test/scala/indigoextras/subsystems/FakeSubSystemFrameContext.scala @@ -3,38 +3,38 @@ package indigoextras.subsystems import indigo.platform.assets.DynamicText import indigo.shared.AnimationsRegister import indigo.shared.BoundaryLocator +import indigo.shared.Context import indigo.shared.FontRegister import indigo.shared.dice.Dice import indigo.shared.events.InputState -import indigo.shared.subsystems.SubSystemFrameContext +import indigo.shared.subsystems.SubSystemContext import indigo.shared.time.GameTime import indigo.shared.time.Seconds object FakeSubSystemFrameContext: - def context(sides: Int): SubSystemFrameContext[Unit] = - SubSystemFrameContext( - GameTime.zero, - Dice.loaded(sides), - InputState.default, - new BoundaryLocator(new AnimationsRegister, new FontRegister, new DynamicText), - () + def context(sides: Int): SubSystemContext[Unit] = + SubSystemContext.fromContext( + Context.initial + .modifyFrame( + _.withDice(Dice.loaded(sides)) + ) ) - def context(sides: Int, time: Seconds): SubSystemFrameContext[Unit] = - SubSystemFrameContext( - GameTime.is(time), - Dice.loaded(sides), - InputState.default, - new BoundaryLocator(new AnimationsRegister, new FontRegister, new DynamicText), - () + def context(sides: Int, time: Seconds): SubSystemContext[Unit] = + SubSystemContext.fromContext( + Context.initial + .modifyFrame( + _.withDice(Dice.loaded(sides)) + .withTime(GameTime.is(time)) + ) ) - def context(sides: Int, time: Seconds, delta: Seconds): SubSystemFrameContext[Unit] = - SubSystemFrameContext( - GameTime.withDelta(time, delta), - Dice.loaded(sides), - InputState.default, - new BoundaryLocator(new AnimationsRegister, new FontRegister, new DynamicText), - () + def context(sides: Int, time: Seconds, delta: Seconds): SubSystemContext[Unit] = + SubSystemContext.fromContext( + Context.initial + .modifyFrame( + _.withDice(Dice.loaded(sides)) + .withTime(GameTime.withDelta(time, delta)) + ) ) diff --git a/indigo/indigo-extras/src/test/scala/indigoextras/ui/InputFieldTests.scala b/indigo/indigo-extras/src/test/scala/indigoextras/ui/InputFieldTests.scala index f50597c12..f0a826f2d 100644 --- a/indigo/indigo-extras/src/test/scala/indigoextras/ui/InputFieldTests.scala +++ b/indigo/indigo-extras/src/test/scala/indigoextras/ui/InputFieldTests.scala @@ -4,8 +4,8 @@ import indigo.platform.assets.DynamicText import indigo.platform.renderer.Renderer import indigo.shared.AnimationsRegister import indigo.shared.BoundaryLocator +import indigo.shared.Context import indigo.shared.FontRegister -import indigo.shared.FrameContext import indigo.shared.assets.AssetName import indigo.shared.collections.Batch import indigo.shared.constants.Key @@ -45,6 +45,8 @@ class InputFieldTests extends munit.FunSuite { val boundaryLocator: BoundaryLocator = new BoundaryLocator(new AnimationsRegister, fontRegister, new DynamicText()) + val bounds: Context.Services.Bounds = + Context.Services.Bounds(boundaryLocator) val atCommaPosition = InputField("Hello, world!", assets).cursorHome.cursorRight.cursorRight.cursorRight.cursorRight.cursorRight @@ -153,7 +155,7 @@ class InputFieldTests extends munit.FunSuite { test("Multi line boxes have bounds correctly caluculated") { val actual = - InputField("ab\nc", assets).moveTo(50, 50).bounds(boundaryLocator).get + InputField("ab\nc", assets).moveTo(50, 50).bounds(bounds).get val expected = Rectangle(50, 50, 26, 36) @@ -170,7 +172,7 @@ class InputFieldTests extends munit.FunSuite { def extractCursorPosition(field: InputField): Point = field - .draw(GameTime.zero, boundaryLocator) + .draw(GameTime.zero, bounds) .collect { case g: Graphic[_] => g } .head .position @@ -276,15 +278,14 @@ class InputFieldTests extends munit.FunSuite { KeyboardEvent.KeyUp(Key.KEY_C) ) - def context: FrameContext[Unit] = - new FrameContext[Unit]( - GameTime.zero, - Dice.loaded(1), - new InputState(Mouse.default, new Keyboard(keysUp, Batch.empty, None), Gamepad.default, Pointers.default), - new BoundaryLocator(new AnimationsRegister, new FontRegister, new DynamicText), - (), - Renderer.blackHole.captureScreen - ) + def context: Context[Unit] = + Context.initial + .modifyFrame( + _.withDice(Dice.loaded(1)) + .withInput( + new InputState(Mouse.default, new Keyboard(keysUp, Batch.empty, None), Gamepad.default, Pointers.default) + ) + ) object Samples { val material = Material.Bitmap(AssetName("font-sheet")) diff --git a/indigo/indigo/src/main/scala/indigo/IndigoDemo.scala b/indigo/indigo/src/main/scala/indigo/IndigoDemo.scala index 62cf10571..7ff23ccb4 100644 --- a/indigo/indigo/src/main/scala/indigo/IndigoDemo.scala +++ b/indigo/indigo/src/main/scala/indigo/IndigoDemo.scala @@ -84,7 +84,7 @@ trait IndigoDemo[BootData, StartUpData, Model, ViewModel] extends GameLauncher[S * A function that maps GlobalEvent's to the next version of your model, and encapsuates failures or resulting * events within the Outcome wrapper. */ - def updateModel(context: FrameContext[StartUpData], model: Model): GlobalEvent => Outcome[Model] + def updateModel(context: Context[StartUpData], model: Model): GlobalEvent => Outcome[Model] /** A pure function for updating your game's view model in the context of the running frame and the events acting upon * it. @@ -101,7 +101,7 @@ trait IndigoDemo[BootData, StartUpData, Model, ViewModel] extends GameLauncher[S * events within the Outcome wrapper. */ def updateViewModel( - context: FrameContext[StartUpData], + context: Context[StartUpData], model: Model, viewModel: ViewModel ): GlobalEvent => Outcome[ViewModel] @@ -120,7 +120,7 @@ trait IndigoDemo[BootData, StartUpData, Model, ViewModel] extends GameLauncher[S * A function that produces a description of what to present next, and encapsuates failures or resulting events * within the Outcome wrapper. */ - def present(context: FrameContext[StartUpData], model: Model, viewModel: ViewModel): Outcome[SceneUpdateFragment] + def present(context: Context[StartUpData], model: Model, viewModel: ViewModel): Outcome[SceneUpdateFragment] private val subSystemsRegister: SubSystemsRegister[Model] = new SubSystemsRegister() diff --git a/indigo/indigo/src/main/scala/indigo/IndigoGame.scala b/indigo/indigo/src/main/scala/indigo/IndigoGame.scala index a7fd0014c..6b5858c8d 100644 --- a/indigo/indigo/src/main/scala/indigo/IndigoGame.scala +++ b/indigo/indigo/src/main/scala/indigo/IndigoGame.scala @@ -105,7 +105,7 @@ trait IndigoGame[BootData, StartUpData, Model, ViewModel] extends GameLauncher[S * A function that maps GlobalEvent's to the next version of your model, and encapsulates failures or resulting * events within the Outcome wrapper. */ - def updateModel(context: FrameContext[StartUpData], model: Model): GlobalEvent => Outcome[Model] + def updateModel(context: Context[StartUpData], model: Model): GlobalEvent => Outcome[Model] /** A pure function for updating your game's view model in the context of the running frame and the events acting upon * it. @@ -122,7 +122,7 @@ trait IndigoGame[BootData, StartUpData, Model, ViewModel] extends GameLauncher[S * resulting events within the Outcome wrapper. */ def updateViewModel( - context: FrameContext[StartUpData], + context: Context[StartUpData], model: Model, viewModel: ViewModel ): GlobalEvent => Outcome[ViewModel] @@ -141,7 +141,7 @@ trait IndigoGame[BootData, StartUpData, Model, ViewModel] extends GameLauncher[S * A function that produces a description of what to present next, and encapsulates failures or resulting events * within the Outcome wrapper. */ - def present(context: FrameContext[StartUpData], model: Model, viewModel: ViewModel): Outcome[SceneUpdateFragment] + def present(context: Context[StartUpData], model: Model, viewModel: ViewModel): Outcome[SceneUpdateFragment] private val subSystemsRegister: SubSystemsRegister[Model] = new SubSystemsRegister() diff --git a/indigo/indigo/src/main/scala/indigo/IndigoSandbox.scala b/indigo/indigo/src/main/scala/indigo/IndigoSandbox.scala index 4993c702f..11259b85f 100644 --- a/indigo/indigo/src/main/scala/indigo/IndigoSandbox.scala +++ b/indigo/indigo/src/main/scala/indigo/IndigoSandbox.scala @@ -75,7 +75,7 @@ trait IndigoSandbox[StartUpData, Model] extends GameLauncher[StartUpData, Model, * A function that maps GlobalEvent's to the next version of your model, and encapsuates failures or resulting * events within the Outcome wrapper. */ - def updateModel(context: FrameContext[StartUpData], model: Model): GlobalEvent => Outcome[Model] + def updateModel(context: Context[StartUpData], model: Model): GlobalEvent => Outcome[Model] /** A pure function for presenting your game. The result is a side effect free declaration of what you intend to be * presented to the player next. @@ -91,11 +91,11 @@ trait IndigoSandbox[StartUpData, Model] extends GameLauncher[StartUpData, Model, * A function that produces a description of what to present next, and encapsuates failures or resulting events * within the Outcome wrapper. */ - def present(context: FrameContext[StartUpData], model: Model): Outcome[SceneUpdateFragment] + def present(context: Context[StartUpData], model: Model): Outcome[SceneUpdateFragment] private def indigoGame: GameEngine[StartUpData, Model, Unit] = { - val updateViewModel: (FrameContext[StartUpData], Model, Unit) => GlobalEvent => Outcome[Unit] = + val updateViewModel: (Context[StartUpData], Model, Unit) => GlobalEvent => Outcome[Unit] = (_, _, vm) => _ => Outcome(vm) val eventFilters: EventFilters = diff --git a/indigo/indigo/src/main/scala/indigo/IndigoShader.scala b/indigo/indigo/src/main/scala/indigo/IndigoShader.scala index 2a7802dfc..5e1eebdf5 100644 --- a/indigo/indigo/src/main/scala/indigo/IndigoShader.scala +++ b/indigo/indigo/src/main/scala/indigo/IndigoShader.scala @@ -116,7 +116,7 @@ trait IndigoShader extends GameLauncher[IndigoShaderModel, IndigoShaderModel, Un Outcome(startupData) private def updateModel( - context: FrameContext[IndigoShaderModel], + context: Context[IndigoShaderModel], model: IndigoShaderModel ): GlobalEvent => Outcome[IndigoShaderModel] = { case ViewportResize(vp) => @@ -130,7 +130,7 @@ trait IndigoShader extends GameLauncher[IndigoShaderModel, IndigoShaderModel, Un } private def present( - context: FrameContext[IndigoShaderModel], + context: Context[IndigoShaderModel], model: IndigoShaderModel ): Outcome[SceneUpdateFragment] = Outcome( @@ -155,7 +155,7 @@ trait IndigoShader extends GameLauncher[IndigoShaderModel, IndigoShaderModel, Un boot: BootResult[IndigoShaderModel, IndigoShaderModel] ): GameEngine[IndigoShaderModel, IndigoShaderModel, Unit] = { - val updateViewModel: (FrameContext[IndigoShaderModel], IndigoShaderModel, Unit) => GlobalEvent => Outcome[Unit] = + val updateViewModel: (Context[IndigoShaderModel], IndigoShaderModel, Unit) => GlobalEvent => Outcome[Unit] = (_, _, vm) => _ => Outcome(vm) val eventFilters: EventFilters = diff --git a/indigo/indigo/src/main/scala/indigo/entry/ScenesFrameProcessor.scala b/indigo/indigo/src/main/scala/indigo/entry/ScenesFrameProcessor.scala index 565f1378b..a9732f03a 100644 --- a/indigo/indigo/src/main/scala/indigo/entry/ScenesFrameProcessor.scala +++ b/indigo/indigo/src/main/scala/indigo/entry/ScenesFrameProcessor.scala @@ -4,7 +4,7 @@ import indigo.gameengine.FrameProcessor import indigo.platform.renderer.Renderer import indigo.scenes.SceneManager import indigo.shared.BoundaryLocator -import indigo.shared.FrameContext +import indigo.shared.Context import indigo.shared.Outcome import indigo.shared.collections.Batch import indigo.shared.datatypes.BindingKey @@ -14,7 +14,7 @@ import indigo.shared.events.EventFilters import indigo.shared.events.GlobalEvent import indigo.shared.events.InputState import indigo.shared.scenegraph.SceneUpdateFragment -import indigo.shared.subsystems.SubSystemFrameContext._ +import indigo.shared.subsystems.SubSystemContext.* import indigo.shared.subsystems.SubSystemsRegister import indigo.shared.time.GameTime @@ -22,57 +22,49 @@ final class ScenesFrameProcessor[StartUpData, Model, ViewModel]( val subSystemsRegister: SubSystemsRegister[Model], val sceneManager: SceneManager[StartUpData, Model, ViewModel], val eventFilters: EventFilters, - val modelUpdate: (FrameContext[StartUpData], Model) => GlobalEvent => Outcome[Model], - val viewModelUpdate: (FrameContext[StartUpData], Model, ViewModel) => GlobalEvent => Outcome[ViewModel], - val viewUpdate: (FrameContext[StartUpData], Model, ViewModel) => Outcome[SceneUpdateFragment] + val modelUpdate: (Context[StartUpData], Model) => GlobalEvent => Outcome[Model], + val viewModelUpdate: (Context[StartUpData], Model, ViewModel) => GlobalEvent => Outcome[ViewModel], + val viewUpdate: (Context[StartUpData], Model, ViewModel) => Outcome[SceneUpdateFragment] ) extends FrameProcessor[StartUpData, Model, ViewModel] with StandardFrameProcessorFunctions[StartUpData, Model, ViewModel]: def run( - startUpData: => StartUpData, model: => Model, viewModel: => ViewModel, - gameTime: GameTime, globalEvents: Batch[GlobalEvent], - inputState: InputState, - dice: Dice, - boundaryLocator: BoundaryLocator, - renderer: => Renderer + context: => Context[StartUpData] ): Outcome[(Model, ViewModel, SceneUpdateFragment)] = { - val frameContext = - new FrameContext[StartUpData](gameTime, dice, inputState, boundaryLocator, startUpData, renderer.captureScreen) - val processSceneViewModel: (Model, ViewModel) => Outcome[ViewModel] = (m, vm) => globalEvents .map(sceneManager.eventFilters.viewModelFilter) .collect { case Some(e) => e } .foldLeft(Outcome(vm)) { (acc, e) => acc.flatMap { next => - sceneManager.updateViewModel(frameContext, m, next)(e) + sceneManager.updateViewModel(context, m, next)(e) } } val processSceneView: (Model, ViewModel) => Outcome[SceneUpdateFragment] = (m, vm) => Outcome.merge( - processView(frameContext, m, vm), - sceneManager.updateView(frameContext, m, vm) + processView(context, m, vm), + sceneManager.updateView(context, m, vm) )(_ |+| _) Outcome.join( for { - m <- processModel(frameContext, model, globalEvents) - sm <- processSceneModel(frameContext, m, globalEvents) - vm <- processViewModel(frameContext, sm, viewModel, globalEvents) + m <- processModel(context, model, globalEvents) + sm <- processSceneModel(context, m, globalEvents) + vm <- processViewModel(context, sm, viewModel, globalEvents) svm <- processSceneViewModel(sm, vm) - e <- processSubSystems(frameContext, m, globalEvents).eventsAsOutcome + e <- processSubSystems(context, m, globalEvents).eventsAsOutcome v <- processSceneView(sm, svm) } yield Outcome((sm, svm, v), e) ) } def processSceneModel( - frameContext: FrameContext[StartUpData], + context: Context[StartUpData], model: Model, globalEvents: Batch[GlobalEvent] ): Outcome[Model] = @@ -81,16 +73,16 @@ final class ScenesFrameProcessor[StartUpData, Model, ViewModel]( .collect { case Some(e) => e } .foldLeft(Outcome(model)) { (acc, e) => acc.flatMap { next => - sceneManager.updateModel(frameContext, next)(e) + sceneManager.updateModel(context, next)(e) } } def processSubSystems( - frameContext: FrameContext[StartUpData], + context: Context[StartUpData], model: Model, globalEvents: Batch[GlobalEvent] ): Outcome[Unit] = Outcome.merge( - subSystemsRegister.update(frameContext.forSubSystems, model, globalEvents.toJSArray), - sceneManager.updateSubSystems(frameContext.forSubSystems, model, globalEvents) + subSystemsRegister.update(context.forSubSystems, model, globalEvents.toJSArray), + sceneManager.updateSubSystems(context.forSubSystems, model, globalEvents) )((_, _) => ()) diff --git a/indigo/indigo/src/main/scala/indigo/entry/StandardFrameProcessor.scala b/indigo/indigo/src/main/scala/indigo/entry/StandardFrameProcessor.scala index 4d4361dad..b57a75bf2 100644 --- a/indigo/indigo/src/main/scala/indigo/entry/StandardFrameProcessor.scala +++ b/indigo/indigo/src/main/scala/indigo/entry/StandardFrameProcessor.scala @@ -3,7 +3,7 @@ package indigo.entry import indigo.gameengine.FrameProcessor import indigo.platform.renderer.Renderer import indigo.shared.BoundaryLocator -import indigo.shared.FrameContext +import indigo.shared.Context import indigo.shared.Outcome import indigo.shared.collections.Batch import indigo.shared.dice.Dice @@ -11,50 +11,43 @@ import indigo.shared.events.EventFilters import indigo.shared.events.GlobalEvent import indigo.shared.events.InputState import indigo.shared.scenegraph.SceneUpdateFragment -import indigo.shared.subsystems.SubSystemFrameContext._ +import indigo.shared.subsystems.SubSystemContext._ import indigo.shared.subsystems.SubSystemsRegister import indigo.shared.time.GameTime final class StandardFrameProcessor[StartUpData, Model, ViewModel]( val subSystemsRegister: SubSystemsRegister[Model], val eventFilters: EventFilters, - val modelUpdate: (FrameContext[StartUpData], Model) => GlobalEvent => Outcome[Model], - val viewModelUpdate: (FrameContext[StartUpData], Model, ViewModel) => GlobalEvent => Outcome[ViewModel], - val viewUpdate: (FrameContext[StartUpData], Model, ViewModel) => Outcome[SceneUpdateFragment] + val modelUpdate: (Context[StartUpData], Model) => GlobalEvent => Outcome[Model], + val viewModelUpdate: (Context[StartUpData], Model, ViewModel) => GlobalEvent => Outcome[ViewModel], + val viewUpdate: (Context[StartUpData], Model, ViewModel) => Outcome[SceneUpdateFragment] ) extends FrameProcessor[StartUpData, Model, ViewModel] with StandardFrameProcessorFunctions[StartUpData, Model, ViewModel]: def run( - startUpData: => StartUpData, model: => Model, viewModel: => ViewModel, - gameTime: GameTime, globalEvents: Batch[GlobalEvent], - inputState: InputState, - dice: Dice, - boundaryLocator: BoundaryLocator, - renderer: => Renderer + context: => Context[StartUpData] ): Outcome[(Model, ViewModel, SceneUpdateFragment)] = - val frameContext = - new FrameContext[StartUpData](gameTime, dice, inputState, boundaryLocator, startUpData, renderer.captureScreen) Outcome.join( for { - m <- processModel(frameContext, model, globalEvents) - vm <- processViewModel(frameContext, m, viewModel, globalEvents) - e <- subSystemsRegister.update(frameContext.forSubSystems, m, globalEvents.toJSArray).eventsAsOutcome - v <- processView(frameContext, m, vm) + m <- processModel(context, model, globalEvents) + vm <- processViewModel(context, m, viewModel, globalEvents) + e <- subSystemsRegister.update(context.forSubSystems, m, globalEvents.toJSArray).eventsAsOutcome + v <- processView(context, m, vm) } yield Outcome((m, vm, v), e) ) trait StandardFrameProcessorFunctions[StartUpData, Model, ViewModel]: def subSystemsRegister: SubSystemsRegister[Model] def eventFilters: EventFilters - def modelUpdate: (FrameContext[StartUpData], Model) => GlobalEvent => Outcome[Model] - def viewModelUpdate: (FrameContext[StartUpData], Model, ViewModel) => GlobalEvent => Outcome[ViewModel] - def viewUpdate: (FrameContext[StartUpData], Model, ViewModel) => Outcome[SceneUpdateFragment] + def modelUpdate: (Context[StartUpData], Model) => GlobalEvent => Outcome[Model] + def viewModelUpdate: (Context[StartUpData], Model, ViewModel) => GlobalEvent => Outcome[ViewModel] + def viewUpdate: (Context[StartUpData], Model, ViewModel) => Outcome[SceneUpdateFragment] def processModel( - frameContext: FrameContext[StartUpData], + context: Context[StartUpData], model: Model, globalEvents: Batch[GlobalEvent] ): Outcome[Model] = @@ -63,12 +56,12 @@ trait StandardFrameProcessorFunctions[StartUpData, Model, ViewModel]: .collect { case Some(e) => e } .foldLeft(Outcome(model)) { (acc, e) => acc.flatMap { next => - modelUpdate(frameContext, next)(e) + modelUpdate(context, next)(e) } } def processViewModel( - frameContext: FrameContext[StartUpData], + context: Context[StartUpData], model: Model, viewModel: ViewModel, globalEvents: Batch[GlobalEvent] @@ -78,16 +71,16 @@ trait StandardFrameProcessorFunctions[StartUpData, Model, ViewModel]: .collect { case Some(e) => e } .foldLeft(Outcome(viewModel)) { (acc, e) => acc.flatMap { next => - viewModelUpdate(frameContext, model, next)(e) + viewModelUpdate(context, model, next)(e) } } def processView( - frameContext: FrameContext[StartUpData], + context: Context[StartUpData], model: Model, viewModel: ViewModel ): Outcome[SceneUpdateFragment] = Outcome.merge( - viewUpdate(frameContext, model, viewModel), - subSystemsRegister.present(frameContext.forSubSystems, model) + viewUpdate(context, model, viewModel), + subSystemsRegister.present(context.forSubSystems, model) )(_ |+| _) diff --git a/indigo/indigo/src/main/scala/indigo/gameengine/FrameProcessor.scala b/indigo/indigo/src/main/scala/indigo/gameengine/FrameProcessor.scala index eff5552eb..999e06fd7 100644 --- a/indigo/indigo/src/main/scala/indigo/gameengine/FrameProcessor.scala +++ b/indigo/indigo/src/main/scala/indigo/gameengine/FrameProcessor.scala @@ -2,6 +2,7 @@ package indigo.gameengine import indigo.platform.renderer.Renderer import indigo.shared.BoundaryLocator +import indigo.shared.Context import indigo.shared.Outcome import indigo.shared.collections.Batch import indigo.shared.dice.Dice @@ -12,13 +13,8 @@ import indigo.shared.time.GameTime trait FrameProcessor[StartUpData, Model, ViewModel]: def run( - startUpData: => StartUpData, model: => Model, viewModel: => ViewModel, - gameTime: GameTime, globalEvents: Batch[GlobalEvent], - inputState: InputState, - dice: Dice, - boundaryLocator: BoundaryLocator, - renderer: => Renderer + context: => Context[StartUpData] ): Outcome[(Model, ViewModel, SceneUpdateFragment)] diff --git a/indigo/indigo/src/main/scala/indigo/gameengine/GameEngine.scala b/indigo/indigo/src/main/scala/indigo/gameengine/GameEngine.scala index 2c5210fd0..3eaf2f796 100644 --- a/indigo/indigo/src/main/scala/indigo/gameengine/GameEngine.scala +++ b/indigo/indigo/src/main/scala/indigo/gameengine/GameEngine.scala @@ -178,12 +178,11 @@ final class GameEngine[StartUpData, GameModel, ViewModel]( audioPlayer.addAudioAssets(accumulatedAssetCollection.sounds) - val randomSeed = (if (firstRun) 0 else gameLoopInstance.runningTimeReference) + gameLoopInstance.initialSeed + val dice = if firstRun then Dice.default else Dice.fromSeed(gameLoopInstance.runningTimeReference.toLong) - if (firstRun) - platform = new Platform(parentElement, gameConfig, globalEventStream, dynamicText) + if firstRun then platform = new Platform(parentElement, gameConfig, globalEventStream, dynamicText) - initialise(accumulatedAssetCollection)(Dice.fromSeed(randomSeed.toLong)) match { + initialise(accumulatedAssetCollection)(dice) match { case oe @ Outcome.Error(error, _) => IndigoLogger.error( if (firstRun) "Error during first initialisation - Halting." diff --git a/indigo/indigo/src/main/scala/indigo/gameengine/GameLoop.scala b/indigo/indigo/src/main/scala/indigo/gameengine/GameLoop.scala index 8ad113980..4933a6e92 100644 --- a/indigo/indigo/src/main/scala/indigo/gameengine/GameLoop.scala +++ b/indigo/indigo/src/main/scala/indigo/gameengine/GameLoop.scala @@ -3,6 +3,7 @@ package indigo.gameengine import indigo.platform.assets.AssetCollection import indigo.platform.renderer.Renderer import indigo.shared.BoundaryLocator +import indigo.shared.Context import indigo.shared.IndigoLogger import indigo.shared.Outcome import indigo.shared.collections.Batch @@ -37,8 +38,6 @@ final class GameLoop[StartUpData, GameModel, ViewModel]( renderer: => Renderer ): - val initialSeed = new Date().valueOf() - @SuppressWarnings(Array("scalafix:DisableSyntax.var")) private var _gameModelState: GameModel = initialModel @SuppressWarnings(Array("scalafix:DisableSyntax.var")) @@ -57,6 +56,11 @@ final class GameLoop[StartUpData, GameModel, ViewModel]( private val frameDeltaRecord: scala.scalajs.js.Array[Double] = scala.scalajs.js.Array(0.0d, 0.0d, 0.0d, 0.0d, 0.0d) + private val _randomInstance: scala.util.Random = new scala.util.Random() + + private lazy val _services: Context.Services = + Context.Services(boundaryLocator, _randomInstance, renderer.captureScreen) + def gameModelState: GameModel = _gameModelState def viewModelState: ViewModel = _viewModelState def runningTimeReference: Double = _runningTimeReference @@ -125,18 +129,20 @@ final class GameLoop[StartUpData, GameModel, ViewModel]( gameEngine.gamepadInputCapture.giveGamepadState ) + val context = + new Context[StartUpData]( + gameEngine.startUpData, + Context.Frame(Dice.fromSeconds(gameTime.running), gameTime, _inputState), + _services + ) + // Run the frame processor val processedFrame: Outcome[(GameModel, ViewModel, SceneUpdateFragment)] = frameProcessor.run( - gameEngine.startUpData, _gameModelState, _viewModelState, - gameTime, events, - _inputState, - Dice.fromSeconds(gameTime.running + initialSeed), - boundaryLocator, - renderer + context ) // Persist frame state diff --git a/indigo/indigo/src/main/scala/indigo/package.scala b/indigo/indigo/src/main/scala/indigo/package.scala index 487e2422b..9c9cca9d7 100644 --- a/indigo/indigo/src/main/scala/indigo/package.scala +++ b/indigo/indigo/src/main/scala/indigo/package.scala @@ -617,8 +617,11 @@ val ImageType: shared.ImageType.type = shared.ImageType type BoundaryLocator = shared.BoundaryLocator -type FrameContext[StartUpData] = shared.FrameContext[StartUpData] -type SubSystemFrameContext[ReferenceData] = shared.subsystems.SubSystemFrameContext[ReferenceData] +type Context[StartUpData] = shared.Context[StartUpData] +val Context: shared.Context.type = shared.Context + +type SubSystemContext[ReferenceData] = shared.subsystems.SubSystemContext[ReferenceData] +val SubSystemContext: shared.subsystems.SubSystemContext.type = shared.subsystems.SubSystemContext //WebSockets diff --git a/indigo/indigo/src/main/scala/indigo/scenes/SceneContext.scala b/indigo/indigo/src/main/scala/indigo/scenes/SceneContext.scala index d85c1b979..0a55a8e1a 100644 --- a/indigo/indigo/src/main/scala/indigo/scenes/SceneContext.scala +++ b/indigo/indigo/src/main/scala/indigo/scenes/SceneContext.scala @@ -1,7 +1,7 @@ package indigo.scenes import indigo.shared.BoundaryLocator -import indigo.shared.FrameContext +import indigo.shared.Context import indigo.shared.datatypes.Rectangle import indigo.shared.dice.Dice import indigo.shared.events.InputState @@ -12,41 +12,33 @@ import indigo.shared.scenegraph.SceneNode import indigo.shared.time.GameTime import indigo.shared.time.Seconds -/** SceneContext is a Scene specific equivalent of `FrameContext`, and exposes all of the fields and methods or a normal - * `FrameContext` object. It adds information about the scene currently running. +/** SceneContext is a Scene specific equivalent of `Context`, and exposes all of the fields and methods of a normal + * `Context` object. It adds information about the scene currently running. * * @param sceneName * The name of the current scene. * @param sceneStartTime * The time that the current scene was entered. - * @param frameContext + * @param context * The normal frame context object that all other fields delegate to. */ final class SceneContext[StartUpData]( val sceneName: SceneName, val sceneStartTime: Seconds, - val frameContext: FrameContext[StartUpData] + val context: Context[StartUpData] ): - export frameContext.gameTime - export frameContext.dice - export frameContext.inputState - export frameContext.boundaryLocator - export frameContext.startUpData - export frameContext.gameTime.running - export frameContext.gameTime.delta - export frameContext.inputState.mouse - export frameContext.inputState.keyboard - export frameContext.inputState.gamepad - export frameContext.inputState.pointers - export frameContext.findBounds - export frameContext.bounds - export frameContext.captureScreen + export context.* /** The running time of the current scene calculated as the game's total running time minus time the scene was * entered. */ lazy val sceneRunning: Seconds = - frameContext.gameTime.running - sceneStartTime + context.frame.time.running - sceneStartTime - def toFrameContext: FrameContext[StartUpData] = - frameContext + def toFrameContext: Context[StartUpData] = + context + +object SceneContext: + + def fromFrameContext[A](sceneName: SceneName, sceneStartTime: Seconds, ctx: Context[A]): SceneContext[A] = + new SceneContext(sceneName, sceneStartTime, ctx) diff --git a/indigo/indigo/src/main/scala/indigo/scenes/SceneManager.scala b/indigo/indigo/src/main/scala/indigo/scenes/SceneManager.scala index 90c826d38..1d2eb254f 100644 --- a/indigo/indigo/src/main/scala/indigo/scenes/SceneManager.scala +++ b/indigo/indigo/src/main/scala/indigo/scenes/SceneManager.scala @@ -1,6 +1,6 @@ package indigo.scenes -import indigo.shared.FrameContext +import indigo.shared.Context import indigo.shared.IndigoLogger import indigo.shared.Outcome import indigo.shared.collections.Batch @@ -8,8 +8,8 @@ import indigo.shared.collections.NonEmptyList import indigo.shared.events.EventFilters import indigo.shared.events.GlobalEvent import indigo.shared.scenegraph.SceneUpdateFragment -import indigo.shared.subsystems.SubSystemFrameContext -import indigo.shared.subsystems.SubSystemFrameContext._ +import indigo.shared.subsystems.SubSystemContext +import indigo.shared.subsystems.SubSystemContext._ import indigo.shared.subsystems.SubSystemsRegister import indigo.shared.time.Seconds @@ -42,9 +42,9 @@ class SceneManager[StartUpData, GameModel, ViewModel]( // Scene delegation - def updateModel(frameContext: FrameContext[StartUpData], model: GameModel): GlobalEvent => Outcome[GameModel] = + def updateModel(ctx: Context[StartUpData], model: GameModel): GlobalEvent => Outcome[GameModel] = case SceneEvent.First => - lastSceneChangeAt = frameContext.gameTime.running + lastSceneChangeAt = ctx.frame.time.running val from = finderInstance.current.name finderInstance = finderInstance.first @@ -57,7 +57,7 @@ class SceneManager[StartUpData, GameModel, ViewModel]( Outcome(model, events) case SceneEvent.Last => - lastSceneChangeAt = frameContext.gameTime.running + lastSceneChangeAt = ctx.frame.time.running val from = finderInstance.current.name finderInstance = finderInstance.last @@ -70,7 +70,7 @@ class SceneManager[StartUpData, GameModel, ViewModel]( Outcome(model, events) case SceneEvent.Next => - lastSceneChangeAt = frameContext.gameTime.running + lastSceneChangeAt = ctx.frame.time.running val from = finderInstance.current.name finderInstance = finderInstance.forward @@ -83,7 +83,7 @@ class SceneManager[StartUpData, GameModel, ViewModel]( Outcome(model, events) case SceneEvent.LoopNext => - lastSceneChangeAt = frameContext.gameTime.running + lastSceneChangeAt = ctx.frame.time.running val from = finderInstance.current.name finderInstance = finderInstance.forwardLoop @@ -96,7 +96,7 @@ class SceneManager[StartUpData, GameModel, ViewModel]( Outcome(model, events) case SceneEvent.Previous => - lastSceneChangeAt = frameContext.gameTime.running + lastSceneChangeAt = ctx.frame.time.running val from = finderInstance.current.name finderInstance = finderInstance.backward @@ -109,7 +109,7 @@ class SceneManager[StartUpData, GameModel, ViewModel]( Outcome(model, events) case SceneEvent.LoopPrevious => - lastSceneChangeAt = frameContext.gameTime.running + lastSceneChangeAt = ctx.frame.time.running val from = finderInstance.current.name finderInstance = finderInstance.backwardLoop @@ -122,7 +122,7 @@ class SceneManager[StartUpData, GameModel, ViewModel]( Outcome(model, events) case SceneEvent.JumpTo(name) => - lastSceneChangeAt = frameContext.gameTime.running + lastSceneChangeAt = ctx.frame.time.running val from = finderInstance.current.name finderInstance = finderInstance.jumpToSceneByName(name) @@ -144,12 +144,12 @@ class SceneManager[StartUpData, GameModel, ViewModel]( Scene .updateModel( scene, - SceneContext(scene.name, lastSceneChangeAt, frameContext), + SceneContext(scene.name, lastSceneChangeAt, ctx), model )(event) def updateSubSystems( - frameContext: SubSystemFrameContext[Unit], + ctx: SubSystemContext[Unit], model: GameModel, globalEvents: Batch[GlobalEvent] ): Outcome[SubSystemsRegister[GameModel]] = @@ -159,7 +159,7 @@ class SceneManager[StartUpData, GameModel, ViewModel]( subSystemStates .get(scene.name.toString) .map { - _.update(frameContext, model, globalEvents.toJSArray) + _.update(ctx, model, globalEvents.toJSArray) } } .getOrElse( @@ -169,7 +169,7 @@ class SceneManager[StartUpData, GameModel, ViewModel]( ) def updateViewModel( - frameContext: FrameContext[StartUpData], + ctx: Context[StartUpData], model: GameModel, viewModel: ViewModel ): GlobalEvent => Outcome[ViewModel] = @@ -181,13 +181,13 @@ class SceneManager[StartUpData, GameModel, ViewModel]( case Some(scene) => Scene.updateViewModel( scene, - SceneContext(scene.name, lastSceneChangeAt, frameContext), + SceneContext(scene.name, lastSceneChangeAt, ctx), model, viewModel ) def updateView( - frameContext: FrameContext[StartUpData], + ctx: Context[StartUpData], model: GameModel, viewModel: ViewModel ): Outcome[SceneUpdateFragment] = @@ -200,14 +200,14 @@ class SceneManager[StartUpData, GameModel, ViewModel]( val subsystemView = subSystemStates .get(scene.name.toString) .map { ssr => - ssr.present(frameContext.forSubSystems, model) + ssr.present(ctx.forSubSystems, model) } .getOrElse(Outcome(SceneUpdateFragment.empty)) Outcome.merge( Scene.updateView( scene, - SceneContext(scene.name, lastSceneChangeAt, frameContext), + SceneContext(scene.name, lastSceneChangeAt, ctx), model, viewModel ), diff --git a/indigo/indigo/src/main/scala/indigo/shared/Context.scala b/indigo/indigo/src/main/scala/indigo/shared/Context.scala new file mode 100644 index 000000000..ee0cc1ac5 --- /dev/null +++ b/indigo/indigo/src/main/scala/indigo/shared/Context.scala @@ -0,0 +1,280 @@ +package indigo.shared + +import indigo.FontKey +import indigo.platform.renderer.ScreenCaptureConfig +import indigo.shared.assets.AssetType +import indigo.shared.collections.Batch +import indigo.shared.datatypes.Rectangle +import indigo.shared.dice.Dice +import indigo.shared.events.InputState +import indigo.shared.scenegraph.SceneNode +import indigo.shared.scenegraph.TextBox +import indigo.shared.scenegraph.TextLine +import indigo.shared.time.GameTime +import org.scalajs.dom.Screen + +/** The Context is the context in which the current frame will be processed. + * + * This is divided into three main areas: + * + * 1. StartUpData: The data that was passed into the game at the start, and is available globally. + * + * 2. Frame: The data that is specific to the current frame, such as the current time, input state, and dice (pseudo + * random number generated seeded on the game's running time at the beginning of the frame), and if only frame values + * are used, then calls to functions like `updateModel` can be considered referentially transparent. + * + * 3. Services: The services that are available to the game, such as the ability to capture the screen, measure text, + * find the bounds of on-screen elements, or access a long running Random instance. Services are side-effecting, long + * running, and / or stateful. + */ +final class Context[StartUpData]( + _startUpData: => StartUpData, + val frame: Context.Frame, + val services: Context.Services +): + lazy val startUpData = _startUpData + + def withStartUpData(newStartUpData: StartUpData): Context[StartUpData] = + new Context(newStartUpData, frame, services) + def modifyStartUpData(modify: StartUpData => StartUpData): Context[StartUpData] = + withStartUpData(modify(startUpData)) + + def withFrame(newFrame: Context.Frame): Context[StartUpData] = + new Context(startUpData, newFrame, services) + def modifyFrame(modify: Context.Frame => Context.Frame): Context[StartUpData] = + withFrame(modify(frame)) + + def withServices(newServices: Context.Services): Context[StartUpData] = + new Context(startUpData, frame, newServices) + def modifyServices(modify: Context.Services => Context.Services): Context[StartUpData] = + withServices(modify(services)) + +object Context: + + def initial: Context[Unit] = + new Context((), Frame.initial, Services.noop) + + def apply(frame: Frame): Context[Unit] = + new Context((), frame, Services.noop) + + def apply(services: Services): Context[Unit] = + new Context((), Frame.initial, services) + + def apply(frame: Frame, services: Services): Context[Unit] = + new Context((), frame, services) + + def apply[StartUpData]( + gameTime: GameTime, + dice: Dice, + inputState: InputState, + boundaryLocator: BoundaryLocator, + startUpData: StartUpData, + _captureScreen: Batch[ScreenCaptureConfig] => Batch[Either[String, AssetType.Image]] + ): Context[StartUpData] = + new Context( + startUpData, + Frame( + dice, + gameTime, + inputState + ), + Services( + boundaryLocator, + scala.util.Random(Dice.DefaultSeed), + _captureScreen + ) + ) + + /** The data that is specific to the current frame, such as the current time, input state, and dice. + */ + final class Frame( + val dice: Dice, + val time: GameTime, + val input: InputState + ): + def withDice(newDice: Dice): Frame = + new Frame(newDice, time, input) + + def withTime(newTime: GameTime): Frame = + new Frame(dice, newTime, input) + + def withInput(newInput: InputState): Frame = + new Frame(dice, time, newInput) + + object Frame: + def apply(dice: Dice, time: GameTime, input: InputState): Frame = + new Frame(dice, time, input) + + val initial: Frame = + new Frame(Dice.default, GameTime.zero, InputState.default) + + /** The services that are available to the game, such as the ability to capture the screen, measure text, find the + * bounds of anything on screen, or access a long running Random instance. Services are side-effecting, long running, + * and / or stateful. + */ + trait Services: + def bounds: Services.Bounds + def random: Services.Random + def screen: Services.Screen + + object Services: + + def apply( + boundaryLocator: BoundaryLocator, + _random: scala.util.Random, + _captureScreen: Batch[ScreenCaptureConfig] => Batch[Either[String, AssetType.Image]] + ): Services = + new Services: + def bounds: Services.Bounds = Bounds(boundaryLocator) + def random: Services.Random = Random(_random) + def screen: Services.Screen = Screen(_captureScreen) + + def noop: Services = + new Services: + def bounds: Bounds = Bounds.noop + def random: Random = Random.noop + def screen: Screen = Screen.noop + + trait Bounds: + def measureText(textBox: TextBox): Rectangle + + /** Safely finds the bounds of any given scene node, if the node has bounds. It is not possible to sensibly + * measure the bounds of some node types, such as clones, and some nodes are dependant on external data that may + * be missing. + */ + def find(sceneNode: SceneNode): Option[Rectangle] + + /** Finds the bounds or returns a `Rectangle` of size zero for convenience. + */ + def get(sceneNode: SceneNode): Rectangle + + def textAsLinesWithBounds(text: String, fontKey: FontKey, letterSpacing: Int, lineHeight: Int): Batch[TextLine] + + object Bounds: + def apply(boundaryLocator: BoundaryLocator): Bounds = + new Bounds: + def measureText(textBox: TextBox): Rectangle = boundaryLocator.measureText(textBox) + def find(sceneNode: SceneNode): Option[Rectangle] = boundaryLocator.findBounds(sceneNode) + def get(sceneNode: SceneNode): Rectangle = boundaryLocator.bounds(sceneNode) + def textAsLinesWithBounds( + text: String, + fontKey: FontKey, + letterSpacing: Int, + lineHeight: Int + ): Batch[TextLine] = + boundaryLocator.textAsLinesWithBounds(text, fontKey, letterSpacing, lineHeight) + + def noop: Bounds = + new Bounds: + def measureText(textBox: TextBox): Rectangle = Rectangle.zero + def find(sceneNode: SceneNode): Option[Rectangle] = None + def get(sceneNode: SceneNode): Rectangle = Rectangle.zero + def textAsLinesWithBounds( + text: String, + fontKey: FontKey, + letterSpacing: Int, + lineHeight: Int + ): Batch[TextLine] = + Batch.empty + + trait Random: + def nextBoolean: Boolean + def nextDouble: Double + def between(minInclusive: Double, maxExclusive: Double): Double + def nextFloat: Float + def between(minInclusive: Float, maxExclusive: Float): Float + def nextInt: Int + def nextInt(n: Int): Int + def between(minInclusive: Int, maxExclusive: Int): Int + def nextLong: Long + def nextLong(n: Long): Long + def between(minInclusive: Long, maxExclusive: Long): Long + def nextString(length: Int): String + def nextPrintableChar(): Char + def setSeed(seed: Long): Unit + def shuffle[A](xs: List[A]): List[A] + def shuffle[A](xs: Batch[A]): Batch[A] + def alphanumeric(take: Int): List[Char] + + object Random: + + def apply(_random: scala.util.Random): Random = + new Random: + def nextBoolean: Boolean = _random.nextBoolean() + def nextDouble: Double = _random.nextDouble() + def between(minInclusive: Double, maxExclusive: Double): Double = _random.between(minInclusive, maxExclusive) + def nextFloat: Float = _random.nextFloat() + def between(minInclusive: Float, maxExclusive: Float): Float = _random.between(minInclusive, maxExclusive) + def nextInt: Int = _random.nextInt() + def nextInt(n: Int): Int = _random.nextInt(n) + def between(minInclusive: Int, maxExclusive: Int): Int = _random.between(minInclusive, maxExclusive) + def nextLong: Long = _random.nextLong() + def nextLong(n: Long): Long = _random.nextLong(n) + def between(minInclusive: Long, maxExclusive: Long): Long = _random.between(minInclusive, maxExclusive) + def nextString(length: Int): String = _random.nextString(length) + def nextPrintableChar(): Char = _random.nextPrintableChar() + def setSeed(seed: Long): Unit = _random.setSeed(seed) + def shuffle[A](xs: List[A]): List[A] = _random.shuffle(xs) + def shuffle[A](xs: Batch[A]): Batch[A] = Batch.fromList(_random.shuffle(xs.toList)) + def alphanumeric(take: Int): List[Char] = _random.alphanumeric.take(take).toList + + val noop: Random = + new Random: + def nextBoolean: Boolean = false + def nextDouble: Double = 0.0 + def between(minInclusive: Double, maxExclusive: Double): Double = 0.0 + def nextFloat: Float = 0.0f + def between(minInclusive: Float, maxExclusive: Float): Float = 0.0f + def nextInt: Int = 0 + def nextInt(n: Int): Int = 0 + def between(minInclusive: Int, maxExclusive: Int): Int = 0 + def nextLong: Long = 0L + def nextLong(n: Long): Long = 0L + def between(minInclusive: Long, maxExclusive: Long): Long = 0L + def nextString(length: Int): String = "" + def nextPrintableChar(): Char = ' ' + def setSeed(seed: Long): Unit = () + def shuffle[A](xs: List[A]): List[A] = xs + def shuffle[A](xs: Batch[A]): Batch[A] = xs + def alphanumeric(take: Int): List[Char] = List.fill(take)(' ') + + trait Screen: + /** Capture the screen as a number of images, each with the specified configuration + * + * @param captureConfig + * The configurations to use when capturing the screen + * @return + * A batch containing either the captured images, or error messages + */ + def capture(captureConfig: Batch[ScreenCaptureConfig]): Batch[Either[String, AssetType.Image]] + + /** Capture the screen as an image, with the specified configuration + * + * @param captureConfig + * The configuration to use when capturing the screen + * @return + * The captured image, or an error message + */ + def capture(captureConfig: ScreenCaptureConfig): Either[String, AssetType.Image] + + object Screen: + + def apply( + _captureScreen: Batch[ScreenCaptureConfig] => Batch[Either[String, AssetType.Image]] + ): Screen = + new Screen: + def capture(captureConfig: Batch[ScreenCaptureConfig]): Batch[Either[String, AssetType.Image]] = + _captureScreen(captureConfig) + + def capture(captureConfig: ScreenCaptureConfig): Either[String, AssetType.Image] = + capture(Batch(captureConfig)).headOption match { + case Some(v) => v + case None => Left("Could not capture image") + } + + val noop: Screen = + new Screen: + def capture(captureConfig: Batch[ScreenCaptureConfig]): Batch[Either[String, AssetType.Image]] = + Batch.empty + def capture(captureConfig: ScreenCaptureConfig): Either[String, AssetType.Image] = + Left("Screen capture not supported in noop implementation") diff --git a/indigo/indigo/src/main/scala/indigo/shared/FrameContext.scala b/indigo/indigo/src/main/scala/indigo/shared/FrameContext.scala deleted file mode 100644 index 7520b892a..000000000 --- a/indigo/indigo/src/main/scala/indigo/shared/FrameContext.scala +++ /dev/null @@ -1,71 +0,0 @@ -package indigo.shared - -import indigo.platform.renderer.ScreenCaptureConfig -import indigo.shared.assets.AssetType -import indigo.shared.collections.Batch -import indigo.shared.datatypes.Rectangle -import indigo.shared.dice.Dice -import indigo.shared.events.InputState -import indigo.shared.input.Gamepad -import indigo.shared.input.Keyboard -import indigo.shared.input.Mouse -import indigo.shared.scenegraph.SceneNode -import indigo.shared.time.GameTime -import indigo.shared.time.Seconds - -/** The FrameContext is the context in which the current frame will be processed. In includes values that are unique to - * this frame, and also globally available services. - * - * @param gameTime - * A sampled instance of time that you should use everywhere that you need a time value. - * @param dice - * A pseudo-random number generator, made predictable / reproducible by being seeded on the current running time. - * @param inputState - * A snapshot of the state of the various input methods, also allows input mapping of combinations of inputs. - * @param boundaryLocator - * A service that can be interrogated for the calculated dimensions of screen elements. - * @param startUpData - * A read only reference to any and all data created during start up / set up. - */ -final class FrameContext[StartUpData]( - val gameTime: GameTime, - val dice: Dice, - val inputState: InputState, - val boundaryLocator: BoundaryLocator, - _startUpData: => StartUpData, - _captureScreen: Batch[ScreenCaptureConfig] => Batch[Either[String, AssetType.Image]] -): - - lazy val startUpData = _startUpData - - export gameTime.running - export gameTime.delta - export inputState.mouse - export inputState.keyboard - export inputState.gamepad - export inputState.pointers - export boundaryLocator.findBounds - export boundaryLocator.bounds - - /** Capture the screen as a number of images, each with the specified configuration - * - * @param captureConfig - * The configurations to use when capturing the screen - * @return - * A batch containing either the captured images, or error messages - */ - def captureScreen(captureConfig: Batch[ScreenCaptureConfig]): Batch[Either[String, AssetType.Image]] = - _captureScreen(captureConfig) - - /** Capture the screen as an image, with the specified configuration - * - * @param captureConfig - * The configuration to use when capturing the screen - * @return - * The captured image, or an error message - */ - def captureScreen(captureConfig: ScreenCaptureConfig): Either[String, AssetType.Image] = - captureScreen(Batch(captureConfig)).headOption match { - case Some(v) => v - case None => Left("Could not capture image") - } diff --git a/indigo/indigo/src/main/scala/indigo/shared/datatypes/Point.scala b/indigo/indigo/src/main/scala/indigo/shared/datatypes/Point.scala index 2ac560f8d..41d045b0c 100644 --- a/indigo/indigo/src/main/scala/indigo/shared/datatypes/Point.scala +++ b/indigo/indigo/src/main/scala/indigo/shared/datatypes/Point.scala @@ -131,10 +131,16 @@ object Point: ) def random(dice: Dice, max: Int): Point = - Point(dice.rollFromZero(max), dice.rollFromZero(max)) + Point( + if max <= 0 then 0 else dice.rollFromZero(max), + if max <= 0 then 0 else dice.rollFromZero(max) + ) def random(dice: Dice, max: Point): Point = - Point(dice.rollFromZero(max.x), dice.rollFromZero(max.y)) + Point( + if max.x <= 0 then 0 else dice.rollFromZero(max.x), + if max.y <= 0 then 0 else dice.rollFromZero(max.y) + ) def random(dice: Dice, min: Int, max: Int): Point = Point(dice.rollFromZero(max - min) + min, dice.rollFromZero(max - min) + min) diff --git a/indigo/indigo/src/main/scala/indigo/shared/datatypes/Size.scala b/indigo/indigo/src/main/scala/indigo/shared/datatypes/Size.scala index dd076c55b..ded733710 100644 --- a/indigo/indigo/src/main/scala/indigo/shared/datatypes/Size.scala +++ b/indigo/indigo/src/main/scala/indigo/shared/datatypes/Size.scala @@ -83,10 +83,16 @@ object Size: ) def random(dice: Dice, max: Int): Size = - Size(dice.rollFromZero(max), dice.rollFromZero(max)) + Size( + if max <= 0 then 0 else dice.rollFromZero(max), + if max <= 0 then 0 else dice.rollFromZero(max) + ) def random(dice: Dice, max: Size): Size = - Size(dice.rollFromZero(max.width), dice.rollFromZero(max.height)) + Size( + if max.width <= 0 then 0 else dice.rollFromZero(max.width), + if max.height <= 0 then 0 else dice.rollFromZero(max.height) + ) def random(dice: Dice, min: Int, max: Int): Size = Size(dice.rollFromZero(max - min) + min, dice.rollFromZero(max - min) + min) diff --git a/indigo/indigo/src/main/scala/indigo/shared/dice/Dice.scala b/indigo/indigo/src/main/scala/indigo/shared/dice/Dice.scala index 62bea348c..fe25f62cb 100644 --- a/indigo/indigo/src/main/scala/indigo/shared/dice/Dice.scala +++ b/indigo/indigo/src/main/scala/indigo/shared/dice/Dice.scala @@ -6,20 +6,22 @@ import indigo.shared.time.Millis import indigo.shared.time.Seconds import scala.annotation.tailrec -import scala.util.Random /** The Dice primitive supplies a consistent way to get psuedo-random values into your game. * - * A dice instance can be found in the FrameContext object with 'max int' sides, and every frame the dice's seed value - * is set to the current running time of the game in milliseconds. + * A dice instance can be found in the Context object with 'max int' sides, and every frame the dice's seed value is + * set to the current running time of the game in milliseconds. * * Dice also serve as a handy proxy to a number of functions found on a normal `Random` instance, like alphanumeric, * but with a predicatable seed. */ trait Dice: - /** The seed value of the dice. The dice supplied in the `FrameContext` has the seed set to the current running time - * of the game in milliseconds. + /** The seed value of the dice. The dice supplied in the `Context` has the seed set to the current running time of the + * game in milliseconds. + * + * If the seed value is 0, it is replaced with the `DefaultSeed` value in order for the underlying PRNG to work + * correctly. */ def seed: Long @@ -31,6 +33,14 @@ trait Dice: */ def roll(sides: Int): Int + /** Roll a Long from 1 to the number of sides on the dice (inclusive) + */ + def rollLong: Long + + /** Roll a Long from 1 to the specified number of sides (inclusive), using this dice instance as the seed. + */ + def rollLong(sides: Int): Long + /** Roll an Int from 0 to the number of sides on the dice (inclusive) */ def rollFromZero: Int @@ -67,14 +77,21 @@ trait Dice: */ def shuffle[A](items: List[A]): List[A] - def shuffle[A](items: Batch[A]): Batch[A] = - Batch.fromSeq(shuffle(items.toList)) + /** Shuffles a Batch of values into a random order + */ + def shuffle[A](items: Batch[A]): Batch[A] override def toString: String = - s"Dice(seed = ${seed.toString()})" + val seedValue = + if seed == 0L then s"${seed.toString} (substituted with ${Dice.DefaultSeed.toString})" + else seed.toString + + s"Dice(seed = $seedValue)" object Dice: + val DefaultSeed: Long = 42L + /** Construct a 'max int' sided dice using a time in seconds (converted to millis) as the seed. */ def fromSeconds(time: Seconds): Dice = @@ -85,12 +102,18 @@ object Dice: def fromMillis(time: Millis): Dice = Sides.MaxInt(time.toLong) - /** Construct a 'max int' sided dice from a given seed value. This is the default dice presented by the - * `FrameContext`. + /** Construct a 'max int' sided dice from a given seed value. This is the default dice presented by the `Context`. + * + * The implementation of this method uses the Xorshift algorithm to generate random numbers, which has a problem: A + * seed value of 0 will produce a value of 0. This is a known issue with the algorithm, and while it is not a bug, it + * will cause unexpected behaviour. Hence, if the seed value is 0, it is replaced with the `DefaultSeed` value. */ def fromSeed(seed: Long): Dice = Sides.MaxInt(seed) + def default: Dice = + Sides.MaxInt(DefaultSeed) + private val isPositive: Int => Boolean = _ > 0 @@ -127,7 +150,7 @@ object Dice: */ def loaded(fixedTo: Int): Dice = new Dice { - val seed: Long = 0 + val seed: Long = Dice.DefaultSeed def roll: Int = fixedTo @@ -135,6 +158,12 @@ object Dice: def roll(sides: Int): Int = fixedTo + def rollLong: Long = + fixedTo.toLong + + def rollLong(sides: Int): Long = + fixedTo.toLong + def rollFromZero: Int = fixedTo @@ -158,22 +187,27 @@ object Dice: def shuffle[A](items: List[A]): List[A] = items + + def shuffle[A](items: Batch[A]): Batch[A] = + items } /** Constructs a dice with a given number of sides and a seed value. */ def diceSidesN(sides: Int, seedValue: Long): Dice = new Dice { - val seed: Long = seedValue - val r: Random = new Random(seed) + // The Xorshift algorithm requires a seed value that is not 0, as a seed of 0 will produce a value of 0. + val seed: Long = if seedValue == 0L then DefaultSeed else seedValue + + private val r: RandomImpl = new RandomImpl(seed.toInt) /** Roll an Int from 1 to the number of sides on the dice (inclusive) * * @return */ def roll: Int = - roll(sides) + r.nextInt(sides) + 1 /** Roll an Int from 1 to the specified number of sides (inclusive) * @@ -181,7 +215,17 @@ object Dice: * @return */ def roll(sides: Int): Int = - r.nextInt(sanitise(sides)) + 1 + r.nextInt(sides) + 1 + + /** Roll a Long from 1 to the number of sides on the dice (inclusive) + */ + def rollLong: Long = + r.nextLong(sides) + 1 + + /** Roll a Long from 1 to the specified number of sides (inclusive), using this dice instance as the seed. + */ + def rollLong(sides: Int): Long = + r.nextLong(sides) + 1 /** Roll an Int from 0 to the number of sides on the dice (exclusive) * @@ -196,7 +240,7 @@ object Dice: * @return */ def rollFromZero(sides: Int): Int = - roll(sides) - 1 + r.nextInt(sides) /** Roll an Int from the range provided (inclusive) * @@ -229,7 +273,7 @@ object Dice: * @return */ def rollAlphaNumeric(length: Int): String = - r.alphanumeric.take(length).mkString + r.alphanumeric(length) /** Produces a random alphanumeric string 16 characters long * @@ -238,13 +282,21 @@ object Dice: def rollAlphaNumeric: String = rollAlphaNumeric(16) - /** Shuffles a list of values into a random order + /** Shuffles a Batch of values into a random order * * @param items * @return */ - def shuffle[A](items: List[A]): List[A] = + def shuffle[A](items: Batch[A]): Batch[A] = r.shuffle(items) + + /** Shuffles a List of values into a random order + * + * @param items + * @return + */ + def shuffle[A](items: List[A]): List[A] = + r.shuffle(Batch.fromList(items)).toList } /** Pre-constructed dice with a fixed number of sides, rolls are includive and start at 1, not 0. You need to provide @@ -319,3 +371,69 @@ object Dice: /** A sixteen-sided dice. */ def Sixteen(seed: Long): Dice = diceSidesN(16, seed) + + /** A simple random number generator based on the Xorshift algorithm. + */ + @SuppressWarnings(Array("scalafix:DisableSyntax.var")) + final class RandomImpl(_seed: Int) { + + private var seed: Int = _seed + + // Xorshift for 32-bit integers, the backbone of this random number generator + def nextInt(): Int = { + var x = seed + x ^= x << 13 + x ^= x >>> 17 + x ^= x << 5 + seed = x + x + } + + // Generate an Int up to a limit value + def nextInt(limit: Int): Int = + Math.abs(nextInt()) % limit + + // Generate a Long by combining two nextInt() calls + def nextLong(): Long = + (nextInt().toLong << 32) | (nextInt().toLong & 0xffffffffL) + + // Generate a Long up to a limit value + def nextLong(limit: Int): Long = + Math.abs(nextLong()) % limit + + // Generates a Float between 0.0 and 1.0 + // Use of the bit mask ensures the result is positive while preserving the number distribution. + def nextFloat(): Float = + (nextInt().toLong & 0xffffffffL) / (1L << 32).toFloat + + // Generates a Double between 0.0 and 1.0 + def nextDouble(): Double = + (nextLong() & Long.MaxValue) / (Long.MaxValue.toDouble + 1) + + // Generates a Boolean + def nextBoolean(): Boolean = + nextInt() % 2 == 0 + + // Fisher-Yates shuffle for List[A] + // More efficient but less readable than `items.sortBy(_ => nextInt())` + def shuffle[A](items: Batch[A]): Batch[A] = + val array = items.toJSArray + + for (i <- array.indices.reverse) { + val j = nextInt().abs % (i + 1) + val temp = array(i) + array(i) = array(j) + array(j) = temp + } + + Batch(array) + + // Generate an alphanumeric string of a given length + def alphanumeric(limit: Int): String = + val chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + (1 to limit).map(_ => chars(nextInt().abs % chars.length)).mkString + + // Method to reset the seed for reproducibility + def setSeed(newSeed: Int): Unit = + seed = newSeed + } diff --git a/indigo/indigo/src/main/scala/indigo/shared/subsystems/SubSystem.scala b/indigo/indigo/src/main/scala/indigo/shared/subsystems/SubSystem.scala index 51e15374c..0cafdc7e5 100644 --- a/indigo/indigo/src/main/scala/indigo/shared/subsystems/SubSystem.scala +++ b/indigo/indigo/src/main/scala/indigo/shared/subsystems/SubSystem.scala @@ -24,11 +24,11 @@ trait SubSystem[Model]: def initialModel: Outcome[SubSystemModel] def update( - context: SubSystemFrameContext[ReferenceData], + context: SubSystemContext[ReferenceData], model: SubSystemModel ): EventType => Outcome[SubSystemModel] def present( - context: SubSystemFrameContext[ReferenceData], + context: SubSystemContext[ReferenceData], model: SubSystemModel ): Outcome[SceneUpdateFragment] diff --git a/indigo/indigo/src/main/scala/indigo/shared/subsystems/SubSystemContext.scala b/indigo/indigo/src/main/scala/indigo/shared/subsystems/SubSystemContext.scala new file mode 100644 index 000000000..6bba5511b --- /dev/null +++ b/indigo/indigo/src/main/scala/indigo/shared/subsystems/SubSystemContext.scala @@ -0,0 +1,62 @@ +package indigo.shared.subsystems + +import indigo.platform.renderer.Renderer +import indigo.shared.BoundaryLocator +import indigo.shared.Context +import indigo.shared.datatypes.Rectangle +import indigo.shared.dice.Dice +import indigo.shared.events.InputState +import indigo.shared.input.Gamepad +import indigo.shared.input.Keyboard +import indigo.shared.input.Mouse +import indigo.shared.scenegraph.SceneNode +import indigo.shared.time.GameTime +import indigo.shared.time.Seconds + +/** Similar to `Context` but without access to start up data. The SubSystemContext is the context in which the current + * frame will be processed. In includes values that are unique to this frame, and also globally available services. + * + * @param gameTime + * A sampled instance of time that you should use everywhere that you need a time value. + * @param dice + * A psuedorandom number generator, made predicatable/reproducable by being seeded on the current running time. + * @param inputState + * A snapshot of the state of the various input methods, also allows input mapping of combinations of inputs. + * @param boundaryLocator + * A service that can be interogated for the calculated dimensions of screen elements. + */ +final case class SubSystemContext[ReferenceData]( + reference: ReferenceData, + frame: Context.Frame, + services: Context.Services +): + + def toContext: Context[Unit] = + new Context[Unit]((), frame, services) + + def withReference[A](newReference: A): SubSystemContext[A] = + new SubSystemContext( + newReference, + frame, + services + ) + + def unit: SubSystemContext[Unit] = + new SubSystemContext( + (), + frame, + services + ) + +object SubSystemContext: + + def fromContext[A](ctx: Context[A]): SubSystemContext[A] = + new SubSystemContext( + ctx.startUpData, + ctx.frame, + ctx.services + ) + + extension (ctx: Context[?]) + def forSubSystems: SubSystemContext[Unit] = + SubSystemContext.fromContext(ctx).unit diff --git a/indigo/indigo/src/main/scala/indigo/shared/subsystems/SubSystemFrameContext.scala b/indigo/indigo/src/main/scala/indigo/shared/subsystems/SubSystemFrameContext.scala deleted file mode 100644 index 794a500d1..000000000 --- a/indigo/indigo/src/main/scala/indigo/shared/subsystems/SubSystemFrameContext.scala +++ /dev/null @@ -1,72 +0,0 @@ -package indigo.shared.subsystems - -import indigo.platform.renderer.Renderer -import indigo.shared.BoundaryLocator -import indigo.shared.FrameContext -import indigo.shared.datatypes.Rectangle -import indigo.shared.dice.Dice -import indigo.shared.events.InputState -import indigo.shared.input.Gamepad -import indigo.shared.input.Keyboard -import indigo.shared.input.Mouse -import indigo.shared.scenegraph.SceneNode -import indigo.shared.time.GameTime -import indigo.shared.time.Seconds - -/** Similar to [FrameContext] but without access to start up data. The SubSystemFrameContext is the context in which the - * current frame will be processed. In includes values that are unique to this frame, and also globally available - * services. - * - * @param gameTime - * A sampled instance of time that you should use everywhere that you need a time value. - * @param dice - * A psuedorandom number generator, made predicatable/reproducable by being seeded on the current running time. - * @param inputState - * A snapshot of the state of the various input methods, also allows input mapping of combinations of inputs. - * @param boundaryLocator - * A service that can be interogated for the calculated dimensions of screen elements. - */ -final case class SubSystemFrameContext[ReferenceData]( - gameTime: GameTime, - dice: Dice, - inputState: InputState, - boundaryLocator: BoundaryLocator, - reference: ReferenceData -): - - val running: Seconds = gameTime.running - val delta: Seconds = gameTime.delta - - val mouse: Mouse = inputState.mouse - val keyboard: Keyboard = inputState.keyboard - val gamepad: Gamepad = inputState.gamepad - - def findBounds(sceneGraphNode: SceneNode): Option[Rectangle] = - boundaryLocator.findBounds(sceneGraphNode) - - def bounds(sceneGraphNode: SceneNode): Rectangle = - boundaryLocator.bounds(sceneGraphNode) - - def toFrameContext: FrameContext[Unit] = - new FrameContext[Unit]( - gameTime, - dice, - inputState, - boundaryLocator, - (), - Renderer.blackHole.captureScreen - ) - -object SubSystemFrameContext { - - extension (frameContext: FrameContext[?]) - def forSubSystems: SubSystemFrameContext[Unit] = - new SubSystemFrameContext( - frameContext.gameTime, - frameContext.dice, - frameContext.inputState, - frameContext.boundaryLocator, - () - ) - -} diff --git a/indigo/indigo/src/main/scala/indigo/shared/subsystems/SubSystemsRegister.scala b/indigo/indigo/src/main/scala/indigo/shared/subsystems/SubSystemsRegister.scala index 2261fe669..26ea3da99 100644 --- a/indigo/indigo/src/main/scala/indigo/shared/subsystems/SubSystemsRegister.scala +++ b/indigo/indigo/src/main/scala/indigo/shared/subsystems/SubSystemsRegister.scala @@ -5,7 +5,7 @@ import indigo.shared.Outcome import indigo.shared.collections.Batch import indigo.shared.events.GlobalEvent import indigo.shared.scenegraph.SceneUpdateFragment -import indigo.shared.subsystems.SubSystemFrameContext +import indigo.shared.subsystems.SubSystemContext import scalajs.js @@ -44,7 +44,7 @@ final class SubSystemsRegister[Model] { // @SuppressWarnings(Array("scalafix:DisableSyntax.asInstanceOf")) def update( - frameContext: SubSystemFrameContext[Unit], + context: SubSystemContext[Unit], gameModel: Model, globalEvents: js.Array[GlobalEvent] ): Outcome[SubSystemsRegister[Model]] = { @@ -67,7 +67,7 @@ final class SubSystemsRegister[Model] { filteredEvents.foldLeft(Outcome(model)) { (acc, e) => acc.flatMap { m => subSystem.update( - frameContext.copy(reference = subSystem.reference(gameModel)), + context.copy(reference = subSystem.reference(gameModel)), m )(e) } @@ -88,11 +88,11 @@ final class SubSystemsRegister[Model] { } // @SuppressWarnings(Array("scalafix:DisableSyntax.asInstanceOf")) - def present(frameContext: SubSystemFrameContext[Unit], gameModel: Model): Outcome[SceneUpdateFragment] = + def present(context: SubSystemContext[Unit], gameModel: Model): Outcome[SceneUpdateFragment] = registeredSubSystems .map { rss => rss.subSystem.present( - frameContext.copy(reference = rss.subSystem.reference(gameModel)), + context.copy(reference = rss.subSystem.reference(gameModel)), stateMap(rss.id).asInstanceOf[rss.subSystem.SubSystemModel] ) } diff --git a/indigo/indigo/src/test/scala/indigo/entry/StandardFrameProcessorTests.scala b/indigo/indigo/src/test/scala/indigo/entry/StandardFrameProcessorTests.scala index 1f5c9a7c6..a002a6fd4 100644 --- a/indigo/indigo/src/test/scala/indigo/entry/StandardFrameProcessorTests.scala +++ b/indigo/indigo/src/test/scala/indigo/entry/StandardFrameProcessorTests.scala @@ -4,8 +4,8 @@ import indigo.platform.assets.DynamicText import indigo.platform.renderer.Renderer import indigo.shared.AnimationsRegister import indigo.shared.BoundaryLocator +import indigo.shared.Context import indigo.shared.FontRegister -import indigo.shared.FrameContext import indigo.shared.Outcome import indigo.shared.collections.Batch import indigo.shared.datatypes.RGBA @@ -28,15 +28,10 @@ class StandardFrameProcessorTests extends munit.FunSuite { test("standard frame processor") { val outcome = standardFrameProcessor.run( - (), model, viewModel, - GameTime.zero, Batch(EventsOnlyEvent.Increment), - InputState.default, - Dice.loaded(0), - boundaryLocator, - Renderer.blackHole + Context.initial ) val outModel = outcome.unsafeGet._1 @@ -72,7 +67,7 @@ object TestFixtures { val viewModel: Int = 0 - val modelUpdate: (FrameContext[Unit], GameModel) => GlobalEvent => Outcome[GameModel] = + val modelUpdate: (Context[Unit], GameModel) => GlobalEvent => Outcome[GameModel] = (_, m) => { case EventsOnlyEvent.Increment => val newCount = m.count + 1 @@ -85,10 +80,10 @@ object TestFixtures { Outcome(m) } - val viewModelUpdate: (FrameContext[Unit], GameModel, Int) => GlobalEvent => Outcome[Int] = + val viewModelUpdate: (Context[Unit], GameModel, Int) => GlobalEvent => Outcome[Int] = (_, _, vm) => _ => Outcome(vm + 10).addGlobalEvents(EventsOnlyEvent.Increment) - val viewUpdate: (FrameContext[Unit], GameModel, Int) => Outcome[SceneUpdateFragment] = + val viewUpdate: (Context[Unit], GameModel, Int) => Outcome[SceneUpdateFragment] = (_, _, _) => Outcome(SceneUpdateFragment.empty.withBlendMaterial(BlendMaterial.Lighting(RGBA.Red.withAlpha(0.5)))) val standardFrameProcessor: StandardFrameProcessor[Unit, GameModel, Int] = diff --git a/indigo/indigo/src/test/scala/indigo/scenes/FakeFrameContext.scala b/indigo/indigo/src/test/scala/indigo/scenes/FakeFrameContext.scala index 0ddc97ac8..5a9ca883c 100644 --- a/indigo/indigo/src/test/scala/indigo/scenes/FakeFrameContext.scala +++ b/indigo/indigo/src/test/scala/indigo/scenes/FakeFrameContext.scala @@ -4,8 +4,8 @@ import indigo.platform.assets.DynamicText import indigo.platform.renderer.Renderer import indigo.shared.AnimationsRegister import indigo.shared.BoundaryLocator +import indigo.shared.Context import indigo.shared.FontRegister -import indigo.shared.FrameContext import indigo.shared.dice.Dice import indigo.shared.events.InputState import indigo.shared.time.GameTime @@ -13,34 +13,24 @@ import indigo.shared.time.Seconds object FakeFrameContext { - def context(sides: Int): FrameContext[Unit] = - new FrameContext[Unit]( - GameTime.zero, - Dice.loaded(sides), - InputState.default, - new BoundaryLocator(new AnimationsRegister, new FontRegister, new DynamicText), - (), - Renderer.blackHole.captureScreen - ) + def context(sides: Int): Context[Unit] = + Context.initial + .modifyFrame( + _.withDice(Dice.loaded(sides)) + ) - def context(sides: Int, time: Seconds): FrameContext[Unit] = - new FrameContext[Unit]( - GameTime.is(time), - Dice.loaded(sides), - InputState.default, - new BoundaryLocator(new AnimationsRegister, new FontRegister, new DynamicText), - (), - Renderer.blackHole.captureScreen - ) + def context(sides: Int, time: Seconds): Context[Unit] = + Context.initial + .modifyFrame( + _.withDice(Dice.loaded(sides)) + .withTime(GameTime.is(time)) + ) - def context(sides: Int, time: Seconds, delta: Seconds): FrameContext[Unit] = - new FrameContext[Unit]( - GameTime.withDelta(time, delta), - Dice.loaded(sides), - InputState.default, - new BoundaryLocator(new AnimationsRegister, new FontRegister, new DynamicText), - (), - Renderer.blackHole.captureScreen - ) + def context(sides: Int, time: Seconds, delta: Seconds): Context[Unit] = + Context.initial + .modifyFrame( + _.withDice(Dice.loaded(sides)) + .withTime(GameTime.withDelta(time, delta)) + ) } diff --git a/indigo/indigo/src/test/scala/indigo/scenes/TestScenes.scala b/indigo/indigo/src/test/scala/indigo/scenes/TestScenes.scala index d3c73be1a..886d96cfb 100644 --- a/indigo/indigo/src/test/scala/indigo/scenes/TestScenes.scala +++ b/indigo/indigo/src/test/scala/indigo/scenes/TestScenes.scala @@ -1,7 +1,7 @@ package indigo.scenes import indigo.* -import indigo.shared.FrameContext +import indigo.shared.Context import indigo.shared.events.EventFilters import indigo.shared.events.GlobalEvent import indigo.shared.scenegraph.SceneUpdateFragment diff --git a/indigo/indigo/src/test/scala/indigo/shared/datatypes/PointSpecification.scala b/indigo/indigo/src/test/scala/indigo/shared/datatypes/PointSpecification.scala index 6a1d854b7..d86c8227e 100644 --- a/indigo/indigo/src/test/scala/indigo/shared/datatypes/PointSpecification.scala +++ b/indigo/indigo/src/test/scala/indigo/shared/datatypes/PointSpecification.scala @@ -6,14 +6,14 @@ import org.scalacheck._ class PointSpecification extends Properties("Dice") { property("all random values are within the max range and >= 0") = Prop.forAll(Gen.choose(0, 500)) { max => - val dice = Dice.fromSeed(0) + val dice = Dice.default val value = Point.random(dice, max) value.x >= 0 && value.y >= 0 && value.x <= max && value.y <= max } property("all random values are within the max range (Point) and >= 0") = Prop.forAll(Gen.choose(0, 500)) { max => - val dice = Dice.fromSeed(0) + val dice = Dice.default val value = Point.random(dice, Point(max)) value.x >= 0 && value.y >= 0 && value.x <= Point(max).x && value.y <= Point(max).y @@ -21,7 +21,7 @@ class PointSpecification extends Properties("Dice") { property("all random values are within the min / max range") = Prop.forAll(Gen.choose(-500, 0), Gen.choose(0, 500)) { (min, max) => - val dice = Dice.fromSeed(0) + val dice = Dice.default val value = Point.random(dice, min, max) value.x >= min && value.y >= min && value.x <= max && value.y <= max @@ -29,7 +29,7 @@ class PointSpecification extends Properties("Dice") { property("all random values are within the min / max range (Point)") = Prop.forAll(Gen.choose(-500, 0), Gen.choose(0, 500)) { (min, max) => - val dice = Dice.fromSeed(0) + val dice = Dice.default val value = Point.random(dice, Point(min), Point(max)) value.x >= Point(min).x && value.y >= Point(min).y && value.x <= Point(max).x && value.y <= Point(max).y diff --git a/indigo/indigo/src/test/scala/indigo/shared/datatypes/SizeSpecification.scala b/indigo/indigo/src/test/scala/indigo/shared/datatypes/SizeSpecification.scala index 2b1a1462f..420f3c8da 100644 --- a/indigo/indigo/src/test/scala/indigo/shared/datatypes/SizeSpecification.scala +++ b/indigo/indigo/src/test/scala/indigo/shared/datatypes/SizeSpecification.scala @@ -6,14 +6,14 @@ import org.scalacheck._ class SizeSpecification extends Properties("Dice") { property("all random values are within the max range and >= 0") = Prop.forAll(Gen.choose(0, 500)) { max => - val dice = Dice.fromSeed(0) + val dice = Dice.default val value = Size.random(dice, max) value.width >= 0 && value.height >= 0 && value.width <= max && value.height <= max } property("all random values are within the max range (Size) and >= 0") = Prop.forAll(Gen.choose(0, 500)) { max => - val dice = Dice.fromSeed(0) + val dice = Dice.default val value = Size.random(dice, Size(max)) value.width >= 0 && value.height >= 0 && value.width <= Size(max).width && value.height <= Size(max).height @@ -21,7 +21,7 @@ class SizeSpecification extends Properties("Dice") { property("all random values are within the min / max range") = Prop.forAll(Gen.choose(-500, 0), Gen.choose(0, 500)) { (min, max) => - val dice = Dice.fromSeed(0) + val dice = Dice.default val value = Size.random(dice, min, max) value.width >= min && value.height >= min && value.width <= max && value.height <= max @@ -29,7 +29,7 @@ class SizeSpecification extends Properties("Dice") { property("all random values are within the min / max range (Size)") = Prop.forAll(Gen.choose(-500, 0), Gen.choose(0, 500)) { (min, max) => - val dice = Dice.fromSeed(0) + val dice = Dice.default val value = Size.random(dice, Size(min), Size(max)) value.width >= Size(min).width && value.height >= Size(min).height && value.width <= Size( diff --git a/indigo/indigo/src/test/scala/indigo/shared/dice/DiceTests.scala b/indigo/indigo/src/test/scala/indigo/shared/dice/DiceTests.scala index a6be621a1..3612cab15 100644 --- a/indigo/indigo/src/test/scala/indigo/shared/dice/DiceTests.scala +++ b/indigo/indigo/src/test/scala/indigo/shared/dice/DiceTests.scala @@ -15,7 +15,7 @@ class DiceTests extends munit.FunSuite { def almostEquals(d: Double, d2: Double, p: Double) = (d - d2).abs <= p test("diceSidesN") { - val roll: Int = Dice.diceSidesN(1, 0).roll(10) + val roll: Int = Dice.diceSidesN(1, 1000).roll(10) assertEquals(checkDice(roll, 10), true) } @@ -38,29 +38,111 @@ class DiceTests extends munit.FunSuite { } test("should be able to produce an alphanumeric string") { - val dice = Dice.fromSeed(0) + val dice = Dice.default val actual = dice.rollAlphaNumeric - // Psuedorandom! Seed of 0 produces "CCzLNHBFHuRvbI1i" val expected = - "CCzLNHBFHuRvbI1i" + "IoLMAKmKvLY5MSfL" assertEquals(actual.length(), 16) assertEquals(actual, expected) } test("shuffle") { - val dice = Dice.fromSeed(0) + val dice = Dice.default val actual = dice.shuffle(List(1, 2, 3, 4, 5)) - // Psuedorandom! Seed of 0 produces List(5, 3, 2, 4, 1) val expected = - List(5, 3, 2, 4, 1) + List(2, 5, 4, 1, 3) assertEquals(actual.length, 5) assertEquals(actual, expected) } + test("should be able to produce boolean values") { + val dice = Dice.default + val actual = List.fill(10)(dice.rollBoolean) + + val expected: List[Boolean] = + List( + true, true, false, true, true, true, true, true, false, false + ) + + assertEquals(actual, expected) + } + + test("should be able to produce Int values") { + val dice = Dice.default + val actual = List.fill(10)(dice.roll) + + val expected: List[Int] = + List( + 11355433, 1458948949, 476557060, 646921281, 534983741, 1441438135, 581500457, 1863322963, 1174750318, 1067267640 + ) + + assertEquals(actual, expected) + } + + test("should be able to produce Long values") { + val dice = Dice.default + val actual = List.fill(10)(dice.rollLong) + + val expected: List[Long] = + List( + 711245566, 306192841, 1776012994, 878840226, 1282232996, 1380324889, 1361527385, 705894190, 1128366901, + 1044750824 + ) + + assertEquals(actual, expected) + } + + test("should be able to produce Double values") { + val dice = Dice.default + val actual = List.fill(10)(dice.rollDouble) + + val expected: List[Double] = + List( + 0.005287785390537222, 0.2219141739650522, 0.7508787830991721, 0.729217749352642, 0.4529642552363186, + 0.39186976607271856, 0.4249471892873046, 0.9117737604549992, 0.25252137333629515, 0.6249562369807594 + ) + + assertEquals(actual, expected) + } + + test("should be able to produce Float values") { + val dice = Dice.default + val actual = List.fill(10)(dice.rollFloat) + + val expected: List[Float] = + List( + 0.002643892541527748f, 0.6603119969367981f, 0.1109570860862732f, 0.849376916885376f, 0.8754394054412842f, + 0.335610955953598f, 0.864608883857727f, 0.5661613345146179f, 0.7264821529388428f, 0.24849261343479156f + ) + + def floatsEqual(a: Float, b: Float): Boolean = + Math.abs(a - b) < 0.0001 + + assert(actual.length == expected.length) + assert( + actual.zip(expected).forall { case (a, b) => + floatsEqual(a, b) + } + ) + } + + test("should be able to roll within a range") { + val dice = Dice.default + val actual = List.fill(10)(dice.rollRange(10, 20)) + + val expected: List[Int] = + List( + 10, 16, 10, 15, 15, 14, 19, 16, 14, 19 + ) + + assert(actual.forall(i => i >= 10 && i <= 20)) + assertEquals(actual, expected) + } + test("all dice rolls have an approximately uniform distribution") { val diceSides = 63 val numRuns = 200_000_000 diff --git a/indigo/indigo/src/test/scala/indigo/shared/subsystems/FakeSubSystemFrameContext.scala b/indigo/indigo/src/test/scala/indigo/shared/subsystems/FakeSubSystemFrameContext.scala index 02d21f055..e2f3aef59 100644 --- a/indigo/indigo/src/test/scala/indigo/shared/subsystems/FakeSubSystemFrameContext.scala +++ b/indigo/indigo/src/test/scala/indigo/shared/subsystems/FakeSubSystemFrameContext.scala @@ -3,38 +3,38 @@ package indigo.shared.subsystems import indigo.platform.assets.DynamicText import indigo.shared.AnimationsRegister import indigo.shared.BoundaryLocator +import indigo.shared.Context import indigo.shared.FontRegister import indigo.shared.dice.Dice import indigo.shared.events.InputState -import indigo.shared.subsystems.SubSystemFrameContext +import indigo.shared.subsystems.SubSystemContext import indigo.shared.time.GameTime import indigo.shared.time.Seconds object FakeSubSystemFrameContext: - def context(sides: Int): SubSystemFrameContext[Unit] = - SubSystemFrameContext( - GameTime.zero, - Dice.loaded(sides), - InputState.default, - new BoundaryLocator(new AnimationsRegister, new FontRegister, new DynamicText), - () + def context(sides: Int): SubSystemContext[Unit] = + SubSystemContext.fromContext( + Context.initial + .modifyFrame( + _.withDice(Dice.loaded(sides)) + ) ) - def context(sides: Int, time: Seconds): SubSystemFrameContext[Unit] = - SubSystemFrameContext( - GameTime.is(time), - Dice.loaded(sides), - InputState.default, - new BoundaryLocator(new AnimationsRegister, new FontRegister, new DynamicText), - () + def context(sides: Int, time: Seconds): SubSystemContext[Unit] = + SubSystemContext.fromContext( + Context.initial + .modifyFrame( + _.withDice(Dice.loaded(sides)) + .withTime(GameTime.is(time)) + ) ) - def context(sides: Int, time: Seconds, delta: Seconds): SubSystemFrameContext[Unit] = - SubSystemFrameContext( - GameTime.withDelta(time, delta), - Dice.loaded(sides), - InputState.default, - new BoundaryLocator(new AnimationsRegister, new FontRegister, new DynamicText), - () + def context(sides: Int, time: Seconds, delta: Seconds): SubSystemContext[Unit] = + SubSystemContext.fromContext( + Context.initial + .modifyFrame( + _.withDice(Dice.loaded(sides)) + .withTime(GameTime.withDelta(time, delta)) + ) ) diff --git a/indigo/indigo/src/test/scala/indigo/shared/subsystems/PointsTrackerExample.scala b/indigo/indigo/src/test/scala/indigo/shared/subsystems/PointsTrackerExample.scala index 502b95116..8777adfae 100644 --- a/indigo/indigo/src/test/scala/indigo/shared/subsystems/PointsTrackerExample.scala +++ b/indigo/indigo/src/test/scala/indigo/shared/subsystems/PointsTrackerExample.scala @@ -7,7 +7,7 @@ import indigo.shared.events.GlobalEvent import indigo.shared.materials.Material import indigo.shared.scenegraph.SceneUpdateFragment import indigo.shared.scenegraph.Text -import indigo.shared.subsystems.SubSystemFrameContext +import indigo.shared.subsystems.SubSystemContext final case class PointsTrackerExample(num: Int, startingPoints: Int) extends SubSystem[Int] { type EventType = PointsTrackerEvent @@ -27,7 +27,7 @@ final case class PointsTrackerExample(num: Int, startingPoints: Int) extends Sub def initialModel: Outcome[Int] = Outcome(startingPoints) - def update(context: SubSystemFrameContext[Int], points: Int): PointsTrackerEvent => Outcome[Int] = { + def update(context: SubSystemContext[Int], points: Int): PointsTrackerEvent => Outcome[Int] = { case PointsTrackerEvent.Add(pts) => Outcome(points + pts + context.reference) @@ -36,7 +36,7 @@ final case class PointsTrackerExample(num: Int, startingPoints: Int) extends Sub .addGlobalEvents(GameOver) } - def present(context: SubSystemFrameContext[Int], points: Int): Outcome[SceneUpdateFragment] = + def present(context: SubSystemContext[Int], points: Int): Outcome[SceneUpdateFragment] = Outcome( SceneUpdateFragment(Text(points.toString, 0, 0, 1, FontKey(""), Material.Bitmap(AssetName("Testing")))) ) diff --git a/indigo/perf/src/main/scala/com/example/perf/PerfGame.scala b/indigo/perf/src/main/scala/com/example/perf/PerfGame.scala index e107a2121..d5ccf5e8a 100644 --- a/indigo/perf/src/main/scala/com/example/perf/PerfGame.scala +++ b/indigo/perf/src/main/scala/com/example/perf/PerfGame.scala @@ -92,13 +92,13 @@ object PerfGame extends IndigoDemo[Unit, Dude, DudeModel, Unit] { Outcome(res.getOrElse(Startup.Failure("Failed to load the dude"))) } - def updateModel(context: FrameContext[Dude], model: DudeModel): GlobalEvent => Outcome[DudeModel] = + def updateModel(context: Context[Dude], model: DudeModel): GlobalEvent => Outcome[DudeModel] = PerfModel.updateModel(model) - def updateViewModel(context: FrameContext[Dude], model: DudeModel, viewModel: Unit): GlobalEvent => Outcome[Unit] = + def updateViewModel(context: Context[Dude], model: DudeModel, viewModel: Unit): GlobalEvent => Outcome[Unit] = _ => Outcome(viewModel) - def present(context: FrameContext[Dude], model: DudeModel, viewModel: Unit): Outcome[SceneUpdateFragment] = + def present(context: Context[Dude], model: DudeModel, viewModel: Unit): Outcome[SceneUpdateFragment] = Outcome(PerfView.updateView(model)) } diff --git a/indigo/physics/src/main/scala/example/IndigoPhysics.scala b/indigo/physics/src/main/scala/example/IndigoPhysics.scala index 234547164..b2dac3131 100644 --- a/indigo/physics/src/main/scala/example/IndigoPhysics.scala +++ b/indigo/physics/src/main/scala/example/IndigoPhysics.scala @@ -27,7 +27,7 @@ object IndigoPhysics extends IndigoGame[Unit, Unit, Model, Unit]: ) def initialModel(startupData: Unit): Outcome[Model] = - Outcome(Model.initial(Dice.fromSeed(0))) + Outcome(Model.initial(Dice.default)) def initialViewModel(startupData: Unit, model: Model): Outcome[Unit] = Outcome(()) @@ -40,7 +40,7 @@ object IndigoPhysics extends IndigoGame[Unit, Unit, Model, Unit]: Outcome(Startup.Success(())) def updateModel( - context: FrameContext[Unit], + context: Context[Unit], model: Model ): GlobalEvent => Outcome[Model] = case KeyboardEvent.KeyUp(Key.PAGE_UP) => @@ -53,14 +53,14 @@ object IndigoPhysics extends IndigoGame[Unit, Unit, Model, Unit]: Outcome(model) def updateViewModel( - context: FrameContext[Unit], + context: Context[Unit], model: Model, viewModel: Unit ): GlobalEvent => Outcome[Unit] = _ => Outcome(viewModel) def present( - context: FrameContext[Unit], + context: Context[Unit], model: Model, viewModel: Unit ): Outcome[SceneUpdateFragment] = diff --git a/indigo/physics/src/main/scala/example/PhysicsScene.scala b/indigo/physics/src/main/scala/example/PhysicsScene.scala index 338d5f9f5..b90365ebe 100644 --- a/indigo/physics/src/main/scala/example/PhysicsScene.scala +++ b/indigo/physics/src/main/scala/example/PhysicsScene.scala @@ -30,7 +30,7 @@ trait PhysicsScene extends Scene[Unit, Model, Unit]: world: World[MyTag] ): GlobalEvent => Outcome[World[MyTag]] = case FrameTick => - world.update(context.delta) + world.update(context.frame.time.delta) case _ => Outcome(world) diff --git a/indigo/sandbox/src/main/scala/com/example/sandbox/SandboxGame.scala b/indigo/sandbox/src/main/scala/com/example/sandbox/SandboxGame.scala index 3b801d029..3875a3ebd 100644 --- a/indigo/sandbox/src/main/scala/com/example/sandbox/SandboxGame.scala +++ b/indigo/sandbox/src/main/scala/com/example/sandbox/SandboxGame.scala @@ -52,7 +52,7 @@ object SandboxGame extends IndigoGame[SandboxBootData, SandboxStartupData, Sandb val viewportHeight: Int = gameHeight * magnificationLevel // 256 def initialScene(bootData: SandboxBootData): Option[SceneName] = - Some(UiScene.name) + Some(ConfettiScene.name) def scenes(bootData: SandboxBootData): NonEmptyList[Scene[SandboxStartupData, SandboxGameModel, SandboxViewModel]] = NonEmptyList( @@ -195,13 +195,13 @@ object SandboxGame extends IndigoGame[SandboxBootData, SandboxStartupData, Sandb } def updateModel( - context: FrameContext[SandboxStartupData], + context: Context[SandboxStartupData], model: SandboxGameModel ): GlobalEvent => Outcome[SandboxGameModel] = SandboxModel.updateModel(model) def updateViewModel( - context: FrameContext[SandboxStartupData], + context: Context[SandboxStartupData], model: SandboxGameModel, viewModel: SandboxViewModel ): GlobalEvent => Outcome[SandboxViewModel] = { @@ -210,7 +210,7 @@ object SandboxGame extends IndigoGame[SandboxBootData, SandboxStartupData, Sandb case FrameTick => val updateOffset: Point = - context.inputState.gamepad.dpad match { + context.frame.input.gamepad.dpad match { case GamepadDPad(true, _, _, _) => viewModel.offset + Point(0, -1) @@ -262,7 +262,7 @@ object SandboxGame extends IndigoGame[SandboxBootData, SandboxStartupData, Sandb } def present( - context: FrameContext[SandboxStartupData], + context: Context[SandboxStartupData], model: SandboxGameModel, viewModel: SandboxViewModel ): Outcome[SceneUpdateFragment] = diff --git a/indigo/sandbox/src/main/scala/com/example/sandbox/SandboxView.scala b/indigo/sandbox/src/main/scala/com/example/sandbox/SandboxView.scala index aca0484ec..cfbb696f8 100644 --- a/indigo/sandbox/src/main/scala/com/example/sandbox/SandboxView.scala +++ b/indigo/sandbox/src/main/scala/com/example/sandbox/SandboxView.scala @@ -1,6 +1,6 @@ package com.example.sandbox -import indigo._ +import indigo.* object SandboxView: @@ -10,7 +10,7 @@ object SandboxView: model: SandboxGameModel, viewModel: SandboxViewModel, mouse: Mouse, - bl: BoundaryLocator + bl: Context.Services.Bounds ): SceneUpdateFragment = { mouse.isClickedAt.headOption match { case Some(position) => println("Mouse clicked at: " + position.toString()) @@ -92,13 +92,13 @@ object SandboxView: .moveTo(mouse.position.x, mouse.position.y) ) - def uiLayer(bl: BoundaryLocator): Batch[SceneNode] = + def uiLayer(bl: Context.Services.Bounds): Batch[SceneNode] = Batch( Text("AB!\n!C", 2, 2, 5, Fonts.fontKey, SandboxAssets.fontMaterial.withAlpha(0.5)).alignLeft, Text("AB!\n!C", 100, 2, 5, Fonts.fontKey, SandboxAssets.fontMaterial.withAlpha(0.5)).alignCenter, Text("AB!\n\n!C", 200, 2, 5, Fonts.fontKey, SandboxAssets.fontMaterial.withAlpha(0.5)).alignRight .withEventHandler { - case (txt, MouseEvent.Click(pt)) if bl.bounds(txt).contains(pt) => + case (txt, MouseEvent.Click(pt)) if bl.get(txt).contains(pt) => println("Clicked me!") None diff --git a/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/BoundsScene.scala b/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/BoundsScene.scala index e934f26de..ac17d3caf 100644 --- a/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/BoundsScene.scala +++ b/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/BoundsScene.scala @@ -53,31 +53,31 @@ object BoundsScene extends Scene[SandboxStartupData, SandboxGameModel, SandboxVi val graphic: Graphic[Material.Bitmap] = Graphic(Rectangle(0, 0, 40, 40), 1, BoundsAssets.junctionBoxMaterialOff) .moveTo(context.startUpData.viewportCenter) - .rotateTo(Radians.fromSeconds(context.running * speed)) + .rotateTo(Radians.fromSeconds(context.frame.time.running * speed)) val text: Text[Material.ImageEffects] = Text("boom!\nfish", Fonts.fontKey, SandboxAssets.fontMaterial).alignRight .moveTo(150, 100) - .rotateTo(Radians.fromSeconds(context.running * speed).negative) + .rotateTo(Radians.fromSeconds(context.frame.time.running * speed).negative) val shapeBox: Shape.Box = Shape .Box(Rectangle(0, 0, 60, 30), Fill.Color(RGBA.Red), Stroke(4, RGBA.Yellow)) .moveTo(180, 130) .withRef(10, 10) - .rotateTo(Radians.fromSeconds(context.running * speed)) + .rotateTo(Radians.fromSeconds(context.frame.time.running * speed)) val shapeCircle: Shape.Circle = Shape .Circle(Point(0), 20, Fill.Color(RGBA.Yellow), Stroke(2, RGBA.Cyan)) .moveTo(180, 80) - .rotateTo(Radians.fromSeconds(context.running * speed).invert) + .rotateTo(Radians.fromSeconds(context.frame.time.running * speed).invert) val shapeLine: Shape.Line = Shape .Line(Point(0), Point(30), Stroke(4, RGBA.Green)) .moveTo(180, 10) - .rotateTo(Radians.fromSeconds(context.running * speed)) + .rotateTo(Radians.fromSeconds(context.frame.time.running * speed)) val shapePolygon: Shape.Polygon = Shape @@ -88,13 +88,13 @@ object BoundsScene extends Scene[SandboxStartupData, SandboxGameModel, SandboxVi Point(70, 20) ) .moveTo(180, 150) - .rotateTo(Radians.fromSeconds(context.running * speed).invert) + .rotateTo(Radians.fromSeconds(context.frame.time.running * speed).invert) val sprite: Sprite[Material.ImageEffects] = context.startUpData.dude.sprite .scaleBy(2, 2) .moveTo(50, 120) - .rotateTo(Radians.fromSeconds(context.running * speed)) + .rotateTo(Radians.fromSeconds(context.frame.time.running * speed)) .withBindingKey("Sprite bounds anim".bindingKey) val group: Group = @@ -103,14 +103,14 @@ object BoundsScene extends Scene[SandboxStartupData, SandboxGameModel, SandboxVi Graphic(Rectangle(0, 0, 40, 40), 1, BoundsAssets.junctionBoxMaterialOff).moveBy(15, 15) ) .moveTo(200, 120) - .rotateTo(Radians.fromSeconds(context.running * speed)) + .rotateTo(Radians.fromSeconds(context.frame.time.running * speed)) .withRef(50, 50) val textBox = TextBox("Hello, World!", 100, 10).alignRight .withColor(RGBA.White) .withFontFamily(FontFamily(SandboxAssets.pixelFont.toString)) .moveTo(100, 50) - .rotateTo(Radians.fromSeconds(context.running * speed)) + .rotateTo(Radians.fromSeconds(context.frame.time.running * speed)) .withRef(50, 10) Outcome( @@ -121,19 +121,19 @@ object BoundsScene extends Scene[SandboxStartupData, SandboxGameModel, SandboxVi Shape.Box(graphic.bounds, Fill.None, Stroke(1, RGBA.Green)), sprite, Shape.Box( - context.findBounds(sprite).getOrElse(Rectangle.zero), + context.services.bounds.find(sprite).getOrElse(Rectangle.zero), Fill.None, Stroke(1, RGBA.Red) ), text, Shape.Box( - context.findBounds(text).getOrElse(Rectangle.zero), + context.services.bounds.find(text).getOrElse(Rectangle.zero), Fill.None, Stroke(1, RGBA.Cyan) ), Shape.Circle(text.position, 3, Fill.None, Stroke(2, RGBA.White)), Shape.Circle( - context.findBounds(text).getOrElse(Rectangle.zero).center, + context.services.bounds.find(text).getOrElse(Rectangle.zero).center, 5, Fill.None, Stroke(2, RGBA.White) @@ -142,37 +142,37 @@ object BoundsScene extends Scene[SandboxStartupData, SandboxGameModel, SandboxVi Shape.Circle(shapeBox.position, 3, Fill.None, Stroke(2, RGBA.White)), Shape .Circle( - context.findBounds(shapeBox).getOrElse(Rectangle.zero).center, + context.services.bounds.find(shapeBox).getOrElse(Rectangle.zero).center, 5, Fill.None, Stroke(2, RGBA.White) ), Shape.Box( - context.findBounds(shapeBox).getOrElse(Rectangle.zero), + context.services.bounds.find(shapeBox).getOrElse(Rectangle.zero), Fill.None, Stroke(1, RGBA.Magenta) ), shapeCircle, Shape.Box( - context.findBounds(shapeCircle).getOrElse(Rectangle.zero), + context.services.bounds.find(shapeCircle).getOrElse(Rectangle.zero), Fill.None, Stroke(1, RGBA.Magenta) ), shapeLine, Shape.Box( - context.findBounds(shapeLine).getOrElse(Rectangle.zero), + context.services.bounds.find(shapeLine).getOrElse(Rectangle.zero), Fill.None, Stroke(1, RGBA.Magenta) ), shapePolygon, Shape.Box( - context.findBounds(shapePolygon).getOrElse(Rectangle.zero), + context.services.bounds.find(shapePolygon).getOrElse(Rectangle.zero), Fill.None, Stroke(1, RGBA.Magenta) ), group, Shape.Box( - context.findBounds(group).getOrElse(Rectangle.zero), + context.services.bounds.find(group).getOrElse(Rectangle.zero), Fill.None, Stroke(1, RGBA.Yellow) ), diff --git a/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/BoxesScene.scala b/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/BoxesScene.scala index 39b192fbe..49c6d86eb 100644 --- a/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/BoxesScene.scala +++ b/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/BoxesScene.scala @@ -61,7 +61,7 @@ object BoxesScene extends Scene[SandboxStartupData, SandboxGameModel, SandboxVie ) val shapes: Batch[Shape[?]] = - val d = Dice.fromSeed(0) + val d = Dice.default Batch.fromList( (1 to 30).toList.flatMap { _ => List(makeLine(d), makeBox(d)) diff --git a/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/CameraScene.scala b/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/CameraScene.scala index 48c360e60..c20f949d8 100644 --- a/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/CameraScene.scala +++ b/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/CameraScene.scala @@ -72,14 +72,14 @@ object CameraScene extends Scene[SandboxStartupData, SandboxGameModel, SandboxVi ).modifyCamera { case c: Camera.Fixed => c.toLookAt - .lookAt(context.mouse.position) - .rotateBy(Radians.fromSeconds(context.running * 0.2)) + .lookAt(context.frame.input.mouse.position) + .rotateBy(Radians.fromSeconds(context.frame.time.running * 0.2)) .withZoom(Zoom(0.75)) case c => c - // _.moveTo(orbit.at(context.running * 0.3)) - // .withZoom(zoom.at(context.running * 0.35)) + // _.moveTo(orbit.at(context.frame.time.running * 0.3)) + // .withZoom(zoom.at(context.frame.time.running * 0.35)) } ) } diff --git a/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/CaptureScreenScene.scala b/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/CaptureScreenScene.scala index 1099bcce8..68ac30f4c 100644 --- a/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/CaptureScreenScene.scala +++ b/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/CaptureScreenScene.scala @@ -62,25 +62,26 @@ object CaptureScreenScene extends Scene[SandboxStartupData, SandboxGameModel, Sa viewModel: CaptureScreenSceneViewModel ): GlobalEvent => Outcome[CaptureScreenSceneViewModel] = { case MouseEvent.Click(x, y) if x >= 250 && x <= 266 && y >= 165 && y <= 181 => - val screenshots: Set[AssetType] = context + val screenshots: Set[AssetType] = // Capture 2 screenshots, 1 of the full screen and the other of the clipping rectangle // These are reduced by 0.125 so that it sits a quarter size of the real screen without further scaling - .captureScreen( - Batch( - // Get the full screen and scale it - ScreenCaptureConfig.default - .withName("screenshot1") - .withScale(0.5) - .withExcludeLayers(Batch(uiKey)), - // Get the screen inside the clipping rectangle and scale it. We don't remove the UI layer here - ScreenCaptureConfig.default - .withName("screenshot2") - .withScale(0.5) - .withCrop(clippingRect) + context.services.screen + .capture( + Batch( + // Get the full screen and scale it + ScreenCaptureConfig.default + .withName("screenshot1") + .withScale(0.5) + .withExcludeLayers(Batch(uiKey)), + // Get the screen inside the clipping rectangle and scale it. We don't remove the UI layer here + ScreenCaptureConfig.default + .withName("screenshot2") + .withScale(0.5) + .withCrop(clippingRect) + ) ) - ) - .collect { case Right(image) => image } - .toSet + .collect { case Right(image) => image } + .toSet // Output each image data URL to the console screenshots.foreach(a => IndigoLogger.info(a.asInstanceOf[AssetType.Image].path.toString())) @@ -104,7 +105,7 @@ object CaptureScreenScene extends Scene[SandboxStartupData, SandboxGameModel, Sa viewModel: CaptureScreenSceneViewModel ): Outcome[SceneUpdateFragment] = val screenshotScale = 0.3 - val viewPort = context.frameContext.startUpData.gameViewport.size / SandboxGame.magnificationLevel + val viewPort = context.startUpData.gameViewport.size / SandboxGame.magnificationLevel val bigRect = Rectangle((viewPort.width * screenshotScale).toInt, (viewPort.height * screenshotScale).toInt) val smallRect = Rectangle( 0, diff --git a/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/ConfettiScene.scala b/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/ConfettiScene.scala index 6ac3a56d2..032eb4acb 100644 --- a/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/ConfettiScene.scala +++ b/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/ConfettiScene.scala @@ -42,11 +42,11 @@ object ConfettiScene extends Scene[SandboxStartupData, SandboxGameModel, Sandbox ): GlobalEvent => Outcome[ConfettiModel] = case FrameTick => - val pos = Signal.Orbit(context.startUpData.viewportCenter * 2, 100).at(context.running * 0.5).toPoint + val pos = Signal.Orbit(context.startUpData.viewportCenter * 2, 100).at(context.frame.time.running * 0.5).toPoint Outcome( model .spawn( - context.dice, + context.services.random, pos.x, pos.y, spawnCount @@ -108,16 +108,16 @@ object ConfettiScene extends Scene[SandboxStartupData, SandboxGameModel, Sandbox ) final case class ConfettiModel(color: Int, particles: js.Array[js.Array[Particle]]): - def spawn(dice: Dice, x: Int, y: Int, count: Int): ConfettiModel = + def spawn(random: Context.Services.Random, x: Int, y: Int, count: Int): ConfettiModel = this.copy( particles = js.Array((0 until count).toJSArray.map { _ => Particle( x, y, - dice.rollFloat * 2.0f - 1.0f, - dice.rollFloat * 2.0f, + random.nextFloat * 2.0f - 1.0f, + random.nextFloat * 2.0f, color, - ((dice.rollFloat * 0.5f) + 0.5f) * 0.5f + ((random.nextFloat * 0.5f) + 0.5f) * 0.5f ) }) ++ particles ) diff --git a/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/CratesScene.scala b/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/CratesScene.scala index 524f5bea3..6697dbd32 100644 --- a/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/CratesScene.scala +++ b/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/CratesScene.scala @@ -77,5 +77,5 @@ object CratesScene extends Scene[SandboxStartupData, SandboxGameModel, SandboxVi ) ) ).addCloneBlanks(cloneBlanks) - .addLights(lights(move.at(context.running * 0.5))) + .addLights(lights(move.at(context.frame.time.running * 0.5))) ) diff --git a/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/LightsScene.scala b/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/LightsScene.scala index 3b6db13bb..e5503a6d8 100644 --- a/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/LightsScene.scala +++ b/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/LightsScene.scala @@ -81,7 +81,7 @@ object LightsScene extends Scene[SandboxStartupData, SandboxGameModel, SandboxVi DirectionLight(RGBA.Cyan.withAmount(0.1), RGBA.Cyan, Radians.zero), PointLight.default .withSpecular(RGBA.White) - .moveTo(context.mouse.position) + .moveTo(context.frame.input.mouse.position) .withColor(RGBA.Red.mix(RGBA.White, 0.1)) .withIntensity(2) .withFalloff(Falloff.smoothQuadratic.withRange(0, 80)), @@ -101,7 +101,7 @@ object LightsScene extends Scene[SandboxStartupData, SandboxGameModel, SandboxVi Signal .Orbit(context.startUpData.viewportCenter, 80, Radians(0)) .affectTime(0.1) - .at(context.running) + .at(context.frame.time.running) .toPoint ) .modifyFalloff(_.withRange(0, 50)), @@ -113,7 +113,7 @@ object LightsScene extends Scene[SandboxStartupData, SandboxGameModel, SandboxVi Signal .Orbit(context.startUpData.viewportCenter, 80, Radians(Radians.TAU.toDouble / 3)) .affectTime(0.1) - .at(context.running) + .at(context.frame.time.running) .toPoint ) .modifyFalloff(_.withRange(0, 50)), @@ -125,7 +125,7 @@ object LightsScene extends Scene[SandboxStartupData, SandboxGameModel, SandboxVi Signal .Orbit(context.startUpData.viewportCenter, 80, Radians(Radians.TAU.toDouble / 3 * 2)) .affectTime(0.1) - .at(context.running) + .at(context.frame.time.running) .toPoint ) .modifyFalloff(_.withRange(0, 50)) diff --git a/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/OriginalScene.scala b/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/OriginalScene.scala index 50a53f51f..ca6925368 100644 --- a/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/OriginalScene.scala +++ b/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/OriginalScene.scala @@ -49,11 +49,11 @@ object OriginalScene extends Scene[SandboxStartupData, SandboxGameModel, Sandbox ): Outcome[SceneUpdateFragment] = { val scene: SceneUpdateFragment = SandboxView - .updateView(model, viewModel, context.inputState.mouse, context.boundaryLocator) + .updateView(model, viewModel, context.frame.input.mouse, context.services.bounds) .addLayer( Layer( // viewModel.single.draw(gameTime, boundaryLocator) //|+| - viewModel.multi.draw(context.gameTime, context.boundaryLocator) + viewModel.multi.draw(context.frame.time, context.services.bounds) ).withDepth(Depth(1000)) ) diff --git a/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/PathFindingScene.scala b/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/PathFindingScene.scala index b96858e83..e00b53bd1 100644 --- a/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/PathFindingScene.scala +++ b/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/PathFindingScene.scala @@ -56,8 +56,8 @@ object PathFindingScene extends Scene[SandboxStartupData, SandboxGameModel, Sand ) else // increase randomly the value at a random point - val n = context.dice.roll(model.data.length) - 1 - val v = (model.data(n) + context.dice.roll(increase)) % 256 + val n = context.frame.dice.roll(model.data.length) - 1 + val v = (model.data(n) + context.frame.dice.roll(increase)) % 256 model.data.update(n, v) Outcome(model) diff --git a/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/RefractionScene.scala b/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/RefractionScene.scala index 5ead1bb1d..b61c09651 100644 --- a/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/RefractionScene.scala +++ b/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/RefractionScene.scala @@ -85,13 +85,13 @@ object RefractionScene extends Scene[SandboxStartupData, SandboxGameModel, Sandb .withBlending(Blending.Lighting(RGBA(0.2, 0.5, 0.3, 0.5))), Layer( distortion.moveTo(viewCenter + Point(50, 0)), - sliding.affectTime(0.3).at(context.gameTime.running) + sliding.affectTime(0.3).at(context.frame.time.running) ).withBlending( Refraction.blending( Signal.SmoothPulse .map(d => 0.25 * d) .affectTime(0.25) - .at(context.running) + .at(context.frame.time.running) ) ) ) diff --git a/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/ShapesScene.scala b/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/ShapesScene.scala index cfca335bc..5047cdcbf 100644 --- a/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/ShapesScene.scala +++ b/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/ShapesScene.scala @@ -64,16 +64,16 @@ object ShapesScene extends Scene[SandboxStartupData, SandboxGameModel, SandboxVi ): Outcome[SceneUpdateFragment] = { val circleGradient: Int = - Signal.SmoothPulse.map(d => 2 + (10 * d).toInt).at(context.running) + Signal.SmoothPulse.map(d => 2 + (10 * d).toInt).at(context.frame.time.running) val circlePosition: Point = - Signal.Orbit(Point(200, 150), 10).map(_.toPoint).affectTime(0.25).at(context.running) + Signal.Orbit(Point(200, 150), 10).map(_.toPoint).affectTime(0.25).at(context.frame.time.running) val lineThickness: Int = - Signal.SmoothPulse.map(d => (10 * d).toInt).at(context.running) + Signal.SmoothPulse.map(d => (10 * d).toInt).at(context.frame.time.running) val squareSize: Size = - val signal = Signal.SmoothPulse.map(d => (100 * d).toInt).affectTime(0.25).at(context.running) + val signal = Signal.SmoothPulse.map(d => (100 * d).toInt).affectTime(0.25).at(context.frame.time.running) Size( signal, @@ -115,21 +115,24 @@ object ShapesScene extends Scene[SandboxStartupData, SandboxGameModel, SandboxVi Fill.LinearGradient(Point(0), RGBA.Magenta, Point(45), RGBA.Cyan), Stroke(4, RGBA.Black.withAlpha(0.75)) )( - Point(10, 10) - (Math.cos(Radians.fromSeconds(context.running).toDouble) * 5).toInt, - Point(20, 70) + (Math.sin(Radians.fromSeconds(context.running * Seconds(1.2)).toDouble) * 10).toInt, - Point(90, 90) + (Math.sin(Radians.fromSeconds(context.running * Seconds(0.8)).toDouble) * 6).toInt, - Point(70, 20) - (Math.cos(Radians.fromSeconds(context.running * Seconds(1.5)).toDouble) * 8).toInt + Point(10, 10) - (Math.cos(Radians.fromSeconds(context.frame.time.running).toDouble) * 5).toInt, + Point(20, 70) + (Math + .sin(Radians.fromSeconds(context.frame.time.running * Seconds(1.2)).toDouble) * 10).toInt, + Point(90, 90) + (Math + .sin(Radians.fromSeconds(context.frame.time.running * Seconds(0.8)).toDouble) * 6).toInt, + Point(70, 20) - (Math + .cos(Radians.fromSeconds(context.frame.time.running * Seconds(1.5)).toDouble) * 8).toInt ) .moveTo(175, 10), blue, Shape.Box( - context.findBounds(blue).getOrElse(Rectangle.zero), + context.services.bounds.get(blue), Fill.None, Stroke(1, RGBA.Blue) ), // outline blue red, Shape.Box( - context.findBounds(red).getOrElse(Rectangle.zero), + context.services.bounds.get(red), Fill.None, Stroke(1, RGBA.Red) ), // outline red diff --git a/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/TextBoxScene.scala b/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/TextBoxScene.scala index fc584e745..fec8efb53 100644 --- a/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/TextBoxScene.scala +++ b/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/TextBoxScene.scala @@ -63,7 +63,7 @@ object TextBoxScene extends Scene[SandboxStartupData, SandboxGameModel, SandboxV 4, LightingAssets.junctionBoxMaterialOn.modifyLighting(_ => LightingModel.Unlit) ).moveTo(10, 10), - Shape.Box(context.findBounds(tb).getOrElse(Rectangle.zero), Fill.None).withStroke(Stroke(1, RGBA.Cyan)), + Shape.Box(context.services.bounds.get(tb), Fill.None).withStroke(Stroke(1, RGBA.Cyan)), tb.withDepth(Depth(3)).bold, tb.moveTo(50, 65).withDepth(Depth(3)), tb.moveTo(50, 80) @@ -73,7 +73,7 @@ object TextBoxScene extends Scene[SandboxStartupData, SandboxGameModel, SandboxV .withStroke(TextStroke(RGBA.Red, Pixels(1))), hello .modifyStyle(_.withSize(Pixels(20))) - .moveTo(Signal.Orbit(Point(180, 70), 20).affectTime(0.25).at(context.running).toPoint) + .moveTo(Signal.Orbit(Point(180, 70), 20).affectTime(0.25).at(context.frame.time.running).toPoint) .withDepth(Depth(2)), model.dude.dude.sprite.play().withDepth(Depth(1)), tb.moveTo(50, 120).withDepth(Depth(3)).alignLeft, diff --git a/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/TimelineScene.scala b/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/TimelineScene.scala index 43a610664..7b14c67ca 100644 --- a/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/TimelineScene.scala +++ b/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/TimelineScene.scala @@ -107,12 +107,12 @@ object TimelineScene extends Scene[SandboxStartupData, SandboxGameModel, Sandbox Outcome( SceneUpdateFragment( - tl(2.seconds).atOrLast(context.running)(crate).toBatch ++ + tl(2.seconds).atOrLast(context.frame.time.running)(crate).toBatch ++ spriteTimeline - .at(context.running)(dude) + .at(context.frame.time.running)(dude) .toBatch ++ clipTimeline - .at(context.running)(trafficLights) + .at(context.frame.time.running)(trafficLights) .toBatch ) ) diff --git a/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/UiScene.scala b/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/UiScene.scala index 208311a80..39a63f7aa 100644 --- a/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/UiScene.scala +++ b/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/UiScene.scala @@ -46,7 +46,7 @@ object UiScene extends Scene[SandboxStartupData, SandboxGameModel, SandboxViewMo viewModel: UiSceneViewModel ): GlobalEvent => Outcome[UiSceneViewModel] = case FrameTick => - viewModel.update(context.mouse, context.frameContext.inputState.pointers) + viewModel.update(context.frame.input.mouse, context.frame.input.pointers) case Log(msg) => println(msg) diff --git a/indigo/tyrian-indigo-bridge/src/main/scala/tyrian/TyrianSubSystem.scala b/indigo/tyrian-indigo-bridge/src/main/scala/tyrian/TyrianSubSystem.scala index 4924aec94..d66119019 100644 --- a/indigo/tyrian-indigo-bridge/src/main/scala/tyrian/TyrianSubSystem.scala +++ b/indigo/tyrian-indigo-bridge/src/main/scala/tyrian/TyrianSubSystem.scala @@ -7,7 +7,7 @@ import indigo.shared.events.FrameTick import indigo.shared.events.GlobalEvent import indigo.shared.scenegraph.SceneUpdateFragment import indigo.shared.subsystems.SubSystem -import indigo.shared.subsystems.SubSystemFrameContext +import indigo.shared.subsystems.SubSystemContext import indigo.shared.subsystems.SubSystemId import scala.collection.mutable @@ -52,7 +52,7 @@ final case class TyrianSubSystem[F[_]: Async, A, Model]( def initialModel: Outcome[Unit] = Outcome(()) - def update(context: SubSystemFrameContext[ReferenceData], model: Unit): GlobalEvent => Outcome[Unit] = + def update(context: SubSystemContext[ReferenceData], model: Unit): GlobalEvent => Outcome[Unit] = case TyrianEvent.Send(value) => bridge.eventTarget.dispatchEvent(TyrianIndigoBridge.BridgeToTyrian(indigoGameId, value)) Outcome(model) @@ -63,7 +63,7 @@ final case class TyrianSubSystem[F[_]: Async, A, Model]( case _ => Outcome(model) - def present(context: SubSystemFrameContext[ReferenceData], model: Unit): Outcome[SceneUpdateFragment] = + def present(context: SubSystemContext[ReferenceData], model: Unit): Outcome[SceneUpdateFragment] = Outcome(SceneUpdateFragment.empty) enum TyrianEvent extends GlobalEvent: diff --git a/indigo/tyrian-sandbox/src/main/scala/example/game/GameScene.scala b/indigo/tyrian-sandbox/src/main/scala/example/game/GameScene.scala index bc0e227fa..947f47847 100644 --- a/indigo/tyrian-sandbox/src/main/scala/example/game/GameScene.scala +++ b/indigo/tyrian-sandbox/src/main/scala/example/game/GameScene.scala @@ -42,8 +42,8 @@ final case class GameScene(clockwise: Boolean) extends Scene[Unit, Unit, Unit]: viewModel: Unit ): Outcome[SceneUpdateFragment] = val rotateAmount = - if clockwise then Radians.fromSeconds(context.running * 0.25) - else Radians(-Radians.fromSeconds(context.running * 0.25).toDouble) + if clockwise then Radians.fromSeconds(context.frame.time.running * 0.25) + else Radians(-Radians.fromSeconds(context.frame.time.running * 0.25).toDouble) Outcome( SceneUpdateFragment( diff --git a/indigo/tyrian-sandbox/src/main/scala/example/game/MyAwesomeGame.scala b/indigo/tyrian-sandbox/src/main/scala/example/game/MyAwesomeGame.scala index 4f17d63ac..e98c52420 100644 --- a/indigo/tyrian-sandbox/src/main/scala/example/game/MyAwesomeGame.scala +++ b/indigo/tyrian-sandbox/src/main/scala/example/game/MyAwesomeGame.scala @@ -51,7 +51,7 @@ final case class MyAwesomeGame(tyrianSubSystem: TyrianSubSystem[IO, String, Unit Outcome(Startup.Success(())) def updateModel( - context: FrameContext[Unit], + context: Context[Unit], model: Unit ): GlobalEvent => Outcome[Unit] = case tyrianSubSystem.TyrianEvent.Receive(msg) => @@ -67,14 +67,14 @@ final case class MyAwesomeGame(tyrianSubSystem: TyrianSubSystem[IO, String, Unit Outcome(model) def updateViewModel( - context: FrameContext[Unit], + context: Context[Unit], model: Unit, viewModel: Unit ): GlobalEvent => Outcome[Unit] = _ => Outcome(viewModel) def present( - context: FrameContext[Unit], + context: Context[Unit], model: Unit, viewModel: Unit ): Outcome[SceneUpdateFragment] =