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 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.subsystems.events.EventsModule.GameEventBus import wow.doge.mygame.subsystems.events.TickEvent import wow.doge.mygame.types._ import wow.doge.mygame.utils.AkkaUtils 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[GameAppActor.Command], gameSpawnProtocol: ActorRef[SpawnProtocol.Command], akkaScheduler: AkkaScheduler ) { def inputManager: UIO[InputManager] = UIO(app.getInputManager()) val assetManager = new AssetManager(app.getAssetManager()) val guiNode: GuiNode = AppNode2(app.getGuiNode()).taggedWith[GameAppTags.GuiNode] // def flyCam = Option(app.getFlyByCamera()) def camera = UIO(app.getCamera()) def viewPort = UIO(app.getViewPort()) val rootNode: 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 whenTerminated: IO[AppError, Unit] = IO.deferFuture(app.whenTerminated).onErrorHandleWith(TimeoutError.from) def spawnGameActor[T]( behavior: Behavior[T], actorName: Option[String] = None, props: Props = Dispatchers.jmeDispatcher )(implicit name: sourcecode.Name) = AkkaUtils.spawnActorL(behavior, actorName, props)( 2.seconds, akkaScheduler.value, gameSpawnProtocol, name ) def scheduler = JmeScheduler(app.scheduler) def jfxUI = JFxUI(app) } class GameAppResource( logger: Logger[Task], jmeThread: JmeScheduler, schedulers: Schedulers, tickEventBus: GameEventBus[TickEvent] )(implicit timeout: Timeout, scheduler: AkkaScheduler, spawnProtocol: ActorRef[SpawnProtocol.Command] ) { implicit val as = scheduler.value def resource : Resource[UIO, Either[AppError, (GameApp, Fiber[Nothing, Unit])]] = Resource.make( (for { app <- UIO(new SimpleAppExt(schedulers, new BulletAppState)) _ <- UIO(JMERunner.runner = app) _ <- 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.value).start _ <- Task.deferFuture(app.started).onErrorHandleWith(TimeoutError.from) gameAppActor <- AkkaUtils.spawnActorL( new GameAppActor.Props(tickEventBus).behavior, Some("testGameActor"), props = Dispatchers.jmeDispatcher ) _ <- gameAppActor !! GameAppActor.Start sp <- gameAppActor .askL(GameAppActor.GetSpawnProtocol) .onErrorHandleWith(TimeoutError.from) gameApp <- UIO(new GameApp(logger, app, gameAppActor, sp, scheduler)) _ <- UIO { val fut = () => gameAppActor.ask(GameAppActor.Stop).flatten app.cancelToken = Some(fut) } } yield (gameApp, fib)).attempt ) { case Right(gameApp -> fib) => fib.cancel case Left(error) => IO.terminate(new Exception(error.toString)) } } object GameApp {} 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 case object Ok extends Result sealed trait Complete 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) }