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.

246 lines
7.2 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
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
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
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
3 years ago
3 years ago
3 years ago
3 years ago
4 years ago
3 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.bullet.BulletAppState
  12. import com.jme3.input.InputManager
  13. import com.jme3.scene.Node
  14. import com.jme3.scene.Spatial
  15. import com.jme3.system.AppSettings
  16. import com.softwaremill.tagging._
  17. import com.typesafe.scalalogging.{Logger => SLogger}
  18. import io.odin.Logger
  19. import monix.bio.Fiber
  20. import monix.bio.IO
  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 wow.doge.mygame.AppError
  27. import wow.doge.mygame.AppError.TimeoutError
  28. import wow.doge.mygame.Dispatchers
  29. import wow.doge.mygame.executors.JMERunner
  30. import wow.doge.mygame.executors.Schedulers
  31. import wow.doge.mygame.game.subsystems.ui.JFxUI
  32. import wow.doge.mygame.implicits._
  33. import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
  34. import wow.doge.mygame.subsystems.events.TickEvent
  35. import wow.doge.mygame.types._
  36. import wow.doge.mygame.utils.AkkaUtils
  37. import wow.doge.mygame.utils.wrappers.jme._
  38. object GameAppTags {
  39. sealed trait RootNode
  40. sealed trait GuiNode
  41. }
  42. class GameApp private[game] (
  43. logger: Logger[Task],
  44. app: SimpleAppExt,
  45. gameActor: ActorRef[GameAppActor.Command],
  46. gameSpawnProtocol: ActorRef[SpawnProtocol.Command],
  47. akkaScheduler: AkkaScheduler
  48. ) {
  49. def inputManager: UIO[InputManager] = UIO(app.getInputManager())
  50. val assetManager = new AssetManager(app.getAssetManager())
  51. val guiNode: GuiNode =
  52. AppNode2(app.getGuiNode()).taggedWith[GameAppTags.GuiNode]
  53. // def flyCam = Option(app.getFlyByCamera())
  54. def camera = UIO(app.getCamera())
  55. def viewPort = UIO(app.getViewPort())
  56. val rootNode: RootNode =
  57. AppNode2(app.getRootNode()).taggedWith[GameAppTags.RootNode]
  58. val physicsSpace =
  59. new PhysicsSpace(app.bulletAppState.physicsSpace)
  60. def enqueue(cb: () => Unit) = app.enqueueR(() => cb())
  61. def enqueueL[T](cb: () => T): Task[T] = app.enqueueL(cb)
  62. def whenTerminated: IO[AppError, Unit] =
  63. IO.deferFuture(app.whenTerminated).onErrorHandleWith(TimeoutError.from)
  64. def spawnGameActor[T](
  65. behavior: Behavior[T],
  66. actorName: Option[String] = None,
  67. props: Props = Dispatchers.jmeDispatcher
  68. )(implicit name: sourcecode.Name) =
  69. AkkaUtils.spawnActorL(behavior, actorName, props)(
  70. 2.seconds,
  71. akkaScheduler.value,
  72. gameSpawnProtocol,
  73. name
  74. )
  75. def scheduler = JmeScheduler(app.scheduler)
  76. def jfxUI = JFxUI(app)
  77. }
  78. class GameAppResource(
  79. logger: Logger[Task],
  80. jmeThread: JmeScheduler,
  81. schedulers: Schedulers,
  82. tickEventBus: GameEventBus[TickEvent]
  83. )(implicit
  84. timeout: Timeout,
  85. scheduler: AkkaScheduler,
  86. spawnProtocol: ActorRef[SpawnProtocol.Command]
  87. ) {
  88. implicit val as = scheduler.value
  89. def resource
  90. : Resource[UIO, Either[AppError, (GameApp, Fiber[Nothing, Unit])]] =
  91. Resource.make(
  92. (for {
  93. app <- UIO(new SimpleAppExt(schedulers, new BulletAppState))
  94. _ <- UIO(JMERunner.runner = app)
  95. _ <- UIO {
  96. val settings = new AppSettings(true)
  97. settings.setVSync(true)
  98. /**
  99. * disables the launcher
  100. * We'll be making our own launcher anyway
  101. */
  102. app.setShowSettings(false)
  103. app.setSettings(settings)
  104. }
  105. fib <- UIO(app.start).executeOn(jmeThread.value).start
  106. _ <- Task.deferFuture(app.started).onErrorHandleWith(TimeoutError.from)
  107. gameAppActor <- AkkaUtils.spawnActorL(
  108. new GameAppActor.Props(tickEventBus).behavior,
  109. Some("testGameActor"),
  110. props = Dispatchers.jmeDispatcher
  111. )
  112. _ <- gameAppActor !! GameAppActor.Start
  113. sp <-
  114. gameAppActor
  115. .askL(GameAppActor.GetSpawnProtocol)
  116. .onErrorHandleWith(TimeoutError.from)
  117. gameApp <- UIO(new GameApp(logger, app, gameAppActor, sp, scheduler))
  118. _ <- UIO {
  119. val fut = () => gameAppActor.ask(GameAppActor.Stop).flatten
  120. app.cancelToken = Some(fut)
  121. }
  122. } yield (gameApp, fib)).attempt
  123. ) {
  124. case Right(gameApp -> fib) => fib.cancel
  125. case Left(error) => IO.terminate(new Exception(error.toString))
  126. }
  127. }
  128. object GameApp {}
  129. object Ops {
  130. final class AddToNode[T <: Node](private val node: T) extends AnyVal {
  131. /**
  132. * Pure version
  133. */
  134. def apply(spatial: Spatial)(implicit logger: Logger[Task]) =
  135. logger.debug(
  136. s"Request to add spatial with name ${spatial.getName()} to node ${node.getName()}"
  137. ) >> Task(node.attachChild(spatial))
  138. /**
  139. * Impure version
  140. */
  141. def apply(spatial: Spatial)(implicit logger: SLogger) =
  142. Coeval {
  143. logger.debug(
  144. s"Request to add spatial with name ${spatial.getName()} to node ${node.getName()}"
  145. )
  146. node.attachChild(spatial)
  147. }
  148. }
  149. }
  150. object SpawnSystem {
  151. sealed trait Result
  152. case object Ok extends Result
  153. sealed trait Complete
  154. case object Complete extends Complete
  155. sealed trait SpawnRequest
  156. final case class SpawnSpatial(nodeTask: Task[Node]) extends SpawnRequest
  157. final case class SpawnRequestWrapper(
  158. spawnRequest: SpawnRequest,
  159. result: Deferred[Task, Result]
  160. )
  161. def apply(logger: Logger[Task]) =
  162. for {
  163. spawnChannel <- ConcurrentChannel[Task].of[Complete, SpawnRequestWrapper]
  164. spawnSystem <- Task(new SpawnSystem(logger, spawnChannel))
  165. consumer <-
  166. spawnChannel.consume
  167. .use(consumer => spawnSystem.receive(consumer))
  168. .startAndForget
  169. } yield (spawnSystem)
  170. }
  171. class SpawnSystem(
  172. logger: Logger[Task],
  173. spawnChannel: ConcurrentChannel[
  174. Task,
  175. SpawnSystem.Complete,
  176. SpawnSystem.SpawnRequestWrapper
  177. ]
  178. ) {
  179. import SpawnSystem._
  180. for {
  181. spawnSystem <- SpawnSystem(logger)
  182. res <- spawnSystem.request(SpawnSpatial(Task(new Node("Test"))))
  183. } yield ()
  184. // val spawnChannel = ConcurrentChannel[Task].of[Result, SpawnRequest]
  185. private def receive(
  186. consumer: ConsumerF[Task, Complete, SpawnRequestWrapper]
  187. ): Task[Unit] =
  188. consumer.pull.flatMap {
  189. case Right(message) =>
  190. for {
  191. _ <-
  192. logger
  193. .debug(s"Received spawn request $message")
  194. _ <- handleSpawn(message)
  195. } yield receive(consumer)
  196. case Left(r) =>
  197. logger.info("Closing Spawn System")
  198. }
  199. private def handleSpawn(spawnRequestWrapper: SpawnRequestWrapper) =
  200. spawnRequestWrapper match {
  201. case SpawnRequestWrapper(spawnRequest, result) =>
  202. spawnRequest match {
  203. case SpawnSpatial(spatialTask) =>
  204. spatialTask.flatMap(spatial =>
  205. logger.debug(
  206. s"Spawning spatial with name ${spatial.getName()}"
  207. ) >> result
  208. .complete(Ok)
  209. )
  210. }
  211. }
  212. def request(spawnRequest: SpawnRequest) =
  213. for {
  214. d <- Deferred[Task, Result]
  215. _ <- spawnChannel.push(SpawnRequestWrapper(spawnRequest, d))
  216. res <- d.get
  217. } yield (res)
  218. def stop = spawnChannel.halt(Complete)
  219. }