diff --git a/game/src/main/scala/hexacraft/main/MainRouter.scala b/game/src/main/scala/hexacraft/main/MainRouter.scala index 06f0e634..d89f9b0e 100644 --- a/game/src/main/scala/hexacraft/main/MainRouter.scala +++ b/game/src/main/scala/hexacraft/main/MainRouter.scala @@ -13,7 +13,7 @@ import java.io.File object MainRouter { enum Event { - case SceneChanged(newScene: Scene) + case ChangeScene(route: SceneRoute) case QuitRequested } } @@ -25,13 +25,26 @@ class MainRouter( window: GameWindow, kb: GameKeyboard, audioSystem: AudioSystem -)(eventListener: Channel.Sender[MainRouter.Event]) { +) { - def route(sceneRoute: SceneRoute): Unit = { - eventListener.send(MainRouter.Event.SceneChanged(createScene(sceneRoute))) + def route(sceneRoute: SceneRoute): (Scene, Channel.Receiver[MainRouter.Event]) = { + import MainRouter.Event + + val (tx, rx) = Channel[Event]() + val scene = createScene( + sceneRoute, + r => tx.send(Event.ChangeScene(r)), + () => tx.send(Event.QuitRequested) + ) + + (scene, rx) } - private def createScene(sceneRoute: SceneRoute): Scene = sceneRoute match { + private def createScene( + sceneRoute: SceneRoute, + route: SceneRoute => Unit, + requestQuit: () => Unit + ): Scene = sceneRoute match { case SceneRoute.Main => import Menus.MainMenu.Event @@ -41,7 +54,7 @@ class MainRouter( case Event.Play => route(SceneRoute.WorldChooser) case Event.Multiplayer => route(SceneRoute.Multiplayer) case Event.Settings => route(SceneRoute.Settings) - case Event.Quit => eventListener.send(MainRouter.Event.QuitRequested) + case Event.Quit => requestQuit() } scene diff --git a/game/src/main/scala/hexacraft/main/MainWindow.scala b/game/src/main/scala/hexacraft/main/MainWindow.scala index 07bbc9d5..345a45dd 100644 --- a/game/src/main/scala/hexacraft/main/MainWindow.scala +++ b/game/src/main/scala/hexacraft/main/MainWindow.scala @@ -8,7 +8,7 @@ import hexacraft.infra.fs.FileSystem import hexacraft.infra.gpu.OpenGL import hexacraft.infra.window.* import hexacraft.renderer.VAO -import hexacraft.util.{Channel, Resource, Result} +import hexacraft.util.{Resource, Result} import hexacraft.world.World import org.joml.Vector2i @@ -41,7 +41,9 @@ class MainWindow( private val keyboard: GameKeyboard = new GameKeyboard.GlfwKeyboard(window) private var scene: Option[Scene] = None - private var nextScene: Option[Scene] = None + private var nextScene: Option[SceneRoute] = None + + private val router = makeSceneRouter() override def setCursorMode(cursorMode: CursorMode): Unit = { window.setCursorMode(cursorMode) @@ -55,6 +57,22 @@ class MainWindow( mouse.skipNextMouseMovedUpdate() } + private def switchSceneIfNeeded(): Unit = { + if nextScene.isDefined then { + val (s, rx) = router.route(nextScene.get) + nextScene = None + + rx.onEvent { + case MainRouter.Event.ChangeScene(newRoute) => + nextScene = Some(newRoute) + case MainRouter.Event.QuitRequested => + window.requestClose() + } + + setScene(s) + } + } + private def loop(): Unit = { var prevTime = System.nanoTime var ticks, frames, fps, titleTicker = 0 @@ -64,10 +82,7 @@ class MainWindow( val delta = ((currentTime - prevTime) * 1e-9 * 60).toInt val realPrevTime = currentTime - if nextScene.isDefined then { - setScene(nextScene.get) - nextScene = None - } + switchSceneIfNeeded() for _ <- 0 until delta do { tick() @@ -228,27 +243,16 @@ class MainWindow( } private def makeSceneRouter(): MainRouter = { - import MainRouter.Event - - val (tx, rx) = Channel[Event]() - val router = MainRouter(saveFolder, multiplayerEnabled, fs, this, keyboard, audioSystem)(tx) - - rx.onEvent { - case Event.SceneChanged(newScene) => nextScene = Some(newScene) - case Event.QuitRequested => tryQuit() - } - - router + MainRouter(saveFolder, multiplayerEnabled, fs, this, keyboard, audioSystem) } def run(): Unit = { initGL() audioSystem.init() - try { - val router = makeSceneRouter() - router.route(SceneRoute.Main) + nextScene = Some(SceneRoute.Main) + try { resetMousePos() loop() } finally { @@ -318,17 +322,10 @@ class MainWindow( } } - private def tryQuit(): Unit = { - window.requestClose() - } - private def destroy(): Unit = { if scene.isDefined then { scene.get.unload() } - if nextScene.isDefined then { - nextScene.get.unload() - } Resource.freeAllResources() } diff --git a/game/src/test/scala/hexacraft/main/MainRouterTest.scala b/game/src/test/scala/hexacraft/main/MainRouterTest.scala index 603518c0..4c941057 100644 --- a/game/src/test/scala/hexacraft/main/MainRouterTest.scala +++ b/game/src/test/scala/hexacraft/main/MainRouterTest.scala @@ -7,7 +7,7 @@ import hexacraft.infra.fs.FileSystem import hexacraft.infra.gpu.OpenGL import hexacraft.infra.window.* import hexacraft.math.GzipAlgorithm -import hexacraft.util.{Channel, Tracker} +import hexacraft.util.Tracker import hexacraft.world.WorldSettings import com.martomate.nbt.Nbt @@ -20,53 +20,50 @@ class MainRouterTest extends FunSuite { private val saveDirPath = Path.of("abc") - def performSingleRoute(route: SceneRoute, fs: FileSystem = FileSystem.createNull()): Scene = - val (tx, rx) = Channel[MainRouter.Event]() - val tracker = Tracker.fromRx(rx) - val router = new MainRouter(saveDirPath.toFile, false, fs, null, null, AudioSystem.createNull())(tx) - - router.route(route) - - assertEquals(tracker.events.size, 1) - val scene = tracker.events.collectFirst: - case MainRouter.Event.SceneChanged(s) => s - - assert(scene.isDefined) - scene.get + def performSingleRoute(route: SceneRoute, fs: FileSystem = FileSystem.createNull()): Scene = { + val router = new MainRouter(saveDirPath.toFile, false, fs, null, null, AudioSystem.createNull()) + val (s, _) = router.route(route) + s + } def performRouteAndSendEvents(route: SceneRoute, events: Seq[Event], fs: FileSystem)(using munit.Location - ): Seq[MainRouter.Event] = - val (tx, rx) = Channel[MainRouter.Event]() + ): MainRouter.Event = { + val router = new MainRouter(saveDirPath.toFile, true, fs, null, null, AudioSystem.createNull()) + + val (s, rx) = router.route(route) val tracker = Tracker.fromRx(rx) - val router = new MainRouter(saveDirPath.toFile, true, fs, null, null, AudioSystem.createNull())(tx) - router.route(route) + for e <- events do { + s.handleEvent(e) + } assertEquals(tracker.events.size, 1) - val scene1 = tracker.events.head.asInstanceOf[MainRouter.Event.SceneChanged].newScene - - for e <- events do scene1.handleEvent(e) - - assertEquals(tracker.events.size, 2) - tracker.events.drop(1) + tracker.events.head + } def performRouteAndClick(route: SceneRoute, clickAt: (Float, Float), fs: FileSystem = FileSystem.createNull())(using munit.Location - ): Seq[MainRouter.Event] = + ): MainRouter.Event = { val events = Seq( Event.MouseClickEvent(MouseButton.Left, MouseAction.Press, KeyMods.none, clickAt), Event.MouseClickEvent(MouseButton.Left, MouseAction.Release, KeyMods.none, clickAt) ) performRouteAndSendEvents(route, events, fs) + } - def assertSingleScene(events: Seq[MainRouter.Event], sceneIsOk: Scene => Boolean): Unit = + def assertSingleScene(events: Seq[MainRouter.Event], sceneIsOk: SceneRoute => Boolean): Unit = { val scene = events.collectFirst: - case MainRouter.Event.SceneChanged(s) => s + case MainRouter.Event.ChangeScene(s) => s assert(scene.isDefined) assert(sceneIsOk(scene.get)) + } + + def assertSceneChange(event: MainRouter.Event, sceneRoute: SceneRoute): Unit = { + assertEquals(event, MainRouter.Event.ChangeScene(sceneRoute)) + } def testMainMenu(): Unit = { test("Main routes to MainMenu") { @@ -75,23 +72,23 @@ class MainRouterTest extends FunSuite { } test("Main with click on Play routes to WorldChooserMenu") { - val events = performRouteAndClick(SceneRoute.Main, (0, 0.2f)) - assertSingleScene(events, _.isInstanceOf[Menus.WorldChooserMenu]) + val event = performRouteAndClick(SceneRoute.Main, (0, 0.2f)) + assertSceneChange(event, SceneRoute.WorldChooser) } test("Main with click on Multiplayer routes to MultiplayerMenu") { - val events = performRouteAndClick(SceneRoute.Main, (0, -0.1f)) - assertSingleScene(events, _.isInstanceOf[Menus.MultiplayerMenu]) + val event = performRouteAndClick(SceneRoute.Main, (0, -0.1f)) + assertSceneChange(event, SceneRoute.Multiplayer) } test("Main with click on Settings routes to SettingsMenu") { - val events = performRouteAndClick(SceneRoute.Main, (0, -0.4f)) - assertSingleScene(events, _.isInstanceOf[Menus.SettingsMenu]) + val event = performRouteAndClick(SceneRoute.Main, (0, -0.4f)) + assertSceneChange(event, SceneRoute.Settings) } test("Main with click on Quit causes a QuitRequest") { - val events = performRouteAndClick(SceneRoute.Main, (0, -0.8f)) - assertEquals(events, Seq(MainRouter.Event.QuitRequested)) + val event = performRouteAndClick(SceneRoute.Main, (0, -0.8f)) + assertEquals(event, MainRouter.Event.QuitRequested) } } @@ -102,13 +99,13 @@ class MainRouterTest extends FunSuite { } test("WorldChooser with click on Back to menu routes to MainMenu") { - val events = performRouteAndClick(SceneRoute.WorldChooser, (-0.1f, -0.8f)) - assertSingleScene(events, _.isInstanceOf[Menus.MainMenu]) + val event = performRouteAndClick(SceneRoute.WorldChooser, (-0.1f, -0.8f)) + assertSceneChange(event, SceneRoute.Main) } test("WorldChooser with click on New world routes to NewWorldMenu") { - val events = performRouteAndClick(SceneRoute.WorldChooser, (0.1f, -0.8f)) - assertSingleScene(events, _.isInstanceOf[Menus.NewWorldMenu]) + val event = performRouteAndClick(SceneRoute.WorldChooser, (0.1f, -0.8f)) + assertSceneChange(event, SceneRoute.NewWorld) } test("WorldChooser with click on one of the worlds routes to GameScene".ignore) { @@ -121,8 +118,8 @@ class MainRouterTest extends FunSuite { ) ) - val events = performRouteAndClick(SceneRoute.WorldChooser, (0, 0.6f), fs) - assertSingleScene(events, _.isInstanceOf[GameScene]) + val event = performRouteAndClick(SceneRoute.WorldChooser, (0, 0.6f), fs) + assertSceneChange(event, SceneRoute.Game(saveDirPath.toFile, WorldSettings(None, None, None), true, false, null)) } } @@ -133,8 +130,8 @@ class MainRouterTest extends FunSuite { } test("NewWorld with click on Cancel routes to WorldChooserMenu") { - val events = performRouteAndClick(SceneRoute.NewWorld, (-0.1f, -0.8f)) - assertSingleScene(events, _.isInstanceOf[Menus.WorldChooserMenu]) + val event = performRouteAndClick(SceneRoute.NewWorld, (-0.1f, -0.8f)) + assertSceneChange(event, SceneRoute.WorldChooser) } test("NewWorld with click on Create world routes to GameScene".ignore) { @@ -147,8 +144,8 @@ class MainRouterTest extends FunSuite { ) ) - val events = performRouteAndClick(SceneRoute.NewWorld, (0.1f, -0.8f)) - assertSingleScene(events, _.isInstanceOf[GameScene]) + val event = performRouteAndClick(SceneRoute.NewWorld, (0.1f, -0.8f)) + assertSceneChange(event, SceneRoute.Game(saveDirPath.toFile, WorldSettings(None, None, None), true, false, null)) } } @@ -159,18 +156,18 @@ class MainRouterTest extends FunSuite { } test("Multiplayer with click on Join routes to JoinWorldChooserMenu") { - val events = performRouteAndClick(SceneRoute.Multiplayer, (0, 0.2f)) - assertSingleScene(events, _.isInstanceOf[Menus.JoinWorldChooserMenu]) + val event = performRouteAndClick(SceneRoute.Multiplayer, (0, 0.2f)) + assertSceneChange(event, SceneRoute.JoinWorld) } test("Multiplayer with click on Host routes to HostWorldChooserMenu") { - val events = performRouteAndClick(SceneRoute.Multiplayer, (0, -0.1f)) - assertSingleScene(events, _.isInstanceOf[Menus.HostWorldChooserMenu]) + val event = performRouteAndClick(SceneRoute.Multiplayer, (0, -0.1f)) + assertSceneChange(event, SceneRoute.HostWorld) } test("Multiplayer with click on Back routes to MainMenu") { - val events = performRouteAndClick(SceneRoute.Multiplayer, (0, -0.8f)) - assertSingleScene(events, _.isInstanceOf[Menus.MainMenu]) + val event = performRouteAndClick(SceneRoute.Multiplayer, (0, -0.8f)) + assertSceneChange(event, SceneRoute.Main) } } @@ -181,8 +178,8 @@ class MainRouterTest extends FunSuite { } test("JoinWorld with click on Back routes to MultiplayerMenu") { - val events = performRouteAndClick(SceneRoute.JoinWorld, (-0.1f, -0.8f)) - assertSingleScene(events, _.isInstanceOf[Menus.MultiplayerMenu]) + val event = performRouteAndClick(SceneRoute.JoinWorld, (-0.1f, -0.8f)) + assertSceneChange(event, SceneRoute.Multiplayer) } } @@ -193,8 +190,8 @@ class MainRouterTest extends FunSuite { } test("HostWorld with click on Back routes to MultiplayerMenu") { - val events = performRouteAndClick(SceneRoute.HostWorld, (-0.1f, -0.8f)) - assertSingleScene(events, _.isInstanceOf[Menus.MultiplayerMenu]) + val event = performRouteAndClick(SceneRoute.HostWorld, (-0.1f, -0.8f)) + assertSceneChange(event, SceneRoute.Multiplayer) } } @@ -205,8 +202,8 @@ class MainRouterTest extends FunSuite { } test("Settings with click on Back routes to MainMenu") { - val events = performRouteAndClick(SceneRoute.Settings, (0, -0.4f)) - assertSingleScene(events, _.isInstanceOf[Menus.MainMenu]) + val event = performRouteAndClick(SceneRoute.Settings, (0, -0.4f)) + assertSceneChange(event, SceneRoute.Main) } } @@ -219,7 +216,7 @@ class MainRouterTest extends FunSuite { test("Game with Escape key and click on Back to menu routes to MainMenu".ignore) { val clickAt = (0f, -0.4f) - val events = performRouteAndSendEvents( + val event = performRouteAndSendEvents( SceneRoute.Game(saveDirPath.toFile, WorldSettings.none, true, false, null), Seq( Event.KeyEvent(KeyboardKey.Escape, 0, KeyAction.Press, KeyMods.none), @@ -228,7 +225,7 @@ class MainRouterTest extends FunSuite { ), FileSystem.createNull() ) - assertSingleScene(events, _.isInstanceOf[Menus.MainMenu]) + assertSceneChange(event, SceneRoute.Main) } }