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.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.IO 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.AppError import wow.doge.mygame.AppError.TimeoutError import wow.doge.mygame.Dispatchers import wow.doge.mygame.executors.JMERunner import wow.doge.mygame.executors.Schedulers import wow.doge.mygame.game.subsystems.ui.JFxUI import wow.doge.mygame.implicits._ import wow.doge.mygame.utils.AkkaUtils import wow.doge.mygame.utils.GenericTimerActor import wow.doge.mygame.utils.wrappers.jme._ 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 inputManager: UIO[InputManager] = UIO(app.getInputManager()) def assetManager = new AssetManager(app.getAssetManager()) def guiNode = UIO(app.getGuiNode().taggedWith[GameAppTags.GuiNode]) val guiNode2 = AppNode2(app.getGuiNode()).taggedWith[GameAppTags.GuiNode] def flyCam = Option(app.getFlyByCamera()) def camera = UIO(app.getCamera()) def viewPort = UIO(app.getViewPort()) // def rootNode = UIO(app.getRootNode().taggedWith[GameAppTags.RootNode]) val rootNode = AppNode2(app.getRootNode()).taggedWith[GameAppTags.RootNode] val physicsSpace = new PhysicsSpace(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[UIO, Either[AppError, (GameApp, Fiber[Nothing, Unit])]] = Resource.make { lazy val bullet = new BulletAppState (for { app <- UIO(new SimpleAppExt(schedulers, bullet)) _ <- UIO(JMERunner.runner = Some(app.enqueue _)) _ <- UIO { 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 <- UIO(app.start).executeOn(jmeThread).start _ <- Task.deferFuture(app.started).onErrorHandleWith(TimeoutError.from) testGameActor <- AkkaUtils.spawnActorL( new TestGameActor.Props().create, "testGameActor", Dispatchers.jmeDispatcher ) sp <- testGameActor .askL(TestGameActor.GetSpawnProtocol(_)) .onErrorHandleWith(TimeoutError.from) gameApp <- UIO(new GameApp(logger, app, testGameActor, sp)) _ <- UIO { val fut = () => testGameActor.ask(TestGameActor.Stop).flatten app.cancelToken = Some(fut) } } yield (gameApp, fib)).attempt } { case Right(gameApp -> fib) => fib.cancel >> UIO(JMERunner.runner = None) case Left(error) => IO.terminate(new Exception(error.toString)) } } 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) }