many changes

This commit is contained in:
Rohan Sircar 2021-01-30 13:58:42 +05:30
parent 67a2bc4385
commit fd8b3819ff
29 changed files with 1012 additions and 409 deletions

View File

@ -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

View File

@ -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 ()

View File

@ -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

View File

@ -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 =>

View 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
// }
// }

View File

@ -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

View File

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

View File

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

View File

@ -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 {

View File

@ -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
// )

View File

@ -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

View File

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

View File

@ -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<