From fd8b3819ff11b5448fb33d35897ea3d406cb512b Mon Sep 17 00:00:00 2001 From: Rohan Sircar Date: Sat, 30 Jan 2021 13:58:42 +0530 Subject: [PATCH] many changes --- build.sbt | 3 +- src/main/scala/wow/doge/mygame/Main.scala | 14 +- src/main/scala/wow/doge/mygame/MainApp.scala | 148 ++++----- .../scala/wow/doge/mygame/MainModule.scala | 13 +- .../doge/mygame/actor/GameActorSystem.scala | 81 +++++ .../mygame/executors/ExecutorsModule.scala | 27 +- .../doge/mygame/executors/GUIExecutor.scala | 10 +- .../doge/mygame/executors/Schedulers.scala | 30 +- .../scala/wow/doge/mygame/game/GameApp.scala | 91 ++--- .../wow/doge/mygame/game/GameAppActor.scala | 149 +++------ .../controls/CameraMovementControls.scala | 46 ++- .../mygame/game/entities/CharacterStats.scala | 116 +++++++ .../player/PlayerActorSupervisor.scala | 136 ++++---- .../entities/player/PlayerController.scala | 5 +- .../player/PlayerEventListeners.scala | 1 + .../subsystems/movement/MovementActor.scala | 1 - .../wow/doge/mygame/implicits/package.scala | 17 +- .../wow/doge/mygame/launcher/Launcher.scala | 8 +- .../mygame/subsystems/events/EventBus.scala | 5 +- .../mygame/subsystems/events/Events.scala | 9 +- .../subsystems/events/EventsModule.scala | 6 +- .../scriptsystem/MonixScriptCompiler.scala | 310 ++++++++++++++++++ .../subsystems/scriptsystem/ScriptActor.scala | 5 +- .../scriptsystem/ScriptCachingActor.scala | 4 +- .../scala/wow/doge/mygame/types/package.scala | 14 +- .../mygame/utils/MonixDirectoryWatcher.scala | 33 ++ src/test/scala/wow/doge/mygame/AnimTest.scala | 63 ++++ .../wow/doge/mygame/FileWatcherTest.scala | 30 ++ .../doge/mygame/MonixScriptCompilerTest.scala | 44 +++ 29 files changed, 1011 insertions(+), 408 deletions(-) create mode 100644 src/main/scala/wow/doge/mygame/actor/GameActorSystem.scala create mode 100644 src/main/scala/wow/doge/mygame/game/entities/CharacterStats.scala create mode 100644 src/main/scala/wow/doge/mygame/subsystems/scriptsystem/MonixScriptCompiler.scala create mode 100644 src/main/scala/wow/doge/mygame/utils/MonixDirectoryWatcher.scala create mode 100644 src/test/scala/wow/doge/mygame/AnimTest.scala create mode 100644 src/test/scala/wow/doge/mygame/FileWatcherTest.scala create mode 100644 src/test/scala/wow/doge/mygame/MonixScriptCompilerTest.scala diff --git a/build.sbt b/build.sbt index 6e517fa..83be22c 100644 --- a/build.sbt +++ b/build.sbt @@ -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 diff --git a/src/main/scala/wow/doge/mygame/Main.scala b/src/main/scala/wow/doge/mygame/Main.scala index 6e43fb4..f0d6d56 100644 --- a/src/main/scala/wow/doge/mygame/Main.scala +++ b/src/main/scala/wow/doge/mygame/Main.scala @@ -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 () diff --git a/src/main/scala/wow/doge/mygame/MainApp.scala b/src/main/scala/wow/doge/mygame/MainApp.scala index b75f720..dffa404 100644 --- a/src/main/scala/wow/doge/mygame/MainApp.scala +++ b/src/main/scala/wow/doge/mygame/MainApp.scala @@ -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 diff --git a/src/main/scala/wow/doge/mygame/MainModule.scala b/src/main/scala/wow/doge/mygame/MainModule.scala index ce77f93..fb82d28 100644 --- a/src/main/scala/wow/doge/mygame/MainModule.scala +++ b/src/main/scala/wow/doge/mygame/MainModule.scala @@ -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 => diff --git a/src/main/scala/wow/doge/mygame/actor/GameActorSystem.scala b/src/main/scala/wow/doge/mygame/actor/GameActorSystem.scala new file mode 100644 index 0000000..eb24612 --- /dev/null +++ b/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 +// } +// } diff --git a/src/main/scala/wow/doge/mygame/executors/ExecutorsModule.scala b/src/main/scala/wow/doge/mygame/executors/ExecutorsModule.scala index ff3bb9f..bc76f36 100644 --- a/src/main/scala/wow/doge/mygame/executors/ExecutorsModule.scala +++ b/src/main/scala/wow/doge/mygame/executors/ExecutorsModule.scala @@ -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 diff --git a/src/main/scala/wow/doge/mygame/executors/GUIExecutor.scala b/src/main/scala/wow/doge/mygame/executors/GUIExecutor.scala index d480bff..b10b419 100644 --- a/src/main/scala/wow/doge/mygame/executors/GUIExecutor.scala +++ b/src/main/scala/wow/doge/mygame/executors/GUIExecutor.scala @@ -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) diff --git a/src/main/scala/wow/doge/mygame/executors/Schedulers.scala b/src/main/scala/wow/doge/mygame/executors/Schedulers.scala index bd1d4d3..a8bee68 100644 --- a/src/main/scala/wow/doge/mygame/executors/Schedulers.scala +++ b/src/main/scala/wow/doge/mygame/executors/Schedulers.scala @@ -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) + } diff --git a/src/main/scala/wow/doge/mygame/game/GameApp.scala b/src/main/scala/wow/doge/mygame/game/GameApp.scala index 8383073..27d35bb 100644 --- a/src/main/scala/wow/doge/mygame/game/GameApp.scala +++ b/src/main/scala/wow/doge/mygame/game/GameApp.scala @@ -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.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.GenericTimerActor import wow.doge.mygame.utils.wrappers.jme._ -import wow.doge.mygame.types._ 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 { diff --git a/src/main/scala/wow/doge/mygame/game/GameAppActor.scala b/src/main/scala/wow/doge/mygame/game/GameAppActor.scala index 3c873ce..ea705f2 100644 --- a/src/main/scala/wow/doge/mygame/game/GameAppActor.scala +++ b/src/main/scala/wow/doge/mygame/game/GameAppActor.scala @@ -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 -// ) diff --git a/src/main/scala/wow/doge/mygame/game/controls/CameraMovementControls.scala b/src/main/scala/wow/doge/mygame/game/controls/CameraMovementControls.scala index e562e66..f2d3527 100644 --- a/src/main/scala/wow/doge/mygame/game/controls/CameraMovementControls.scala +++ b/src/main/scala/wow/doge/mygame/game/controls/CameraMovementControls.scala @@ -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 diff --git a/src/main/scala/wow/doge/mygame/game/entities/CharacterStats.scala b/src/main/scala/wow/doge/mygame/game/entities/CharacterStats.scala new file mode 100644 index 0000000..1f79e09 --- /dev/null +++ b/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 + } +} diff --git a/src/main/scala/wow/doge/mygame/game/entities/player/PlayerActorSupervisor.scala b/src/main/scala/wow/doge/mygame/game/entities/player/PlayerActorSupervisor.scala index 30e98de..788f208 100644 --- a/src/main/scala/wow/doge/mygame/game/entities/player/PlayerActorSupervisor.scala +++ b/src/main/scala/wow/doge/mygame/game/entities/player/PlayerActorSupervisor.scala @@ -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] { - // 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 - } -} diff --git a/src/main/scala/wow/doge/mygame/game/entities/player/PlayerController.scala b/src/main/scala/wow/doge/mygame/game/entities/player/PlayerController.scala index bdfd199..42a20dd 100644 --- a/src/main/scala/wow/doge/mygame/game/entities/player/PlayerController.scala +++ b/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.Task import wow.doge.mygame.AppError +import wow.doge.mygame.executors.Schedulers.AsyncScheduler import wow.doge.mygame.game.GameApp import wow.doge.mygame.implicits._ 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.TickEvent import wow.doge.mygame.subsystems.movement.ImMovementActor -import wow.doge.mygame.utils.wrappers.jme._ import wow.doge.mygame.types._ +import wow.doge.mygame.utils.wrappers.jme._ object PlayerController { sealed trait Error @@ -49,7 +50,7 @@ object PlayerController { playerEventBus: GameEventBus[PlayerEvent], playerPhysicsControl: BetterCharacterControl, // appScheduler: monix.execution.Scheduler, - scheduler: monix.execution.Scheduler, + scheduler: AsyncScheduler, playerNode: PlayerNode, cameraNode: PlayerCameraNode, cameraPivotNode: PlayerCameraPivotNode, diff --git a/src/main/scala/wow/doge/mygame/game/entities/player/PlayerEventListeners.scala b/src/main/scala/wow/doge/mygame/game/entities/player/PlayerEventListeners.scala index 4506df2..4ee4799 100644 --- a/src/main/scala/wow/doge/mygame/game/entities/player/PlayerEventListeners.scala +++ b/src/main/scala/wow/doge/mygame/game/entities/player/PlayerEventListeners.scala @@ -46,6 +46,7 @@ object PlayerMovementEventListener { ) } +//not used object PlayerCameraEventListener { import PlayerCameraEvent._ def apply(playerCameraActor: ActorRef[PlayerCameraActor.Command]) = diff --git a/src/main/scala/wow/doge/mygame/game/subsystems/movement/MovementActor.scala b/src/main/scala/wow/doge/mygame/game/subsystems/movement/MovementActor.scala index 8efef1c..0a3c909 100644 --- a/src/main/scala/wow/doge/mygame/game/subsystems/movement/MovementActor.scala +++ b/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 wow.doge.mygame.game.subsystems.movement.CanMove import wow.doge.mygame.implicits._ -import wow.doge.mygame.math.ImVector3f final case class CardinalDirection( left: Boolean = false, diff --git a/src/main/scala/wow/doge/mygame/implicits/package.scala b/src/main/scala/wow/doge/mygame/implicits/package.scala index 4b64dac..41d6bed 100644 --- a/src/main/scala/wow/doge/mygame/implicits/package.scala +++ b/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 def +=(that: Vector3f) = v.addLocal(that) def +=(f: Float) = v.addLocal(f, f, f) @@ -777,7 +777,7 @@ package object implicits { // 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 +(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) @@ -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 { def debugU[M](msg: => M)(implicit render: Render[M], position: Position) = logger.debug(msg).hideErrors @@ -881,7 +881,7 @@ package object implicits { 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 { def spawnN[U](behavior: Behavior[U], props: Props = Props.empty)(implicit name: sourcecode.Name @@ -889,19 +889,20 @@ package object implicits { 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 { 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 { def toTask = 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 } } diff --git a/src/main/scala/wow/doge/mygame/launcher/Launcher.scala b/src/main/scala/wow/doge/mygame/launcher/Launcher.scala index 707d60d..f0c0b03 100644 --- a/src/main/scala/wow/doge/mygame/launcher/Launcher.scala +++ b/src/main/scala/wow/doge/mygame/launcher/Launcher.scala @@ -15,7 +15,7 @@ import scalafx.application.JFXApp import scalafx.application.JFXApp.PrimaryStage import scalafx.scene.control.Button 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.utils.IOUtils._ object Launcher { @@ -28,7 +28,8 @@ object Launcher { } class Props( - val schedulers: Schedulers, + // val schedulers: Schedulers, + val fxScheduler: FxScheduler, val signal: Deferred[Task, LauncherResult] ) { val create = UIO(new Launcher(this)) @@ -105,7 +106,7 @@ class Launcher private (props: Launcher.Props) { Observable(launchAction, exitAction).merge .doOnNext(_ => me.Task(delegate.stage.close()) - .executeOn(props.schedulers.fx) + .executeOn(props.fxScheduler.value) ) .completedL ) @@ -113,6 +114,7 @@ class Launcher private (props: Launcher.Props) { ) ) .start + _ <- Task.fromCancelablePromise(startSignal) c <- CancelableF[Task](combinedFib.cancel) } yield c)(_.cancel) diff --git a/src/main/scala/wow/doge/mygame/subsystems/events/EventBus.scala b/src/main/scala/wow/doge/mygame/subsystems/events/EventBus.scala index 91be47f..28adf7d 100644 --- a/src/main/scala/wow/doge/mygame/subsystems/events/EventBus.scala +++ b/src/main/scala/wow/doge/mygame/subsystems/events/EventBus.scala @@ -43,9 +43,8 @@ object EventBus { final case class ObservableSubscription[A, E <: A]( replyTo: ActorRef[Observable[E]] - )(implicit - classTag: ClassTag[E] - ) extends Command[A] { + )(implicit classTag: ClassTag[E]) + extends Command[A] { def ct = classTag } diff --git a/src/main/scala/wow/doge/mygame/subsystems/events/Events.scala b/src/main/scala/wow/doge/mygame/subsystems/events/Events.scala index 3674019..f54101b 100644 --- a/src/main/scala/wow/doge/mygame/subsystems/events/Events.scala +++ b/src/main/scala/wow/doge/mygame/subsystems/events/Events.scala @@ -1,5 +1,7 @@ package wow.doge.mygame.subsystems.events +import wow.doge.mygame.game.entities.CharacterStats + sealed trait Event case object BulletFired extends Event // type BulletFired = BulletFired.type @@ -25,6 +27,9 @@ object EntityMovementEvent { sealed trait StatsEvent extends Event 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 } diff --git a/src/main/scala/wow/doge/mygame/subsystems/events/EventsModule.scala b/src/main/scala/wow/doge/mygame/subsystems/events/EventsModule.scala index 345f88b..570a293 100644 --- a/src/main/scala/wow/doge/mygame/subsystems/events/EventsModule.scala +++ b/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.LogOptions import akka.actor.typed.Props -import akka.actor.typed.Scheduler import akka.actor.typed.SpawnProtocol import akka.actor.typed.SupervisorStrategy 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.EventBus import wow.doge.mygame.subsystems.events.TickEvent +import wow.doge.mygame.types.AkkaScheduler class EventsModule( - scheduler: Scheduler, + scheduler: AkkaScheduler, spawnProtocol: ActorRef[SpawnProtocol.Command] ) { import EventsModule._ - implicit val s = scheduler + implicit val s = scheduler.value implicit val sp = spawnProtocol implicit val timeout = Timeout(1.second) diff --git a/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/MonixScriptCompiler.scala b/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/MonixScriptCompiler.scala new file mode 100644 index 0000000..0e5c088 --- /dev/null +++ b/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) + } + +} diff --git a/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptActor.scala b/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptActor.scala index a48b617..6ab54b1 100644 --- a/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptActor.scala +++ b/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] = scalaRunner - .runScript( - path, - Seq.empty - ) + .runScript(path, Seq.empty) ._1 match { case ammonite.util.Res.Exception(t, msg) => Left(Error(msg)) diff --git a/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptCachingActor.scala b/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptCachingActor.scala index 4fae7c6..f36fc3b 100644 --- a/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptCachingActor.scala +++ b/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 Put(scriptPath: os.Path, script: ScriptObject) 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], scriptPath: os.Path, requester: ActorRef[ScriptResult] diff --git a/src/main/scala/wow/doge/mygame/types/package.scala b/src/main/scala/wow/doge/mygame/types/package.scala index 0ab4e6d..19087b8 100644 --- a/src/main/scala/wow/doge/mygame/types/package.scala +++ b/src/main/scala/wow/doge/mygame/types/package.scala @@ -1,17 +1,15 @@ package wow.doge.mygame -import wow.doge.mygame.utils.wrappers.jme.AppNode2 +import akka.actor.typed.Scheduler import com.softwaremill.tagging._ -import wow.doge.mygame.game.GameAppTags -import monix.execution.Scheduler 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 { type RootNode = AppNode2 @@ GameAppTags.RootNode 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) } diff --git a/src/main/scala/wow/doge/mygame/utils/MonixDirectoryWatcher.scala b/src/main/scala/wow/doge/mygame/utils/MonixDirectoryWatcher.scala new file mode 100644 index 0000000..f7365a6 --- /dev/null +++ b/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 + + } +} diff --git a/src/test/scala/wow/doge/mygame/AnimTest.scala b/src/test/scala/wow/doge/mygame/AnimTest.scala new file mode 100644 index 0000000..2ad6811 --- /dev/null +++ b/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)) + } +} diff --git a/src/test/scala/wow/doge/mygame/FileWatcherTest.scala b/src/test/scala/wow/doge/mygame/FileWatcherTest.scala new file mode 100644 index 0000000..d55f1d6 --- /dev/null +++ b/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) + } +} diff --git a/src/test/scala/wow/doge/mygame/MonixScriptCompilerTest.scala b/src/test/scala/wow/doge/mygame/MonixScriptCompilerTest.scala new file mode 100644 index 0000000..ef24806 --- /dev/null +++ b/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) + } +}