Testing out JmonkeyEngine to make a game in Scala with Akka Actors within a pure FP layer
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

193 lines
5.2 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. package wow.doge.mygame.game
  2. import cats.effect.concurrent.Deferred
  3. import com.jme3.app.state.AppStateManager
  4. import com.jme3.asset.AssetManager
  5. import com.jme3.input.InputManager
  6. import com.jme3.scene.Node
  7. import com.jme3.scene.Spatial
  8. import com.softwaremill.tagging._
  9. import com.typesafe.scalalogging.{Logger => SLogger}
  10. import io.odin.Logger
  11. import monix.bio.IO
  12. import monix.bio.Task
  13. import monix.catnap.ConcurrentChannel
  14. import monix.catnap.ConsumerF
  15. import monix.catnap.Semaphore
  16. import monix.eval.Coeval
  17. import wow.doge.mygame.game.subsystems.ui.JFxUI
  18. sealed trait Error
  19. case object FlyCamNotExists extends Error
  20. object GameAppTags {
  21. sealed trait RootNode
  22. sealed trait GuiNode
  23. }
  24. class GameApp(logger: Logger[Task], val app: SimpleAppExt) {
  25. import Ops._
  26. def stateManager: Task[AppStateManager] = Task(app.getStateManager())
  27. def inputManager: Task[InputManager] = Task(app.getInputManager())
  28. def assetManager: Task[AssetManager] = Task(app.getAssetManager())
  29. def guiNode = Task(app.getGuiNode().taggedWith[GameAppTags.GuiNode])
  30. def addToGuiNode = guiNode.flatMap(rn => Task(new AddToNode(rn)))
  31. def flyCam =
  32. IO(app.getFlyByCamera()).onErrorHandleWith(_ =>
  33. IO.raiseError(FlyCamNotExists)
  34. )
  35. def camera = Task(app.getCamera())
  36. def viewPort = Task(app.getViewPort())
  37. def rootNode = Task(app.getRootNode().taggedWith[GameAppTags.RootNode])
  38. // def rootNode2 = SynchedObject(app.getRootNode())
  39. def addToRootNode = rootNode.flatMap(rn => Task(new AddToNode(rn)))
  40. def enqueue(cb: () => Unit) =
  41. app.enqueue(new Runnable {
  42. override def run() = cb()
  43. })
  44. def enqueueL[T](cb: () => T): Task[T] = app.enqueueL(cb)
  45. def start = Task(app.start())
  46. def stop = Task(app.stop())
  47. def scheduler = app.scheduler
  48. def jfxUI = JFxUI(app)
  49. }
  50. object GameApp {
  51. class WrappedNode(node: Node, lock: Semaphore[Task]) {
  52. def +=(spat: Spatial) = lock.withPermit(Task(node.attachChild(spat)))
  53. }
  54. /**
  55. * Synchronization wrapper for a mutable object
  56. *
  57. * @param obj the mutable object
  58. * @param lock lock for synchronization
  59. */
  60. class SynchedObject[A](obj: A, lock: Semaphore[Task]) {
  61. def modify(f: A => Unit): Task[Unit] =
  62. lock.withPermit(Task(f(obj)))
  63. def flatModify(f: A => Task[Unit]): Task[Unit] =
  64. lock.withPermit(f(obj))
  65. def get: Task[A] = lock.withPermit(Task(obj))
  66. }
  67. object SynchedObject {
  68. def apply[A](obj: A) =
  69. Semaphore[Task](1).flatMap(lock => Task(new SynchedObject(obj, lock)))
  70. }
  71. }
  72. object Ops {
  73. final class AddToNode[T <: Node](private val node: T) extends AnyVal {
  74. /**
  75. * Pure version
  76. */
  77. def apply(spatial: Spatial)(implicit logger: Logger[Task]) =
  78. logger.debug(
  79. s"Request to add spatial with name ${spatial.getName()} to node ${node.getName()}"
  80. ) >> Task(node.attachChild(spatial))
  81. /**
  82. * Impure version
  83. */
  84. def apply(spatial: Spatial)(implicit logger: SLogger) =
  85. Coeval {
  86. logger.debug(
  87. s"Request to add spatial with name ${spatial.getName()} to node ${node.getName()}"
  88. )
  89. node.attachChild(spatial)
  90. }
  91. }
  92. }
  93. object SpawnSystem {
  94. sealed trait Result
  95. final case object Ok extends Result
  96. sealed trait Complete
  97. final case object Complete extends Complete
  98. sealed trait SpawnRequest
  99. final case class SpawnSpatial(nodeTask: Task[Node]) extends SpawnRequest
  100. final case class SpawnRequestWrapper(
  101. spawnRequest: SpawnRequest,
  102. result: Deferred[Task, Result]
  103. )
  104. def apply(logger: Logger[Task]) =
  105. for {
  106. spawnChannel <- ConcurrentChannel[Task].of[Complete, SpawnRequestWrapper]
  107. spawnSystem <- Task(new SpawnSystem(logger, spawnChannel))
  108. consumer <-
  109. spawnChannel.consume
  110. .use(consumer => spawnSystem.receive(consumer))
  111. .startAndForget
  112. } yield (spawnSystem)
  113. }
  114. class SpawnSystem(
  115. logger: Logger[Task],
  116. spawnChannel: ConcurrentChannel[
  117. Task,
  118. SpawnSystem.Complete,
  119. SpawnSystem.SpawnRequestWrapper
  120. ]
  121. ) {
  122. import SpawnSystem._
  123. for {
  124. spawnSystem <- SpawnSystem(logger)
  125. res <- spawnSystem.request(SpawnSpatial(Task(new Node("Test"))))
  126. } yield ()
  127. // val spawnChannel = ConcurrentChannel[Task].of[Result, SpawnRequest]
  128. private def receive(
  129. consumer: ConsumerF[Task, Complete, SpawnRequestWrapper]
  130. ): Task[Unit] =
  131. consumer.pull.flatMap {
  132. case Right(message) =>
  133. for {
  134. _ <-
  135. logger
  136. .debug(s"Received spawn request $message")
  137. _ <- handleSpawn(message)
  138. } yield receive(consumer)
  139. case Left(r) =>
  140. logger.info("Closing Spawn System")
  141. }
  142. private def handleSpawn(spawnRequestWrapper: SpawnRequestWrapper) =
  143. spawnRequestWrapper match {
  144. case SpawnRequestWrapper(spawnRequest, result) =>
  145. spawnRequest match {
  146. case SpawnSpatial(spatialTask) =>
  147. spatialTask.flatMap(spatial =>
  148. logger.debug(
  149. s"Spawning spatial with name ${spatial.getName()}"
  150. ) >> result
  151. .complete(Ok)
  152. )
  153. }
  154. }
  155. def request(spawnRequest: SpawnRequest) =
  156. for {
  157. d <- Deferred[Task, Result]
  158. _ <- spawnChannel.push(SpawnRequestWrapper(spawnRequest, d))
  159. res <- d.get
  160. } yield (res)
  161. def stop = spawnChannel.halt(Complete)
  162. }