forked from nova/jmonkey-test
many changes
This commit is contained in:
parent
67a2bc4385
commit
fd8b3819ff
@ -66,7 +66,8 @@ lazy val root = (project in file(".")).settings(
|
||||
"com.lihaoyi" %% "pprint" % "0.6.0",
|
||||
"org.scalatest" %% "scalatest" % "3.2.2" % "test",
|
||||
"org.typelevel" %% "cats-mtl" % "1.1.1",
|
||||
"io.estatico" %% "newtype" % "0.4.4"
|
||||
"io.estatico" %% "newtype" % "0.4.4",
|
||||
"io.methvin" %% "directory-watcher-better-files" % "0.14.0"
|
||||
),
|
||||
// Determine OS version of JavaFX binaries
|
||||
|
||||
|
@ -14,14 +14,16 @@ import io.odin._
|
||||
import io.odin.json.Formatter
|
||||
import io.odin.syntax._
|
||||
import scalafx.scene.control.TextArea
|
||||
import wow.doge.mygame.ActorSystemResource
|
||||
import wow.doge.mygame.executors.ExecutorsModule
|
||||
import wow.doge.mygame.types.AkkaScheduler
|
||||
import wow.doge.mygame.utils.GenericConsoleStream
|
||||
|
||||
object Main extends BIOApp with MainModule {
|
||||
object Main extends BIOApp with ExecutorsModule {
|
||||
import java.util.logging.{Logger => JLogger, Level}
|
||||
JLogger.getLogger("").setLevel(Level.SEVERE)
|
||||
implicit val timeout = Timeout(1.second)
|
||||
|
||||
override def scheduler: Scheduler = schedulers.async
|
||||
override def scheduler: Scheduler = schedulers.async.value
|
||||
|
||||
def appResource(consoleStream: GenericConsoleStream[TextArea]) =
|
||||
for {
|
||||
@ -34,18 +36,18 @@ object Main extends BIOApp with MainModule {
|
||||
"application-log-1.log",
|
||||
Formatter.json
|
||||
).withAsync(timeWindow = 1.milliseconds, maxBufferSize = Some(2000))
|
||||
jmeScheduler <- jMESchedulerResource
|
||||
jmeScheduler <- jmeSchedulerResource
|
||||
// backend <- Resource.make(toIO(HttpClientMonixBackend()))(backend =>
|
||||
// toIO(backend.close())
|
||||
// )
|
||||
actorSystem <- actorSystemResource(logger, schedulers.async)
|
||||
actorSystem <- new ActorSystemResource(logger, schedulers.async).get
|
||||
_ <- Resource.liftF(
|
||||
new MainApp(
|
||||
logger,
|
||||
jmeScheduler,
|
||||
schedulers,
|
||||
consoleStream
|
||||
)(actorSystem, timeout, actorSystem.scheduler).program
|
||||
)(actorSystem, timeout, AkkaScheduler(actorSystem.scheduler)).program
|
||||
)
|
||||
|
||||
} yield ()
|
||||
|
@ -3,7 +3,6 @@ package wow.doge.mygame
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import akka.actor.typed.ActorRef
|
||||
import akka.actor.typed.Scheduler
|
||||
import akka.actor.typed.SpawnProtocol
|
||||
import akka.util.Timeout
|
||||
import cats.effect.Resource
|
||||
@ -16,6 +15,8 @@ import com.jme3.material.Material
|
||||
import com.jme3.material.MaterialDef
|
||||
import com.jme3.math.ColorRGBA
|
||||
import com.jme3.math.FastMath
|
||||
import com.jme3.math.Quaternion
|
||||
import com.jme3.math.Vector3f
|
||||
import com.jme3.renderer.Camera
|
||||
import com.jme3.renderer.ViewPort
|
||||
import com.jme3.scene.Node
|
||||
@ -27,19 +28,22 @@ import monix.bio.IO
|
||||
import monix.bio.Task
|
||||
import monix.bio.UIO
|
||||
import monix.eval.Coeval
|
||||
import monix.execution.exceptions.DummyException
|
||||
import monix.reactive.Observable
|
||||
import scalafx.scene.control.TextArea
|
||||
import wow.doge.mygame.AppError.TimeoutError
|
||||
import wow.doge.mygame.executors.Schedulers
|
||||
import wow.doge.mygame.game.GameApp
|
||||
import wow.doge.mygame.game.GameAppActor
|
||||
import wow.doge.mygame.game.GameAppResource
|
||||
import wow.doge.mygame.game.controls.FollowControl
|
||||
import wow.doge.mygame.game.entities.CharacterStats
|
||||
import wow.doge.mygame.game.entities.EntityIds
|
||||
import wow.doge.mygame.game.entities.NpcActorSupervisor
|
||||
import wow.doge.mygame.game.entities.NpcMovementActor
|
||||
import wow.doge.mygame.game.entities.PlayerActorSupervisor
|
||||
import wow.doge.mygame.game.entities.PlayerController
|
||||
import wow.doge.mygame.game.subsystems.input.GameInputHandler
|
||||
import wow.doge.mygame.game.subsystems.input.PlayerCameraInput
|
||||
import wow.doge.mygame.game.subsystems.level.DefaultGameLevel
|
||||
import wow.doge.mygame.implicits._
|
||||
import wow.doge.mygame.launcher.Launcher
|
||||
@ -50,50 +54,45 @@ import wow.doge.mygame.subsystems.events.EventBus
|
||||
import wow.doge.mygame.subsystems.events.EventBus.ObservableSubscription
|
||||
import wow.doge.mygame.subsystems.events.EventsModule
|
||||
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
|
||||
import wow.doge.mygame.subsystems.events.PlayerCameraEvent
|
||||
import wow.doge.mygame.subsystems.events.PlayerEvent
|
||||
import wow.doge.mygame.subsystems.events.PlayerMovementEvent
|
||||
import wow.doge.mygame.subsystems.events.StatsEvent.DamageEvent
|
||||
import wow.doge.mygame.subsystems.events.TickEvent
|
||||
import wow.doge.mygame.subsystems.scriptsystem.ScriptInitMode
|
||||
import wow.doge.mygame.subsystems.scriptsystem.ScriptSystemResource
|
||||
import wow.doge.mygame.types._
|
||||
import wow.doge.mygame.utils.AkkaUtils
|
||||
import wow.doge.mygame.utils.GenericConsoleStream
|
||||
import wow.doge.mygame.utils.IOUtils
|
||||
import wow.doge.mygame.utils.wrappers.jme.AssetManager
|
||||
import wow.doge.mygame.utils.wrappers.jme.PhysicsSpace
|
||||
import wow.doge.mygame.subsystems.events.PlayerCameraEvent
|
||||
import com.jme3.math.Quaternion
|
||||
import com.jme3.math.Vector3f
|
||||
import wow.doge.mygame.types._
|
||||
import wow.doge.mygame.game.controls.FollowControl
|
||||
import wow.doge.mygame.game.subsystems.input.PlayerCameraInput
|
||||
class MainApp(
|
||||
logger: Logger[Task],
|
||||
jmeThread: monix.execution.Scheduler,
|
||||
jmeThread: JmeScheduler,
|
||||
schedulers: Schedulers,
|
||||
consoleStream: GenericConsoleStream[TextArea]
|
||||
)(implicit
|
||||
spawnProtocol: ActorRef[SpawnProtocol.Command],
|
||||
timeout: Timeout,
|
||||
scheduler: Scheduler
|
||||
scheduler: AkkaScheduler
|
||||
) {
|
||||
implicit val as = scheduler.value
|
||||
|
||||
val scriptSystemInit =
|
||||
new ScriptSystemResource(os.pwd, ScriptInitMode.Eager).init
|
||||
|
||||
val eventsModule = new EventsModule(scheduler, spawnProtocol)
|
||||
|
||||
class TestClass(
|
||||
playerEventBus: GameEventBus[PlayerEvent],
|
||||
tickEventBus: GameEventBus[TickEvent]
|
||||
)
|
||||
|
||||
def eval(gameApp: GameApp, fib: Fiber[Nothing, Unit]) =
|
||||
def eval(
|
||||
tickEventBus: GameEventBus[TickEvent],
|
||||
gameApp: GameApp,
|
||||
fib: Fiber[Nothing, Unit]
|
||||
) =
|
||||
for {
|
||||
// g <- UIO.pure(gameApp)
|
||||
playerEventBus <- eventsModule.playerEventBus
|
||||
mainEventBus <- eventsModule.mainEventBus
|
||||
tickEventBus <- eventsModule.tickEventBus
|
||||
obs <-
|
||||
playerEventBus
|
||||
.askL[Observable[PlayerMovementEvent]](ObservableSubscription(_))
|
||||
@ -116,11 +115,6 @@ class MainApp(
|
||||
physicsSpace <- UIO.pure(gameApp.physicsSpace)
|
||||
_ <- logger.infoU("before")
|
||||
// jfxUI <- gameApp.jfxUI
|
||||
gameAppActor <- gameApp.spawnGameActor(
|
||||
GameAppActor.Props(tickEventBus).behavior,
|
||||
Some("gameAppActor")
|
||||
)
|
||||
_ <- gameAppActor !! GameAppActor.Start
|
||||
consoleTextArea <- UIO(new TextArea {
|
||||
text = "hello \n"
|
||||
editable = false
|
||||
@ -138,25 +132,25 @@ class MainApp(
|
||||
.executeOn(gameApp.scheduler.value)
|
||||
} yield fib
|
||||
|
||||
def gameInit: Resource[UIO, Either[AppError, Fiber[Nothing, Unit]]] =
|
||||
def gameInit(
|
||||
tickEventBus: GameEventBus[TickEvent]
|
||||
): Resource[UIO, Either[AppError, Fiber[Nothing, Unit]]] =
|
||||
wire[GameAppResource].resource.evalMap {
|
||||
case Right(gameApp -> gameAppFib) =>
|
||||
eval(gameApp, gameAppFib).attempt
|
||||
eval(tickEventBus, gameApp, gameAppFib).attempt
|
||||
case Left(error) => IO.terminate(new Exception(error.toString))
|
||||
}
|
||||
|
||||
// val x: Task[Unit] = for {
|
||||
// tickEventBus <- eventsModule.tickEventBusTask
|
||||
// playerEventBus <- eventsModule.playerEventBusTask
|
||||
// _ <- UIO(wire[TestClass])
|
||||
// _ <- gameInit(tickEventBus).use(_.join)
|
||||
// } yield ()
|
||||
|
||||
val program = for {
|
||||
// scriptSystem <- scriptSystemInit
|
||||
launchSignal <- Deferred[Task, Launcher.LauncherResult].hideErrors
|
||||
launcher <- new Launcher.Props(schedulers, launchSignal).create
|
||||
launchResult <- launcher.init.use(_ => launchSignal.get).hideErrors
|
||||
launcher <- new Launcher.Props(schedulers.fx, launchSignal).create
|
||||
launchResult <-
|
||||
launcher.init
|
||||
.use(_ => launchSignal.get)
|
||||
.hideErrors
|
||||
tickEventBus <-
|
||||
eventsModule.tickEventBus.hideErrorsWith(e => DummyException(e.toString))
|
||||
_ <-
|
||||
/**
|
||||
* User chose to quit
|
||||
@ -167,7 +161,7 @@ class MainApp(
|
||||
* User chose launch. Wait for game window to close
|
||||
*/
|
||||
else
|
||||
gameInit.use {
|
||||
gameInit(tickEventBus).use {
|
||||
case Right(fib) => fib.join >> Task.unit
|
||||
case Left(error) => IO.terminate(new Exception(error.toString))
|
||||
}.hideErrors
|
||||
@ -195,8 +189,9 @@ class MainAppDelegate(
|
||||
)(implicit
|
||||
spawnProtocol: ActorRef[SpawnProtocol.Command],
|
||||
timeout: Timeout,
|
||||
scheduler: Scheduler
|
||||
scheduler: AkkaScheduler
|
||||
) {
|
||||
implicit val as = scheduler.value
|
||||
|
||||
def init(
|
||||
// appScheduler: monix.execution.Scheduler
|
||||
@ -237,9 +232,12 @@ class MainAppDelegate(
|
||||
damageObs
|
||||
.doOnNextF(event =>
|
||||
(loggerL.debug(s"Received Damage Event $event") >>
|
||||
IO(
|
||||
playerActor ! PlayerActorSupervisor.TakeDamage(event.amount)
|
||||
)).toTask
|
||||
(if (event.victimName === "PlayerNode")
|
||||
// playerActor !! PlayerActorSupervisor.TakeDamage(event.amount)
|
||||
playerActor.askL(
|
||||
PlayerActorSupervisor.TakeDamage2(event.amount, _)
|
||||
)
|
||||
else IO.unit)).toTask
|
||||
)
|
||||
.completedL
|
||||
.toIO
|
||||
@ -251,10 +249,8 @@ class MainAppDelegate(
|
||||
.doOnNextF(_ =>
|
||||
playerActor
|
||||
.askL(PlayerActorSupervisor.GetStatus)
|
||||
.flatMap(s =>
|
||||
loggerL.debug(s"Player actor status: $s") >> UIO.pure(s)
|
||||
)
|
||||
.void
|
||||
.flatMap(s => loggerL.debug(s"Player actor status: $s"))
|
||||
|
||||
// .flatMap(s =>
|
||||
// if (s == Status.Alive)
|
||||
// playerActor
|
||||
@ -276,19 +272,21 @@ class MainAppDelegate(
|
||||
.startAndForget
|
||||
_ <-
|
||||
physicsSpace.collisionObservable
|
||||
.filter(event =>
|
||||
(for {
|
||||
nodeA <- event.nodeA
|
||||
nodeB <- event.nodeB
|
||||
} yield nodeA.getName === "PlayerNode" && nodeB.getName === "John" ||
|
||||
nodeB.getName === "PlayerNode" && nodeA.getName === "John")
|
||||
.getOrElse(false)
|
||||
)
|
||||
.doOnNextF(event =>
|
||||
loggerL
|
||||
.debug(s"$event ${event.appliedImpulse()}")
|
||||
.toTask
|
||||
)
|
||||
// .filter(event =>
|
||||
// (for {
|
||||
// nodeA <- event.nodeA
|
||||
// nodeB <- event.nodeB
|
||||
// } yield nodeA.getName === "PlayerNode" && nodeB.getName === "John" ||
|
||||
// nodeB.getName === "PlayerNode" && nodeA.getName === "John")
|
||||
// .getOrElse(false)
|
||||
// )
|
||||
// .doOnNextF(event =>
|
||||
// loggerL
|
||||
// .debug(s"$event ${event.appliedImpulse()}")
|
||||
// .toTask
|
||||
// )
|
||||
.filter(_.nodeA.map(_.getName =!= "main-scene_node").getOrElse(false))
|
||||
.filter(_.nodeB.map(_.getName =!= "main-scene_node").getOrElse(false))
|
||||
.doOnNextF(event =>
|
||||
(for {
|
||||
victim <- Coeval(for {
|
||||
@ -299,7 +297,11 @@ class MainAppDelegate(
|
||||
victim.foreach { v =>
|
||||
pprint.log(s"emitted event ${v.getName}")
|
||||
mainEventBus ! EventBus.Publish(
|
||||
DamageEvent("John", v.getName, 10),
|
||||
DamageEvent(
|
||||
"John",
|
||||
v.getName,
|
||||
CharacterStats.DamageHealth(10)
|
||||
),
|
||||
"damageHandler"
|
||||
)
|
||||
}
|
||||
@ -327,6 +329,7 @@ class MainAppDelegate(
|
||||
): IO[AppError, PlayerActorSupervisor.Ref] = {
|
||||
val playerPos = ImVector3f.Zero
|
||||
val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o"
|
||||
// val modelPath = os.rel / "Models" / "Oto" / "Oto.mesh.xml"
|
||||
val playerPhysicsControl =
|
||||
PlayerController.Defaults.defaultPlayerPhysicsControl
|
||||
|
||||
@ -335,6 +338,7 @@ class MainAppDelegate(
|
||||
assetManager
|
||||
.loadModelAs[Node](modelPath)
|
||||
.map(_.withRotate(0, FastMath.PI, 0))
|
||||
.tapEval(m => UIO(m.center()))
|
||||
.mapError(AppError.AssetManagerError)
|
||||
playerNode <- UIO(
|
||||
PlayerController.Defaults
|
||||
@ -346,9 +350,7 @@ class MainAppDelegate(
|
||||
)
|
||||
cameraPivotNode <- UIO(
|
||||
new Node(EntityIds.CameraPivot.value)
|
||||
.withControl(
|
||||
new FollowControl(playerNode)
|
||||
)
|
||||
.withControl(new FollowControl(playerNode))
|
||||
.taggedWith[PlayerController.Tags.PlayerCameraPivotNode]
|
||||
)
|
||||
camNode <- UIO(
|
||||
@ -405,24 +407,24 @@ class MainAppDelegate(
|
||||
.toIO
|
||||
.hideErrors
|
||||
.startAndForget
|
||||
_ <-
|
||||
Observable
|
||||
.interval(10.millis)
|
||||
.doOnNextF(_ =>
|
||||
Coeval {
|
||||
val location = playerNode.getWorldTranslation()
|
||||
cameraPivotNode.setLocalTranslation(location)
|
||||
}
|
||||
)
|
||||
.completedL
|
||||
.toIO
|
||||
.hideErrors
|
||||
.startAndForget
|
||||
// _ <-
|
||||
// Observable
|
||||
// .interval(10.millis)
|
||||
// .doOnNextF(_ =>
|
||||
// Coeval {
|
||||
// val location = playerNode.getWorldTranslation()
|
||||
// cameraPivotNode.setLocalTranslation(location)
|
||||
// }
|
||||
// )
|
||||
// .completedL
|
||||
// .toIO
|
||||
// .hideErrors
|
||||
// .startAndForget
|
||||
sched <- UIO.pure(schedulers.async)
|
||||
playerActor <- wire[PlayerController.Props].create
|
||||
obs <-
|
||||
playerActor
|
||||
.askL(PlayerActorSupervisor.GetStatsObservable)
|
||||
.askL(PlayerActorSupervisor.GetStatsObservable2)
|
||||
.onErrorHandleWith(TimeoutError.from)
|
||||
_ <-
|
||||
obs
|
||||
|
@ -5,21 +5,16 @@ import akka.actor.typed.SpawnProtocol
|
||||
import cats.effect.Resource
|
||||
import io.odin.Logger
|
||||
import monix.bio.Task
|
||||
import monix.execution.Scheduler
|
||||
import wow.doge.mygame.executors.ExecutorsModule
|
||||
import wow.doge.mygame.executors.Schedulers.AsyncScheduler
|
||||
|
||||
trait MainModule extends ExecutorsModule {
|
||||
|
||||
def actorSystemResource(
|
||||
logger: Logger[Task],
|
||||
scheduler: Scheduler
|
||||
): Resource[Task, ActorSystem[SpawnProtocol.Command]] =
|
||||
class ActorSystemResource(logger: Logger[Task], scheduler: AsyncScheduler) {
|
||||
def get: Resource[Task, ActorSystem[SpawnProtocol.Command]] =
|
||||
Resource.make(
|
||||
logger.info("Creating Actor System") >> Task(
|
||||
ActorSystem(
|
||||
SpawnProtocol(),
|
||||
name = "GameActorSystem",
|
||||
BootstrapSetup().withDefaultExecutionContext(scheduler)
|
||||
BootstrapSetup().withDefaultExecutionContext(scheduler.value)
|
||||
)
|
||||
)
|
||||
)(sys =>
|
||||
|
81
src/main/scala/wow/doge/mygame/actor/GameActorSystem.scala
Normal file
81
src/main/scala/wow/doge/mygame/actor/GameActorSystem.scala
Normal file
@ -0,0 +1,81 @@
|
||||
package wow.doge.mygame.actor
|
||||
|
||||
import akka.actor.typed.ActorRef
|
||||
import akka.actor.typed.SpawnProtocol
|
||||
import akka.actor.typed.scaladsl.ActorContext
|
||||
import akka.actor.typed.scaladsl.Behaviors
|
||||
import wow.doge.mygame.implicits._
|
||||
// import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
|
||||
// import wow.doge.mygame.subsystems.events.Event
|
||||
// import scala.reflect.ClassTag
|
||||
// import akka.actor.typed.LogOptions
|
||||
// import wow.doge.mygame.subsystems.events.EventBus
|
||||
// import scala.concurrent.duration._
|
||||
// import akka.util.Timeout
|
||||
// import akka.actor.typed.SupervisorStrategy
|
||||
|
||||
object GameActorSystem {
|
||||
sealed trait Command
|
||||
case class GetSpawnProtocol(
|
||||
replyTo: ActorRef[ActorRef[SpawnProtocol.Command]]
|
||||
) extends Command
|
||||
|
||||
class Props() {
|
||||
def create =
|
||||
Behaviors.setup[Command] { ctx =>
|
||||
val systemSpawnProtocol = ctx.spawnN(SpawnProtocol())
|
||||
new GameActorSystem(ctx, this, systemSpawnProtocol).receive
|
||||
}
|
||||
}
|
||||
}
|
||||
class GameActorSystem(
|
||||
ctx: ActorContext[GameActorSystem.Command],
|
||||
props: GameActorSystem.Props,
|
||||
sp: ActorRef[SpawnProtocol.Command]
|
||||
) {
|
||||
import GameActorSystem._
|
||||
def receive =
|
||||
Behaviors.receiveMessage[Command] {
|
||||
case GetSpawnProtocol(replyTo) =>
|
||||
replyTo ! sp
|
||||
Behaviors.same
|
||||
}
|
||||
}
|
||||
|
||||
// object EventBusSupervisor {
|
||||
// sealed trait Command
|
||||
// case class GetMainEventBus(replyTo: ActorRef[GameEventBus[Event]])
|
||||
// extends Command
|
||||
// case class GetEventBus[T](replyTo: ActorRef[GameEventBus[T]])(implicit
|
||||
// classTag: ClassTag[T]
|
||||
// ) extends Command {
|
||||
// def ct = classTag
|
||||
// }
|
||||
|
||||
// class Props(val spawnProtocol: ActorRef[SpawnProtocol.Command]) {
|
||||
// def create =
|
||||
// Behaviors.setup[Command] { ctx =>
|
||||
// new EventBusSupervisor(ctx, this).receive
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// class EventBusSupervisor(
|
||||
// ctx: ActorContext[EventBusSupervisor.Command],
|
||||
// props: EventBusSupervisor.Props
|
||||
// ) {
|
||||
// import EventBusSupervisor._
|
||||
// implicit val timeout = Timeout(1.second)
|
||||
// implicit val sp = props.spawnProtocol
|
||||
// def receive =
|
||||
// Behaviors.receiveMessage[Command] {
|
||||
// case g @ GetEventBus(replyTo) =>
|
||||
// implicit val ct = g.ct
|
||||
// Behaviors
|
||||
// .supervise(EventBus())
|
||||
// .onFailure[Exception](
|
||||
// SupervisorStrategy.restart.withLimit(2, 100.millis)
|
||||
// )
|
||||
// Behaviors.same
|
||||
// case _ => Behaviors.same
|
||||
// }
|
||||
// }
|
@ -1,31 +1,22 @@
|
||||
package wow.doge.mygame.executors
|
||||
|
||||
import cats.effect.Resource
|
||||
import monix.bio.IO
|
||||
import monix.bio.Task
|
||||
import monix.bio.UIO
|
||||
import monix.execution.Scheduler
|
||||
import wow.doge.mygame.types.JmeScheduler
|
||||
|
||||
trait ExecutorsModule {
|
||||
val schedulers = Schedulers()
|
||||
val acquire: UIO[Either[Error, Int]] =
|
||||
IO.pure(1).onErrorHandleWith(_ => IO.raiseError(Error)).attempt
|
||||
// : Resource[IO[Error, Unit], Unit]
|
||||
val res = Resource.make(acquire)(_ => IO.unit)
|
||||
val x: Task[Either[Error, Unit]] = res.use {
|
||||
case Right(value) => Task(Right(println(s"got $value")))
|
||||
case Left(value) => Task(Left(value))
|
||||
}
|
||||
val z = x.onErrorHandleWith(ex => UIO(Right(ex.printStackTrace())))
|
||||
val y: IO[Error, Unit] = z >>
|
||||
x.hideErrors.rethrow
|
||||
|
||||
val jMESchedulerResource = Resource.make(
|
||||
val schedulers = Schedulers.default
|
||||
|
||||
val jmeSchedulerResource = Resource.make(
|
||||
Task(
|
||||
Scheduler
|
||||
.singleThread(name = "JME-Application-Thread", daemonic = false)
|
||||
JmeScheduler(
|
||||
Scheduler
|
||||
.singleThread(name = "JME-Application-Thread", daemonic = false)
|
||||
)
|
||||
)
|
||||
)(e => Task(e.shutdown()))
|
||||
)(s => Task(s.value.shutdown()))
|
||||
}
|
||||
|
||||
sealed trait Error
|
||||
|
@ -13,6 +13,7 @@ import scala.concurrent.ExecutionContext
|
||||
import akka.dispatch.DispatcherPrerequisites
|
||||
import akka.dispatch.ExecutorServiceConfigurator
|
||||
import akka.dispatch.ExecutorServiceFactory
|
||||
import com.jme3.app.Application
|
||||
import com.typesafe.config.Config
|
||||
import javafx.application.Platform
|
||||
import monix.execution.Scheduler
|
||||
@ -42,13 +43,11 @@ object SwingExecutorService extends GUIExecutorService {
|
||||
|
||||
object JMEExecutorService extends GUIExecutorService {
|
||||
override def execute(command: Runnable) =
|
||||
JMERunner.runner.get.apply(command)
|
||||
// new SingleThreadEventExecutor()
|
||||
sys.addShutdownHook(JMEExecutorService.shutdown())
|
||||
JMERunner.runner.enqueue(command)
|
||||
}
|
||||
|
||||
object JMERunner {
|
||||
var runner: Option[Runnable => Unit] = None
|
||||
var runner: Application = null
|
||||
|
||||
}
|
||||
|
||||
@ -98,9 +97,8 @@ class SwingEventThreadExecutorServiceConfigurator(
|
||||
object JFXExecutionContexts {
|
||||
val javaFxExecutionContext: ExecutionContext =
|
||||
ExecutionContext.fromExecutor(new Executor {
|
||||
def execute(command: Runnable): Unit = {
|
||||
def execute(command: Runnable): Unit =
|
||||
Platform.runLater(command)
|
||||
}
|
||||
})
|
||||
val fxScheduler =
|
||||
Scheduler(javaFxExecutionContext)
|
||||
|
@ -5,13 +5,9 @@ import monix.execution.Scheduler
|
||||
import monix.execution.UncaughtExceptionReporter
|
||||
|
||||
final case class Schedulers(
|
||||
blockingIO: Scheduler = Scheduler
|
||||
.io()
|
||||
.withUncaughtExceptionReporter(Schedulers.reporter),
|
||||
async: Scheduler = Scheduler.global
|
||||
.withUncaughtExceptionReporter(Schedulers.reporter),
|
||||
fx: Scheduler = JFXExecutionContexts.fxScheduler
|
||||
.withUncaughtExceptionReporter(Schedulers.reporter)
|
||||
blockingIO: Schedulers.IoScheduler,
|
||||
async: Schedulers.AsyncScheduler,
|
||||
fx: Schedulers.FxScheduler
|
||||
)
|
||||
|
||||
object Schedulers {
|
||||
@ -20,4 +16,24 @@ object Schedulers {
|
||||
logger.error("Uncaught exception", ex)
|
||||
}
|
||||
|
||||
val default = Schedulers(
|
||||
IoScheduler(
|
||||
Scheduler
|
||||
.io()
|
||||
.withUncaughtExceptionReporter(Schedulers.reporter)
|
||||
),
|
||||
AsyncScheduler(
|
||||
Scheduler.global
|
||||
.withUncaughtExceptionReporter(Schedulers.reporter)
|
||||
),
|
||||
FxScheduler(
|
||||
JFXExecutionContexts.fxScheduler
|
||||
.withUncaughtExceptionReporter(Schedulers.reporter)
|
||||
)
|
||||
)
|
||||
|
||||
case class AsyncScheduler(value: Scheduler)
|
||||
case class IoScheduler(value: Scheduler)
|
||||
case class FxScheduler(value: Scheduler)
|
||||
|
||||
}
|
||||
|
@ -25,9 +25,6 @@ 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
|
||||
@ -35,10 +32,11 @@ 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._
|
||||
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
|
||||
import wow.doge.mygame.subsystems.events.TickEvent
|
||||
import wow.doge.mygame.types._
|
||||
import wow.doge.mygame.utils.AkkaUtils
|
||||
import wow.doge.mygame.utils.wrappers.jme._
|
||||
object GameAppTags {
|
||||
sealed trait RootNode
|
||||
sealed trait GuiNode
|
||||
@ -47,9 +45,9 @@ object GameAppTags {
|
||||
class GameApp private[game] (
|
||||
logger: Logger[Task],
|
||||
app: SimpleAppExt,
|
||||
gameActor: ActorRef[TestGameActor.Command],
|
||||
gameActor: ActorRef[GameAppActor.Command],
|
||||
gameSpawnProtocol: ActorRef[SpawnProtocol.Command],
|
||||
scheduler: akka.actor.typed.Scheduler
|
||||
akkaScheduler: AkkaScheduler
|
||||
) {
|
||||
def inputManager: UIO[InputManager] = UIO(app.getInputManager())
|
||||
val assetManager = new AssetManager(app.getAssetManager())
|
||||
@ -75,7 +73,7 @@ class GameApp private[game] (
|
||||
)(implicit name: sourcecode.Name) =
|
||||
AkkaUtils.spawnActorL(behavior, actorName, props)(
|
||||
2.seconds,
|
||||
scheduler,
|
||||
akkaScheduler.value,
|
||||
gameSpawnProtocol,
|
||||
name
|
||||
)
|
||||
@ -87,19 +85,21 @@ class GameApp private[game] (
|
||||
|
||||
class GameAppResource(
|
||||
logger: Logger[Task],
|
||||
jmeThread: Scheduler,
|
||||
schedulers: Schedulers
|
||||
jmeThread: JmeScheduler,
|
||||
schedulers: Schedulers,
|
||||
tickEventBus: GameEventBus[TickEvent]
|
||||
)(implicit
|
||||
timeout: Timeout,
|
||||
scheduler: akka.actor.typed.Scheduler,
|
||||
scheduler: AkkaScheduler,
|
||||
spawnProtocol: ActorRef[SpawnProtocol.Command]
|
||||
) {
|
||||
implicit val as = scheduler.value
|
||||
def resource
|
||||
: Resource[UIO, Either[AppError, (GameApp, Fiber[Nothing, Unit])]] =
|
||||
Resource.make(
|
||||
(for {
|
||||
app <- UIO(new SimpleAppExt(schedulers, new BulletAppState))
|
||||
_ <- UIO(JMERunner.runner = Some(app.enqueue _))
|
||||
_ <- UIO(JMERunner.runner = app)
|
||||
_ <- UIO {
|
||||
val settings = new AppSettings(true)
|
||||
settings.setVSync(true)
|
||||
@ -112,20 +112,21 @@ class GameAppResource(
|
||||
app.setSettings(settings)
|
||||
}
|
||||
|
||||
fib <- UIO(app.start).executeOn(jmeThread).start
|
||||
fib <- UIO(app.start).executeOn(jmeThread.value).start
|
||||
_ <- Task.deferFuture(app.started).onErrorHandleWith(TimeoutError.from)
|
||||
testGameActor <- AkkaUtils.spawnActorL(
|
||||
new TestGameActor.Props().create,
|
||||
gameAppActor <- AkkaUtils.spawnActorL(
|
||||
new GameAppActor.Props(tickEventBus).behavior,
|
||||
Some("testGameActor"),
|
||||
props = Dispatchers.jmeDispatcher
|
||||
)
|
||||
_ <- gameAppActor !! GameAppActor.Start
|
||||
sp <-
|
||||
testGameActor
|
||||
.askL(TestGameActor.GetSpawnProtocol)
|
||||
gameAppActor
|
||||
.askL(GameAppActor.GetSpawnProtocol)
|
||||
.onErrorHandleWith(TimeoutError.from)
|
||||
gameApp <- UIO(new GameApp(logger, app, testGameActor, sp, scheduler))
|
||||
gameApp <- UIO(new GameApp(logger, app, gameAppActor, sp, scheduler))
|
||||
_ <- UIO {
|
||||
val fut = () => testGameActor.ask(TestGameActor.Stop).flatten
|
||||
val fut = () => gameAppActor.ask(GameAppActor.Stop).flatten
|
||||
app.cancelToken = Some(fut)
|
||||
}
|
||||
} yield (gameApp, fib)).attempt
|
||||
@ -137,58 +138,6 @@ class GameAppResource(
|
||||
|
||||
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 {
|
||||
|
||||
|
@ -2,14 +2,18 @@ package wow.doge.mygame.game
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import akka.actor.typed.ActorRef
|
||||
import akka.actor.typed.PostStop
|
||||
import akka.actor.typed.SpawnProtocol
|
||||
import akka.actor.typed.SupervisorStrategy
|
||||
import akka.actor.typed.scaladsl.Behaviors
|
||||
import monix.execution.CancelableFuture
|
||||
import monix.execution.CancelablePromise
|
||||
import wow.doge.mygame.game.TickGenerator.Send
|
||||
import wow.doge.mygame.implicits._
|
||||
import wow.doge.mygame.subsystems.events.EventBus
|
||||
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
|
||||
import wow.doge.mygame.subsystems.events.TickEvent
|
||||
import wow.doge.mygame.subsystems.events.TickEvent.PhysicsTick
|
||||
import wow.doge.mygame.utils.GenericTimerActor
|
||||
|
||||
object GameAppActor {
|
||||
@ -17,7 +21,11 @@ object GameAppActor {
|
||||
sealed trait Command
|
||||
case object Start extends Command
|
||||
case object Pause extends Command
|
||||
case object Stop extends 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
|
||||
|
||||
case class Props(tickEventBus: GameEventBus[TickEvent]) {
|
||||
def behavior =
|
||||
@ -38,19 +46,43 @@ object GameAppActor {
|
||||
.behavior
|
||||
)
|
||||
|
||||
Behaviors.receiveMessage {
|
||||
case Start =>
|
||||
tickGeneratorTimer ! GenericTimerActor.Start
|
||||
Behaviors.same
|
||||
case Pause =>
|
||||
tickGeneratorTimer ! GenericTimerActor.Stop
|
||||
Behaviors.same
|
||||
case Stop =>
|
||||
ctx.log.info("Received stop")
|
||||
tickGeneratorTimer ! GenericTimerActor.Stop
|
||||
Behaviors.stopped
|
||||
val sp = ctx.spawn(SpawnProtocol(), "gameSpawnProtocol")
|
||||
|
||||
}
|
||||
ctx.spawn(
|
||||
GenericTimerActor
|
||||
.Props(ctx.self, Ping, 1000.millis)
|
||||
.behavior,
|
||||
"pingTimer"
|
||||
) ! GenericTimerActor.Start
|
||||
|
||||
val stopPromise = CancelablePromise[Unit]()
|
||||
|
||||
Behaviors
|
||||
.receiveMessage[Command] {
|
||||
case Start =>
|
||||
tickGeneratorTimer ! GenericTimerActor.Start
|
||||
Behaviors.same
|
||||
case Pause =>
|
||||
tickGeneratorTimer ! GenericTimerActor.Stop
|
||||
Behaviors.same
|
||||
case Stop(replyTo) =>
|
||||
ctx.log.infoP("Received stop")
|
||||
tickGeneratorTimer ! GenericTimerActor.Stop
|
||||
replyTo ! stopPromise.future
|
||||
Behaviors.stopped
|
||||
case Ping =>
|
||||
ctx.log.debugP("ping")
|
||||
Behaviors.same
|
||||
case GetSpawnProtocol(replyTo) =>
|
||||
replyTo ! sp
|
||||
Behaviors.same
|
||||
|
||||
}
|
||||
.receiveSignal {
|
||||
case (_, PostStop) =>
|
||||
stopPromise.success(())
|
||||
Behaviors.same
|
||||
}
|
||||
}
|
||||
|
||||
val renderTickGeneratorBehavior =
|
||||
@ -69,92 +101,3 @@ object TickGenerator {
|
||||
sealed trait Command
|
||||
case object Send extends Command
|
||||
}
|
||||
object SubscribingActor {
|
||||
def apply() =
|
||||
Behaviors.receive[PhysicsTick.type] { (ctx, msg) =>
|
||||
ctx.log.debugP(s"received event $msg")
|
||||
Behaviors.same
|
||||
}
|
||||
}
|
||||
object Methods {
|
||||
|
||||
def old() = {
|
||||
// val movementActor =
|
||||
// ctx.spawn(
|
||||
// MovementActor(MovementActor.Props(app, geom)),
|
||||
// "movementActor"
|
||||
// // DispatcherSelector.fromConfig("jme-dispatcher")
|
||||
// )
|
||||
|
||||
// val movementActorTimer = ctx.spawn(
|
||||
// MovementActorTimer(movementActor),
|
||||
// "movementActorTimer"
|
||||
// )
|
||||
}
|
||||
|
||||
def old2() = {
|
||||
// ctx.log.info("here")
|
||||
|
||||
// {
|
||||
// implicit val s = schedulers.async
|
||||
// Task
|
||||
// .parZip2(
|
||||
// loggerL.info("Test").executeOn(app.scheduler),
|
||||
// app
|
||||
// .enqueueL(() => loggerL.info("here 2").executeOn(app.scheduler))
|
||||
// .flatten
|
||||
// )
|
||||
// .runToFuture
|
||||
// }
|
||||
|
||||
// app
|
||||
// .getRootNode()
|
||||
// .depthFirst(s =>
|
||||
// // s match {
|
||||
// // case node: Node =>
|
||||
// // println("node" + s.getName() + " children " + node.getChildren())
|
||||
// // case g: Geometry => println(s.getName())
|
||||
// // }
|
||||
// println(s.getName())
|
||||
// )
|
||||
|
||||
// println("----------------")
|
||||
|
||||
// {
|
||||
// app
|
||||
// .getRootNode()
|
||||
// .observableDepthFirst()
|
||||
// .map(s => s.getName())
|
||||
// // .takeWhileInclusive(_.getName() != "level")
|
||||
// .onErrorHandle(e => e.getMessage())
|
||||
// .foreach(println)(schedulers.async)
|
||||
|
||||
// }
|
||||
|
||||
// println("----------------")
|
||||
|
||||
// {
|
||||
// app
|
||||
// .getRootNode()
|
||||
// .observableBreadthFirst()
|
||||
// .map(s => s.getName())
|
||||
// // .takeWhileInclusive(_.getName() != "level")
|
||||
// .onErrorHandle(e => e.getMessage())
|
||||
// .foreach(println)(schedulers.async)
|
||||
|
||||
// }
|
||||
|
||||
// app.start()
|
||||
// Behaviors.same
|
||||
}
|
||||
}
|
||||
|
||||
// new PlayerMovementState(
|
||||
// // movementActor,
|
||||
// // movementActorTimer,
|
||||
// imMovementActor,
|
||||
// // geom,
|
||||
// // camNode,
|
||||
// playerNode
|
||||
// // ctx.self
|
||||
// )
|
||||
|
@ -1,19 +1,31 @@
|
||||
package wow.doge.mygame.game.controls
|
||||
|
||||
import scala.concurrent.Future
|
||||
|
||||
import com.jme3.math.FastMath
|
||||
import com.jme3.math.Quaternion
|
||||
import com.jme3.scene.Node
|
||||
import com.jme3.scene.control.AbstractControl
|
||||
import com.jme3.math.Vector3f
|
||||
import com.jme3.renderer.RenderManager
|
||||
import com.jme3.renderer.ViewPort
|
||||
import monix.reactive.Observable
|
||||
import wow.doge.mygame.game.subsystems.input.PlayerCameraInput
|
||||
import com.jme3.scene.Node
|
||||
import com.jme3.scene.Spatial
|
||||
import monix.{eval => me}
|
||||
import com.jme3.math.FastMath
|
||||
import com.jme3.math.Vector3f
|
||||
import com.jme3.scene.control.AbstractControl
|
||||
import monix.execution.Ack
|
||||
import monix.execution.Cancelable
|
||||
import monix.execution.Scheduler
|
||||
import monix.reactive.Observable
|
||||
import monix.reactive.Observer
|
||||
import wow.doge.mygame.game.subsystems.input.PlayerCameraInput
|
||||
|
||||
/**
|
||||
* A very low level (and error prone) camera movement control implementation.
|
||||
* Not used currently
|
||||
*
|
||||
* @param rotationBuf
|
||||
* @param obs
|
||||
* @param rotateFn
|
||||
* @param s
|
||||
*/
|
||||
class CameraMovementControl(
|
||||
rotationBuf: Quaternion,
|
||||
obs: Observable[PlayerCameraInput],
|
||||
@ -23,8 +35,21 @@ class CameraMovementControl(
|
||||
private var _event: PlayerCameraInput = null
|
||||
private var _subscriptionToken: Cancelable = null
|
||||
|
||||
private val sink = new Observer[PlayerCameraInput] {
|
||||
|
||||
override def onNext(event: PlayerCameraInput): Future[Ack] = {
|
||||
_event = event
|
||||
Ack.Continue
|
||||
}
|
||||
|
||||
override def onError(ex: Throwable): Unit = {}
|
||||
|
||||
override def onComplete(): Unit = {}
|
||||
|
||||
}
|
||||
|
||||
override def controlUpdate(tpf: Float): Unit =
|
||||
if (_event != null)
|
||||
if (_event != null) {
|
||||
_event match {
|
||||
case PlayerCameraInput.CameraRotateLeft =>
|
||||
val rot = rotationBuf
|
||||
@ -43,6 +68,8 @@ class CameraMovementControl(
|
||||
.fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X)
|
||||
rotateFn(rot)
|
||||
}
|
||||
_event = null
|
||||
}
|
||||
|
||||
override def controlRender(
|
||||
x$1: RenderManager,
|
||||
@ -51,8 +78,7 @@ class CameraMovementControl(
|
||||
override def setSpatial(spatial: Spatial): Unit = {
|
||||
super.setSpatial(spatial)
|
||||
if (this.spatial != null)
|
||||
_subscriptionToken =
|
||||
obs.doOnNext(event => me.Task { _event = event }).subscribe()
|
||||
_subscriptionToken = obs.subscribe(sink)
|
||||
else {
|
||||
_subscriptionToken.cancel()
|
||||
_subscriptionToken = null
|
||||
|
@ -0,0 +1,116 @@
|
||||
package wow.doge.mygame.game.entities
|
||||
|
||||
import akka.actor.typed.ActorRef
|
||||
import akka.actor.typed.Behavior
|
||||
import akka.actor.typed.scaladsl.ActorContext
|
||||
import akka.actor.typed.scaladsl.Behaviors
|
||||
import io.estatico.newtype.macros.newtype
|
||||
import wow.doge.mygame.game.entities.CharacterStats.HealHealth
|
||||
|
||||
case class CharacterStats(hp: CharacterStats.Health, stamina: Int)
|
||||
object CharacterStats {
|
||||
@newtype case class HealHealth(toInt: Int)
|
||||
@newtype case class DamageHealth(toInt: Int)
|
||||
@newtype case class Health(toInt: Int)
|
||||
object Health {
|
||||
implicit class HealthOps(private val h: Health) extends AnyVal {
|
||||
// def +(v: Int): Health = Health(h.toInt + v)
|
||||
// def -(v: Int): Health = Health(h.toInt - v)
|
||||
// def *(v: Int): Health = Health(h.toInt * v)
|
||||
// def /(v: Int): Health = Health(h.toInt / v)
|
||||
def :+(v: HealHealth): Health = Health(h.toInt + v.toInt)
|
||||
def -(v: DamageHealth): Health = Health(h.toInt - v.toInt)
|
||||
}
|
||||
}
|
||||
@newtype case class HealStamina(toInt: Int)
|
||||
@newtype case class DamageStamina(toInt: Int)
|
||||
@newtype case class Stamina(toInt: Int)
|
||||
object Stamina {
|
||||
implicit class StaminaOps(private val h: Stamina) extends AnyVal {
|
||||
// def +(v: Int): Stamina = Stamina(h.toInt + v)
|
||||
// def -(v: Int): Stamina = Stamina(h.toInt - v)
|
||||
// def *(v: Int): Stamina = Stamina(h.toInt * v)
|
||||
// def /(v: Int): Stamina = Stamina(h.toInt / v)
|
||||
def :+(v: HealStamina): Stamina = Stamina(h.toInt + v.toInt)
|
||||
def -(v: DamageStamina): Stamina = Stamina(h.toInt - v.toInt)
|
||||
}
|
||||
}
|
||||
// object Stamina {
|
||||
// implicit class StaminaOps(private val h: Stamina) extends AnyVal {
|
||||
// def +(v: Health): Stamina = Stamina(h.toInt + v.toInt)
|
||||
// def -(v: Health): Stamina = Stamina(h.toInt - v.toInt)
|
||||
// def *(v: Health): Stamina = Stamina(h.toInt * v.toInt)
|
||||
// def /(v: Health): Stamina = Stamina(h.toInt / v.toInt)
|
||||
// }
|
||||
// }
|
||||
|
||||
// object Damage {
|
||||
// implicit class DamageOps(private val h: Damage) extends AnyVal {
|
||||
// def +(v: Health): Damage = Damage(h.toInt + v.toInt)
|
||||
// def -(v: Health): Damage = Damage(h.toInt - v.toInt)
|
||||
// def *(v: Health): Damage = Damage(h.toInt * v.toInt)
|
||||
// def /(v: Health): Damage = Damage(h.toInt / v.toInt)
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
object StatsActor {
|
||||
|
||||
sealed trait Command
|
||||
// case class TakeDamage(value: Int) extends Command
|
||||
case class TakeDamageResult(
|
||||
value: CharacterStats.DamageHealth,
|
||||
replyTo: ActorRef[(Boolean, CharacterStats)]
|
||||
) extends Command
|
||||
case class HealResult(value: HealHealth) extends Command
|
||||
case class CurrentStats(replyTo: ActorRef[CharacterStats]) extends Command
|
||||
|
||||
class Props(
|
||||
startingHealth: CharacterStats.Health,
|
||||
startingStamina: CharacterStats.Stamina
|
||||
) {
|
||||
def behavior =
|
||||
Behaviors.setup[Command] { ctx =>
|
||||
new StatsActor(ctx, this)
|
||||
.receive(
|
||||
State(CharacterStats(startingHealth, startingStamina.toInt))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
case class State(stats: CharacterStats)
|
||||
}
|
||||
class StatsActor(
|
||||
ctx: ActorContext[StatsActor.Command],
|
||||
props: StatsActor.Props
|
||||
) {
|
||||
import StatsActor._
|
||||
import CharacterStats._
|
||||
import com.softwaremill.quicklens._
|
||||
def receive(state: State): Behavior[Command] =
|
||||
Behaviors.receiveMessage[Command] {
|
||||
// Todo add min max values
|
||||
// case TakeDamage(value) =>
|
||||
// val nextState =
|
||||
// if (state.stats.hp - value <= 0)
|
||||
// state.modify(_.stats.hp).setTo(0)
|
||||
// else
|
||||
// state.modify(_.stats.hp).using(_ - value)
|
||||
// receive(nextState)
|
||||
case TakeDamageResult(value, replyTo) =>
|
||||
val nextState = if ((state.stats.hp - value).toInt <= 0) {
|
||||
replyTo ! true -> state.stats
|
||||
state.modify(_.stats.hp).setTo(Health(0))
|
||||
} else {
|
||||
replyTo ! false -> state.stats
|
||||
state.modify(_.stats.hp).using(_ - value)
|
||||
}
|
||||
receive(nextState)
|
||||
case HealResult(value) =>
|
||||
receive(state.modify(_.stats.hp).using(_ :+ value))
|
||||
case CurrentStats(replyTo) =>
|
||||
replyTo ! state.stats
|
||||
Behaviors.same
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
package wow.doge.mygame.game.entities
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import scala.util.Failure
|
||||
import scala.util.Success
|
||||
|
||||
import akka.actor.typed.ActorRef
|
||||
import akka.actor.typed.Behavior
|
||||
@ -9,9 +11,15 @@ import akka.actor.typed.PostStop
|
||||
import akka.actor.typed.SupervisorStrategy
|
||||
import akka.actor.typed.scaladsl.ActorContext
|
||||
import akka.actor.typed.scaladsl.Behaviors
|
||||
import akka.util.Timeout
|
||||
import com.typesafe.scalalogging.Logger
|
||||
import monix.reactive.Observable
|
||||
import monix.reactive.OverflowStrategy
|
||||
import monix.reactive.subjects.ConcurrentSubject
|
||||
import org.slf4j.event.Level
|
||||
import wow.doge.mygame.Dispatchers
|
||||
import wow.doge.mygame.executors.Schedulers.AsyncScheduler
|
||||
import wow.doge.mygame.game.entities.StatsActor
|
||||
import wow.doge.mygame.implicits._
|
||||
import wow.doge.mygame.subsystems.events.EventBus
|
||||
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
|
||||
@ -19,13 +27,8 @@ import wow.doge.mygame.subsystems.events.PlayerEvent
|
||||
import wow.doge.mygame.subsystems.events.TickEvent
|
||||
import wow.doge.mygame.subsystems.events.TickEvent.RenderTick
|
||||
import wow.doge.mygame.subsystems.movement.ImMovementActor
|
||||
import scala.util.Success
|
||||
import scala.util.Failure
|
||||
import akka.util.Timeout
|
||||
import monix.reactive.Observable
|
||||
import monix.reactive.subjects.ConcurrentSubject
|
||||
import monix.execution.Scheduler
|
||||
import monix.reactive.OverflowStrategy
|
||||
import monix.eval.Coeval
|
||||
import monix.execution.AsyncQueue
|
||||
object PlayerActorSupervisor {
|
||||
|
||||
type Ref = ActorRef[PlayerActorSupervisor.Command]
|
||||
@ -37,23 +40,33 @@ object PlayerActorSupervisor {
|
||||
}
|
||||
|
||||
sealed trait Command
|
||||
case class TakeDamage(value: Int) extends Command
|
||||
case class Heal(value: Int) extends Command
|
||||
case class CurrentStats(replyTo: ActorRef[StatsActor.State]) extends Command
|
||||
case class TakeDamage(value: CharacterStats.DamageHealth) extends Command
|
||||
case class TakeDamage2(
|
||||
value: CharacterStats.DamageHealth,
|
||||
replyTo: ActorRef[Unit]
|
||||
) extends Command
|
||||
case class Heal(value: CharacterStats.HealHealth) extends Command
|
||||
case class CurrentStats(replyTo: ActorRef[CharacterStats]) extends Command
|
||||
case class GetStatus(replyTo: ActorRef[Status]) extends Command
|
||||
case class GetStatsObservable(replyTo: ActorRef[Observable[StatsActor.State]])
|
||||
case class GetStatsObservable(replyTo: ActorRef[Observable[CharacterStats]])
|
||||
extends Command
|
||||
case class GetStatsObservable2(replyTo: ActorRef[Observable[CharacterStats]])
|
||||
extends Command
|
||||
|
||||
private case object Die extends Command
|
||||
private case class DamageResponse(response: (Boolean, StatsActor.State))
|
||||
private case class DamageResponse(response: (Boolean, CharacterStats))
|
||||
extends Command
|
||||
private case class DamageResponse2(
|
||||
response: (Boolean, CharacterStats),
|
||||
replyTo: ActorRef[Unit]
|
||||
) extends Command
|
||||
// private case class InternalTakeDamage(old: Int, value: Int) extends Command
|
||||
private case class LogError(ex: Throwable) extends Command
|
||||
class Props(
|
||||
val playerEventBus: GameEventBus[PlayerEvent],
|
||||
val tickEventBus: GameEventBus[TickEvent],
|
||||
val imMovementActorBehavior: Behavior[ImMovementActor.Command],
|
||||
val scheduler: Scheduler
|
||||
val scheduler: AsyncScheduler
|
||||
) {
|
||||
def behavior =
|
||||
Behaviors.logMessages(
|
||||
@ -78,7 +91,12 @@ object PlayerActorSupervisor {
|
||||
)
|
||||
|
||||
val playerStatsActor =
|
||||
ctx.spawnN(new StatsActor.Props(100, 100).behavior)
|
||||
ctx.spawnN(
|
||||
new StatsActor.Props(
|
||||
CharacterStats.Health(100),
|
||||
CharacterStats.Stamina(100)
|
||||
).behavior
|
||||
)
|
||||
|
||||
val playerMovementEl = ctx.spawnN(
|
||||
Behaviors
|
||||
@ -115,7 +133,13 @@ object PlayerActorSupervisor {
|
||||
ctx,
|
||||
this,
|
||||
Children(playerMovementActor, playerStatsActor),
|
||||
ConcurrentSubject.publish(OverflowStrategy.DropOld(50))(scheduler)
|
||||
ConcurrentSubject.publish(
|
||||
OverflowStrategy.DropOldAndSignal(
|
||||
50,
|
||||
dropped => Coeval.pure(None)
|
||||
)
|
||||
)(scheduler.value),
|
||||
AsyncQueue.bounded(10)(scheduler.value)
|
||||
).aliveState
|
||||
}
|
||||
)
|
||||
@ -131,7 +155,8 @@ class PlayerActorSupervisor(
|
||||
ctx: ActorContext[PlayerActorSupervisor.Command],
|
||||
props: PlayerActorSupervisor.Props,
|
||||
children: PlayerActorSupervisor.Children,
|
||||
statsSubject: ConcurrentSubject[StatsActor.State, StatsActor.State]
|
||||
statsSubject: ConcurrentSubject[CharacterStats, CharacterStats],
|
||||
statsQueue: AsyncQueue[CharacterStats]
|
||||
) {
|
||||
import PlayerActorSupervisor._
|
||||
implicit val timeout = Timeout(1.second)
|
||||
@ -149,12 +174,23 @@ class PlayerActorSupervisor(
|
||||
case Failure(ex) => LogError(ex)
|
||||
}
|
||||
Behaviors.same
|
||||
case TakeDamage2(value, replyTo) =>
|
||||
// children.movementActor ! ImMovementActor.MovedDown(true)
|
||||
// ctx.ask(children.statsActor, StatsActor.CurrentStats(_)) {
|
||||
// case Success(status) => InternalTakeDamage(status.hp, value)
|
||||
// case Failure(ex) => LogError(ex)
|
||||
// }
|
||||
ctx.ask(children.statsActor, StatsActor.TakeDamageResult(value, _)) {
|
||||
case Success(response) => DamageResponse2(response, replyTo)
|
||||
case Failure(ex) => LogError(ex)
|
||||
}
|
||||
Behaviors.same
|
||||
case CurrentStats(replyTo) =>
|
||||
// ctx.ask(children.statsActor, StatsActor.CurrentStats())
|
||||
children.statsActor ! StatsActor.CurrentStats(replyTo)
|
||||
Behaviors.same
|
||||
case Heal(value) =>
|
||||
children.statsActor ! StatsActor.Heal(value)
|
||||
children.statsActor ! StatsActor.HealResult(value)
|
||||
Behaviors.same
|
||||
case GetStatus(replyTo) =>
|
||||
replyTo ! Status.Alive
|
||||
@ -169,6 +205,12 @@ class PlayerActorSupervisor(
|
||||
case GetStatsObservable(replyTo) =>
|
||||
replyTo ! statsSubject
|
||||
Behaviors.same
|
||||
case GetStatsObservable2(replyTo) =>
|
||||
import monix.{eval => me}
|
||||
replyTo ! Observable.repeatEvalF(
|
||||
me.Task.deferFuture(statsQueue.poll())
|
||||
)
|
||||
Behaviors.same
|
||||
case DamageResponse(response) =>
|
||||
response match {
|
||||
case (dead, state) =>
|
||||
@ -176,6 +218,15 @@ class PlayerActorSupervisor(
|
||||
statsSubject.onNext(state)
|
||||
}
|
||||
Behaviors.same
|
||||
case DamageResponse2(response, replyTo) =>
|
||||
response match {
|
||||
case (dead, stats) =>
|
||||
if (dead) ctx.self ! Die
|
||||
statsQueue
|
||||
.offer(stats)
|
||||
.foreach(_ => replyTo ! ())(props.scheduler.value)
|
||||
}
|
||||
Behaviors.same
|
||||
case Die => deadState
|
||||
case LogError(ex) =>
|
||||
ctx.log.error(ex.getMessage)
|
||||
@ -215,54 +266,3 @@ class PlayerActorSupervisor(
|
||||
Behaviors.same
|
||||
}
|
||||
}
|
||||
|
||||
object StatsActor {
|
||||
|
||||
sealed trait Command
|
||||
case class TakeDamage(value: Int) extends Command
|
||||
case class TakeDamageResult(value: Int, replyTo: ActorRef[(Boolean, State)])
|
||||
extends Command
|
||||
case class Heal(value: Int) extends Command
|
||||
case class CurrentStats(replyTo: ActorRef[State]) extends Command
|
||||
|
||||
class Props(startingHealth: Int, startingStamina: Int) {
|
||||
def behavior =
|
||||
Behaviors.setup[Command] { ctx =>
|
||||
new StatsActor(ctx, this)
|
||||
.receive(State(startingHealth, startingStamina))
|
||||
}
|
||||
}
|
||||
|
||||
case class State(hp: Int, stamina: Int)
|
||||
}
|
||||
class StatsActor(
|
||||
ctx: ActorContext[StatsActor.Command],
|
||||
props: StatsActor.Props
|
||||
) {
|
||||
import StatsActor._
|
||||
import com.softwaremill.quicklens._
|
||||
def receive(state: State): Behavior[Command] =
|
||||
Behaviors.receiveMessage[Command< |