forked from nova/jmonkey-test
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
248 lines
7.0 KiB
248 lines
7.0 KiB
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.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.CancelablePromise
|
|
import monix.execution.Scheduler
|
|
import wow.doge.mygame.executors.Schedulers
|
|
import wow.doge.mygame.game.subsystems.ui.JFxUI
|
|
import wow.doge.mygame.implicits._
|
|
import monix.execution.annotations.UnsafeBecauseImpure
|
|
import monix.reactive.Observable
|
|
|
|
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 =
|
|
WrappedNode(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)
|
|
|
|
}
|
|
|
|
class WrappedNode private (node: Node) {
|
|
|
|
// def +=(spat: Spatial) = lock.withPermit(Task(node.attachChild(spat)))
|
|
def children: Observable[Spatial] = node.observableChildren
|
|
def attachChild(n: Node): Task[Unit] = Task(node.attachChild(node))
|
|
def add(wn: WrappedNode): Task[Unit] =
|
|
Task(node.attachChild(wn.unsafeDelegate))
|
|
|
|
/**
|
|
* Get the underlying wrapped value
|
|
*/
|
|
@UnsafeBecauseImpure
|
|
def unsafeDelegate = node
|
|
}
|
|
object WrappedNode {
|
|
|
|
def apply(name: String) = new WrappedNode(new Node(name))
|
|
def apply(n: Node) = new WrappedNode(n)
|
|
implicit class WrappedNodeOps(private val wn: WrappedNode) extends AnyVal {
|
|
def +=(n: Node) = wn.attachChild(n)
|
|
def +=(wn: WrappedNode) = wn.add(wn)
|
|
}
|
|
}
|
|
|
|
object GameApp {
|
|
|
|
def resource(
|
|
logger: Logger[Task],
|
|
jmeThread: Scheduler,
|
|
schedulers: Schedulers
|
|
) =
|
|
Resource.make(
|
|
for {
|
|
startSignal <- Task(CancelablePromise[Unit]())
|
|
app <- Task(new SimpleAppExt(schedulers, startSignal))
|
|
_ <- 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)
|
|
}
|
|
gameApp <- Task(new GameApp(logger, app))
|
|
fib <- gameApp.start.executeOn(jmeThread).start
|
|
_ <- Task.fromCancelablePromise(startSignal)
|
|
} 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)
|
|
}
|