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.
295 lines
8.5 KiB
295 lines
8.5 KiB
package wow.doge.mygame.game
|
|
|
|
import scala.concurrent.duration._
|
|
|
|
import akka.actor.typed.ActorRef
|
|
import akka.actor.typed.Behavior
|
|
import akka.actor.typed.Props
|
|
import akka.actor.typed.SpawnProtocol
|
|
import akka.actor.typed.scaladsl.AskPattern._
|
|
import akka.util.Timeout
|
|
import cats.effect.Resource
|
|
import cats.effect.concurrent.Deferred
|
|
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.Fiber
|
|
import monix.bio.IO
|
|
import monix.bio.Task
|
|
import monix.bio.UIO
|
|
import monix.catnap.ConcurrentChannel
|
|
import monix.catnap.ConsumerF
|
|
import monix.eval.Coeval
|
|
import monix.execution.CancelableFuture
|
|
import monix.execution.CancelablePromise
|
|
import monix.execution.Scheduler
|
|
import wow.doge.mygame.AppError
|
|
import wow.doge.mygame.AppError.TimeoutError
|
|
import wow.doge.mygame.Dispatchers
|
|
import wow.doge.mygame.executors.JMERunner
|
|
import wow.doge.mygame.executors.Schedulers
|
|
import wow.doge.mygame.game.subsystems.ui.JFxUI
|
|
import wow.doge.mygame.implicits._
|
|
import wow.doge.mygame.utils.AkkaUtils
|
|
import wow.doge.mygame.utils.GenericTimerActor
|
|
import wow.doge.mygame.utils.wrappers.jme._
|
|
|
|
object GameAppTags {
|
|
sealed trait RootNode
|
|
|
|
sealed trait GuiNode
|
|
}
|
|
|
|
class GameApp private[game] (
|
|
logger: Logger[Task],
|
|
app: SimpleAppExt,
|
|
gameActor: ActorRef[TestGameActor.Command],
|
|
gameSpawnProtocol: ActorRef[SpawnProtocol.Command]
|
|
) {
|
|
def inputManager: UIO[InputManager] = UIO(app.getInputManager())
|
|
def assetManager = new AssetManager(app.getAssetManager())
|
|
def guiNode = UIO(app.getGuiNode().taggedWith[GameAppTags.GuiNode])
|
|
val guiNode2 = AppNode2(app.getGuiNode()).taggedWith[GameAppTags.GuiNode]
|
|
def flyCam = Option(app.getFlyByCamera())
|
|
def camera = UIO(app.getCamera())
|
|
def viewPort = UIO(app.getViewPort())
|
|
// def rootNode = UIO(app.getRootNode().taggedWith[GameAppTags.RootNode])
|
|
val rootNode =
|
|
AppNode2(app.getRootNode()).taggedWith[GameAppTags.RootNode]
|
|
val physicsSpace = new PhysicsSpace(app.bulletAppState.physicsSpace)
|
|
def enqueue(cb: () => Unit) = app.enqueueR(() => cb())
|
|
def enqueueL[T](cb: () => T): Task[T] = app.enqueueL(cb)
|
|
|
|
def spawnGameActor[T](
|
|
behavior: Behavior[T],
|
|
actorName: String,
|
|
props: Props = Dispatchers.jmeDispatcher
|
|
)(implicit scheduler: akka.actor.typed.Scheduler) =
|
|
AkkaUtils.spawnActorL(behavior, actorName, props)(
|
|
2.seconds,
|
|
scheduler,
|
|
gameSpawnProtocol
|
|
)
|
|
|
|
def scheduler = app.scheduler
|
|
def jfxUI = JFxUI(app)
|
|
|
|
}
|
|
|
|
class GameAppResource(
|
|
logger: Logger[Task],
|
|
jmeThread: Scheduler,
|
|
schedulers: Schedulers
|
|
)(implicit
|
|
timeout: Timeout,
|
|
scheduler: akka.actor.typed.Scheduler,
|
|
spawnProtocol: ActorRef[SpawnProtocol.Command]
|
|
) {
|
|
def resource
|
|
: Resource[UIO, Either[AppError, (GameApp, Fiber[Nothing, Unit])]] =
|
|
Resource.make {
|
|
lazy val bullet = new BulletAppState
|
|
(for {
|
|
app <- UIO(new SimpleAppExt(schedulers, bullet))
|
|
_ <- UIO(JMERunner.runner = Some(app.enqueue _))
|
|
_ <- UIO {
|
|
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 <- UIO(app.start).executeOn(jmeThread).start
|
|
_ <- Task.deferFuture(app.started).onErrorHandleWith(TimeoutError.from)
|
|
testGameActor <- AkkaUtils.spawnActorL(
|
|
new TestGameActor.Props().create,
|
|
"testGameActor",
|
|
Dispatchers.jmeDispatcher
|
|
)
|
|
sp <-
|
|
testGameActor
|
|
.askL(TestGameActor.GetSpawnProtocol(_))
|
|
.onErrorHandleWith(TimeoutError.from)
|
|
gameApp <- UIO(new GameApp(logger, app, testGameActor, sp))
|
|
_ <- UIO {
|
|
val fut = () => testGameActor.ask(TestGameActor.Stop).flatten
|
|
app.cancelToken = Some(fut)
|
|
}
|
|
} yield (gameApp, fib)).attempt
|
|
} {
|
|
case Right(gameApp -> fib) =>
|
|
fib.cancel >> UIO(JMERunner.runner = None)
|
|
case Left(error) => IO.terminate(new Exception(error.toString))
|
|
}
|
|
}
|
|
|
|
object GameApp {}
|
|
|
|
import akka.actor.typed.scaladsl.Behaviors
|
|
import akka.actor.typed.scaladsl.ActorContext
|
|
|
|
object TestGameActor {
|
|
sealed trait Command
|
|
case object Ping extends Command
|
|
case class Stop(stopSignal: ActorRef[CancelableFuture[Unit]]) extends Command
|
|
case class GetSpawnProtocol(
|
|
replyTo: ActorRef[ActorRef[SpawnProtocol.Command]]
|
|
) extends Command
|
|
import scala.concurrent.duration._
|
|
class Props() {
|
|
def create =
|
|
Behaviors.setup[Command] { ctx =>
|
|
ctx.spawn(
|
|
GenericTimerActor
|
|
.Props(ctx.self, Ping, 1000.millis)
|
|
.behavior,
|
|
"pingTimer"
|
|
) ! GenericTimerActor.Start
|
|
new TestGameActor(ctx, this).receive
|
|
}
|
|
}
|
|
}
|
|
class TestGameActor(
|
|
ctx: ActorContext[TestGameActor.Command],
|
|
props: TestGameActor.Props
|
|
) {
|
|
import TestGameActor._
|
|
val stopPromise = CancelablePromise[Unit]()
|
|
def receive =
|
|
Behaviors
|
|
.receiveMessage[Command] {
|
|
case Stop(replyTo) =>
|
|
ctx.log.infoP("stopping")
|
|
replyTo ! stopPromise.future
|
|
Behaviors.stopped
|
|
case Ping =>
|
|
ctx.log.debugP("ping")
|
|
Behaviors.same
|
|
case GetSpawnProtocol(replyTo) =>
|
|
val sp = ctx.spawn(SpawnProtocol(), "gameSpawnProtocol")
|
|
replyTo ! sp
|
|
Behaviors.same
|
|
}
|
|
.receiveSignal {
|
|
case (_, akka.actor.typed.PostStop) =>
|
|
stopPromise.success(())
|
|
Behaviors.same
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|