Browse Source

many changes

development
Rohan Sircar 3 years ago
parent
commit
fd8b3819ff
  1. 3
      build.sbt
  2. 14
      src/main/scala/wow/doge/mygame/Main.scala
  3. 148
      src/main/scala/wow/doge/mygame/MainApp.scala
  4. 13
      src/main/scala/wow/doge/mygame/MainModule.scala
  5. 81
      src/main/scala/wow/doge/mygame/actor/GameActorSystem.scala
  6. 27
      src/main/scala/wow/doge/mygame/executors/ExecutorsModule.scala
  7. 10
      src/main/scala/wow/doge/mygame/executors/GUIExecutor.scala
  8. 30
      src/main/scala/wow/doge/mygame/executors/Schedulers.scala
  9. 91
      src/main/scala/wow/doge/mygame/game/GameApp.scala
  10. 149
      src/main/scala/wow/doge/mygame/game/GameAppActor.scala
  11. 46
      src/main/scala/wow/doge/mygame/game/controls/CameraMovementControls.scala
  12. 116
      src/main/scala/wow/doge/mygame/game/entities/CharacterStats.scala
  13. 136
      src/main/scala/wow/doge/mygame/game/entities/player/PlayerActorSupervisor.scala
  14. 5
      src/main/scala/wow/doge/mygame/game/entities/player/PlayerController.scala
  15. 1
      src/main/scala/wow/doge/mygame/game/entities/player/PlayerEventListeners.scala
  16. 1
      src/main/scala/wow/doge/mygame/game/subsystems/movement/MovementActor.scala
  17. 17
      src/main/scala/wow/doge/mygame/implicits/package.scala
  18. 8
      src/main/scala/wow/doge/mygame/launcher/Launcher.scala
  19. 5
      src/main/scala/wow/doge/mygame/subsystems/events/EventBus.scala
  20. 9
      src/main/scala/wow/doge/mygame/subsystems/events/Events.scala
  21. 6
      src/main/scala/wow/doge/mygame/subsystems/events/EventsModule.scala
  22. 310
      src/main/scala/wow/doge/mygame/subsystems/scriptsystem/MonixScriptCompiler.scala
  23. 5
      src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptActor.scala
  24. 4
      src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptCachingActor.scala
  25. 14
      src/main/scala/wow/doge/mygame/types/package.scala
  26. 33
      src/main/scala/wow/doge/mygame/utils/MonixDirectoryWatcher.scala
  27. 63
      src/test/scala/wow/doge/mygame/AnimTest.scala
  28. 30
      src/test/scala/wow/doge/mygame/FileWatcherTest.scala
  29. 44
      src/test/scala/wow/doge/mygame/MonixScriptCompilerTest.scala

3
build.sbt

@ -66,7 +66,8 @@ lazy val root = (project in file(".")).settings(
"com.lihaoyi" %% "pprint" % "0.6.0", "com.lihaoyi" %% "pprint" % "0.6.0",
"org.scalatest" %% "scalatest" % "3.2.2" % "test", "org.scalatest" %% "scalatest" % "3.2.2" % "test",
"org.typelevel" %% "cats-mtl" % "1.1.1", "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 // Determine OS version of JavaFX binaries

14
src/main/scala/wow/doge/mygame/Main.scala

@ -14,14 +14,16 @@ import io.odin._
import io.odin.json.Formatter import io.odin.json.Formatter
import io.odin.syntax._ import io.odin.syntax._
import scalafx.scene.control.TextArea 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 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} import java.util.logging.{Logger => JLogger, Level}
JLogger.getLogger("").setLevel(Level.SEVERE) JLogger.getLogger("").setLevel(Level.SEVERE)
implicit val timeout = Timeout(1.second) implicit val timeout = Timeout(1.second)
override def scheduler: Scheduler = schedulers.async
override def scheduler: Scheduler = schedulers.async.value
def appResource(consoleStream: GenericConsoleStream[TextArea]) = def appResource(consoleStream: GenericConsoleStream[TextArea]) =
for { for {
@ -34,18 +36,18 @@ object Main extends BIOApp with MainModule {
"application-log-1.log", "application-log-1.log",
Formatter.json Formatter.json
).withAsync(timeWindow = 1.milliseconds, maxBufferSize = Some(2000)) ).withAsync(timeWindow = 1.milliseconds, maxBufferSize = Some(2000))
jmeScheduler <- jMESchedulerResource
jmeScheduler <- jmeSchedulerResource
// backend <- Resource.make(toIO(HttpClientMonixBackend()))(backend => // backend <- Resource.make(toIO(HttpClientMonixBackend()))(backend =>
// toIO(backend.close()) // toIO(backend.close())
// ) // )
actorSystem <- actorSystemResource(logger, schedulers.async)
actorSystem <- new ActorSystemResource(logger, schedulers.async).get
_ <- Resource.liftF( _ <- Resource.liftF(
new MainApp( new MainApp(
logger, logger,
jmeScheduler, jmeScheduler,
schedulers, schedulers,
consoleStream consoleStream
)(actorSystem, timeout, actorSystem.scheduler).program
)(actorSystem, timeout, AkkaScheduler(actorSystem.scheduler)).program
) )
} yield () } yield ()

148
src/main/scala/wow/doge/mygame/MainApp.scala

@ -3,7 +3,6 @@ package wow.doge.mygame
import scala.concurrent.duration._ import scala.concurrent.duration._
import akka.actor.typed.ActorRef import akka.actor.typed.ActorRef
import akka.actor.typed.Scheduler
import akka.actor.typed.SpawnProtocol import akka.actor.typed.SpawnProtocol
import akka.util.Timeout import akka.util.Timeout
import cats.effect.Resource import cats.effect.Resource
@ -16,6 +15,8 @@ import com.jme3.material.Material
import com.jme3.material.MaterialDef import com.jme3.material.MaterialDef
import com.jme3.math.ColorRGBA import com.jme3.math.ColorRGBA
import com.jme3.math.FastMath import com.jme3.math.FastMath
import com.jme3.math.Quaternion
import com.jme3.math.Vector3f
import com.jme3.renderer.Camera import com.jme3.renderer.Camera
import com.jme3.renderer.ViewPort import com.jme3.renderer.ViewPort
import com.jme3.scene.Node import com.jme3.scene.Node
@ -27,19 +28,22 @@ import monix.bio.IO
import monix.bio.Task import monix.bio.Task
import monix.bio.UIO import monix.bio.UIO
import monix.eval.Coeval import monix.eval.Coeval
import monix.execution.exceptions.DummyException
import monix.reactive.Observable import monix.reactive.Observable
import scalafx.scene.control.TextArea import scalafx.scene.control.TextArea
import wow.doge.mygame.AppError.TimeoutError import wow.doge.mygame.AppError.TimeoutError
import wow.doge.mygame.executors.Schedulers import wow.doge.mygame.executors.Schedulers
import wow.doge.mygame.game.GameApp import wow.doge.mygame.game.GameApp
import wow.doge.mygame.game.GameAppActor
import wow.doge.mygame.game.GameAppResource 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.EntityIds
import wow.doge.mygame.game.entities.NpcActorSupervisor import wow.doge.mygame.game.entities.NpcActorSupervisor
import wow.doge.mygame.game.entities.NpcMovementActor import wow.doge.mygame.game.entities.NpcMovementActor
import wow.doge.mygame.game.entities.PlayerActorSupervisor import wow.doge.mygame.game.entities.PlayerActorSupervisor
import wow.doge.mygame.game.entities.PlayerController import wow.doge.mygame.game.entities.PlayerController
import wow.doge.mygame.game.subsystems.input.GameInputHandler 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.game.subsystems.level.DefaultGameLevel
import wow.doge.mygame.implicits._ import wow.doge.mygame.implicits._
import wow.doge.mygame.launcher.Launcher 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.EventBus.ObservableSubscription
import wow.doge.mygame.subsystems.events.EventsModule import wow.doge.mygame.subsystems.events.EventsModule
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus 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.PlayerEvent
import wow.doge.mygame.subsystems.events.PlayerMovementEvent import wow.doge.mygame.subsystems.events.PlayerMovementEvent
import wow.doge.mygame.subsystems.events.StatsEvent.DamageEvent import wow.doge.mygame.subsystems.events.StatsEvent.DamageEvent
import wow.doge.mygame.subsystems.events.TickEvent import wow.doge.mygame.subsystems.events.TickEvent
import wow.doge.mygame.subsystems.scriptsystem.ScriptInitMode import wow.doge.mygame.subsystems.scriptsystem.ScriptInitMode
import wow.doge.mygame.subsystems.scriptsystem.ScriptSystemResource import wow.doge.mygame.subsystems.scriptsystem.ScriptSystemResource
import wow.doge.mygame.types._
import wow.doge.mygame.utils.AkkaUtils import wow.doge.mygame.utils.AkkaUtils
import wow.doge.mygame.utils.GenericConsoleStream import wow.doge.mygame.utils.GenericConsoleStream
import wow.doge.mygame.utils.IOUtils import wow.doge.mygame.utils.IOUtils
import wow.doge.mygame.utils.wrappers.jme.AssetManager import wow.doge.mygame.utils.wrappers.jme.AssetManager
import wow.doge.mygame.utils.wrappers.jme.PhysicsSpace 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( class MainApp(
logger: Logger[Task], logger: Logger[Task],
jmeThread: monix.execution.Scheduler,
jmeThread: JmeScheduler,
schedulers: Schedulers, schedulers: Schedulers,
consoleStream: GenericConsoleStream[TextArea] consoleStream: GenericConsoleStream[TextArea]
)(implicit )(implicit
spawnProtocol: ActorRef[SpawnProtocol.Command], spawnProtocol: ActorRef[SpawnProtocol.Command],
timeout: Timeout, timeout: Timeout,
scheduler: Scheduler
scheduler: AkkaScheduler
) { ) {
implicit val as = scheduler.value
val scriptSystemInit = val scriptSystemInit =
new ScriptSystemResource(os.pwd, ScriptInitMode.Eager).init new ScriptSystemResource(os.pwd, ScriptInitMode.Eager).init
val eventsModule = new EventsModule(scheduler, spawnProtocol) 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 { for {
// g <- UIO.pure(gameApp) // g <- UIO.pure(gameApp)
playerEventBus <- eventsModule.playerEventBus playerEventBus <- eventsModule.playerEventBus
mainEventBus <- eventsModule.mainEventBus mainEventBus <- eventsModule.mainEventBus
tickEventBus <- eventsModule.tickEventBus
obs <- obs <-
playerEventBus playerEventBus
.askL[Observable[PlayerMovementEvent]](ObservableSubscription(_)) .askL[Observable[PlayerMovementEvent]](ObservableSubscription(_))
@ -116,11 +115,6 @@ class MainApp(
physicsSpace <- UIO.pure(gameApp.physicsSpace) physicsSpace <- UIO.pure(gameApp.physicsSpace)
_ <- logger.infoU("before") _ <- logger.infoU("before")
// jfxUI <- gameApp.jfxUI // jfxUI <- gameApp.jfxUI
gameAppActor <- gameApp.spawnGameActor(
GameAppActor.Props(tickEventBus).behavior,
Some("gameAppActor")
)
_ <- gameAppActor !! GameAppActor.Start
consoleTextArea <- UIO(new TextArea { consoleTextArea <- UIO(new TextArea {
text = "hello \n" text = "hello \n"
editable = false editable = false
@ -138,25 +132,25 @@ class MainApp(
.executeOn(gameApp.scheduler.value) .executeOn(gameApp.scheduler.value)
} yield fib } 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 { wire[GameAppResource].resource.evalMap {
case Right(gameApp -> gameAppFib) => case Right(gameApp -> gameAppFib) =>
eval(gameApp, gameAppFib).attempt
eval(tickEventBus, gameApp, gameAppFib).attempt
case Left(error) => IO.terminate(new Exception(error.toString)) 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 { val program = for {
// scriptSystem <- scriptSystemInit // scriptSystem <- scriptSystemInit
launchSignal <- Deferred[Task, Launcher.LauncherResult].hideErrors 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 * User chose to quit
@ -167,7 +161,7 @@ class MainApp(
* User chose launch. Wait for game window to close * User chose launch. Wait for game window to close
*/ */
else else
gameInit.use {
gameInit(tickEventBus).use {
case Right(fib) => fib.join >> Task.unit case Right(fib) => fib.join >> Task.unit
case Left(error) => IO.terminate(new Exception(error.toString)) case Left(error) => IO.terminate(new Exception(error.toString))
}.hideErrors }.hideErrors
@ -195,8 +189,9 @@ class MainAppDelegate(
)(implicit )(implicit
spawnProtocol: ActorRef[SpawnProtocol.Command], spawnProtocol: ActorRef[SpawnProtocol.Command],
timeout: Timeout, timeout: Timeout,
scheduler: Scheduler
scheduler: AkkaScheduler
) { ) {
implicit val as = scheduler.value
def init( def init(
// appScheduler: monix.execution.Scheduler // appScheduler: monix.execution.Scheduler
@ -237,9 +232,12 @@ class MainAppDelegate(
damageObs damageObs
.doOnNextF(event => .doOnNextF(event =>
(loggerL.debug(s"Received Damage Event $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 .completedL
.toIO .toIO
@ -251,10 +249,8 @@ class MainAppDelegate(
.doOnNextF(_ => .doOnNextF(_ =>
playerActor playerActor
.askL(PlayerActorSupervisor.GetStatus) .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 => // .flatMap(s =>
// if (s == Status.Alive) // if (s == Status.Alive)
// playerActor // playerActor
@ -276,19 +272,21 @@ class MainAppDelegate(
.startAndForget .startAndForget
_ <- _ <-
physicsSpace.collisionObservable 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 => .doOnNextF(event =>
(for { (for {
victim <- Coeval(for { victim <- Coeval(for {
@ -299,7 +297,11 @@ class MainAppDelegate(
victim.foreach { v => victim.foreach { v =>
pprint.log(s"emitted event ${v.getName}") pprint.log(s"emitted event ${v.getName}")
mainEventBus ! EventBus.Publish( mainEventBus ! EventBus.Publish(
DamageEvent("John", v.getName, 10),
DamageEvent(
"John",
v.getName,
CharacterStats.DamageHealth(10)
),
"damageHandler" "damageHandler"
) )
} }
@ -327,6 +329,7 @@ class MainAppDelegate(
): IO[AppError, PlayerActorSupervisor.Ref] = { ): IO[AppError, PlayerActorSupervisor.Ref] = {
val playerPos = ImVector3f.Zero val playerPos = ImVector3f.Zero
val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o" val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o"
// val modelPath = os.rel / "Models" / "Oto" / "Oto.mesh.xml"
val playerPhysicsControl = val playerPhysicsControl =
PlayerController.Defaults.defaultPlayerPhysicsControl PlayerController.Defaults.defaultPlayerPhysicsControl
@ -335,6 +338,7 @@ class MainAppDelegate(
assetManager assetManager
.loadModelAs[Node](modelPath) .loadModelAs[Node](modelPath)
.map(_.withRotate(0, FastMath.PI, 0)) .map(_.withRotate(0, FastMath.PI, 0))
.tapEval(m => UIO(m.center()))
.mapError(AppError.AssetManagerError) .mapError(AppError.AssetManagerError)
playerNode <- UIO( playerNode <- UIO(
PlayerController.Defaults PlayerController.Defaults
@ -346,9 +350,7 @@ class MainAppDelegate(
) )
cameraPivotNode <- UIO( cameraPivotNode <- UIO(
new Node(EntityIds.CameraPivot.value) new Node(EntityIds.CameraPivot.value)
.withControl(
new FollowControl(playerNode)
)
.withControl(new FollowControl(playerNode))
.taggedWith[PlayerController.Tags.PlayerCameraPivotNode] .taggedWith[PlayerController.Tags.PlayerCameraPivotNode]
) )
camNode <- UIO( camNode <- UIO(
@ -405,24 +407,24 @@ class MainAppDelegate(
.toIO .toIO
.hideErrors .hideErrors
.startAndForget .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) sched <- UIO.pure(schedulers.async)
playerActor <- wire[PlayerController.Props].create playerActor <- wire[PlayerController.Props].create
obs <- obs <-
playerActor playerActor
.askL(PlayerActorSupervisor.GetStatsObservable)
.askL(PlayerActorSupervisor.GetStatsObservable2)
.onErrorHandleWith(TimeoutError.from) .onErrorHandleWith(TimeoutError.from)
_ <- _ <-
obs obs

13
src/main/scala/wow/doge/mygame/MainModule.scala

@ -5,21 +5,16 @@ import akka.actor.typed.SpawnProtocol
import cats.effect.Resource import cats.effect.Resource
import io.odin.Logger import io.odin.Logger
import monix.bio.Task 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( Resource.make(
logger.info("Creating Actor System") >> Task( logger.info("Creating Actor System") >> Task(
ActorSystem( ActorSystem(
SpawnProtocol(), SpawnProtocol(),
name = "GameActorSystem", name = "GameActorSystem",
BootstrapSetup().withDefaultExecutionContext(scheduler)
BootstrapSetup().withDefaultExecutionContext(scheduler.value)
) )
) )
)(sys => )(sys =>

81
src/main/scala/wow/doge/mygame/actor/GameActorSystem.scala

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

27
src/main/scala/wow/doge/mygame/executors/ExecutorsModule.scala

@ -1,31 +1,22 @@
package wow.doge.mygame.executors package wow.doge.mygame.executors
import cats.effect.Resource import cats.effect.Resource
import monix.bio.IO
import monix.bio.Task import monix.bio.Task
import monix.bio.UIO
import monix.execution.Scheduler import monix.execution.Scheduler
import wow.doge.mygame.types.JmeScheduler
trait ExecutorsModule { 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( 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 sealed trait Error

10
src/main/scala/wow/doge/mygame/executors/GUIExecutor.scala

@ -13,6 +13,7 @@ import scala.concurrent.ExecutionContext
import akka.dispatch.DispatcherPrerequisites import akka.dispatch.DispatcherPrerequisites
import akka.dispatch.ExecutorServiceConfigurator import akka.dispatch.ExecutorServiceConfigurator
import akka.dispatch.ExecutorServiceFactory import akka.dispatch.ExecutorServiceFactory
import com.jme3.app.Application
import com.typesafe.config.Config import com.typesafe.config.Config
import javafx.application.Platform import javafx.application.Platform
import monix.execution.Scheduler import monix.execution.Scheduler
@ -42,13 +43,11 @@ object SwingExecutorService extends GUIExecutorService {
object JMEExecutorService extends GUIExecutorService { object JMEExecutorService extends GUIExecutorService {
override def execute(command: Runnable) = override def execute(command: Runnable) =
JMERunner.runner.get.apply(command)
// new SingleThreadEventExecutor()
sys.addShutdownHook(JMEExecutorService.shutdown())
JMERunner.runner.enqueue(command)
} }
object JMERunner { object JMERunner {
var runner: Option[Runnable => Unit] = None
var runner: Application = null
} }
@ -98,9 +97,8 @@ class SwingEventThreadExecutorServiceConfigurator(
object JFXExecutionContexts { object JFXExecutionContexts {
val javaFxExecutionContext: ExecutionContext = val javaFxExecutionContext: ExecutionContext =
ExecutionContext.fromExecutor(new Executor { ExecutionContext.fromExecutor(new Executor {
def execute(command: Runnable): Unit = {
def execute(command: Runnable): Unit =
Platform.runLater(command) Platform.runLater(command)
}
}) })
val fxScheduler = val fxScheduler =
Scheduler(javaFxExecutionContext) Scheduler(javaFxExecutionContext)

30
src/main/scala/wow/doge/mygame/executors/Schedulers.scala

@ -5,13 +5,9 @@ import monix.execution.Scheduler
import monix.execution.UncaughtExceptionReporter import monix.execution.UncaughtExceptionReporter
final case class Schedulers( 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 { object Schedulers {
@ -20,4 +16,24 @@ object Schedulers {
logger.error("Uncaught exception", ex) 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)
} }

91
src/main/scala/wow/doge/mygame/game/GameApp.scala

@ -25,9 +25,6 @@ import monix.bio.UIO
import monix.catnap.ConcurrentChannel import monix.catnap.ConcurrentChannel
import monix.catnap.ConsumerF import monix.catnap.ConsumerF
import monix.eval.Coeval 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
import wow.doge.mygame.AppError.TimeoutError import wow.doge.mygame.AppError.TimeoutError
import wow.doge.mygame.Dispatchers 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.executors.Schedulers
import wow.doge.mygame.game.subsystems.ui.JFxUI import wow.doge.mygame.game.subsystems.ui.JFxUI
import wow.doge.mygame.implicits._ import wow.doge.mygame.implicits._
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.AkkaUtils
import wow.doge.mygame.utils.GenericTimerActor
import wow.doge.mygame.utils.wrappers.jme._ import wow.doge.mygame.utils.wrappers.jme._
import wow.doge.mygame.types._
object GameAppTags { object GameAppTags {
sealed trait RootNode sealed trait RootNode
sealed trait GuiNode sealed trait GuiNode
@ -47,9 +45,9 @@ object GameAppTags {
class GameApp private[game] ( class GameApp private[game] (
logger: Logger[Task], logger: Logger[Task],
app: SimpleAppExt, app: SimpleAppExt,
gameActor: ActorRef[TestGameActor.Command],
gameActor: ActorRef[GameAppActor.Command],
gameSpawnProtocol: ActorRef[SpawnProtocol.Command], gameSpawnProtocol: ActorRef[SpawnProtocol.Command],
scheduler: akka.actor.typed.Scheduler
akkaScheduler: AkkaScheduler
) { ) {
def inputManager: UIO[InputManager] = UIO(app.getInputManager()) def inputManager: UIO[InputManager] = UIO(app.getInputManager())
val assetManager = new AssetManager(app.getAssetManager()) val assetManager = new AssetManager(app.getAssetManager())
@ -75,7 +73,7 @@ class GameApp private[game] (
)(implicit name: sourcecode.Name) = )(implicit name: sourcecode.Name) =
AkkaUtils.spawnActorL(behavior, actorName, props)( AkkaUtils.spawnActorL(behavior, actorName, props)(
2.seconds, 2.seconds,
scheduler,
akkaScheduler.value,
gameSpawnProtocol, gameSpawnProtocol,
name name
) )
@ -87,19 +85,21 @@ class GameApp private[game] (
class GameAppResource( class GameAppResource(
logger: Logger[Task], logger: Logger[Task],
jmeThread: Scheduler,
schedulers: Schedulers
jmeThread: JmeScheduler,
schedulers: Schedulers,
tickEventBus: GameEventBus[TickEvent]
)(implicit )(implicit
timeout: Timeout, timeout: Timeout,
scheduler: akka.actor.typed.Scheduler,
scheduler: AkkaScheduler,
spawnProtocol: ActorRef[SpawnProtocol.Command] spawnProtocol: ActorRef[SpawnProtocol.Command]
) { ) {
implicit val as = scheduler.value
def resource def resource
: Resource[UIO, Either[AppError, (GameApp, Fiber[Nothing, Unit])]] = : Resource[UIO, Either[AppError, (GameApp, Fiber[Nothing, Unit])]] =
Resource.make( Resource.make(
(for { (for {
app <- UIO(new SimpleAppExt(schedulers, new BulletAppState)) app <- UIO(new SimpleAppExt(schedulers, new BulletAppState))
_ <- UIO(JMERunner.runner = Some(app.enqueue _))
_ <- UIO(JMERunner.runner = app)
_ <- UIO { _ <- UIO {
val settings = new AppSettings(true) val settings = new AppSettings(true)
settings.setVSync(true) settings.setVSync(true)
@ -112,20 +112,21 @@ class GameAppResource(
app.setSettings(settings) 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) _ <- Task.deferFuture(app.started).onErrorHandleWith(TimeoutError.from)
testGameActor <- AkkaUtils.spawnActorL(
new TestGameActor.Props().create,
gameAppActor <- AkkaUtils.spawnActorL(
new GameAppActor.Props(tickEventBus).behavior,
Some("testGameActor"), Some("testGameActor"),
props = Dispatchers.jmeDispatcher props = Dispatchers.jmeDispatcher
) )
_ <- gameAppActor !! GameAppActor.Start
sp <- sp <-
testGameActor
.askL(TestGameActor.GetSpawnProtocol)
gameAppActor
.askL(GameAppActor.GetSpawnProtocol)
.onErrorHandleWith(TimeoutError.from) .onErrorHandleWith(TimeoutError.from)
gameApp <- UIO(new GameApp(logger, app, testGameActor, sp, scheduler))
gameApp <- UIO(new GameApp(logger, app, gameAppActor, sp, scheduler))
_ <- UIO { _ <- UIO {
val fut = () => testGameActor.ask(TestGameActor.Stop).flatten
val fut = () => gameAppActor.ask(GameAppActor.Stop).flatten
app.cancelToken = Some(fut) app.cancelToken = Some(fut)
} }
} yield (gameApp, fib)).attempt } yield (gameApp, fib)).attempt
@ -137,58 +138,6 @@ class GameAppResource(
object GameApp {} 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 { object Ops {
final class AddToNode[T <: Node](private val node: T) extends AnyVal { final class AddToNode[T <: Node](private val node: T) extends AnyVal {

149
src/main/scala/wow/doge/mygame/game/GameAppActor.scala

@ -2,14 +2,18 @@ package wow.doge.mygame.game
import scala.concurrent.duration._ 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.SupervisorStrategy
import akka.actor.typed.scaladsl.Behaviors 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.game.TickGenerator.Send
import wow.doge.mygame.implicits._ import wow.doge.mygame.implicits._
import wow.doge.mygame.subsystems.events.EventBus import wow.doge.mygame.subsystems.events.EventBus
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
import wow.doge.mygame.subsystems.events.TickEvent import wow.doge.mygame.subsystems.events.TickEvent
import wow.doge.mygame.subsystems.events.TickEvent.PhysicsTick
import wow.doge.mygame.utils.GenericTimerActor import wow.doge.mygame.utils.GenericTimerActor
object GameAppActor { object GameAppActor {
@ -17,7 +21,11 @@ object GameAppActor {
sealed trait Command sealed trait Command
case object Start extends Command case object Start extends Command
case object Pause 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]) { case class Props(tickEventBus: GameEventBus[TickEvent]) {
def behavior = def behavior =
@ -38,19 +46,43 @@ object GameAppActor {
.behavior .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 = val renderTickGeneratorBehavior =
@ -69,92 +101,3 @@ object TickGenerator {
sealed trait Command sealed trait Command
case object Send extends 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
// )

46
src/main/scala/wow/doge/mygame/game/controls/CameraMovementControls.scala

@ -1,19 +1,31 @@
package wow.doge.mygame.game.controls package wow.doge.mygame.game.controls
import scala.concurrent.Future
import com.jme3.math.FastMath
import com.jme3.math.Quaternion 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.RenderManager
import com.jme3.renderer.ViewPort 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 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.Cancelable
import monix.execution.Scheduler 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( class CameraMovementControl(
rotationBuf: Quaternion, rotationBuf: Quaternion,
obs: Observable[PlayerCameraInput], obs: Observable[PlayerCameraInput],
@ -23,8 +35,21 @@ class CameraMovementControl(
private var _event: PlayerCameraInput = null private var _event: PlayerCameraInput = null
private var _subscriptionToken: Cancelable = 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 = override def controlUpdate(tpf: Float): Unit =
if (_event != null)
if (_event != null) {
_event match { _event match {
case PlayerCameraInput.CameraRotateLeft => case PlayerCameraInput.CameraRotateLeft =>
val rot = rotationBuf val rot = rotationBuf
@ -43,6 +68,8 @@ class CameraMovementControl(
.fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X) .fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X)
rotateFn(rot) rotateFn(rot)
} }
_event = null
}
override def controlRender( override def controlRender(
x$1: RenderManager, x$1: RenderManager,
@ -51,8 +78,7 @@ class CameraMovementControl(
override def setSpatial(spatial: Spatial): Unit = { override def setSpatial(spatial: Spatial): Unit = {
super.setSpatial(spatial) super.setSpatial(spatial)
if (this.spatial != null) if (this.spatial != null)
_subscriptionToken =
obs.doOnNext(event => me.Task { _event = event }).subscribe()
_subscriptionToken = obs.subscribe(sink)
else { else {
_subscriptionToken.cancel() _subscriptionToken.cancel()
_subscriptionToken = null _subscriptionToken = null

116
src/main/scala/wow/doge/mygame/game/entities/CharacterStats.scala

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

136
src/main/scala/wow/doge/mygame/game/entities/player/PlayerActorSupervisor.scala

@ -1,6 +1,8 @@
package wow.doge.mygame.game.entities package wow.doge.mygame.game.entities
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.util.Failure
import scala.util.Success
import akka.actor.typed.ActorRef import akka.actor.typed.ActorRef
import akka.actor.typed.Behavior import akka.actor.typed.Behavior
@ -9,9 +11,15 @@ import akka.actor.typed.PostStop
import akka.actor.typed.SupervisorStrategy import akka.actor.typed.SupervisorStrategy
import akka.actor.typed.scaladsl.ActorContext import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.scaladsl.Behaviors
import akka.util.Timeout
import com.typesafe.scalalogging.Logger import com.typesafe.scalalogging.Logger
import monix.reactive.Observable
import monix.reactive.OverflowStrategy
import monix.reactive.subjects.ConcurrentSubject
import org.slf4j.event.Level import org.slf4j.event.Level
import wow.doge.mygame.Dispatchers 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.implicits._
import wow.doge.mygame.subsystems.events.EventBus import wow.doge.mygame.subsystems.events.EventBus
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus 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
import wow.doge.mygame.subsystems.events.TickEvent.RenderTick import wow.doge.mygame.subsystems.events.TickEvent.RenderTick
import wow.doge.mygame.subsystems.movement.ImMovementActor 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 { object PlayerActorSupervisor {
type Ref = ActorRef[PlayerActorSupervisor.Command] type Ref = ActorRef[PlayerActorSupervisor.Command]
@ -37,23 +40,33 @@ object PlayerActorSupervisor {
} }
sealed trait Command 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 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 extends Command
private case object Die 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 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 InternalTakeDamage(old: Int, value: Int) extends Command
private case class LogError(ex: Throwable) extends Command private case class LogError(ex: Throwable) extends Command
class Props( class Props(
val playerEventBus: GameEventBus[PlayerEvent], val playerEventBus: GameEventBus[PlayerEvent],
val tickEventBus: GameEventBus[TickEvent], val tickEventBus: GameEventBus[TickEvent],
val imMovementActorBehavior: Behavior[ImMovementActor.Command], val imMovementActorBehavior: Behavior[ImMovementActor.Command],
val scheduler: Scheduler
val scheduler: AsyncScheduler
) { ) {
def behavior = def behavior =
Behaviors.logMessages( Behaviors.logMessages(
@ -78,7 +91,12 @@ object PlayerActorSupervisor {
) )
val playerStatsActor = 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( val playerMovementEl = ctx.spawnN(
Behaviors Behaviors
@ -115,7 +133,13 @@ object PlayerActorSupervisor {
ctx, ctx,
this, this,
Children(playerMovementActor, playerStatsActor), 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 ).aliveState
} }
) )
@ -131,7 +155,8 @@ class PlayerActorSupervisor(
ctx: ActorContext[PlayerActorSupervisor.Command], ctx: ActorContext[PlayerActorSupervisor.Command],
props: PlayerActorSupervisor.Props, props: PlayerActorSupervisor.Props,
children: PlayerActorSupervisor.Children, children: PlayerActorSupervisor.Children,
statsSubject: ConcurrentSubject[StatsActor.State, StatsActor.State]
statsSubject: ConcurrentSubject[CharacterStats, CharacterStats],
statsQueue: AsyncQueue[CharacterStats]
) { ) {
import PlayerActorSupervisor._ import PlayerActorSupervisor._
implicit val timeout = Timeout(1.second) implicit val timeout = Timeout(1.second)
@ -149,12 +174,23 @@ class PlayerActorSupervisor(
case Failure(ex) => LogError(ex) case Failure(ex) => LogError(ex)
} }
Behaviors.same 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) => case CurrentStats(replyTo) =>
// ctx.ask(children.statsActor, StatsActor.CurrentStats()) // ctx.ask(children.statsActor, StatsActor.CurrentStats())
children.statsActor ! StatsActor.CurrentStats(replyTo) children.statsActor ! StatsActor.CurrentStats(replyTo)
Behaviors.same Behaviors.same
case Heal(value) => case Heal(value) =>
children.statsActor ! StatsActor.Heal(value)
children.statsActor ! StatsActor.HealResult(value)
Behaviors.same Behaviors.same
case GetStatus(replyTo) => case GetStatus(replyTo) =>
replyTo ! Status.Alive replyTo ! Status.Alive
@ -169,6 +205,12 @@ class PlayerActorSupervisor(
case GetStatsObservable(replyTo) => case GetStatsObservable(replyTo) =>
replyTo ! statsSubject replyTo ! statsSubject
Behaviors.same Behaviors.same
case GetStatsObservable2(replyTo) =>
import monix.{eval => me}
replyTo ! Observable.repeatEvalF(
me.Task.deferFuture(statsQueue.poll())
)
Behaviors.same
case DamageResponse(response) => case DamageResponse(response) =>
response match { response match {
case (dead, state) => case (dead, state) =>
@ -176,6 +218,15 @@ class PlayerActorSupervisor(
statsSubject.onNext(state) statsSubject.onNext(state)
} }
Behaviors.same 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 Die => deadState
case LogError(ex) => case LogError(ex) =>
ctx.log.error(ex.getMessage) ctx.log.error(ex.getMessage)
@ -215,54 +266,3 @@ class PlayerActorSupervisor(
Behaviors.same 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] {
// Todo add min max values
case TakeDamage(value) =>
val nextState =
if (state.hp - value <= 0)
state.modify(_.hp).setTo(0)
else
state.modify(_.hp).using(_ - value)
receive(nextState)
case TakeDamageResult(value, replyTo) =>
val nextState = if (state.hp - value <= 0) {
replyTo ! true -> state
state
} else {
replyTo ! false -> state
state.modify(_.hp).using(_ - value)
}
receive(nextState)
case Heal(value) => receive(state.modify(_.hp).using(_ + value))
case CurrentStats(replyTo) =>
replyTo ! state
Behaviors.same
}
}

5
src/main/scala/wow/doge/mygame/game/entities/player/PlayerController.scala

@ -14,6 +14,7 @@ import io.odin.Logger
import monix.bio.IO import monix.bio.IO
import monix.bio.Task import monix.bio.Task
import wow.doge.mygame.AppError import wow.doge.mygame.AppError
import wow.doge.mygame.executors.Schedulers.AsyncScheduler
import wow.doge.mygame.game.GameApp import wow.doge.mygame.game.GameApp
import wow.doge.mygame.implicits._ import wow.doge.mygame.implicits._
import wow.doge.mygame.math.ImVector3f import wow.doge.mygame.math.ImVector3f
@ -21,8 +22,8 @@ import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
import wow.doge.mygame.subsystems.events.PlayerEvent import wow.doge.mygame.subsystems.events.PlayerEvent
import wow.doge.mygame.subsystems.events.TickEvent import wow.doge.mygame.subsystems.events.TickEvent
import wow.doge.mygame.subsystems.movement.ImMovementActor import wow.doge.mygame.subsystems.movement.ImMovementActor
import wow.doge.mygame.utils.wrappers.jme._
import wow.doge.mygame.types._ import wow.doge.mygame.types._
import wow.doge.mygame.utils.wrappers.jme._
object PlayerController { object PlayerController {
sealed trait Error sealed trait Error
@ -49,7 +50,7 @@ object PlayerController {
playerEventBus: GameEventBus[PlayerEvent], playerEventBus: GameEventBus[PlayerEvent],
playerPhysicsControl: BetterCharacterControl, playerPhysicsControl: BetterCharacterControl,
// appScheduler: monix.execution.Scheduler, // appScheduler: monix.execution.Scheduler,
scheduler: monix.execution.Scheduler,
scheduler: AsyncScheduler,
playerNode: PlayerNode, playerNode: PlayerNode,
cameraNode: PlayerCameraNode, cameraNode: PlayerCameraNode,
cameraPivotNode: PlayerCameraPivotNode, cameraPivotNode: PlayerCameraPivotNode,

1
src/main/scala/wow/doge/mygame/game/entities/player/PlayerEventListeners.scala

@ -46,6 +46,7 @@ object PlayerMovementEventListener {
) )
} }
//not used
object PlayerCameraEventListener { object PlayerCameraEventListener {
import PlayerCameraEvent._ import PlayerCameraEvent._
def apply(playerCameraActor: ActorRef[PlayerCameraActor.Command]) = def apply(playerCameraActor: ActorRef[PlayerCameraActor.Command]) =

1
src/main/scala/wow/doge/mygame/game/subsystems/movement/MovementActor.scala

@ -10,7 +10,6 @@ import com.jme3.scene.Geometry
import com.softwaremill.quicklens._ import com.softwaremill.quicklens._
import wow.doge.mygame.game.subsystems.movement.CanMove import wow.doge.mygame.game.subsystems.movement.CanMove
import wow.doge.mygame.implicits._ import wow.doge.mygame.implicits._
import wow.doge.mygame.math.ImVector3f
final case class CardinalDirection( final case class CardinalDirection(
left: Boolean = false, left: Boolean = false,

17
src/main/scala/wow/doge/mygame/implicits/package.scala

@ -756,7 +756,7 @@ package object implicits {
} }
implicit final class Vector3fExt(private val v: Vector3f) extends AnyVal {
implicit final class Vector3fOps(private val v: Vector3f) extends AnyVal {
//TODO add more operations //TODO add more operations
def +=(that: Vector3f) = v.addLocal(that) def +=(that: Vector3f) = v.addLocal(that)
def +=(f: Float) = v.addLocal(f, f, f) def +=(f: Float) = v.addLocal(f, f, f)
@ -777,7 +777,7 @@ package object implicits {
// def transformImmutable(f: Vector3f => Vector3f) = f(v).immutable // def transformImmutable(f: Vector3f => Vector3f) = f(v).immutable
} }
implicit final class ImVector3fExt(private val v: ImVector3f) extends AnyVal {
implicit final class ImVector3fOps(private val v: ImVector3f) extends AnyVal {
def +(that: ImVector3f) = v.copy(v.x + that.x, v.y + that.y, v.z + that.z) def +(that: ImVector3f) = v.copy(v.x + that.x, v.y + that.y, v.z + that.z)
def +(f: Float): ImVector3f = v.copy(v.x + f, v.y + f, v.z + f) def +(f: Float): ImVector3f = v.copy(v.x + f, v.y + f, v.z + f)
def *(that: ImVector3f) = v.copy(v.x * that.x, v.y * that.y, v.z * that.z) def *(that: ImVector3f) = v.copy(v.x * that.x, v.y * that.y, v.z * that.z)
@ -867,7 +867,7 @@ package object implicits {
} }
implicit class OdinLoggerExt(private val logger: io.odin.Logger[Task])
implicit final class OdinLoggerExt(private val logger: io.odin.Logger[Task])
extends AnyVal { extends AnyVal {
def debugU[M](msg: => M)(implicit render: Render[M], position: Position) = def debugU[M](msg: => M)(implicit render: Render[M], position: Position) =
logger.debug(msg).hideErrors logger.debug(msg).hideErrors
@ -881,7 +881,7 @@ package object implicits {
logger.error(msg).hideErrors logger.error(msg).hideErrors
} }
implicit class TypedActorContextExt[T](private val ctx: ActorContext[T])
implicit final class TypedActorContextExt[T](private val ctx: ActorContext[T])
extends AnyVal { extends AnyVal {
def spawnN[U](behavior: Behavior[U], props: Props = Props.empty)(implicit def spawnN[U](behavior: Behavior[U], props: Props = Props.empty)(implicit
name: sourcecode.Name name: sourcecode.Name
@ -889,19 +889,20 @@ package object implicits {
ctx.spawn(behavior, name.value, props) ctx.spawn(behavior, name.value, props)
} }
implicit class MonixEvalTaskExt[T](private val task: monix.eval.Task[T])
implicit final class MonixEvalTaskExt[T](private val task: monix.eval.Task[T])
extends AnyVal { extends AnyVal {
def toIO = IO.deferAction(implicit s => IO.from(task)) def toIO = IO.deferAction(implicit s => IO.from(task))
} }
implicit class MonixBioTaskExt[T](private val task: monix.bio.Task[T])
implicit final class MonixBioTaskExt[T](private val task: monix.bio.Task[T])
extends AnyVal { extends AnyVal {
def toTask = def toTask =
monix.eval.Task.deferAction(implicit s => monix.eval.Task.from(task)) monix.eval.Task.deferAction(implicit s => monix.eval.Task.from(task))
} }
implicit class CoevalEitherExt[L, R](private val coeval: Coeval[Either[L, R]])
extends AnyVal {
implicit final class CoevalEitherExt[L, R](
private val coeval: Coeval[Either[L, R]]
) extends AnyVal {
def toIO = coeval.to[Task].hideErrors.rethrow def toIO = coeval.to[Task].hideErrors.rethrow
} }
} }

8
src/main/scala/wow/doge/mygame/launcher/Launcher.scala

@ -15,7 +15,7 @@ import scalafx.application.JFXApp
import scalafx.application.JFXApp.PrimaryStage import scalafx.application.JFXApp.PrimaryStage
import scalafx.scene.control.Button import scalafx.scene.control.Button
import scalafx.stage.StageStyle import scalafx.stage.StageStyle
import wow.doge.mygame.executors.Schedulers
import wow.doge.mygame.executors.Schedulers.FxScheduler
import wow.doge.mygame.implicits.JavaFXMonixObservables._ import wow.doge.mygame.implicits.JavaFXMonixObservables._
import wow.doge.mygame.utils.IOUtils._ import wow.doge.mygame.utils.IOUtils._
object Launcher { object Launcher {
@ -28,7 +28,8 @@ object Launcher {
} }
class Props( class Props(
val schedulers: Schedulers,
// val schedulers: Schedulers,
val fxScheduler: FxScheduler,
val signal: Deferred[Task, LauncherResult] val signal: Deferred[Task, LauncherResult]
) { ) {
val create = UIO(new Launcher(this)) val create = UIO(new Launcher(this))
@ -105,7 +106,7 @@ class Launcher private (props: Launcher.Props) {
Observable(launchAction, exitAction).merge Observable(launchAction, exitAction).merge
.doOnNext(_ => .doOnNext(_ =>
me.Task(delegate.stage.close()) me.Task(delegate.stage.close())
.executeOn(props.schedulers.fx)
.executeOn(props.fxScheduler.value)
) )
.completedL .completedL
) )
@ -113,6 +114,7 @@ class Launcher private (props: Launcher.Props) {
) )
) )
.start .start
_ <- Task.fromCancelablePromise(startSignal)
c <- CancelableF[Task](combinedFib.cancel) c <- CancelableF[Task](combinedFib.cancel)
} yield c)(_.cancel) } yield c)(_.cancel)

5
src/main/scala/wow/doge/mygame/subsystems/events/EventBus.scala

@ -43,9 +43,8 @@ object EventBus {
final case class ObservableSubscription[A, E <: A]( final case class ObservableSubscription[A, E <: A](
replyTo: ActorRef[Observable[E]] replyTo: ActorRef[Observable[E]]
)(implicit
classTag: ClassTag[E]
) extends Command[A] {
)(implicit classTag: ClassTag[E])
extends Command[A] {
def ct = classTag def ct = classTag
} }

9
src/main/scala/wow/doge/mygame/subsystems/events/Events.scala

@ -1,5 +1,7 @@
package wow.doge.mygame.subsystems.events package wow.doge.mygame.subsystems.events
import wow.doge.mygame.game.entities.CharacterStats
sealed trait Event sealed trait Event
case object BulletFired extends Event case object BulletFired extends Event
// type BulletFired = BulletFired.type // type BulletFired = BulletFired.type
@ -25,6 +27,9 @@ object EntityMovementEvent {
sealed trait StatsEvent extends Event sealed trait StatsEvent extends Event
object StatsEvent { object StatsEvent {
case class DamageEvent(hitBy: String, victimName: String, amount: Int)
extends StatsEvent
case class DamageEvent(
hitBy: String,
victimName: String,
amount: CharacterStats.DamageHealth
) extends StatsEvent
} }

6
src/main/scala/wow/doge/mygame/subsystems/events/EventsModule.scala

@ -6,7 +6,6 @@ import scala.reflect.ClassTag
import akka.actor.typed.ActorRef import akka.actor.typed.ActorRef
import akka.actor.typed.LogOptions import akka.actor.typed.LogOptions
import akka.actor.typed.Props import akka.actor.typed.Props
import akka.actor.typed.Scheduler
import akka.actor.typed.SpawnProtocol import akka.actor.typed.SpawnProtocol
import akka.actor.typed.SupervisorStrategy import akka.actor.typed.SupervisorStrategy
import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.scaladsl.Behaviors
@ -20,13 +19,14 @@ import wow.doge.mygame.implicits._
import wow.doge.mygame.subsystems.events.Event import wow.doge.mygame.subsystems.events.Event
import wow.doge.mygame.subsystems.events.EventBus import wow.doge.mygame.subsystems.events.EventBus
import wow.doge.mygame.subsystems.events.TickEvent import wow.doge.mygame.subsystems.events.TickEvent
import wow.doge.mygame.types.AkkaScheduler
class EventsModule( class EventsModule(
scheduler: Scheduler,
scheduler: AkkaScheduler,
spawnProtocol: ActorRef[SpawnProtocol.Command] spawnProtocol: ActorRef[SpawnProtocol.Command]
) { ) {
import EventsModule._ import EventsModule._
implicit val s = scheduler
implicit val s = scheduler.value
implicit val sp = spawnProtocol implicit val sp = spawnProtocol
implicit val timeout = Timeout(1.second) implicit val timeout = Timeout(1.second)

310
src/main/scala/wow/doge/mygame/subsystems/scriptsystem/MonixScriptCompiler.scala

@ -0,0 +1,310 @@
package wow.doge.mygame.subsystems.scriptsystem
import javax.script.ScriptEngine
import javax.script.ScriptEngineManager
import javax.script.ScriptException
import scala.concurrent.duration._
import ammonite.main.Defaults
import ammonite.runtime.Storage.Folder
import ammonite.util.Res
import ammonite.util.Res.Failure
import cats.effect.Resource
import cats.effect.concurrent.Deferred
import cats.syntax.either._
import cats.syntax.flatMap._
import com.softwaremill.macwire._
import com.softwaremill.tagging._
import groovy.util.GroovyScriptEngine
import io.odin.Logger
import monix.bio.IO
import monix.bio.Task
import monix.bio.UIO
import monix.catnap.ConcurrentQueue
import monix.reactive.Observable
import wow.doge.mygame.implicits._
import monix.{eval => me}
import groovy.util.ResourceException
import monix.catnap.MVar
trait Requestable[A] {
protected def queue: ConcurrentQueue[Task, A]
def request[T](
compileRequest: Deferred[Task, T] => A
)(implicit timeout: FiniteDuration) =
for {
d <- Deferred[Task, T]
req = compileRequest(d)
_ <- queue.offer(req)
res <- d.get.timeout(timeout).map(_.get)
} yield res
}
class ScriptCompiler private (
_queue: ConcurrentQueue[Task, ScriptCompiler.Command]
) extends Requestable[ScriptCompiler.Command] {
override protected def queue = _queue
// def tell(item: Command) = queue.offer(item)
}
object ScriptCompiler {
sealed trait State
case object Idle extends State
case object Active extends State
/**
* script representation
*/
sealed trait ScriptTag
type ScriptObject = Any @@ ScriptTag
sealed trait KotlinEngineTag
type KotlinScriptEngine = ScriptEngine @@ KotlinEngineTag
sealed trait Error
final case class AmmoniteFailure(error: Res.Failure) extends Error
final case class AmmoniteException(error: Res.Exception) extends Error
final case class ScriptExceptionError(error: ScriptException) extends Error
final case class ResourceExceptionError(error: ResourceException)
extends Error
final case class GroovyScriptExceptionError(
error: groovy.util.ScriptException
) extends Error
final case class SomeError(reason: String) extends Error
sealed trait Command
final case class Get(
path: os.Path,
result: Deferred[Task, ScriptResult],
force: Boolean
) extends Command
final case class GetData(result: Deferred[Task, Data])
final case class ObservableData(result: Deferred[Task, Observable[Data]])
extends Command
// extends Command
// final case class CompileAll(paths: Seq[os.Path]) extends Command
type ScriptsMap = Map[os.Path, Any]
type ScriptResult = Either[Error, Any]
sealed trait ScriptType
case object ScalaType extends ScriptType
case object KotlinType extends ScriptType
case object GroovyType extends ScriptType
val defaultScalaRunner =
ammonite
.Main(
storageBackend = new Folder(
// os.pwd / "target"
Defaults.ammoniteHome,
isRepl = false
)
)
val defaultKotlinRunner: KotlinScriptEngine = {
val manager = new ScriptEngineManager()
val engine = manager.getEngineByExtension("main.kts")
engine.taggedWith[KotlinEngineTag]
}
val defaultGroovyRunner: GroovyScriptEngine =
new GroovyScriptEngine(os.pwd.toString)
case class Data(scriptsMap: ScriptsMap)
class SourceMaker(
queue: ConcurrentQueue[Task, Command],
worker: ScriptCompilerWorker
) {
import com.softwaremill.quicklens._
def get =
for {
dataVar <- MVar[Task].of(Data(Map.empty))
obs <- Task.deferAction(implicit s =>
Task(
Observable
.repeatEvalF(queue.poll)
.scanEval0(me.Task.pure((Active: State) -> Data(Map.empty))) {
case state -> data -> command =>
val nextState: IO[Error, (State, Data)] = state match {
case Idle => IO.pure(Idle -> data)
case Active =>
command match {
case Get(path, result, force) =>
def getAndUpdate =
worker
.request(
ScriptCompilerWorker.CompileAny(path, _)
)(
20.seconds
)
.flatTap(result.complete)
.hideErrors
.rethrow
.flatMap(res =>
UIO(pprint.log(res)) >>
UIO.pure(
data
.modify(_.scriptsMap)
.using(_ + (path -> res))
)
)
for {
nextData <-
if (force) getAndUpdate
else
data.scriptsMap.get(path) match {
case Some(e) =>
result
.complete(e.asRight[Error])
.hideErrors >> UIO.pure(data)
case None => getAndUpdate
}
} yield Active -> nextData
case ObservableData(result) =>
result
.complete(Observable.repeatEvalF(dataVar.take))
.hideErrors >> IO.pure(Active -> data)
}
}
nextState
.flatTap { case (_, data) => dataVar.put(data).hideErrors }
.tapError(err => UIO(pprint.log(err.toString)))
.attempt
// .mapFilter(_.toOption)
.map(_.getOrElse(state -> data))
.toTask
}
)
)
} yield obs
}
class ScriptCompileFns(
val scalaRunner: ammonite.Main,
val kotlinRunner: KotlinScriptEngine,
val groovyRunner: GroovyScriptEngine
) {
def runScala(path: os.Path): Either[Error, Any] =
scalaRunner
.runScript(path, Seq.empty)
._1 match {
case e @ Res.Exception(t, msg) => Left(AmmoniteException(e))
case f @ Failure(msg) => Left(AmmoniteFailure(f))
case Res.Success(obj) => Right(obj)
case _ => Left(SomeError("Failed to run script"))
}
def runKotlin(path: os.Path): Either[Error, Any] =
Either
.catchNonFatal(kotlinRunner.eval(os.read(path)))
.leftMap {
case ex: ScriptException => ScriptExceptionError(ex)
}
def runGroovy(path: os.Path): Either[Error, Any] =
Either
.catchNonFatal(groovyRunner.run(path.relativeTo(os.pwd).toString, ""))
.leftMap {
case ex: ResourceException => ResourceExceptionError(ex)
case ex: groovy.util.ScriptException => GroovyScriptExceptionError(ex)
}
def ensureReturnedObjectNotNull(scriptObject: Any): Either[Error, Any] =
Either.fromOption(Option(scriptObject), SomeError("unknown object"))
}
class ScriptCompileSource(
fns: ScriptCompileFns,
logger: Logger[Task],
queue: ConcurrentQueue[Task, ScriptCompilerWorker.CompileRequest]
) {
import fns._
val source =
Task.deferAction(implicit s =>
Task(
Observable
.repeatEvalF(queue.poll)
.doOnNextF(el => logger.debug(s"Got $el"))
.mapParallelUnorderedF(4) {
case ScriptCompilerWorker.CompileAny(path, result) =>
for {
mbRes <- Task(
runScala(path)
.flatMap(ensureReturnedObjectNotNull)
// .map(_.taggedWith[ScriptTag])
)
_ <- result.complete(mbRes)
} yield mbRes
}
)
)
}
// override private val
final class ScriptCompilerWorker(
logger: Logger[Task],
_queue: ConcurrentQueue[Task, ScriptCompilerWorker.CompileRequest]
) extends Requestable[ScriptCompilerWorker.CompileRequest] {
override def queue = _queue
}
object ScriptCompilerWorker {
sealed trait CompileRequest
final case class CompileAny(
path: os.Path,
result: Deferred[Task, ScriptResult]
) extends CompileRequest
def apply(
logger: Logger[Task],
scalaRunner: ammonite.Main = defaultScalaRunner,
kotlinRunner: KotlinScriptEngine = defaultKotlinRunner,
groovyRunner: GroovyScriptEngine = defaultGroovyRunner
) = {
val acquire = for {
queue <- ConcurrentQueue[Task].bounded[CompileRequest](10)
fns <- UIO.pure(wire[ScriptCompileFns])
worker = wire[ScriptCompilerWorker]
fib <- wire[ScriptCompileSource].source.flatMap(_.completedL.toIO.start)
// resource = Concurrent[Task].background(
// wire[ScriptCompileSource].source.flatMap(_.completedL.toIO)
// )
} yield worker -> fib
Resource
.make(acquire) { case worker -> fib => fib.cancel }
.map(_._1)
}
}
def apply(logger: Logger[Task]) = {
def acquire(worker: ScriptCompilerWorker) =
for {
queue <- ConcurrentQueue.bounded[Task, Command](10)
fib <- wire[SourceMaker].get.flatMap(_.completedL.toIO.start)
} yield new ScriptCompiler(queue) -> fib
ScriptCompilerWorker(logger)
.flatMap(worker =>
Resource.make(acquire(worker)) { case (_, fib) => fib.cancel }
)
.map(_._1)
}
}

5
src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptActor.scala

@ -96,10 +96,7 @@ object ScriptActor {
def runScala(path: os.Path, scalaRunner: ammonite.Main): Either[Error, Any] = def runScala(path: os.Path, scalaRunner: ammonite.Main): Either[Error, Any] =
scalaRunner scalaRunner
.runScript(
path,
Seq.empty
)
.runScript(path, Seq.empty)
._1 match { ._1 match {
case ammonite.util.Res.Exception(t, msg) => Left(Error(msg)) case ammonite.util.Res.Exception(t, msg) => Left(Error(msg))

4
src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptCachingActor.scala

@ -45,9 +45,9 @@ object ScriptCachingActor {
final case class GetMap(requester: ActorRef[ScriptsMap]) extends Command final case class GetMap(requester: ActorRef[ScriptsMap]) extends Command
final case class Put(scriptPath: os.Path, script: ScriptObject) final case class Put(scriptPath: os.Path, script: ScriptObject)
extends Command extends Command
private[scriptsystem] case object NoOp extends Command
private case object NoOp extends Command
private[scriptsystem] final case class DelegateToChild(
private final case class DelegateToChild(
scriptActor: ActorRef[ScriptActor.Command], scriptActor: ActorRef[ScriptActor.Command],
scriptPath: os.Path, scriptPath: os.Path,
requester: ActorRef[ScriptResult] requester: ActorRef[ScriptResult]

14
src/main/scala/wow/doge/mygame/types/package.scala

@ -1,17 +1,15 @@
package wow.doge.mygame package wow.doge.mygame
import wow.doge.mygame.utils.wrappers.jme.AppNode2
import akka.actor.typed.Scheduler
import com.softwaremill.tagging._ import com.softwaremill.tagging._
import wow.doge.mygame.game.GameAppTags
import monix.execution.Scheduler
import io.estatico.newtype.macros.newtype import io.estatico.newtype.macros.newtype
import monix.execution.schedulers.SchedulerService
import wow.doge.mygame.game.GameAppTags
import wow.doge.mygame.utils.wrappers.jme.AppNode2
package object types { package object types {
type RootNode = AppNode2 @@ GameAppTags.RootNode type RootNode = AppNode2 @@ GameAppTags.RootNode
type GuiNode = AppNode2 @@ GameAppTags.GuiNode type GuiNode = AppNode2 @@ GameAppTags.GuiNode
@newtype case class AsyncScheduler(value: Scheduler)
@newtype case class IoScheduler(value: Scheduler)
@newtype case class FxScheduler(value: Scheduler)
@newtype case class JmeScheduler(value: Scheduler)
@newtype case class AkkaScheduler(value: akka.actor.typed.Scheduler)
@newtype case class JmeScheduler(value: SchedulerService)
@newtype case class AkkaScheduler(value: Scheduler)
} }

33
src/main/scala/wow/doge/mygame/utils/MonixDirectoryWatcher.scala

@ -0,0 +1,33 @@
package wow.doge.mygame.utils
import monix.reactive.Observable
import monix.reactive.OverflowStrategy
import monix.execution.Cancelable
import monix.execution.cancelables.SingleAssignCancelable
import monix.execution.Ack
object MonixDirectoryWatcher {
import better.files._
import io.methvin.better.files._
def apply(path: os.Path) =
Observable.create[String](OverflowStrategy.DropNew(50)) { sub =>
import sub.scheduler
val c = SingleAssignCancelable()
val myDir = File(path.toString)
val watcher = new RecursiveFileMonitor(myDir) {
override def onCreate(file: File, count: Int) =
println(s"$file got created")
override def onModify(file: File, count: Int) =
// println(s"$file got modified $count times")
if (sub.onNext(file.name) == Ack.Stop) c.cancel()
override def onDelete(file: File, count: Int) =
println(s"$file got deleted")
}
watcher.start()(scheduler)
c := Cancelable(() => watcher.stop())
c
}
}

63
src/test/scala/wow/doge/mygame/AnimTest.scala

@ -0,0 +1,63 @@
package wow.doge.mygame
import org.scalatest.funsuite.AnyFunSuite
import com.jme3.anim.AnimClip
import wow.doge.mygame.implicits._
import wow.doge.mygame.utils.wrappers.jme.AssetManager
import com.jme3.asset.DesktopAssetManager
import com.jme3.scene.Spatial
import monix.bio.UIO
import scala.concurrent.duration._
import com.jme3.scene.Node
import com.jme3.anim.AnimComposer
import com.jme3.anim.SkinningControl
import com.jme3.anim.tween.action.BaseAction
import com.jme3.anim.tween.Tweens
import scala.jdk.javaapi.CollectionConverters._
import com.jme3.anim.tween.Tween
import com.jme3.anim.tween.action.ClipAction
import cats.syntax.all._
class AnimTest extends AnyFunSuite {
import monix.execution.Scheduler.Implicits.global
val assetManager = new AssetManager(new DesktopAssetManager(true))
test("test 1") {
println((for {
_ <- UIO.unit
model <- assetManager.loadModelAs[Node](
os.rel / "Models" / "Oto" / "Oto.mesh.xml"
)
animcontrol <- UIO(model.getControlMaybe(classOf[AnimComposer]))
skinningcontrol <- UIO(model.getControlMaybe(classOf[SkinningControl]))
_ <- UIO(println(animcontrol))
_ <- UIO(println(skinningcontrol))
_ <- UIO {
animcontrol.map { ac =>
// ac.actionSequence()
// ac.makeAction()
// new BaseAction(Tweens.sequence())
// ac.getAnimClips().a
Option(ac.getAnimClip("hmm"))
.map(clip => new ClipAction(clip))
.map(Tweens.sequence(_))
.foreach { t =>
val actions = new BaseAction(t)
ac.addAction("hmm", actions)
}
val names = List("Walk", "Jump")
for {
clips <- names.traverse(name =>
Option(ac.getAnimClip(name)).map(clip => new ClipAction(clip))
)
tween <- Tweens.sequence(clips: _*).some
actions <- new BaseAction(tween).some
_ <- ac.addAction("Sequence 1", actions).some
} yield ()
()
}
}
} yield model).attempt.runSyncUnsafe(10.seconds))
}
}

30
src/test/scala/wow/doge/mygame/FileWatcherTest.scala

@ -0,0 +1,30 @@
package wow.doge.mygame
import org.scalatest.funsuite.AnyFunSuite
import cats.effect.{Resource => CResource}
import monix.eval.Task
import scala.concurrent.duration._
class FileWatcherTest extends AnyFunSuite {
test("1") {
import better.files._
import io.methvin.better.files._
val myDir =
File((os.pwd / "assets" / "scripts").toString)
val watcher = new RecursiveFileMonitor(myDir) {
override def onCreate(file: File, count: Int) =
println(s"$file got created")
override def onModify(file: File, count: Int) =
println(s"$file got modified $count times")
override def onDelete(file: File, count: Int) =
println(s"$file got deleted")
}
import monix.execution.Scheduler.Implicits.global
CResource
.make(Task { watcher.start(); watcher })(w => Task(w.stop()))
.use(_ => Task.never)
.runSyncUnsafe(10.seconds)
}
}

44
src/test/scala/wow/doge/mygame/MonixScriptCompilerTest.scala

@ -0,0 +1,44 @@
package wow.doge.mygame
import org.scalatest.funsuite.AnyFunSuite
import monix.bio.Task
import scala.concurrent.duration._
import monix.execution.Scheduler.Implicits.global
import wow.doge.mygame.subsystems.scriptsystem.ScriptCompiler
import io.odin.consoleLogger
import wow.doge.mygame.implicits._
class MonixScriptCompilerTest extends AnyFunSuite {
test("some-test") {
ScriptCompiler(consoleLogger[Task]())
.use(scriptCompiler =>
for {
// _ <-
// scriptCompiler.source
// .doOnNextF(el => Task(println(s"Got $el")))
// .completedL
// .toIO
// .hideErrors
// .startAndForget
response <- scriptCompiler.request(
ScriptCompiler.Get(
os.pwd / "assets" / "scripts" / "scala" / "hello2.sc",
_,
false
)
)(20.seconds)
_ <- Task(pprint.log(response.toString))
// _ <- Task.sleep(4.seconds)
// _ <- scriptCompiler.tell(
// ScriptCompiler.CompileAny(
// os.pwd / "assets" / "scripts" / "scala" / "hello2.sc"
// )
// )
// _ <- Task.sleep(4.seconds)
// _ <- Task.sleep(8.seconds)
} yield ()
)
.runSyncUnsafe(20.seconds)
}
}
Loading…
Cancel
Save