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.

248 lines
7.0 KiB

4 years ago
3 years ago
3 years ago
3 years ago
3 years ago
4 years ago
3 years ago
3 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
4 years ago
  1. package wow.doge.mygame.game
  2. import cats.effect.Resource
  3. import cats.effect.concurrent.Deferred
  4. import com.jme3.app.state.AppStateManager
  5. import com.jme3.asset.AssetManager
  6. import com.jme3.input.InputManager
  7. import com.jme3.scene.Node
  8. import com.jme3.scene.Spatial
  9. import com.jme3.system.AppSettings
  10. import com.softwaremill.tagging._
  11. import com.typesafe.scalalogging.{Logger => SLogger}
  12. import io.odin.Logger
  13. import monix.bio.IO
  14. import monix.bio.Task
  15. import monix.catnap.ConcurrentChannel
  16. import monix.catnap.ConsumerF
  17. import monix.catnap.Semaphore
  18. import monix.eval.Coeval
  19. import monix.execution.CancelablePromise
  20. import monix.execution.Scheduler
  21. import wow.doge.mygame.executors.Schedulers
  22. import wow.doge.mygame.game.subsystems.ui.JFxUI
  23. import wow.doge.mygame.implicits._
  24. import monix.execution.annotations.UnsafeBecauseImpure
  25. import monix.reactive.Observable
  26. sealed trait Error
  27. case object FlyCamNotExists extends Error
  28. object GameAppTags {
  29. sealed trait RootNode
  30. sealed trait GuiNode
  31. }
  32. class GameApp(logger: Logger[Task], val app: SimpleAppExt) {
  33. import Ops._
  34. def stateManager: Task[AppStateManager] = Task(app.getStateManager())
  35. def inputManager: Task[InputManager] = Task(app.getInputManager())
  36. def assetManager: Task[AssetManager] = Task(app.getAssetManager())
  37. def guiNode = Task(app.getGuiNode().taggedWith[GameAppTags.GuiNode])
  38. def addToGuiNode = guiNode.flatMap(rn => Task(new AddToNode(rn)))
  39. def flyCam =
  40. IO(app.getFlyByCamera()).onErrorHandleWith(_ =>
  41. IO.raiseError(FlyCamNotExists)
  42. )
  43. def camera = Task(app.getCamera())
  44. def viewPort = Task(app.getViewPort())
  45. def rootNode = Task(app.getRootNode().taggedWith[GameAppTags.RootNode])
  46. def rootNode2 =
  47. WrappedNode(app.getRootNode()).taggedWith[GameAppTags.RootNode]
  48. // def rootNode2 = SynchedObject(app.getRootNode())
  49. def addToRootNode = rootNode.flatMap(rn => Task(new AddToNode(rn)))
  50. def enqueue(cb: () => Unit) =
  51. app.enqueue(new Runnable {
  52. override def run() = cb()
  53. })
  54. def enqueueL[T](cb: () => T): Task[T] = app.enqueueL(cb)
  55. def start = Task(app.start())
  56. def stop = Task(app.stop())
  57. def scheduler = app.scheduler
  58. def jfxUI = JFxUI(app)
  59. }
  60. class WrappedNode private (node: Node) {
  61. // def +=(spat: Spatial) = lock.withPermit(Task(node.attachChild(spat)))
  62. def children: Observable[Spatial] = node.observableChildren
  63. def attachChild(n: Node): Task[Unit] = Task(node.attachChild(node))
  64. def add(wn: WrappedNode): Task[Unit] =
  65. Task(node.attachChild(wn.unsafeDelegate))
  66. /**
  67. * Get the underlying wrapped value
  68. */
  69. @UnsafeBecauseImpure
  70. def unsafeDelegate = node
  71. }
  72. object WrappedNode {
  73. def apply(name: String) = new WrappedNode(new Node(name))
  74. def apply(n: Node) = new WrappedNode(n)
  75. implicit class WrappedNodeOps(private val wn: WrappedNode) extends AnyVal {
  76. def +=(n: Node) = wn.attachChild(n)
  77. def +=(wn: WrappedNode) = wn.add(wn)
  78. }
  79. }
  80. object GameApp {
  81. def resource(
  82. logger: Logger[Task],
  83. jmeScheduler: Scheduler,
  84. schedulers: Schedulers
  85. ) =
  86. Resource.make(
  87. for {
  88. startSignal <- Task(CancelablePromise[Unit]())
  89. app <- Task(new SimpleAppExt(schedulers, startSignal))
  90. _ <- Task {
  91. val settings = new AppSettings(true)
  92. settings.setVSync(true)
  93. /**
  94. * disables the launcher
  95. * We'll be making our own launcher anyway
  96. */
  97. app.setShowSettings(false)
  98. app.setSettings(settings)
  99. }
  100. gameApp <- Task(new GameApp(logger, app))
  101. fib <- gameApp.start.executeOn(jmeScheduler).start
  102. _ <- Task.fromCancelablePromise(startSignal)
  103. } yield gameApp -> fib
  104. )(_._2.cancel)
  105. /**
  106. * Synchronization wrapper for a mutable object
  107. *
  108. * @param obj the mutable object
  109. * @param lock lock for synchronization
  110. */
  111. class SynchedObject[A](obj: A, lock: Semaphore[Task]) {
  112. def modify(f: A => Unit): Task[Unit] =
  113. lock.withPermit(Task(f(obj)))
  114. def flatModify(f: A => Task[Unit]): Task[Unit] =
  115. lock.withPermit(f(obj))
  116. def get: Task[A] = lock.withPermit(Task(obj))
  117. }
  118. object SynchedObject {
  119. def apply[A](obj: A) =
  120. Semaphore[Task](1).flatMap(lock => Task(new SynchedObject(obj, lock)))
  121. }
  122. }
  123. object Ops {
  124. final class AddToNode[T <: Node](private val node: T) extends AnyVal {
  125. /**
  126. * Pure version
  127. */
  128. def apply(spatial: Spatial)(implicit logger: Logger[Task]) =
  129. logger.debug(
  130. s"Request to add spatial with name ${spatial.getName()} to node ${node.getName()}"
  131. ) >> Task(node.attachChild(spatial))
  132. /**
  133. * Impure version
  134. */
  135. def apply(spatial: Spatial)(implicit logger: SLogger) =
  136. Coeval {
  137. logger.debug(
  138. s"Request to add spatial with name ${spatial.getName()} to node ${node.getName()}"
  139. )
  140. node.attachChild(spatial)
  141. }
  142. }
  143. }
  144. object SpawnSystem {
  145. sealed trait Result
  146. final case object Ok extends Result
  147. sealed trait Complete
  148. final case object Complete extends Complete
  149. sealed trait SpawnRequest
  150. final case class SpawnSpatial(nodeTask: Task[Node]) extends SpawnRequest
  151. final case class SpawnRequestWrapper(
  152. spawnRequest: SpawnRequest,
  153. result: Deferred[Task, Result]
  154. )
  155. def apply(logger: Logger[Task]) =
  156. for {
  157. spawnChannel <- ConcurrentChannel[Task].of[Complete, SpawnRequestWrapper]
  158. spawnSystem <- Task(new SpawnSystem(logger, spawnChannel))
  159. consumer <-
  160. spawnChannel.consume
  161. .use(consumer => spawnSystem.receive(consumer))
  162. .startAndForget
  163. } yield (spawnSystem)
  164. }
  165. class SpawnSystem(
  166. logger: Logger[Task],
  167. spawnChannel: ConcurrentChannel[
  168. Task,
  169. SpawnSystem.Complete,
  170. SpawnSystem.SpawnRequestWrapper
  171. ]
  172. ) {
  173. import SpawnSystem._
  174. for {
  175. spawnSystem <- SpawnSystem(logger)
  176. res <- spawnSystem.request(SpawnSpatial(Task(new Node("Test"))))
  177. } yield ()
  178. // val spawnChannel = ConcurrentChannel[Task].of[Result, SpawnRequest]
  179. private def receive(
  180. consumer: ConsumerF[Task, Complete, SpawnRequestWrapper]
  181. ): Task[Unit] =
  182. consumer.pull.flatMap {
  183. case Right(message) =>
  184. for {
  185. _ <-
  186. logger
  187. .debug(s"Received spawn request $message")
  188. _ <- handleSpawn(message)
  189. } yield receive(consumer)
  190. case Left(r) =>
  191. logger.info("Closing Spawn System")
  192. }
  193. private def handleSpawn(spawnRequestWrapper: SpawnRequestWrapper) =
  194. spawnRequestWrapper match {
  195. case SpawnRequestWrapper(spawnRequest, result) =>
  196. spawnRequest match {
  197. case SpawnSpatial(spatialTask) =>
  198. spatialTask.flatMap(spatial =>
  199. logger.debug(
  200. s"Spawning spatial with name ${spatial.getName()}"
  201. ) >> result
  202. .complete(Ok)
  203. )
  204. }
  205. }
  206. def request(spawnRequest: SpawnRequest) =
  207. for {
  208. d <- Deferred[Task, Result]
  209. _ <- spawnChannel.push(SpawnRequestWrapper(spawnRequest, d))
  210. res <- d.get
  211. } yield (res)
  212. def stop = spawnChannel.halt(Complete)
  213. }