package wow.doge.mygame.game import scala.concurrent.duration._ import akka.actor.typed.ActorRef import akka.actor.typed.Behavior import akka.actor.typed.Props import akka.actor.typed.SpawnProtocol import akka.actor.typed.scaladsl.AskPattern._ import akka.util.Timeout import cats.effect.Resource import cats.effect.concurrent.Deferred import com.jme3.app.state.AppStateManager import com.jme3.bullet.BulletAppState import com.jme3.input.InputManager import com.jme3.scene.Node import com.jme3.scene.Spatial import com.jme3.system.AppSettings import com.softwaremill.tagging._ import com.typesafe.scalalogging.{Logger => SLogger} import io.odin.Logger import monix.bio.Fiber import monix.bio.Task import monix.bio.UIO import monix.catnap.ConcurrentChannel import monix.catnap.ConsumerF import monix.eval.Coeval import monix.execution.CancelableFuture import monix.execution.CancelablePromise import monix.execution.Scheduler import wow.doge.mygame.executors.JMERunner import wow.doge.mygame.executors.Schedulers import wow.doge.mygame.utils.GenericTimerActor import wow.doge.mygame.game.subsystems.ui.JFxUI import wow.doge.mygame.implicits._ import wow.doge.mygame.utils.AkkaUtils import wow.doge.mygame.utils.wrappers.jme._ import wow.doge.mygame.Dispatchers object GameAppTags { sealed trait RootNode sealed trait GuiNode } class GameApp private[game] ( logger: Logger[Task], app: SimpleAppExt, gameActor: ActorRef[TestGameActor.Command], gameSpawnProtocol: ActorRef[SpawnProtocol.Command] ) { def stateManager: Task[AppStateManager] = Task(app.getStateManager()) def inputManager: Task[InputManager] = Task(app.getInputManager()) def assetManager = new AssetManager(app.getAssetManager()) def guiNode = Task(app.getGuiNode().taggedWith[GameAppTags.GuiNode]) val guiNode2 = AppNode[Task](app.getGuiNode()).taggedWith[GameAppTags.GuiNode] def flyCam = Option(app.getFlyByCamera()) def camera = Task(app.getCamera()) def viewPort = Task(app.getViewPort()) // def rootNode = Task(app.getRootNode().taggedWith[GameAppTags.RootNode]) val rootNode = AppNode[Task](app.getRootNode()).taggedWith[GameAppTags.RootNode] val physicsSpace = new PhysicsSpace[Task](app.bulletAppState.physicsSpace) def enqueue(cb: () => Unit) = app.enqueueR(() => cb()) def enqueueL[T](cb: () => T): Task[T] = app.enqueueL(cb) def spawnGameActor[T]( behavior: Behavior[T], actorName: String, props: Props = Dispatchers.jmeDispatcher )(implicit scheduler: akka.actor.typed.Scheduler) = AkkaUtils.spawnActorL(behavior, actorName, props)( 2.seconds, scheduler, gameSpawnProtocol ) def scheduler = app.scheduler def jfxUI = JFxUI(app) } class GameAppResource( logger: Logger[Task], jmeThread: Scheduler, schedulers: Schedulers )(implicit timeout: Timeout, scheduler: akka.actor.typed.Scheduler, spawnProtocol: ActorRef[SpawnProtocol.Command] ) { def resource: Resource[Task, (GameApp, Fiber[Throwable, Unit])] = Resource.make { lazy val bullet = new BulletAppState for { app <- Task(new SimpleAppExt(schedulers, bullet)) _ <- UIO(JMERunner.runner = Some(app.enqueue _)) _ <- Task { val settings = new AppSettings(true) settings.setVSync(true) /** * disables the launcher * We'll be making our own launcher anyway */ app.setShowSettings(false) app.setSettings(settings) } fib <- Task(app.start).executeOn(jmeThread).start _ <- Task.deferFuture(app.started) testGameActor <- AkkaUtils.spawnActorL( new TestGameActor.Props().create, "testGameActor", Dispatchers.jmeDispatcher ) sp <- testGameActor.askL(TestGameActor.GetSpawnProtocol(_)) gameApp <- Task(new GameApp(logger, app, testGameActor, sp)) _ <- Task { val fut = () => testGameActor.ask(TestGameActor.Stop).flatten app.cancelToken = Some(fut) } } yield (gameApp, fib) } { case (gameApp, fib) => fib.cancel >> UIO(JMERunner.runner = None) } } object GameApp {} import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.scaladsl.ActorContext object TestGameActor { sealed trait Command case object Ping extends Command case class Stop(stopSignal: ActorRef[CancelableFuture[Unit]]) extends Command case class GetSpawnProtocol( replyTo: ActorRef[ActorRef[SpawnProtocol.Command]] ) extends Command import scala.concurrent.duration._ class Props() { def create = Behaviors.setup[Command] { ctx => ctx.spawn( GenericTimerActor .Props(ctx.self, Ping, 1000.millis) .behavior, "pingTimer" ) ! GenericTimerActor.Start new TestGameActor(ctx, this).receive } } } class TestGameActor( ctx: ActorContext[TestGameActor.Command], props: TestGameActor.Props ) { import TestGameActor._ val stopPromise = CancelablePromise[Unit]() def receive = Behaviors .receiveMessage[Command] { case Stop(replyTo) => ctx.log.infoP("stopping") replyTo ! stopPromise.future Behaviors.stopped case Ping => ctx.log.debugP("ping") Behaviors.same case GetSpawnProtocol(replyTo) => val sp = ctx.spawn(SpawnProtocol(), "gameSpawnProtocol") replyTo ! sp Behaviors.same } .receiveSignal { case (_, akka.actor.typed.PostStop) => stopPromise.success(()) Behaviors.same } } object Ops { final class AddToNode[T <: Node](private val node: T) extends AnyVal { /** * Pure version */ def apply(spatial: Spatial)(implicit logger: Logger[Task]) = logger.debug( s"Request to add spatial with name ${spatial.getName()} to node ${node.getName()}" ) >> Task(node.attachChild(spatial)) /** * Impure version */ def apply(spatial: Spatial)(implicit logger: SLogger) = Coeval { logger.debug( s"Request to add spatial with name ${spatial.getName()} to node ${node.getName()}" ) node.attachChild(spatial) } } } object SpawnSystem { sealed trait Result final case object Ok extends Result sealed trait Complete final case object Complete extends Complete sealed trait SpawnRequest final case class SpawnSpatial(nodeTask: Task[Node]) extends SpawnRequest final case class SpawnRequestWrapper( spawnRequest: SpawnRequest, result: Deferred[Task, Result] ) def apply(logger: Logger[Task]) = for { spawnChannel <- ConcurrentChannel[Task].of[Complete, SpawnRequestWrapper] spawnSystem <- Task(new SpawnSystem(logger, spawnChannel)) consumer <- spawnChannel.consume .use(consumer => spawnSystem.receive(consumer)) .startAndForget } yield (spawnSystem) } class SpawnSystem( logger: Logger[Task], spawnChannel: ConcurrentChannel[ Task, SpawnSystem.Complete, SpawnSystem.SpawnRequestWrapper ] ) { import SpawnSystem._ for { spawnSystem <- SpawnSystem(logger) res <- spawnSystem.request(SpawnSpatial(Task(new Node("Test")))) } yield () // val spawnChannel = ConcurrentChannel[Task].of[Result, SpawnRequest] private def receive( consumer: ConsumerF[Task, Complete, SpawnRequestWrapper] ): Task[Unit] = consumer.pull.flatMap { case Right(message) => for { _ <- logger .debug(s"Received spawn request $message") _ <- handleSpawn(message) } yield receive(consumer) case Left(r) => logger.info("Closing Spawn System") } private def handleSpawn(spawnRequestWrapper: SpawnRequestWrapper) = spawnRequestWrapper match { case SpawnRequestWrapper(spawnRequest, result) => spawnRequest match { case SpawnSpatial(spatialTask) => spatialTask.flatMap(spatial => logger.debug( s"Spawning spatial with name ${spatial.getName()}" ) >> result .complete(Ok) ) } } def request(spawnRequest: SpawnRequest) = for { d <- Deferred[Task, Result] _ <- spawnChannel.push(SpawnRequestWrapper(spawnRequest, d)) res <- d.get } yield (res) def stop = spawnChannel.halt(Complete) }