Testing out JmonkeyEngine to make a game in Scala with Akka Actors within a pure FP layer
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

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)
}