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.
 
 

193 lines
5.2 KiB

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