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.

289 lines
8.3 KiB

4 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 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
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
4 years ago
3 years ago
3 years ago
  1. package wow.doge.mygame.game
  2. import scala.concurrent.duration._
  3. import akka.actor.typed.ActorRef
  4. import akka.actor.typed.Behavior
  5. import akka.actor.typed.Props
  6. import akka.actor.typed.SpawnProtocol
  7. import akka.actor.typed.scaladsl.AskPattern._
  8. import akka.util.Timeout
  9. import cats.effect.Resource
  10. import cats.effect.concurrent.Deferred
  11. import com.jme3.app.state.AppStateManager
  12. import com.jme3.bullet.BulletAppState
  13. import com.jme3.input.InputManager
  14. import com.jme3.scene.Node
  15. import com.jme3.scene.Spatial
  16. import com.jme3.system.AppSettings
  17. import com.softwaremill.tagging._
  18. import com.typesafe.scalalogging.{Logger => SLogger}
  19. import io.odin.Logger
  20. import monix.bio.Fiber
  21. import monix.bio.Task
  22. import monix.bio.UIO
  23. import monix.catnap.ConcurrentChannel
  24. import monix.catnap.ConsumerF
  25. import monix.eval.Coeval
  26. import monix.execution.CancelableFuture
  27. import monix.execution.CancelablePromise
  28. import monix.execution.Scheduler
  29. import wow.doge.mygame.executors.JMERunner
  30. import wow.doge.mygame.executors.Schedulers
  31. import wow.doge.mygame.utils.GenericTimerActor
  32. import wow.doge.mygame.game.subsystems.ui.JFxUI
  33. import wow.doge.mygame.implicits._
  34. import wow.doge.mygame.utils.AkkaUtils
  35. import wow.doge.mygame.utils.wrappers.jme._
  36. import wow.doge.mygame.Dispatchers
  37. object GameAppTags {
  38. sealed trait RootNode
  39. sealed trait GuiNode
  40. }
  41. class GameApp private[game] (
  42. logger: Logger[Task],
  43. app: SimpleAppExt,
  44. gameActor: ActorRef[TestGameActor.Command],
  45. gameSpawnProtocol: ActorRef[SpawnProtocol.Command]
  46. ) {
  47. def stateManager: Task[AppStateManager] = Task(app.getStateManager())
  48. def inputManager: Task[InputManager] = Task(app.getInputManager())
  49. def assetManager = new AssetManager(app.getAssetManager())
  50. def guiNode = Task(app.getGuiNode().taggedWith[GameAppTags.GuiNode])
  51. val guiNode2 = AppNode[Task](app.getGuiNode()).taggedWith[GameAppTags.GuiNode]
  52. def flyCam = Option(app.getFlyByCamera())
  53. def camera = Task(app.getCamera())
  54. def viewPort = Task(app.getViewPort())
  55. // def rootNode = Task(app.getRootNode().taggedWith[GameAppTags.RootNode])
  56. val rootNode =
  57. AppNode[Task](app.getRootNode()).taggedWith[GameAppTags.RootNode]
  58. val physicsSpace = new PhysicsSpace[Task](app.bulletAppState.physicsSpace)
  59. def enqueue(cb: () => Unit) = app.enqueueR(() => cb())
  60. def enqueueL[T](cb: () => T): Task[T] = app.enqueueL(cb)
  61. def spawnGameActor[T](
  62. behavior: Behavior[T],
  63. actorName: String,
  64. props: Props = Dispatchers.jmeDispatcher
  65. )(implicit scheduler: akka.actor.typed.Scheduler) =
  66. AkkaUtils.spawnActorL(behavior, actorName, props)(
  67. 2.seconds,
  68. scheduler,
  69. gameSpawnProtocol
  70. )
  71. def scheduler = app.scheduler
  72. def jfxUI = JFxUI(app)
  73. }
  74. class GameAppResource(
  75. logger: Logger[Task],
  76. jmeThread: Scheduler,
  77. schedulers: Schedulers
  78. )(implicit
  79. timeout: Timeout,
  80. scheduler: akka.actor.typed.Scheduler,
  81. spawnProtocol: ActorRef[SpawnProtocol.Command]
  82. ) {
  83. def resource: Resource[Task, (GameApp, Fiber[Throwable, Unit])] =
  84. Resource.make {
  85. lazy val bullet = new BulletAppState
  86. for {
  87. app <- Task(new SimpleAppExt(schedulers, bullet))
  88. _ <- UIO(JMERunner.runner = Some(app.enqueue _))
  89. _ <- Task {
  90. val settings = new AppSettings(true)
  91. settings.setVSync(true)
  92. /**
  93. * disables the launcher
  94. * We'll be making our own launcher anyway
  95. */
  96. app.setShowSettings(false)
  97. app.setSettings(settings)
  98. }
  99. fib <- Task(app.start).executeOn(jmeThread).start
  100. _ <- Task.deferFuture(app.started)
  101. testGameActor <- AkkaUtils.spawnActorL(
  102. new TestGameActor.Props().create,
  103. "testGameActor",
  104. Dispatchers.jmeDispatcher
  105. )
  106. sp <- testGameActor.askL(TestGameActor.GetSpawnProtocol(_))
  107. gameApp <- Task(new GameApp(logger, app, testGameActor, sp))
  108. _ <- Task {
  109. val fut = () => testGameActor.ask(TestGameActor.Stop).flatten
  110. app.cancelToken = Some(fut)
  111. }
  112. } yield (gameApp, fib)
  113. } {
  114. case (gameApp, fib) =>
  115. fib.cancel >> UIO(JMERunner.runner = None)
  116. }
  117. }
  118. object GameApp {}
  119. import akka.actor.typed.scaladsl.Behaviors
  120. import akka.actor.typed.scaladsl.ActorContext
  121. object TestGameActor {
  122. sealed trait Command
  123. case object Ping extends Command
  124. case class Stop(stopSignal: ActorRef[CancelableFuture[Unit]]) extends Command
  125. case class GetSpawnProtocol(
  126. replyTo: ActorRef[ActorRef[SpawnProtocol.Command]]
  127. ) extends Command
  128. import scala.concurrent.duration._
  129. class Props() {
  130. def create =
  131. Behaviors.setup[Command] { ctx =>
  132. ctx.spawn(
  133. GenericTimerActor
  134. .Props(ctx.self, Ping, 1000.millis)
  135. .behavior,
  136. "pingTimer"
  137. ) ! GenericTimerActor.Start
  138. new TestGameActor(ctx, this).receive
  139. }
  140. }
  141. }
  142. class TestGameActor(
  143. ctx: ActorContext[TestGameActor.Command],
  144. props: TestGameActor.Props
  145. ) {
  146. import TestGameActor._
  147. val stopPromise = CancelablePromise[Unit]()
  148. def receive =
  149. Behaviors
  150. .receiveMessage[Command] {
  151. case Stop(replyTo) =>
  152. ctx.log.infoP("stopping")
  153. replyTo ! stopPromise.future
  154. Behaviors.stopped
  155. case Ping =>
  156. ctx.log.debugP("ping")
  157. Behaviors.same
  158. case GetSpawnProtocol(replyTo) =>
  159. val sp = ctx.spawn(SpawnProtocol(), "gameSpawnProtocol")
  160. replyTo ! sp
  161. Behaviors.same
  162. }
  163. .receiveSignal {
  164. case (_, akka.actor.typed.PostStop) =>
  165. stopPromise.success(())
  166. Behaviors.same
  167. }
  168. }
  169. object Ops {
  170. final class AddToNode[T <: Node](private val node: T) extends AnyVal {
  171. /**
  172. * Pure version
  173. */
  174. def apply(spatial: Spatial)(implicit logger: Logger[Task]) =
  175. logger.debug(
  176. s"Request to add spatial with name ${spatial.getName()} to node ${node.getName()}"
  177. ) >> Task(node.attachChild(spatial))
  178. /**
  179. * Impure version
  180. */
  181. def apply(spatial: Spatial)(implicit logger: SLogger) =
  182. Coeval {
  183. logger.debug(
  184. s"Request to add spatial with name ${spatial.getName()} to node ${node.getName()}"
  185. )
  186. node.attachChild(spatial)
  187. }
  188. }
  189. }
  190. object SpawnSystem {
  191. sealed trait Result
  192. final case object Ok extends Result
  193. sealed trait Complete
  194. final case object Complete extends Complete
  195. sealed trait SpawnRequest
  196. final case class SpawnSpatial(nodeTask: Task[Node]) extends SpawnRequest
  197. final case class SpawnRequestWrapper(
  198. spawnRequest: SpawnRequest,
  199. result: Deferred[Task, Result]
  200. )
  201. def apply(logger: Logger[Task]) =
  202. for {
  203. spawnChannel <- ConcurrentChannel[Task].of[Complete, SpawnRequestWrapper]
  204. spawnSystem <- Task(new SpawnSystem(logger, spawnChannel))
  205. consumer <-
  206. spawnChannel.consume
  207. .use(consumer => spawnSystem.receive(consumer))
  208. .startAndForget
  209. } yield (spawnSystem)
  210. }
  211. class SpawnSystem(
  212. logger: Logger[Task],
  213. spawnChannel: ConcurrentChannel[
  214. Task,
  215. SpawnSystem.Complete,
  216. SpawnSystem.SpawnRequestWrapper
  217. ]
  218. ) {
  219. import SpawnSystem._
  220. for {
  221. spawnSystem <- SpawnSystem(logger)
  222. res <- spawnSystem.request(SpawnSpatial(Task(new Node("Test"))))
  223. } yield ()
  224. // val spawnChannel = ConcurrentChannel[Task].of[Result, SpawnRequest]
  225. private def receive(
  226. consumer: ConsumerF[Task, Complete, SpawnRequestWrapper]
  227. ): Task[Unit] =
  228. consumer.pull.flatMap {
  229. case Right(message) =>
  230. for {
  231. _ <-
  232. logger
  233. .debug(s"Received spawn request $message")
  234. _ <- handleSpawn(message)
  235. } yield receive(consumer)
  236. case Left(r) =>
  237. logger.info("Closing Spawn System")
  238. }
  239. private def handleSpawn(spawnRequestWrapper: SpawnRequestWrapper) =
  240. spawnRequestWrapper match {
  241. case SpawnRequestWrapper(spawnRequest, result) =>
  242. spawnRequest match {
  243. case SpawnSpatial(spatialTask) =>
  244. spatialTask.flatMap(spatial =>
  245. logger.debug(
  246. s"Spawning spatial with name ${spatial.getName()}"
  247. ) >> result
  248. .complete(Ok)
  249. )
  250. }
  251. }
  252. def request(spawnRequest: SpawnRequest) =
  253. for {
  254. d <- Deferred[Task, Result]
  255. _ <- spawnChannel.push(SpawnRequestWrapper(spawnRequest, d))
  256. res <- d.get
  257. } yield (res)
  258. def stop = spawnChannel.halt(Complete)
  259. }