package wow.doge.mygame.game import cats.effect.concurrent.Deferred import com.jme3.app.state.AppStateManager import com.jme3.asset.AssetManager import com.jme3.input.InputManager import com.jme3.scene.Node import com.jme3.scene.Spatial import com.softwaremill.tagging._ import com.typesafe.scalalogging.{Logger => SLogger} import io.odin.Logger import monix.bio.IO import monix.bio.Task import monix.catnap.ConcurrentChannel import monix.catnap.ConsumerF import monix.catnap.Semaphore import monix.eval.Coeval import wow.doge.mygame.game.subsystems.ui.JFxUI sealed trait Error case object FlyCamNotExists extends Error object GameAppTags { sealed trait RootNode sealed trait GuiNode } class GameApp(logger: Logger[Task], val app: SimpleAppExt) { import Ops._ def stateManager: Task[AppStateManager] = Task(app.getStateManager()) def inputManager: Task[InputManager] = Task(app.getInputManager()) def assetManager: Task[AssetManager] = Task(app.getAssetManager()) def guiNode = Task(app.getGuiNode().taggedWith[GameAppTags.GuiNode]) def addToGuiNode = guiNode.flatMap(rn => Task(new AddToNode(rn))) def flyCam = IO(app.getFlyByCamera()).onErrorHandleWith(_ => IO.raiseError(FlyCamNotExists) ) def camera = Task(app.getCamera()) def viewPort = Task(app.getViewPort()) def rootNode = Task(app.getRootNode().taggedWith[GameAppTags.RootNode]) // def rootNode2 = SynchedObject(app.getRootNode()) def addToRootNode = rootNode.flatMap(rn => Task(new AddToNode(rn))) def enqueue(cb: () => Unit) = app.enqueue(new Runnable { override def run() = cb() }) def enqueueL[T](cb: () => T): Task[T] = app.enqueueL(cb) def start = Task(app.start()) def stop = Task(app.stop()) def scheduler = app.scheduler def jfxUI = JFxUI(app) } object GameApp { class WrappedNode(node: Node, lock: Semaphore[Task]) { def +=(spat: Spatial) = lock.withPermit(Task(node.attachChild(spat))) } /** * Synchronization wrapper for a mutable object * * @param obj the mutable object * @param lock lock for synchronization */ class SynchedObject[A](obj: A, lock: Semaphore[Task]) { def modify(f: A => Unit): Task[Unit] = lock.withPermit(Task(f(obj))) def flatModify(f: A => Task[Unit]): Task[Unit] = lock.withPermit(f(obj)) def get: Task[A] = lock.withPermit(Task(obj)) } object SynchedObject { def apply[A](obj: A) = Semaphore[Task](1).flatMap(lock => Task(new SynchedObject(obj, lock))) } } 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) }