package wow.doge.mygame.game import cats.effect.Resource import cats.effect.concurrent.Deferred import com.jme3.app.state.AppStateManager import com.jme3.asset.AssetManager 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.IO import monix.bio.Task import monix.catnap.ConcurrentChannel import monix.catnap.ConsumerF import monix.catnap.Semaphore import monix.eval.Coeval import monix.execution.Scheduler import wow.doge.mygame.executors.Schedulers import wow.doge.mygame.game.subsystems.ui.JFxUI import wow.doge.mygame.implicits._ import wow.doge.mygame.utils.wrappers.jme._ sealed trait Error case object FlyCamNotExists extends Error object GameAppTags { sealed trait RootNode sealed trait GuiNode } class GameApp private (logger: Logger[Task], app: SimpleAppExt) { 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 guiNode2 = AppNode[Task](app.getGuiNode()).taggedWith[GameAppTags.GuiNode] 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]) val rootNode = AppNode[Task](app.getRootNode()).taggedWith[GameAppTags.RootNode] val physicsSpace = new PhysicsSpace[Task](app.bulletAppState.physicsSpace) def enqueue(cb: () => Unit) = app.enqueue(new Runnable { override def run() = cb() }) def enqueueL[T](cb: () => T): Task[T] = app.enqueueL(cb) def scheduler = app.scheduler def jfxUI = JFxUI(app) } object GameApp { def resource( logger: Logger[Task], jmeThread: Scheduler, schedulers: Schedulers ) = Resource.make { lazy val bullet = new BulletAppState for { // bullet <- Task(new BulletAppState) // startSignal <- Task(CancelablePromise[Unit]()) app <- Task(new SimpleAppExt(schedulers, bullet)) _ <- 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) // _ <- Task.fromCancelablePromise(startSignal) _ <- Task(pprint.log(bullet.toString())) _ <- Task(println(bullet.physicsSpace.toString())) gameApp <- Task(new GameApp(logger, app)) } yield gameApp -> fib }(_._2.cancel) /** * 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) }