Skip to content

Commit

Permalink
Refactor: Made the router easier to test
Browse files Browse the repository at this point in the history
  • Loading branch information
Martomate committed Apr 22, 2024
1 parent ee62aa8 commit 830c78f
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 92 deletions.
25 changes: 19 additions & 6 deletions game/src/main/scala/hexacraft/main/MainRouter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import java.io.File

object MainRouter {
enum Event {
case SceneChanged(newScene: Scene)
case ChangeScene(route: SceneRoute)
case QuitRequested
}
}
Expand All @@ -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

Expand All @@ -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
Expand Down
51 changes: 24 additions & 27 deletions game/src/main/scala/hexacraft/main/MainWindow.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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()
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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()
}
Expand Down
115 changes: 56 additions & 59 deletions game/src/test/scala/hexacraft/main/MainRouterTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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") {
Expand All @@ -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)
}
}

Expand All @@ -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) {
Expand All @@ -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))
}
}

Expand All @@ -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) {
Expand All @@ -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))
}
}

Expand All @@ -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)
}
}

Expand All @@ -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)
}
}

Expand All @@ -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)
}
}

Expand All @@ -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)
}
}

Expand All @@ -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),
Expand All @@ -228,7 +225,7 @@ class MainRouterTest extends FunSuite {
),
FileSystem.createNull()
)
assertSingleScene(events, _.isInstanceOf[Menus.MainMenu])
assertSceneChange(event, SceneRoute.Main)
}
}

Expand Down

0 comments on commit 830c78f

Please sign in to comment.