diff --git a/build.sbt b/build.sbt index cbe57ae..2f4d59c 100644 --- a/build.sbt +++ b/build.sbt @@ -82,12 +82,14 @@ lazy val root = (project in file(".")).settings( "com.softwaremill.sttp.client" %% "circe" % "2.2.5", "com.softwaremill.sttp.client" %% "async-http-client-backend-monix" % "2.2.5", "com.github.valskalla" %% "odin-monix" % "0.8.1", + "com.github.valskalla" %% "odin-json" % "0.9.1", "com.softwaremill.macwire" %% "util" % "2.3.7", "com.softwaremill.macwire" %% "macros" % "2.3.6" % "provided", "com.softwaremill.macwire" %% "macrosakka" % "2.3.6" % "provided", "com.github.valskalla" %% "odin-slf4j" % "0.8.1", "com.softwaremill.quicklens" %% "quicklens" % "1.6.1", - "org.scala-lang.modules" %% "scala-parallel-collections" % "1.0.0-RC1" + "org.scala-lang.modules" %% "scala-parallel-collections" % "1.0.0-RC1", + "com.typesafe.scala-logging" %% "scala-logging" % "3.9.2" ), // Determine OS version of JavaFX binaries diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index 7a8406a..0580551 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -1,7 +1,7 @@ -jme-dispatcher { - type = "Dispatcher" - name = "JME-Thread" - executor = "wow.doge.mygame.executors.JMEThreadExecutorServiceConfigurator" - throughput = 1 -} -akka.jvm-exit-on-fatal-error = on \ No newline at end of file +# jme-dispatcher { +# type = "Dispatcher" +# name = "JME-Thread" +# executor = "wow.doge.mygame.executors.JMEThreadExecutorServiceConfigurator" +# throughput = 1 +# } +# akka.jvm-exit-on-fatal-error = on \ No newline at end of file diff --git a/src/main/scala/com/jme3/animation/package.scala b/src/main/scala/com/jme3/animation/package.scala index aaf3725..ee24f30 100644 --- a/src/main/scala/com/jme3/animation/package.scala +++ b/src/main/scala/com/jme3/animation/package.scala @@ -4,7 +4,7 @@ import com.jme3.input.Action package object animation { - implicit class AnimChannelWrap(val uval: AnimChannel) extends AnyVal { + implicit class AnimChannelWrap(private val uval: AnimChannel) extends AnyVal { /** * Set the current animation that is played by this AnimChannel. @@ -33,7 +33,7 @@ package object animation { } - implicit class AnimEventListenerWrap(val uval: AnimEventListener) extends AnyVal { + implicit class AnimEventListenerWrap(private val uval: AnimEventListener) extends AnyVal { /** * Invoked when an animation "cycle" is done. For non-looping animations, diff --git a/src/main/scala/com/jme3/app/package.scala b/src/main/scala/com/jme3/app/package.scala index 881b465..658ea99 100644 --- a/src/main/scala/com/jme3/app/package.scala +++ b/src/main/scala/com/jme3/app/package.scala @@ -5,7 +5,7 @@ package com.jme3 */ package object app { - implicit class SimpleApplicationWrap(val uval: SimpleApplication) extends AnyVal { + implicit class SimpleApplicationWrap(private val uval: SimpleApplication) extends AnyVal { //FIXME: proof of concept, remove later def testWrap: String = uval.hashCode().toString diff --git a/src/main/scala/com/jme3/input/controls/package.scala b/src/main/scala/com/jme3/input/controls/package.scala index f4f3e17..ac29ed3 100644 --- a/src/main/scala/com/jme3/input/controls/package.scala +++ b/src/main/scala/com/jme3/input/controls/package.scala @@ -5,7 +5,7 @@ package com.jme3.input */ package object controls { - implicit class ActionListenerWrap(val uval: ActionListener) extends AnyVal { + implicit class ActionListenerWrap(private val uval: ActionListener) extends AnyVal { /** * Called when an input to which this listener is registered to is invoked. @@ -18,7 +18,7 @@ package object controls { uval.onAction(action.name, keyPressed, tpf) } - implicit class AnalogListenerWrap(val uval: AnalogListener) extends AnyVal { + implicit class AnalogListenerWrap(private val uval: AnalogListener) extends AnyVal { /** * Called to notify the implementation that an analog event has occurred. diff --git a/src/main/scala/com/jme3/input/package.scala b/src/main/scala/com/jme3/input/package.scala index 57a7f4e..48ddc07 100644 --- a/src/main/scala/com/jme3/input/package.scala +++ b/src/main/scala/com/jme3/input/package.scala @@ -7,7 +7,7 @@ import com.jme3.input.controls.{InputListener, Trigger} */ package object input { - implicit class InputManagerWrap(val uval: InputManager) extends AnyVal { + implicit class InputManagerWrap(private val uval: InputManager) extends AnyVal { def addMapping(action: Action, triggers: Trigger*): Unit = uval.addMapping(action.name, triggers: _*) def addListener(listener: InputListener, actions: Action*): Unit = diff --git a/src/main/scala/com/jme3/scene/package.scala b/src/main/scala/com/jme3/scene/package.scala index b243318..3b6698b 100644 --- a/src/main/scala/com/jme3/scene/package.scala +++ b/src/main/scala/com/jme3/scene/package.scala @@ -33,14 +33,14 @@ package object scene { def apply(name: String): Node = new Node(name) } - implicit class NodeWrap(val uval: Node) extends AnyVal { + implicit class NodeWrap(private val uval: Node) extends AnyVal { def getControlMaybe[T <: Control](controlType: Class[T]): Option[T] = Option(uval.getControl(controlType)) } - implicit class SpatialWrap(val uval: Spatial) extends AnyVal { + implicit class SpatialWrap(private val uval: Spatial) extends AnyVal { def toNode: Either[ClassCastException, Node] = uval match { diff --git a/src/main/scala/org/slf4j/impl/StaticLoggerBuilder.scala b/src/main/scala/org/slf4j/impl/StaticLoggerBuilder.scala index 1cfcdb7..5e4dbf3 100644 --- a/src/main/scala/org/slf4j/impl/StaticLoggerBuilder.scala +++ b/src/main/scala/org/slf4j/impl/StaticLoggerBuilder.scala @@ -11,6 +11,8 @@ import cats.arrow.FunctionK import _root_.monix.execution.Scheduler.Implicits.global import io.odin.syntax._ import scala.concurrent.duration._ +import scala.collection.immutable.ArraySeq +import io.odin.json.Formatter //effect type should be specified inbefore //log line will be recorded right after the call with no suspension @@ -26,27 +28,54 @@ class StaticLoggerBinder extends OdinLoggerBinder[IO] { def apply[A](fa: _root_.monix.bio.Task[A]): IO[A] = fa.to[IO] } - val (fLogger, release) = - // MainModule.DefaultFileLogger.mapK(monixToCats).allocated.unsafeRunSync() + private lazy val (defaultConsoleLogger, release1) = + consoleLogger[IO](minLevel = Level.Debug) + .withAsync(timeWindow = 1.milliseconds) + .allocated + .unsafeRunSync() + + private lazy val (mainFileLogger, release2) = + fileLogger[IO]( + "application-log-2.log", + Formatter.json, + minLevel = Level.Debug + ).withAsync(timeWindow = 1.milliseconds) + .allocated + .unsafeRunSync() + + private lazy val (eventBusFileLogger, release3) = fileLogger[IO]( - "log2.log" - ).withAsync(timeWindow = 1.seconds).allocated.unsafeRunSync() - // Runtime - // .getRuntime() - // .addShutdownHook(new Thread { - // release.unsafeRunSync() - // }) - scala.sys.addShutdownHook(release.unsafeRunSync()) + "eventbus.log", + Formatter.json, + minLevel = Level.Debug + ).withAsync(timeWindow = 1.milliseconds) + .allocated + .unsafeRunSync() + + { + ArraySeq(release1, release2, release3).foreach(r => + sys.addShutdownHook(r.unsafeRunSync()) + ) + } + val loggers: PartialFunction[String, Logger[IO]] = { case "some.external.package.SpecificClass" => - consoleLogger[IO](minLevel = Level.Warn) //disable noisy external logs + //disable noisy external logs + defaultConsoleLogger.withMinimalLevel(Level.Warn) case asyncHttpClient if asyncHttpClient.startsWith("org.asynchttpclient.netty") => - consoleLogger[IO](minLevel = Level.Warn) + defaultConsoleLogger.withMinimalLevel(Level.Warn) + // case s + // if s.startsWith( + // "wow.doge.mygame.subsystems.movement.PlayerMovementEventHandler" + // ) => + // defaultConsoleLogger.withMinimalLevel( Level.Trace) //selectively turn on trace logging for specific classes + case s if s.startsWith("wow.doge.mygame.events.EventBus") => + defaultConsoleLogger.withMinimalLevel(Level.Debug) |+| eventBusFileLogger case s if s.startsWith("akka.actor") || s.startsWith("wow.doge.mygame") => - consoleLogger[IO]() |+| fLogger + defaultConsoleLogger.withMinimalLevel(Level.Debug) |+| mainFileLogger case _ => //if wildcard case isn't provided, default logger is no-op - consoleLogger[IO]() + defaultConsoleLogger.withMinimalLevel(Level.Debug) } } diff --git a/src/main/scala/wow/doge/mygame/ActorSystemModule.scala b/src/main/scala/wow/doge/mygame/ActorSystemModule.scala new file mode 100644 index 0000000..ec94405 --- /dev/null +++ b/src/main/scala/wow/doge/mygame/ActorSystemModule.scala @@ -0,0 +1,27 @@ +package wow.doge.mygame + +import akka.actor.typed.ActorSystem +import cats.effect.Resource +import monix.bio.Task +import io.odin.Logger +import wow.doge.mygame.game.GameApp +import wow.doge.mygame.executors.Schedulers + +trait ActorSystemModule { + + def logger: Logger[Task] + def app: GameApp + def schedulers: Schedulers + + lazy val actorsResource = + Resource.make(logger.info("Creating Actor System") >> Task { + ActorSystem( + RootActor(app, schedulers, logger = logger), + name = "GameActorSystem" + ) + })(sys => + logger.info("Shutting down actor system") >> Task( + sys.terminate() + ) + ) +} diff --git a/src/main/scala/wow/doge/mygame/Main.scala b/src/main/scala/wow/doge/mygame/Main.scala index 4c7fd95..a6f6a5b 100644 --- a/src/main/scala/wow/doge/mygame/Main.scala +++ b/src/main/scala/wow/doge/mygame/Main.scala @@ -1,12 +1,9 @@ package wow.doge.mygame -import com.jme3.app.StatsAppState - import monix.bio.Task import cats.effect.Resource import io.odin.syntax._ -// import io.odin.monix._ import cats.effect.ExitCode import cats.implicits._ import com.softwaremill.macwire._ @@ -15,72 +12,68 @@ import monix.bio.BIOApp import monix.bio.UIO import monix.bio.IO import io.odin._ -import wow.doge.mygame.executors.JMERunner -import com.jme3.bullet.BulletAppState import wow.doge.mygame.implicits._ - -// import wow.doge.mygame.implicits._ - -// object Main extends App { -// import java.util.logging.{Logger, Level} -// Logger.getLogger("").setLevel(Level.SEVERE) - -// // runner.runCode("""|println("starting scala script engine")""".stripMargin) -// val gameApp = new GameApp( -// // new EntityDataState(), -// // new TestAppState(), -// // new PlayerMovementState(), -// // new FlyCamAppState(), -// new StatsAppState() -// ) -// val settings = new AppSettings(true) -// // settings.setVSync(true) -// settings.setFrameRate(144) -// gameApp.setSettings(settings) -// val actorSystem = ActorSystem(RootActor(gameApp), "rootActor") -// // actorSystem.eventStream -// // gameApp.start() -// println("here 1") -// // actorSystem.terminate() -// // JMEExecutorService.shutdown() -// // println("here 2") -// //FIXME remove this -// // System.exit(0) -// } +import wow.doge.mygame.game.GameAppResource +import io.odin.json.Formatter object Main extends BIOApp with MainModule { import java.util.logging.{Logger => JLogger, Level} JLogger.getLogger("").setLevel(Level.SEVERE) - def run(args: List[String]): UIO[ExitCode] = { - - lazy val appResource = for { + def appResource = + for { logger <- - consoleLogger().withAsync(timeWindow = 1.seconds) |+| fileLogger( - "log.log" - ).withAsync(timeWindow = 1.seconds) + consoleLogger().withAsync(timeWindow = 1.milliseconds) |+| + fileLogger( + "application-log-1.log", + Formatter.json + ).withAsync(timeWindow = 1.milliseconds) jmeScheduler <- jMESchedulerResource // consoleTextArea <- Resource.liftF(Task(new TextArea())) // consoleStream <- wireWith(JFXConsoleStream.textAreaStream _) - gameApp <- { - new BulletAppState() + (gameApp, gameAppFib) <- { + // new BulletAppState() // bas.setThreadingType(Thr) - gameAppResource(new StatsAppState()) + // gameAppResource(new StatsAppState()) + wire[GameAppResource].make } - _ <- Resource.liftF(IO(JMERunner.runner = gameApp)) + + // _ <- Resource.liftF(IO(JMERunner.runner = gameApp)) + // _ <- Resource.liftF(IO { + // new ActorSystemModule {} + // }) actorSystem <- wireWith(actorSystemResource _) + _ <- Resource.liftF( + gameApp.enqueueT(actorSystem ! RootActor.Start(actorSystem.scheduler)) + ) + // _ <- Resource.liftF { + // Task { + // implicit val sched = actorSystem.scheduler + // implicit val timeout = Timeout(2.seconds) + // // val module = new EventsModule {} + // } + // } + // actorSystem <- wireWith(actorSystemResource2 _) + // (tickEventBus, playerMovementEventBus) <- wireWith(eventBusesResource _) + // rootActor <- wireWith(rootActorResource _) + // inputSystemHandler <- { + // inputHandlerSystemResource( + // GameInputHandler.Props(gameApp.inputManager, playerMovementEventBus) + // ) + // } + // gameSystemsInitializer <- + // gameSystemsResource(actorSystem, inputSystemHandler) // _ <- Resource.liftF( - // Task(gameApp.start()).asyncBoundary - // .executeOn(Scheduler(JMEExecutorService)) + // gameApp.enqueueT(rootActor ! RootActor.Start(actorSystem.scheduler)) // ) - - _ <- Resource.liftF(gameApp.enqueueT(actorSystem ! RootActor.Start)) - _ <- Resource.liftF { - IO(gameApp.start()) - .executeOn(jmeScheduler) - } + // _ <- Resource.liftF { + // IO(gameApp.start()) + // .executeOn(jmeScheduler) + // } // (_ => IO(gameApp.stop(() => actorSystem ! RootActor.Stop))) - } yield () + } yield (gameAppFib) + + def run(args: List[String]): UIO[ExitCode] = { // Console.withOut( // new JFXConsoleStream( @@ -89,16 +82,7 @@ object Main extends BIOApp with MainModule { // ) // )(()) appResource - .use(_ => - // Task(gameApp.start()) - // .executeOn(Scheduler(JMEExecutorService)) - // .asyncBoundary - // Task.never - Task.unit - // >> - - // .executeOn(Scheduler(JMEExecutorService)) - ) + .use(_.join) .onErrorHandle(_.printStackTrace()) .as(ExitCode.Success) } diff --git a/src/main/scala/wow/doge/mygame/MainModule.scala b/src/main/scala/wow/doge/mygame/MainModule.scala index fab14a0..7b2c2bb 100644 --- a/src/main/scala/wow/doge/mygame/MainModule.scala +++ b/src/main/scala/wow/doge/mygame/MainModule.scala @@ -13,6 +13,12 @@ import wow.doge.mygame.executors.ExecutorsModule import akka.actor.typed.scaladsl.ActorContext import wow.doge.mygame.executors.Schedulers import com.softwaremill.macwire._ +import akka.actor.typed.SpawnProtocol +import akka.actor.typed.Scheduler +import wow.doge.mygame.utils.AkkaUtils +import scala.concurrent.duration._ +import akka.actor.typed.ActorRef +import wow.doge.mygame.implicits._ trait MainModule extends GameModule with ExecutorsModule { def actorSystemResource( @@ -21,34 +27,74 @@ trait MainModule extends GameModule with ExecutorsModule { schedulers: Schedulers ): Resource[Task, ActorSystem[RootActor.Command]] = Resource.make(logger.info("Creating Actor System") >> Task { - ActorSystem(RootActor(app, schedulers), name = "GameActorSystem") + ActorSystem( + wire[RootActor.Props].create(RootActor.State.empty), + name = "GameActorSystem" + ) })(sys => logger.info("Shutting down actor system") >> Task( sys.terminate() ) ) -} -object MainModule { + def actorSystemResource2( + logger: Logger[Task] + ): Resource[Task, ActorSystem[SpawnProtocol.Command]] = + Resource.make(logger.info("Creating Actor System") >> Task { + ActorSystem( + SpawnProtocol(), + name = "GameActorSystem2" + ) + })(sys => + logger.info("Shutting down actor system") >> Task( + sys.terminate() + ) + ) - // import cats.implicits._ - import scala.concurrent.duration._ - val DefaultFileLogger: Resource[Task, Logger[Task]] = - fileLogger[Task]( - "log.log" - ).withAsync(timeWindow = 1.seconds) + def rootActorResource( + loggerL: Logger[Task], + app: GameApp, + schedulers: Schedulers, + spawnProtocol: ActorSystem[SpawnProtocol.Command] + ): Resource[Task, ActorRef[RootActor.Command]] = + Resource.make( + loggerL.info("Creating Root Actor") >> + AkkaUtils.spawnActorL( + behavior = RootActor(app, schedulers, logger = loggerL), + actorName = "RootActor", + spawnProtocol = spawnProtocol + )(1.seconds, spawnProtocol.scheduler) + )(actor => + loggerL.info("Shutting down root actor") >> (actor !! RootActor.Stop) + ) } object RootActor { sealed trait Command - final case object Start extends Command + final case class Start(akkaScheduler: Scheduler) extends Command final case object Stop extends Command + final case class Props( + app: GameApp, + schedulers: Schedulers, + logger: Logger[Task] + ) { + def create(state: State): Behavior[Command] = + Behaviors.setup { ctx => + ctx.log.info("Hello from root actor") + wire[RootActor].receive(State.empty) + } + } + final case class State(initialized: Boolean = false) + object State { + val empty = State() + } def apply( app: GameApp, schedulers: Schedulers, - state: State = State() + state: State = State.empty, + logger: Logger[Task] ): Behavior[Command] = Behaviors.setup { ctx => ctx.log.info("Hello from root actor") @@ -59,17 +105,19 @@ object RootActor { class RootActor( ctx: ActorContext[RootActor.Command], app: GameApp, - schedulers: Schedulers + schedulers: Schedulers, + logger: Logger[Task] ) { import RootActor._ def receive(state: State): Behavior[Command] = Behaviors.receiveMessage(msg => msg match { - case Start => + case Start(akkaScheduler) => if (!state.initialized) { ctx.log.info("Starting GameAppActor") + val spawnProtocol = ctx.spawn(SpawnProtocol(), "spawnProtocol") val _ = ctx.spawn( - wireWith(GameAppActor.apply _), + wire[GameAppActor.Props].create, "gameAppActor" // DispatcherSelector.fromConfig("jme-dispatcher") ) diff --git a/src/main/scala/wow/doge/mygame/executors/Schedulers.scala b/src/main/scala/wow/doge/mygame/executors/Schedulers.scala index 6eb2fb8..7c982f4 100644 --- a/src/main/scala/wow/doge/mygame/executors/Schedulers.scala +++ b/src/main/scala/wow/doge/mygame/executors/Schedulers.scala @@ -1,10 +1,23 @@ package wow.doge.mygame.executors import monix.execution.Scheduler +import monix.execution.UncaughtExceptionReporter +import com.typesafe.scalalogging.Logger final case class Schedulers( - blockingIO: Scheduler = Scheduler.io(), - async: Scheduler = Scheduler.global, + blockingIO: Scheduler = Scheduler + .io() + .withUncaughtExceptionReporter(Schedulers.reporter), + async: Scheduler = Scheduler.global + .withUncaughtExceptionReporter(Schedulers.reporter), fx: Scheduler = JFXExecutionContexts.fxScheduler - // jme: SchedulerService + .withUncaughtExceptionReporter(Schedulers.reporter) ) + +object Schedulers { + val reporter = UncaughtExceptionReporter { ex => + val logger = Logger[Schedulers] + logger.error("Uncaught exception", ex) + } + +} diff --git a/src/main/scala/wow/doge/mygame/game/GameApp.scala b/src/main/scala/wow/doge/mygame/game/GameApp.scala index fb4e7c9..880174f 100644 --- a/src/main/scala/wow/doge/mygame/game/GameApp.scala +++ b/src/main/scala/wow/doge/mygame/game/GameApp.scala @@ -3,62 +3,35 @@ package wow.doge.mygame.game import com.jme3.app.SimpleApplication import com.jme3.app.state.AppState -import com.jme3.bullet.BulletAppState -import com.jme3.bullet.collision.shapes.CapsuleCollisionShape -import com.jme3.bullet.control.CharacterControl -import com.jme3.bullet.control.RigidBodyControl -import com.jme3.bullet.util.CollisionShapeFactory -import com.jme3.scene.Spatial -import com.jme3.syntax._ -import com.jme3.asset.plugins.ZipLocator -import com.jme3.math.ColorRGBA -import wow.doge.mygame.implicits._ +import monix.execution.{CancelablePromise => Promise} +import monix.execution.CancelableFuture +import monix.bio.Task +import monix.execution.Scheduler +import wow.doge.mygame.executors.GUIExecutorService +import monix.reactive.subjects.ConcurrentSubject +import monix.reactive.MulticastStrategy +import monix.reactive.Observable +import monix.execution.atomic.Atomic +import scala.collection.immutable.Queue class GameApp( // actorSystem: ActorSystem[SpawnProtocol.Command], appStates: AppState* ) extends SimpleApplication(appStates: _*) { + import GameApp._ // implicit val timeout = Timeout(10.seconds) // implicit val scheduler = actorSystem.scheduler - private lazy val sceneModel: Spatial = assetManager.loadModel("main.scene") - private lazy val bulletAppState: BulletAppState = new BulletAppState() - // bulletAppState.setThreadingType(ThreadingType.SEQUENTIAL) - - // We set up collision detection for the scene by creating a - // compound collision shape and a static RigidBodyControl with mass zero. - private lazy val sceneShape = CollisionShapeFactory.createMeshShape( - sceneModel.toNode match { - case util.Right(node) => node - case util.Left(ex) => - throw new NotImplementedError("No fallback sceneshape") - } - ) - private lazy val landscape: RigidBodyControl = - new RigidBodyControl(sceneShape, 0) - // We set up collision detection for the player by creating - // a capsule collision shape and a CharacterControl. - // The CharacterControl offers extra settings for - // size, stepheight, jumping, falling, and gravity. - // We also put the player in its starting position. - protected lazy val capsuleShape = new CapsuleCollisionShape(1.5f, 6f, 1) + // private lazy val taskQueueS = new ConcurrentLinkedQueue[MyTask[_]]() + private lazy val taskQueue2 = Atomic(Queue.empty[MyTask[_]]) - private lazy val player: CharacterControl = - new CharacterControl(capsuleShape, 0.05f) + private val tickSubject = + ConcurrentSubject[Float](multicast = MulticastStrategy.publish)( + monix.execution.Scheduler.Implicits.global + ) + // (scheduler) override def simpleInitApp(): Unit = { - discard { stateManager.attach(bulletAppState) } - assetManager.registerLocator( - // "src/main/resources/assets/town.zip", - (os.rel / "src" / "main" / "resources" / "assets" / "town.zip"), - classOf[ZipLocator] - ) - viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f)) - sceneModel.setLocalScale(2f) - sceneModel.addControl(landscape) - discard { rootNode.attachChild(sceneModel) } - bulletAppState.getPhysicsSpace.add(landscape) - bulletAppState.getPhysicsSpace.add(player) println("gameapp" + Thread.currentThread().getName()) @@ -105,6 +78,9 @@ class GameApp( // wbActor.map(a => a.ask(Greeter.Greet("hello", _)).map(println)) } + + def tickObservable: Observable[Float] = tickSubject + override def simpleUpdate(tpf: Float): Unit = { // val rot2 = rot.fromAngleAxis(FastMath.PI, new Vector3f(0, 0, 1)) // val rotation = geom.getLocalRotation() @@ -113,30 +89,62 @@ class GameApp( // geom.updateModelBound() // geom.updateGeometricState() + tickSubject.onNext(tpf) } - // override def stop(): Unit = { - // actorSystem.terminate() - // super.stop() - // } - - // override def start(): Unit = { - // monix.eval.Task(super.start()).runToFuture(Scheduler(JMEExecutorService)) - // } - - // def start(system: ActorRef[RootActor.Command]) = { - // // system ! RootActor.Start - // super.start() - - // } - // override def stop(): Unit = { - // println("stopping") - // } - def stop[T](cb: () => T): Unit = { - println("destroy") - cb() + override def stop(): Unit = { + tickSubject.onComplete() super.stop() } - // override def stop(): Unit = {} + def enqueueScala[T](cb: () => T): CancelableFuture[T] = { + val p = Promise[T]() + // p.success(cb()) + // taskQueueS.add(MyTask(p, cb)) + taskQueue2.transform(_ :+ MyTask(p, cb)) + p.future + } +// taskQueue2.transform(_ :+ MyTask(p, cb)) +// p + def enqueueL[T](cb: () => T): Task[T] = + // Task(Promise[T]()).flatMap { p => + // Task(taskQueue2.transform(_ :+ MyTask(p, cb))) >> + // Task.fromCancelablePromise(p) + // } + // Task.fromCancelablePromise { + // val p = Promise[T]() + // taskQueue2.transform(_ :+ MyTask(p, cb)) + // p + // } + Task.deferFuture(enqueueScala(cb)) + + // taskQueueS.add(MyTask(p, cb)) + + override protected def runQueuedTasks(): Unit = { + // Option(taskQueueS.poll()).foreach { + // case MyTask(p, cb) => + // p.success(cb()) + // } + taskQueue2.transform { current => + current.dequeueOption.fold(current) { + case (MyTask(p, cb), updated) => + p.success(cb()) + updated + } + } + + super.runQueuedTasks() + } + + object JMEExecutorService extends GUIExecutorService { + override def execute(command: Runnable) = + enqueue(command) + // new SingleThreadEventExecutor() + // sys.addShutdownHook(JMEExecutorService.shutdown()) + } + + lazy val scheduler = Scheduler(JMEExecutorService) +} +object GameApp { + private[game] case class MyTask[T](p: Promise[T], cb: () => T) } diff --git a/src/main/scala/wow/doge/mygame/game/GameAppActor.scala b/src/main/scala/wow/doge/mygame/game/GameAppActor.scala index 6fd282c..90ad67d 100644 --- a/src/main/scala/wow/doge/mygame/game/GameAppActor.scala +++ b/src/main/scala/wow/doge/mygame/game/GameAppActor.scala @@ -1,161 +1,86 @@ package wow.doge.mygame.game import akka.actor.typed.scaladsl.Behaviors -import wow.doge.mygame.state.PlayerMovementState import com.jme3.scene.Geometry -import wow.doge.mygame.events.EventBus import wow.doge.mygame.events.Events -import wow.doge.mygame.state.ImMovementActor import com.jme3.scene.CameraNode import com.jme3.scene.Node import com.jme3.renderer.Camera import wow.doge.mygame.executors.Schedulers -import wow.doge.mygame.game.nodes.PlayerNode import com.softwaremill.macwire._ import wow.doge.mygame.implicits._ +import akka.actor.typed.SpawnProtocol +import akka.actor.typed.ActorRef +import io.odin.Logger +import monix.bio.Task +import akka.actor.typed.Scheduler +import scala.util.Failure +import scala.util.Success +import com.jme3.app.StatsAppState object GameAppActor { import Methods._ sealed trait Command - case object XD extends Command + case object ApplicationStarted extends Command + case class ApplicationStartFailed(reason: String) extends Command case object Stop extends Command - def apply(app: GameApp, schedulers: Schedulers) = - Behaviors.setup[Command] { ctx => - ctx.log.info("Hello from GameAppActor") - // lazy val b = new Box(1, 1, 1) - // lazy val geom = new Geometry("Box", b) - // lazy val playerNode = new Node("PlayerNode") - // lazy val camNode = new CameraNode("CameraNode", app.getCamera()) - // lazy val players = createPlayer(geom, app.getCamera()) - - // ctx.pipeToSelf( - // app.enqueueF(() => ())(monix.execution.Scheduler.io("aege")) - // ) { - // case x => - // println("SENDEDEEEEEd") - // XD - // } - // ctx.pipeToSelf( - // createPlayer( - // geom, - // app.getCamera(), - // schedulers.jme - // ) - // ) { - // case x => - // println(x) - // XD - // } - val subscribingActor = ctx.spawn(SubscribingActor(), "subscriber-1") - - val tickEventBus = - ctx.spawn(Behaviors.logMessages(EventBus[Events.Tick]()), "eventBus1") - - tickEventBus ! EventBus.Subscribe(subscribingActor) - - tickEventBus ! EventBus.Publish(Events.PhysicsTick, ctx.self) - - // { - // app - // .getInputManager() - // .observableAction("Left") - // .map { action => - // action.binding.name match { - // case "Left" => Task(println("Pressed left")) - // } - // } - // } - // binding match { - // case "Left" => - - def playerNodeFactory = - PlayerNode( - modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o", - cam = app.camera - ) _ - // (assetManager = app.assetManager) - { - lazy val playerNode = playerNodeFactory(app.assetManager) - - lazy val actor = ctx.spawn( - ImMovementActor(ImMovementActor.Props(app, playerNode)), - "imMovementActor" - ) - lazy val state = wire[PlayerMovementState] - app.stateManager.attach(state) - } - - Thread.sleep(2000) - app - .getRootNode() - .depthFirst(s => - // s match { - // case node: Node => - // println("node" + s.getName() + " children " + node.getChildren()) - // case g: Geometry => println(s.getName()) + case class Props( + app: GameApp, + akkaScheduler: Scheduler, + schedulers: Schedulers, + spawnProtocol: ActorRef[SpawnProtocol.Command], + loggerL: Logger[Task] + ) { + def create = + Behaviors.setup[Command] { ctx => + ctx.log.info("Hello from GameAppActor") + + { + implicit val s = schedulers.async + + val initializer: GameSystemsInitializer = wire[GameSystemsInitializer] + + schedulers.async.execute(() => initializer.init.runAsyncAndForget) + + // ctx.pipeToSelf(application.timed.runToFuture) { + // case Failure(exception) => + // ApplicationStartFailed(exception.getMessage()) + // case Success(value) => + // println("here applications started") + // ApplicationStarted // } - 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 - Behaviors.receiveMessage { msg => - msg match { - case XD => - ctx.log.info("RECEEEEEIVED") - ctx.log.info(app.camera.toString()) - Behaviors.same - case Stop => - ctx.log.info("Received stop") - Behaviors.stopped + } + Behaviors.receiveMessage { msg => + msg match { + case Stop => + ctx.log.info("Received stop") + Behaviors.stopped + case ApplicationStarted => + ctx.log.info("Application started") + Behaviors.same + case ApplicationStartFailed(reason) => + ctx.log.error( + s"Failed to start application - $reason" + ) + Behaviors.stopped + } } } + } +} +object SubscribingActor { + def apply() = + Behaviors.receive[Events.Tick.PhysicsTick.type] { (ctx, msg) => + ctx.log.debug(s"received event $msg") + Behaviors.same } } - object Methods { - def createPlayer( - geom: Geometry, - cam: Camera - ): Node = { - val playerNode = new Node("PlayerNode") - lazy val camNode = new CameraNode("CameraNode", cam) - playerNode - .child(camNode) - .child(geom) - playerNode - } def old() = { // val movementActor = @@ -170,15 +95,64 @@ object Methods { // "movementActorTimer" // ) } -} -object SubscribingActor { - def apply() = - Behaviors.receive[Events.PhysicsTick.type] { (ctx, msg) => - ctx.log.debug(s"received event $msg") - Behaviors.same - } + 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, diff --git a/src/main/scala/wow/doge/mygame/game/GameModule.scala b/src/main/scala/wow/doge/mygame/game/GameModule.scala index 632bbcc..515c5bc 100644 --- a/src/main/scala/wow/doge/mygame/game/GameModule.scala +++ b/src/main/scala/wow/doge/mygame/game/GameModule.scala @@ -1,25 +1,70 @@ package wow.doge.mygame.game import cats.effect.Resource -import com.jme3.app.state.AppState import com.jme3.system.AppSettings import monix.bio.Task +import io.odin.Logger +import akka.actor.typed.ActorRef +import akka.actor.typed.SpawnProtocol +import wow.doge.mygame.game.subsystems.input.GameInputHandler +import monix.bio.IO +import monix.bio.Fiber +import monix.execution.Scheduler +import com.jme3.app.StatsAppState +import com.jme3.app.FlyCamAppState // import wow.doge.mygame.executors.JMERunner +class GameAppResource(logger: Logger[Task], jmeScheduler: Scheduler) { + def make: Resource[Task, (GameApp, Fiber[Throwable, Unit])] = + Resource.make( + for { + _ <- logger.info("Creating game app") + app <- Task(new GameApp(new StatsAppState())) + _ <- Task { + val settings = new AppSettings(true) + settings.setVSync(true) + settings.setUseInput(true) + // new FlyCamAppState + // settings.setFrameRate(250) + app.setSettings(settings) + // JMERunner.runner = app + app + } + fib <- Task(app.start()).executeOn(jmeScheduler).start + } yield (app -> fib) + )(logger.info("Closing game app") >> _._2.cancel) +} trait GameModule { - def gameAppResource(appStates: AppState*): Resource[Task, GameApp] = - Resource.liftF { - for { - app <- Task(new GameApp(appStates: _*)) + def gameAppResource( + logger: Logger[Task], + jmeScheduler: Scheduler + ): Resource[Task, (GameApp, Fiber[Throwable, Unit])] = + Resource.make( + (for { + _ <- logger.info("Creating game app") + app <- Task(new GameApp()) _ <- Task { val settings = new AppSettings(true) - // settings.setVSync(true) - settings.setFrameRate(144) + settings.setVSync(true) + // settings.setFrameRate(250) app.setSettings(settings) // JMERunner.runner = app app } - } yield (app) + fib <- Task(app.start()).executeOn(jmeScheduler).start + } yield (app -> fib)) + )(_._2.cancel) + + def inputHandlerSystemResource( + props: GameInputHandler.Props + ): Resource[Task, Task[Unit]] = + Resource.liftF { + Task.evalAsync(props.begin) } + def gameSystemsResource( + spawnProtocol: ActorRef[SpawnProtocol.Command], + gameSystems: Task[Unit]* + ): Resource[Task, List[Unit]] = + Resource.liftF(IO.defer(Task.parSequence(gameSystems))) } diff --git a/src/main/scala/wow/doge/mygame/game/GameSystemsInitializer.scala b/src/main/scala/wow/doge/mygame/game/GameSystemsInitializer.scala index b17a583..8ad4b86 100644 --- a/src/main/scala/wow/doge/mygame/game/GameSystemsInitializer.scala +++ b/src/main/scala/wow/doge/mygame/game/GameSystemsInitializer.scala @@ -1,13 +1,107 @@ package wow.doge.mygame.game -import wow.doge.mygame.state.MyBaseState +import scala.concurrent.duration._ -class GameSystemsInitializer extends MyBaseState { +import akka.actor.typed.ActorRef +import akka.actor.typed.Props +import akka.actor.typed.Scheduler +import akka.actor.typed.SpawnProtocol +import akka.util.Timeout +import com.jme3.asset.plugins.ZipLocator +import com.jme3.bullet.BulletAppState +import com.jme3.bullet.control.BetterCharacterControl +import com.softwaremill.macwire._ +import com.softwaremill.tagging._ +import io.odin.Logger +import monix.bio.Task +import monix.reactive.Consumer +import wow.doge.mygame.events.EventBus +import wow.doge.mygame.events.EventsModule +import wow.doge.mygame.game.nodes.Player +import wow.doge.mygame.game.nodes.PlayerController +import wow.doge.mygame.game.subsystems.input.GameInputHandler +import wow.doge.mygame.game.subsystems.level.DefaultGameLevel +import wow.doge.mygame.implicits._ +import wow.doge.mygame.subsystems.events.MovementEvent.PlayerMovementEvent +import wow.doge.mygame.subsystems.movement.ImMovementActor +import wow.doge.mygame.utils.IOUtils - override protected def onEnable(): Unit = {} +class GameSystemsInitializer()( + override val spawnProtocol: ActorRef[SpawnProtocol.Command], + override implicit val akkaScheduler: Scheduler, + app: GameApp, + loggerL: Logger[Task] +) extends EventsModule { + override implicit val timeout: Timeout = Timeout(1.second) - override protected def onDisable(): Unit = {} + import GameSystemsInitializer._ - override protected def init(): Unit = {} - override def stop(): Unit = {} + def init = + for { + playerMovementEventBus <- playerMovementEventBusTask + inputManager = app.inputManager + bulletAppState = new BulletAppState() + _ <- Task(app.stateManager.attach(bulletAppState)) + _ <- Task( + app.assetManager.registerLocator( + // "src/main/resources/assets/town.zip", + (os.rel / "src" / "main" / "resources" / "assets" / "town.zip"), + classOf[ZipLocator] + ) + ) + _ <- app.enqueueL(() => DefaultGameLevel(app, bulletAppState)) + playerController <- app.enqueueL(() => + PlayerController( + app, + modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o", + cam = app.camera + )(app.assetManager, bulletAppState) + .taggedWith[Player] + ) + // _ <- loggerL.debug(playerNode.getName()) + // _ <- Task(app.rootNode.attachChild(playerNode)) + // playerMovementActor <- wireWith(spawnMovementActor _) + // _ <- + // IOUtils + // .toIO( + // app.tickObservable + // .doOnNext { tpf => + // IOUtils.toTask(playerMovementActor !! ImMovementActor.Tick(tpf)) + // } + // .completedL + // .startAndForget + // .onErrorRestart(3) + // ) + _ <- wire[GameInputHandler.Props].begin + + } yield () +} + +object GameSystemsInitializer { + def spawnMovementActor( + app: GameApp, + spawnProtocol: ActorRef[SpawnProtocol.Command], + playerNode: BetterCharacterControl @@ Player, + playerMovementEventBus: ActorRef[ + EventBus.Command[PlayerMovementEvent] + ], + loggerL: Logger[Task] + )(implicit timeout: Timeout, scheduler: Scheduler) = + spawnProtocol.askL[ActorRef[ImMovementActor.Command]]( + SpawnProtocol.Spawn( + ImMovementActor.Props(app, playerNode, playerMovementEventBus).create, + "imMovementActor", + Props.empty, + _ + ) + ) + + def playerMovementActorTickConsumer( + playerMovementActor: ActorRef[ImMovementActor.Command] + ) = + Consumer + .foreachTask[Float](tpf => + IOUtils.toTask(playerMovementActor !! ImMovementActor.Tick(tpf)) + ) + // .mapTask() } diff --git a/src/main/scala/wow/doge/mygame/game/appstates/MovementActor.scala b/src/main/scala/wow/doge/mygame/game/appstates/MovementActor.scala deleted file mode 100644 index 50cb0b9..0000000 --- a/src/main/scala/wow/doge/mygame/game/appstates/MovementActor.scala +++ /dev/null @@ -1,92 +0,0 @@ -package wow.doge.mygame.state - -import akka.actor.typed.scaladsl.ActorContext -import akka.actor.typed.Behavior -import akka.actor.typed.scaladsl.Behaviors -import com.softwaremill.quicklens._ -import wow.doge.mygame.implicits._ -import com.jme3.renderer.Camera -import wow.doge.mygame.math.ImVector3f - -trait CanMove[-A] { - def getDirection(cam: Camera, cardinalDir: CardinalDirection): ImVector3f - def move(inst: A, direction: ImVector3f): Unit -} - -object ImMovementActor { - sealed trait Command - // final case class Tick(tpf: Float) extends Command - final case class Tick(tpf: Float) extends Command - - sealed trait Movement extends Command - final case class MovedLeft(pressed: Boolean) extends Movement - final case class MovedUp(pressed: Boolean) extends Movement - final case class MovedRight(pressed: Boolean) extends Movement - final case class MovedDown(pressed: Boolean) extends Movement - - final case class Props[T: CanMove]( - app: com.jme3.app.Application, - movable: T - ) - - /** - * Internal state of the actor - * - * @param cardinalDir Immutable, can be shared as is - * @param walkDirection Immutable - */ - final case class State( - cardinalDir: CardinalDirection = CardinalDirection() - ) - - def apply[T: CanMove](props: Props[T]): Behavior[Command] = - Behaviors.setup(ctx => { - ctx.log.info("Hello from MovementActor") - new ImMovementActor(ctx, props).receive(State()) - }) - -} - -class ImMovementActor[T]( - ctx: ActorContext[ImMovementActor.Command], - props: ImMovementActor.Props[T] -) { - import ImMovementActor._ - - def receive( - state: ImMovementActor.State - )(implicit cm: CanMove[T]): Behavior[Command] = - Behaviors.receiveMessage { msg => - msg match { - case m: Movement => - m match { - case MovedLeft(pressed) => - receive(state = state.modify(_.cardinalDir.left).setTo(pressed)) - case MovedUp(pressed) => - receive(state = state.modify(_.cardinalDir.up).setTo(pressed)) - case MovedRight(pressed) => - receive(state = state.modify(_.cardinalDir.right).setTo(pressed)) - case MovedDown(pressed) => - receive(state = state.modify(_.cardinalDir.down).setTo(pressed)) - } - - case Tick(tpf) => - val walkDir = - cm.getDirection(props.app.getCamera(), state.cardinalDir) - if (walkDir != ImVector3f.ZERO) { - val tmp = walkDir * 25f * tpf - // props.app.enqueue(new Runnable { - // override def run(): Unit = { - // cm.move(props.movable, tmp) - // } - // }) - props.app.enqueueF { - cm.move(props.movable, tmp) - } - } - Behaviors.same - // receive(state = state.modify(_.walkDirection).setTo(walkDir)) - - } - } -} diff --git a/src/main/scala/wow/doge/mygame/game/appstates/PlayerMovementState.scala b/src/main/scala/wow/doge/mygame/game/appstates/PlayerMovementState.scala index b188362..88c8769 100644 --- a/src/main/scala/wow/doge/mygame/game/appstates/PlayerMovementState.scala +++ b/src/main/scala/wow/doge/mygame/game/appstates/PlayerMovementState.scala @@ -4,7 +4,6 @@ import scala.concurrent.duration.DurationInt import com.jme3.input.InputManager import com.jme3.input.KeyInput -import com.jme3.input.controls.ActionListener import com.jme3.input.controls.KeyTrigger import com.jme3.math.Vector3f @@ -16,19 +15,19 @@ import com.jme3.scene.Geometry import akka.actor.typed.scaladsl.TimerScheduler import wow.doge.mygame.implicits._ -import com.jme3.scene.Node -import com.jme3.syntax._ +import wow.doge.mygame.subsystems.movement.ImMovementActor class PlayerMovementState( // movementActor: ActorRef[MovementActor.Command], // movementActorTimer: ActorRef[MovementActorTimer.Command], - imMovementActor: ActorRef[ImMovementActor.Command], + imMovementActor: ActorRef[ImMovementActor.Command] // geom: Geometry, // camNode: CameraNode, - playerNode: Node + // playerNode: Node // gameAppActor: ActorRef[GameAppActor.Command] ) extends MyBaseState - with ActionListener { + // with ActionListener + { protected lazy val mat = MyMaterial( assetManager = assetManager, @@ -37,8 +36,8 @@ class PlayerMovementState( override protected def init(): Unit = { - setupKeys(inputManager) - println("playermovementstate " + Thread.currentThread().getName()) + // setupKeys(inputManager) + // println("playermovementstate " + Thread.currentThread().getName()) // geom.setMaterial(mat) @@ -51,7 +50,7 @@ class PlayerMovementState( // .child(geom) // // playerNode.children(Seq(camNode, geom)) // } - discard { rootNode.child(playerNode) } + // discard { rootNode.withChild(playerNode) } // camNode.setLocalTranslation( // new Vector3f(0, 1.5f, 10) // ) @@ -107,23 +106,23 @@ class PlayerMovementState( new KeyTrigger(KeyInput.KEY_R), new KeyTrigger(KeyInput.KEY_RETURN) ) - .withListener(this, "Left") - .withListener(this, "Right") - .withListener(this, "Up") - .withListener(this, "Down") - .withListener(this, "Space") - .withListener(this, "Reset") + // .withListener(this, "Left") + // .withListener(this, "Right") + // .withListener(this, "Up") + // .withListener(this, "Down") + // .withListener(this, "Space") + // .withListener(this, "Reset") } - def onAction(binding: String, value: Boolean, tpf: Float) = - binding match { - case "Left" => imMovementActor ! ImMovementActor.MovedLeft(value) - case "Right" => imMovementActor ! ImMovementActor.MovedRight(value) - case "Up" => imMovementActor ! ImMovementActor.MovedUp(value) - case "Down" => imMovementActor ! ImMovementActor.MovedDown(value) - case "Space" => - case _ => - } + // def onAction(binding: String, value: Boolean, tpf: Float) = + // binding match { + // case "Left" => imMovementActor ! ImMovementActor.MovedLeft(value) + // case "Right" => imMovementActor ! ImMovementActor.MovedRight(value) + // case "Up" => imMovementActor ! ImMovementActor.MovedUp(value) + // case "Down" => imMovementActor ! ImMovementActor.MovedDown(value) + // case "Space" => + // case _ => + // } override protected def onEnable(): Unit = {} diff --git a/src/main/scala/wow/doge/mygame/game/nodes/PlayerNode.scala b/src/main/scala/wow/doge/mygame/game/nodes/PlayerController.scala similarity index 59% rename from src/main/scala/wow/doge/mygame/game/nodes/PlayerNode.scala rename to src/main/scala/wow/doge/mygame/game/nodes/PlayerController.scala index 19b4864..42d9968 100644 --- a/src/main/scala/wow/doge/mygame/game/nodes/PlayerNode.scala +++ b/src/main/scala/wow/doge/mygame/game/nodes/PlayerController.scala @@ -4,17 +4,23 @@ import com.jme3.scene.Node import com.jme3.scene.CameraNode import com.jme3.scene.Geometry import com.jme3.renderer.Camera -import wow.doge.mygame.implicits._ import com.jme3.asset.AssetManager import wow.doge.mygame.state.MyMaterial import com.jme3.math.Vector3f import com.jme3.scene.control.CameraControl.ControlDirection import com.jme3.syntax._ import com.jme3.scene.shape.Box +import com.jme3.bullet.control.BetterCharacterControl +import com.jme3.bullet.BulletAppState +import wow.doge.mygame.game.GameApp +import wow.doge.mygame.implicits._ +import wow.doge.mygame.math.ImVector3f +import com.jme3.math.FastMath // class PlayerNode(val name: String) extends Node(name) {} -object PlayerNode { - def defaultMesh() = { +trait Player +object PlayerController { + def defaultMesh = { lazy val b = new Box(1, 1, 1) lazy val geom = new Geometry("playerMesh", b) geom @@ -26,36 +32,40 @@ object PlayerNode { ) def apply( + app: GameApp, modelPath: os.RelPath, cam: Camera - )(assetManager: AssetManager) = { + )(assetManager: AssetManager, bulletAppState: BulletAppState) = { + lazy val playerPos = ImVector3f.ZERO + lazy val playerPhysicsControl = new BetterCharacterControl(1.5f, 6f, 1f) + .withJumpForce(ImVector3f(0, 5f, 0)) lazy val playerNode = new Node("PlayerNode") - lazy val camNode = new CameraNode("CameraNode", cam) - - // lazy val camNode = new CameraNode("CameraNode", simpleApp.getCamera()) - // camNode.setCamera(simpleApp.getCamera()) + .withChildren( + new CameraNode("CameraNode", cam) + .withControlDir(ControlDirection.SpatialToCamera) + .withLocalTranslation(ImVector3f(0, 1.5f, 10)) + .withLookAt(playerPos, ImVector3f.UNIT_Y), + assetManager + .loadModel(modelPath) + .asInstanceOf[Node] + .withRotate(0, FastMath.PI, 0) + ) + .withLocalTranslation(playerPos) + .withControl(playerPhysicsControl) - val playerModel: Node = - assetManager.loadModel(modelPath).asInstanceOf[Node] - discard { - playerNode - .child(camNode) - .child(playerModel) - // playerNode.children(Seq(camNode, geom)) + { + bulletAppState.physicsSpace += playerNode + bulletAppState.physicsSpace += playerPhysicsControl } { - camNode.setControlDir(ControlDirection.SpatialToCamera) - camNode.setLocalTranslation( - new Vector3f(0, 1.5f, 10) - ) - camNode.lookAt(playerNode.getLocalTranslation(), Vector3f.UNIT_Y) + app.rootNode += playerNode } - playerNode + playerPhysicsControl } def apply( - mesh: Geometry = defaultMesh(), + mesh: Geometry = defaultMesh, texturePath: os.RelPath = os.rel / "Common" / "MatDefs" / "Misc" / "Unshaded.j3md", cam: Camera @@ -76,8 +86,8 @@ object PlayerNode { // camNode.setCamera(simpleApp.getCamera()) discard { playerNode - .child(camNode) - .child(mesh) + .withChild(camNode) + .withChild(mesh) // playerNode.children(Seq(camNode, geom)) } diff --git a/src/main/scala/wow/doge/mygame/game/nodes/PlayerEventListeners.scala b/src/main/scala/wow/doge/mygame/game/nodes/PlayerEventListeners.scala new file mode 100644 index 0000000..b3f28e1 --- /dev/null +++ b/src/main/scala/wow/doge/mygame/game/nodes/PlayerEventListeners.scala @@ -0,0 +1,54 @@ +package wow.doge.mygame.game.nodes + +import akka.actor.typed.ActorRef +import akka.actor.typed.scaladsl.Behaviors +import wow.doge.mygame.subsystems.movement.ImMovementActor +import org.slf4j.event.Level +import akka.actor.typed.LogOptions +import wow.doge.mygame.subsystems.events.MovementEvent.PlayerMovementEvent +import com.typesafe.scalalogging.Logger + +object PlayerMovementEventHandler { + import PlayerMovementEvent._ + def apply(movementActor: ActorRef[ImMovementActor.Command]) = + Behaviors.logMessages( + LogOptions() + .withLevel(Level.TRACE) + .withLogger( + Logger[PlayerMovementEventHandler.type].underlying + ), + Behaviors.setup[PlayerMovementEvent](ctx => + Behaviors.receiveMessage { + case PlayerMovedLeft(pressed) => + movementActor ! ImMovementActor.MovedLeft(pressed) + Behaviors.same + case PlayerMovedRight(pressed) => + movementActor ! ImMovementActor.MovedRight(pressed) + Behaviors.same + case PlayerMovedForward(pressed) => + movementActor ! ImMovementActor.MovedUp(pressed) + Behaviors.same + case PlayerMovedBackward(pressed) => + movementActor ! ImMovementActor.MovedDown(pressed) + Behaviors.same + case PlayerJumped => + movementActor ! ImMovementActor.Jump + Behaviors.same + case PlayerRotatedRight => + // ctx.log.warn("right rotate not implemented yet") + movementActor ! ImMovementActor.RotateRight + Behaviors.same + case PlayerRotatedLeft => + // ctx.log.warn("left rotate not implemented yet") + movementActor ! ImMovementActor.RotateLeft + Behaviors.same + case PlayerCameraUp => + ctx.log.warn("camera up not implemented yet") + Behaviors.same + case PlayerCameraDown => + ctx.log.warn("camera down not implemented yet") + Behaviors.same + } + ) + ) +} diff --git a/src/main/scala/wow/doge/mygame/game/subsystems/input/GameInputHandler.scala b/src/main/scala/wow/doge/mygame/game/subsystems/input/GameInputHandler.scala new file mode 100644 index 0000000..5659fa8 --- /dev/null +++ b/src/main/scala/wow/doge/mygame/game/subsystems/input/GameInputHandler.scala @@ -0,0 +1,237 @@ +package wow.doge.mygame.game.subsystems.input + +import com.jme3.input.InputManager +import wow.doge.mygame.implicits._ +import akka.actor.typed.ActorRef +import wow.doge.mygame.events.EventBus +import com.jme3.input.KeyInput +import com.jme3.input.controls.KeyTrigger +import monix.bio.UIO +import wow.doge.mygame.utils.IOUtils._ +import wow.doge.mygame.subsystems.events.MovementEvent.PlayerMovementEvent +import scala.concurrent.duration._ +import com.jme3.input.controls.MouseAxisTrigger +import com.jme3.input.MouseInput + +// class GameInputHandler( +// inputManager: InputManager +// // inputEventBus: InputEventBus +// ) {} + +object GameInputHandler { + + final case class Props( + inputManager: InputManager, + playerMovementEventBus: ActorRef[ + EventBus.Command[PlayerMovementEvent] + ] + ) { + def begin = + for { + _ <- UIO(setupKeys(inputManager)) + _ <- toIO( + generateMovementInputEvents( + inputManager, + playerMovementEventBus + ).completedL.startAndForget + ) + _ <- toIO( + generateRotateEvents( + inputManager, + playerMovementEventBus + ).completedL.startAndForget + ) + _ <- toIO( + generateCameraEvents( + inputManager, + playerMovementEventBus + ).completedL.startAndForget + ) + } yield () + } + + def setupKeys(inputManager: InputManager) = + inputManager + .withMapping( + "Left", + new KeyTrigger(KeyInput.KEY_A) + // new KeyTrigger(KeyInput.KEY_LEFT) + ) + .withMapping( + "Right", + new KeyTrigger(KeyInput.KEY_D) + // new KeyTrigger(KeyInput.KEY_RIGHT) + ) + .withMapping( + "Up", + new KeyTrigger(KeyInput.KEY_W) + // new KeyTrigger(KeyInput.KEY_UP) + ) + .withMapping( + "Down", + new KeyTrigger(KeyInput.KEY_S) + // new KeyTrigger(KeyInput.KEY_DOWN) + ) + .withMapping( + "Jump", + new KeyTrigger(KeyInput.KEY_SPACE) + ) + .withMapping( + "ROTATE_RIGHT", + new KeyTrigger(KeyInput.KEY_RIGHT), + new MouseAxisTrigger(MouseInput.AXIS_X, true) + ) + .withMapping( + "ROTATE_LEFT", + new KeyTrigger(KeyInput.KEY_LEFT), + new MouseAxisTrigger(MouseInput.AXIS_X, false) + ) + .withMapping( + "CAMERA_UP", + // new KeyTrigger(KeyInput.KEY_LEFT), + new MouseAxisTrigger(MouseInput.AXIS_Y, false) + ) + .withMapping( + "CAMERA_DOWN", + // new KeyTrigger(KeyInput.KEY_LEFT), + new MouseAxisTrigger(MouseInput.AXIS_Y, true) + ) + .setCursorVisible(false) + + def generateMovementInputEvents( + inputManager: InputManager, + playerMovementEventBus: ActorRef[ + EventBus.Command[PlayerMovementEvent] + ] + ) = { + val name = "movementInputEventsGenerator" + inputManager + .observableAction( + "Left", + "Right", + "Up", + "Down", + "Jump", + "ROTATE_RIGHT", + "ROTATE_LEFT" + ) + // .dump("O") + .doOnNext { action => + action.binding.name match { + case "Left" => + toTask( + playerMovementEventBus !! EventBus.Publish( + PlayerMovementEvent.PlayerMovedLeft(pressed = action.value), + name + ) + ) + case "Right" => + toTask( + playerMovementEventBus !! EventBus.Publish( + PlayerMovementEvent.PlayerMovedRight(pressed = action.value), + name + ) + ) + case "Up" => + toTask( + playerMovementEventBus !! EventBus.Publish( + PlayerMovementEvent.PlayerMovedForward(pressed = action.value), + name + ) + ) + case "Down" => + toTask( + playerMovementEventBus !! EventBus.Publish( + PlayerMovementEvent.PlayerMovedBackward(pressed = action.value), + name + ) + ) + + case "Jump" if action.value => + toTask( + playerMovementEventBus !! EventBus.Publish( + PlayerMovementEvent.PlayerJumped, + name + ) + ) + + case _ => monix.eval.Task.unit + } + } + } + + def generateRotateEvents( + inputManager: InputManager, + playerMovementEventBus: ActorRef[ + EventBus.Command[PlayerMovementEvent] + ] + ) = { + val name = "rotateMovementEventsGenerator" + inputManager + .analogObservable("ROTATE_RIGHT", "ROTATE_LEFT") + .sample(1.millis) + .mapEval(analogEvent => + analogEvent.binding.name match { + case "ROTATE_RIGHT" => + toTask( + playerMovementEventBus !! EventBus.Publish( + PlayerMovementEvent.PlayerRotatedRight, + name + ) + ) + case "ROTATE_LEFT" => + toTask( + playerMovementEventBus !! EventBus.Publish( + PlayerMovementEvent.PlayerRotatedLeft, + name + ) + ) + case _ => monix.eval.Task.unit + } + ) + } + + def generateCameraEvents( + inputManager: InputManager, + playerMovementEventBus: ActorRef[ + EventBus.Command[PlayerMovementEvent] + ] + ) = { + val name = "cameraMovementEventsGenerator" + inputManager + .analogObservable("CAMERA_UP", "CAMERA_DOWN") + .sample(1.millis) + .mapEval(analogEvent => + analogEvent.binding.name match { + case "CAMERA_UP" => + toTask( + playerMovementEventBus !! EventBus.Publish( + PlayerMovementEvent.PlayerCameraUp, + name + ) + ) + case "CAMERA_DOWN" => + toTask( + playerMovementEventBus !! EventBus.Publish( + PlayerMovementEvent.PlayerCameraDown, + name + ) + ) + case _ => monix.eval.Task.unit + } + ) + } + + // def bindMappings(inputManager: InputManager, mappings: ActionMapping*) = { + // inputManager + // .observableAction(mappings.map(_.name): _*) + // .doOnNext(action => + // mappings.map(m => + // if (action.binding.name == m.name) toTask(m.cb(action)) + // else monix.eval.Task.unit + // ) + // ) + // } +} + +// case class ActionMapping(name: String, cb: ActionEvent => Task[Unit]) diff --git a/src/main/scala/wow/doge/mygame/game/subsystems/input/InputConstant.scala b/src/main/scala/wow/doge/mygame/game/subsystems/input/InputConstant.scala new file mode 100644 index 0000000..939114d --- /dev/null +++ b/src/main/scala/wow/doge/mygame/game/subsystems/input/InputConstant.scala @@ -0,0 +1,9 @@ +package wow.doge.mygame.game.subsystems.input + +object InputConstants { + val PLAYER_MOVE_LEFT = "PLAYER_MOVE_LEFT" + val PLAYER_MOVE_RIGHT = "PLAYER_MOVE_RIGHT" + val PLAYER_MOVE_FORWARD = "PLAYER_MOVE_FORWARD" + val PLAYER_MOVE_BACKWARD = "PLAYER_MOVE_BACKWARD" + val PLAYER_JUMP = "PLAYER_JUMP " +} diff --git a/src/main/scala/wow/doge/mygame/game/subsystems/level/DefaultGameLevel.scala b/src/main/scala/wow/doge/mygame/game/subsystems/level/DefaultGameLevel.scala new file mode 100644 index 0000000..7f98903 --- /dev/null +++ b/src/main/scala/wow/doge/mygame/game/subsystems/level/DefaultGameLevel.scala @@ -0,0 +1,63 @@ +package wow.doge.mygame.game.subsystems.level +import com.jme3.bullet.BulletAppState +import com.jme3.bullet.collision.shapes.CapsuleCollisionShape +import com.jme3.bullet.control.CharacterControl +import com.jme3.bullet.control.RigidBodyControl +import com.jme3.bullet.util.CollisionShapeFactory +import com.jme3.scene.Spatial +import wow.doge.mygame.implicits._ +import wow.doge.mygame.game.GameApp +import com.jme3.syntax._ +import com.jme3.math.ColorRGBA +import com.jme3.light.DirectionalLight +import com.jme3.math.Vector3f +import com.jme3.light.AmbientLight +object DefaultGameLevel { + +// lazy valbulletAppState: BulletAppState + // bulletAppState.setThreadingType(ThreadingType.SEQUENTIAL) + + // We set up collision detection for the scene by creating a + // compound collision shape and a static RigidBodyControl with mass zero. + + // We set up collision detection for the player by creating + // a capsule collision shape and a CharacterControl. + // The CharacterControl offers extra settings for + // size, stepheight, jumping, falling, and gravity. + // We also put the player in its starting position. + lazy val capsuleShape = new CapsuleCollisionShape(1.5f, 6f, 1) + + lazy val player: CharacterControl = + new CharacterControl(capsuleShape, 0.05f) + def apply(app: GameApp, bulletAppState: BulletAppState) = { + lazy val sceneModel: Spatial = app.assetManager.loadModel("main.scene") + lazy val sceneShape = CollisionShapeFactory.createMeshShape( + sceneModel.toNode match { + case util.Right(node) => node + case util.Left(ex) => + throw new NotImplementedError("No fallback sceneshape") + } + ) + lazy val landscape: RigidBodyControl = + new RigidBodyControl(sceneShape, 0) + + // // discard { app.stateManager.attach(bulletAppState) } + + app.viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f)) + sceneModel.setLocalScale(2f) + sceneModel.addControl(landscape) + discard { app.rootNode.attachChild(sceneModel) } + bulletAppState.getPhysicsSpace.add(landscape) + bulletAppState.getPhysicsSpace.add(player) + + val al = new AmbientLight(); + al.setColor(ColorRGBA.White.mult(1.3f)); + app.rootNode.addLight(al); + + val dl = new DirectionalLight(); + dl.setColor(ColorRGBA.White); + dl.setDirection(new Vector3f(2.8f, -2.8f, -2.8f).normalizeLocal()); + app.rootNode.addLight(dl); + + } +} 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 new file mode 100644 index 0000000..f24270d --- /dev/null +++ b/src/main/scala/wow/doge/mygame/game/subsystems/movement/MovementActor.scala @@ -0,0 +1,184 @@ +package wow.doge.mygame.subsystems.movement + +import akka.actor.typed.scaladsl.ActorContext +import akka.actor.typed.Behavior +import akka.actor.typed.scaladsl.Behaviors +import com.softwaremill.quicklens._ +import wow.doge.mygame.implicits._ +import com.jme3.renderer.Camera +import wow.doge.mygame.math.ImVector3f +import wow.doge.mygame.game.GameApp + +import akka.actor.typed.ActorRef +import wow.doge.mygame.events.EventBus +import com.jme3.math.Vector3f +import wow.doge.mygame.state.CardinalDirection +import wow.doge.mygame.subsystems.events.MovementEvent.PlayerMovementEvent +import akka.actor.typed.LogOptions +import com.typesafe.scalalogging.Logger +import org.slf4j.event.Level + +sealed trait RotateDir +object RotateDir { + case object Left extends RotateDir + case object Right extends RotateDir +} + +trait CanMove[-A] { + // def getDirection(cam: Camera, cardinalDir: CardinalDirection): ImVector3f + def move(inst: A, direction: ImVector3f): Unit + def jump(inst: A): Unit + def stop(inst: A): Unit + def rotate(inst: A, rotateDir: RotateDir): Unit +} + +object ImMovementActor { + sealed trait Command + // final case class Tick(tpf: Float) extends Command + final case class Tick(tpf: Float) extends Command + + sealed trait Movement extends Command + final case class MovedLeft(pressed: Boolean) extends Movement + final case class MovedUp(pressed: Boolean) extends Movement + final case class MovedRight(pressed: Boolean) extends Movement + final case class MovedDown(pressed: Boolean) extends Movement + final case object Jump extends Movement + final case object RotateRight extends Movement + final case object RotateLeft extends Movement + + final case class Props[T: CanMove]( + app: GameApp, + movable: T, + playerMovementEventBus: ActorRef[ + EventBus.Command[PlayerMovementEvent] + ] + ) { + def create: Behavior[Command] = + Behaviors.setup(ctx => { + ctx.log.info("Hello from MovementActor") + // val playerMovementEventHandler = ctx.spawn( + // PlayerMovementEventHandler(ctx.self), + // "playerMovementEventHandler" + // ) + // playerMovementEventBus ! EventBus.Subscribe(playerMovementEventHandler) + new ImMovementActor(ctx, this).receive(State()) + }) + } + + /** + * Internal state of the actor + * + * @param cardinalDir The four directions the character can move + */ + final case class State(cardinalDir: CardinalDirection = CardinalDirection()) + + // def apply[T: CanMove](props: Props[T]): Behavior[Command] = + // Behaviors.setup(ctx => { + // ctx.log.info("Hello from MovementActor") + // val playerMovementEventHandler = ctx.spawn( + // PlayerMovementEventHandler(ctx.self), + // "playerMovementEventHandler" + // ) + // props.playerMovementEventBus ! EventBus.Subscribe( + // playerMovementEventHandler + // ) + // new ImMovementActor(ctx, props).receive(State()) + // }) + +} + +class ImMovementActor[T]( + ctx: ActorContext[ImMovementActor.Command], + props: ImMovementActor.Props[T] +) { + import ImMovementActor._ + import Methods._ + + def receive( + state: ImMovementActor.State + )(implicit cm: CanMove[T]): Behavior[Command] = + Behaviors.receiveMessage { + case m: Movement => + m match { + case MovedLeft(pressed) => + props.app.enqueueF(stopIfNotPressed(pressed, props.movable)) + receive(state = state.modify(_.cardinalDir.left).setTo(pressed)) + case MovedUp(pressed) => + props.app.enqueueF(stopIfNotPressed(pressed, props.movable)) + receive(state = state.modify(_.cardinalDir.up).setTo(pressed)) + case MovedRight(pressed) => + props.app.enqueueF(stopIfNotPressed(pressed, props.movable)) + receive(state = state.modify(_.cardinalDir.right).setTo(pressed)) + case MovedDown(pressed) => + props.app.enqueueF(stopIfNotPressed(pressed, props.movable)) + receive(state = state.modify(_.cardinalDir.down).setTo(pressed)) + case Jump => + props.app.enqueueF(cm.jump(props.movable)) + Behaviors.same + case RotateLeft => + props.app.enqueueF(cm.rotate(props.movable, RotateDir.Left)) + Behaviors.same + case RotateRight => + props.app.enqueueF(cm.rotate(props.movable, RotateDir.Right)) + Behaviors.same + } + + case Tick(tpf) => + val walkDir = + getDirection(state.cardinalDir, ctx.log.trace) + if (walkDir != ImVector3f.ZERO) { + val tmp = walkDir * 25f * tpf + props.app.enqueueF { + cm.move(props.movable, tmp) + } + + // props.app + // .enqueueScala { () => + // 1 + // } + // .map(println)(scala.concurrent.ExecutionContext.global) + // monix.eval.Task + // .fromFuture( + // props.app + // .enqueueScala { () => + // 1 + // } + // ) + // .flatMap(i => monix.eval.Task(println(i))) + // .runToFuture(monix.execution.Scheduler.Implicits.global) + } + Behaviors.same + } +} +object Methods { + def getDirection(cardinalDir: CardinalDirection, trace: String => Unit) = { + val zero = ImVector3f.ZERO + val dir = cardinalDir + val walkDir = { + val mutWalkDir = new Vector3f() + if (dir.left) { + trace("left") + mutWalkDir += zero +=: new Vector3f(-1, 0, 0) + } + if (dir.right) { + trace("right") + mutWalkDir += zero +=: new Vector3f(1, 0, 0) + } + if (dir.up) { + trace("up") + mutWalkDir += zero +=: new Vector3f(0, 0, -1) + } + if (dir.down) { + trace("down") + mutWalkDir += zero +=: new Vector3f(0, 0, 1) + } + mutWalkDir.immutable + } + walkDir + } + + def stopIfNotPressed[T](pressed: Boolean, movable: T)(implicit + cm: CanMove[T] + ) = + if (!pressed) cm.stop(movable) +} diff --git a/src/main/scala/wow/doge/mygame/implicits/package.scala b/src/main/scala/wow/doge/mygame/implicits/package.scala index c53476f..5fe9617 100644 --- a/src/main/scala/wow/doge/mygame/implicits/package.scala +++ b/src/main/scala/wow/doge/mygame/implicits/package.scala @@ -20,14 +20,11 @@ import com.jme3.math.Vector3f import wow.doge.mygame.math.ImVector3f import com.jme3.scene.Geometry import wow.doge.mygame.state.CardinalDirection -import wow.doge.mygame.state.CanMove +import wow.doge.mygame.subsystems.movement.CanMove import com.jme3.renderer.Camera import scala.jdk.CollectionConverters._ import wow.doge.mygame.utils.JFXConsoleStreamable import com.jme3.app.Application -import java.util.concurrent.Callable -import scala.concurrent.Future -import scala.concurrent.ExecutionContext import com.jme3.scene.SceneGraphVisitor import monix.reactive.Observable import com.jme3.asset.AssetManager @@ -45,8 +42,24 @@ import com.jme3.bullet.PhysicsTickListener import monix.reactive.observers.Subscriber import monix.execution.Ack.Continue import monix.execution.Ack.Stop +import com.jme3.bullet.BulletAppState +import wow.doge.mygame.state.MyBaseState +import monix.bio.UIO +import com.jme3.bullet.control.BetterCharacterControl +import com.jme3.scene.control.AbstractControl +import com.jme3.scene.CameraNode +import com.jme3.scene.control.CameraControl.ControlDirection +import com.jme3.bullet.control.AbstractPhysicsControl +import com.jme3.scene.control.Control +import com.typesafe.scalalogging.Logger +import com.typesafe.scalalogging.LazyLogging +import com.jme3.input.controls.AnalogListener +import com.jme3.math.Quaternion +import com.jme3.math.FastMath +import wow.doge.mygame.subsystems.movement.RotateDir case class ActionEvent(binding: Action, value: Boolean, tpf: Float) +case class AnalogEvent(binding: Action, value: Float, tpf: Float) case class PhysicsTickEvent(space: PhysicsSpace, tpf: Float) package object implicits { @@ -54,35 +67,35 @@ package object implicits { type PhysicsTickObservable = Observable[Either[PrePhysicsTickEvent, PhysicsTickEvent]] - implicit class JMEAppExt(val app: Application) extends AnyVal { - - /** - * Blocking task. Execute on a thread pool meant for blocking operations. - * Prefer [[wow.doge.mygame.implicits.JMEAppExt#enqueueT]] instead. - * - * @param cb - * @param ec - * @return - */ - def enqueueF[T](cb: () => T)(implicit ec: ExecutionContext): Future[T] = - Future { - app - .enqueue(new Callable[T]() { - override def call(): T = cb() - }) - .get() - } - - /** - * Blocking task. Execute on a thread pool meant for blocking operations. - * Same as enqueue, but returns a Monix Task instead of Future - * @param cb - * @param ec - * @return - */ - def enqueueL[T](cb: () => T): Task[T] = - Task - .deferFutureAction(implicit s => enqueueF(cb)) + implicit class JMEAppExt(private val app: Application) extends AnyVal { + + // /** + // * Blocking task. Execute on a thread pool meant for blocking operations. + // * Prefer [[wow.doge.mygame.implicits.JMEAppExt#enqueueT]] instead. + // * + // * @param cb + // * @param ec + // * @return + // */ + // def enqueueF[T](cb: () => T)(implicit ec: ExecutionContext): Future[T] = + // Future { + // app + // .enqueue(new Callable[T]() { + // override def call(): T = cb() + // }) + // .get() + // } + + // /** + // * Blocking task. Execute on a thread pool meant for blocking operations. + // * Same as enqueue, but returns a Monix Task instead of Future + // * @param cb + // * @param ec + // * @return + // */ + // def enqueueL[T](cb: () => T): Task[T] = + // Task + // .deferFutureAction(implicit s => enqueueF(cb)) def enqueueF[T](cb: => T) = app.enqueue(new Runnable { @@ -92,13 +105,14 @@ package object implicits { def enqueueT(cb: => Unit) = Task(enqueueF(cb)) } - implicit class StateManagerExt(val sm: AppStateManager) extends AnyVal { + implicit class StateManagerExt(private val sm: AppStateManager) + extends AnyVal { def state[S <: AppState]()(implicit c: ClassTag[S]): S = sm.getState(c.runtimeClass.asInstanceOf[Class[S]]) } - implicit class SimpleApplicationExt(val sa: SimpleApplication) + implicit class SimpleApplicationExt[T <: SimpleApplication](private val sa: T) extends AnyVal { def stateManager: AppStateManager = sa.getStateManager() def inputManager: InputManager = sa.getInputManager() @@ -107,9 +121,32 @@ package object implicits { def flyCam = Option(sa.getFlyByCamera()) def camera = sa.getCamera() def viewPort = sa.getViewPort() + def rootNode = sa.getRootNode() + + def observableTick: Observable[Float] = + Observable.create(OverflowStrategy.Unbounded) { sub => + val c = SingleAssignCancelable() + val as = new MyBaseState { + + override def init(): Unit = {} + + override def update(tpf: Float) = { + if (sub.onNext(tpf) == Ack.Stop) + c.cancel() + } + + override def stop(): Unit = {} + + override def onEnable() = {} + override def onDisable() = {} + } + sa.stateManager.attach(as) + c := Cancelable(() => sa.stateManager.detach(as)) + c + } } - implicit class NodeExt(val n: Node) extends AnyVal { + implicit class NodeExt[T <: Node](private val n: T) extends AnyVal { /** * Attaches the given child @@ -117,28 +154,34 @@ package object implicits { * @param s * @return */ - def child(s: Spatial): Node = { + def withChild(s: Spatial): Node = { n.attachChild(s) n } /** - * Gets the list of children as a scala collection + * Gets the list of children as a monix observable * * @return */ // def children = n.getChildren().asScala.toSeq - def children = Observable.fromIterable(n.getChildren().asScala) + def observableChildren = + Observable.fromIterable(n.getChildren().asScala) + + def children = LazyList.from(n.getChildren().asScala) /** * Attach given children * * @param lst */ - def children(lst: Iterable[Spatial]): Unit = { - for (c <- lst) n.child(c) + def withChildren(lst: Spatial*): Node = { + for (c <- lst) n.withChild(c) + n } + def +=(spatial: Spatial) = n.attachChild(spatial) + def depthFirst(cb: Spatial => Unit) = n.depthFirstTraversal(new SceneGraphVisitor() { override def visit(s: Spatial) = cb(s) @@ -156,7 +199,7 @@ package object implicits { Task.deferFuture(subscriber.onNext(node)).flatMap { case Ack.Continue => { //modifying a node's children list is forbidden - val children = node.getChildren().asScala.to(LazyList) + val children = node.children if (!children.isEmpty) { Task.sequence( children.map(c => loop(subscriber, c)) @@ -191,9 +234,9 @@ package object implicits { def loop( subscriber: Subscriber[Spatial], spatials: LazyList[Spatial] - ): Task[Unit] = { - // spatial can be either a node or a geometry, but it's not a sealed trait + ): Task[Unit] = spatials match { + // spatial can be either a node or a geometry, but it's not a sealed trait case head #:: tail => head match { case g: Geometry => @@ -203,7 +246,7 @@ package object implicits { case Stop => Task.unit } case node: Node => - val children = node.getChildren().asScala.to(LazyList) + val children = node.children Task.deferFuture(subscriber.onNext(node)).flatMap { case Continue => loop(subscriber, tail #::: children) @@ -213,23 +256,54 @@ package object implicits { } case LazyList() => Task.unit } - } Observable.create(OverflowStrategy.Unbounded) { sub => implicit val sched = sub.scheduler loop(sub, LazyList(n)).runToFuture } } + + def withControl[C <: Control](ctrl: C) = { + n.addControl(ctrl) + n + } + + def withLocalTranslation(dir: ImVector3f) = { + n.setLocalTranslation(dir.mutable) + n + } + + def withRotate(xAngle: Float, yAngle: Float, zAngle: Float) = { + n.rotate(xAngle, yAngle, zAngle) + n + } + + def localRotation = n.getLocalRotation() + + def localTranslation = n.getLocalTranslation() + + } + + implicit class CameraNodeExt(private val cn: CameraNode) { + def withControlDir(controlDir: ControlDirection) = { + cn.setControlDir(controlDir) + cn + } + + def withLookAt(position: ImVector3f, upVector: ImVector3f) = { + cn.lookAt(position.mutable, upVector.mutable) + cn + } } - implicit class EntityDataExt(val ed: EntityData) extends AnyVal { + implicit class EntityDataExt(private val ed: EntityData) extends AnyVal { def query = new EntityQuery(ed) // def entities[T <: EntityComponent](entities: Seq[T]) } - implicit class EntityExt(val e: EntityId) extends AnyVal { + implicit class EntityExt(private val e: EntityId) extends AnyVal { def withComponents(classes: EntityComponent*)(implicit ed: EntityData ): EntityId = { @@ -238,19 +312,40 @@ package object implicits { } } - implicit class ActorRefExt[Req](val a: ActorRef[Req]) extends AnyVal { + implicit class ActorRefExt[Req](private val a: ActorRef[Req]) extends AnyVal { import akka.actor.typed.scaladsl.AskPattern._ - def askT[Res]( + + /** + * @param replyTo + * @param timeout + * @param scheduler + * @return + */ + def askL[Res]( replyTo: ActorRef[Res] => Req )(implicit timeout: Timeout, scheduler: Scheduler): Task[Res] = { Task.deferFuture(a.ask(replyTo)(timeout, scheduler)) } + def ??[Res]( + replyTo: ActorRef[Res] => Req + )(implicit timeout: Timeout, scheduler: Scheduler): Task[Res] = + askL(replyTo) + + /** + * Same as [[tell]], but wrapped in a Task + * + * @param msg + * @return + */ + def tellL(msg: Req) = UIO(a.tell(msg)) + def !!(msg: Req) = tellL(msg) + } // def ?[Res](replyTo: ActorRef[Res] => Req)(implicit timeout: Timeout, scheduler: Scheduler): Future[Res] = { // ask(replyTo)(timeout, scheduler) // } - implicit class InputManagerExt(val inputManager: InputManager) + implicit class InputManagerExt(private val inputManager: InputManager) extends AnyVal { def withMapping(mapping: String, triggers: Trigger*): InputManager = { inputManager.addMapping(mapping, triggers: _*) @@ -264,7 +359,7 @@ package object implicits { def observableAction(mappingNames: String*): Observable[ActionEvent] = { - Observable.create(OverflowStrategy.Unbounded) { sub => + Observable.create(OverflowStrategy.DropOld(10)) { sub => val c = SingleAssignCancelable() val al = new ActionListener { override def onAction( @@ -281,13 +376,37 @@ package object implicits { inputManager.addListener(al, mappingNames: _*) + c := Cancelable(() => inputManager.removeListener(al)) + c + } + } + def analogObservable(mappingNames: String*): Observable[AnalogEvent] = { + + Observable.create(OverflowStrategy.DropOld(100)) { sub => + val c = SingleAssignCancelable() + val al = new AnalogListener { + override def onAnalog( + binding: String, + value: Float, + tpf: Float + ): Unit = { + if ( + sub.onNext(AnalogEvent(Action(binding), value, tpf)) == Ack.Stop + ) + c.cancel() + } + } + + inputManager.addListener(al, mappingNames: _*) + c := Cancelable(() => inputManager.removeListener(al)) c } } } - implicit class PhysicsSpaceExt(val space: PhysicsSpace) extends AnyVal { + implicit class PhysicsSpaceExt(private val space: PhysicsSpace) + extends AnyVal { def collisionObservable(): Observable[PhysicsCollisionEvent] = { @@ -335,9 +454,15 @@ package object implicits { c } } + + //TODO Create a typeclass for this + def +=(anyObject: Any) = space.add(anyObject) + + def +=(spatial: Spatial) = space.addAll(spatial) + } - implicit class AssetManagerExt(val am: AssetManager) extends AnyVal { + implicit class AssetManagerExt(private val am: AssetManager) extends AnyVal { def registerLocator( assetPath: os.RelPath, locator: Class[_ <: AssetLocator] @@ -350,8 +475,26 @@ package object implicits { } } - implicit class Vector3fExt(val v: Vector3f) extends AnyVal { + implicit class BulletAppStateExt(private val bas: BulletAppState) + extends AnyVal { + def physicsSpace = bas.getPhysicsSpace() + def speed = bas.getSpeed() + } + + implicit class BetterCharacterControlExt( + private val bcc: BetterCharacterControl + ) { + def withJumpForce(force: ImVector3f) = { + bcc.setJumpForce(force.mutable) + bcc + } + } + + implicit class Vector3fExt(private val v: Vector3f) extends AnyVal { + //TODO add more operations def +=(that: Vector3f) = v.addLocal(that) + def +=(that: ImVector3f) = v.addLocal(that.x, that.y, that.z) + def +=:(that: ImVector3f) = v += that def *=(that: Vector3f) = v.multLocal(that) def -=(that: Vector3f) = v.subtractLocal(that) def /=(that: Vector3f) = v.divideLocal(that) @@ -359,12 +502,12 @@ package object implicits { def immutable = ImVector3f(v.x, v.y, v.z) } - implicit class ImVector3fExt(val v: ImVector3f) extends AnyVal { + implicit class ImVector3fExt(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.x * f) - v * ImVector3f(f, f, f) + v.copy(v.x * f, v.y * f, v.z * f) + // v * ImVector3f(f, f, 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) def unary_- = v.copy(-v.x, -v.y, -v.z) @@ -372,106 +515,53 @@ package object implicits { def mutable = new Vector3f(v.x, v.y, v.z) } - // implicit val implVector3fForVector3 = new Vector3[Vector3f] { - // override def +(implicit v: Vector3f, that: com.jme3.math.Vector3f): Unit = - // v += that - - // override def *(implicit v: Vector3f, that: Vector3f): Unit = v *= that - - // override def -(implicit v: Vector3f, that: Vector3f): Unit = v -= that - - // override def /(implicit v: Vector3f, that: Vector3f): Unit = v /= that - - // } - - // implicit val implImVector3fForVector3 = new Vector3[ImVector3f] { - // override def +(implicit v: ImVector3f, that: ImVector3f): Unit = - // v + that - - // override def *(implicit v: ImVector3f, that: ImVector3f): Unit = v * that - - // override def -(implicit v: ImVector3f, that: ImVector3f): Unit = v - that - - // override def /(implicit v: ImVector3f, that: ImVector3f): Unit = v / that - - // } - - // def test[T](v: T)(implicit ev: Vector3[T]) = { - // import ev._ - // ev.+ - // } + implicit val implCanMoveForBetterCharacterControl = + new CanMove[BetterCharacterControl] { + override def move( + inst: BetterCharacterControl, + direction: ImVector3f + ): Unit = { + // val dir = direction.mutable + // inst.setViewDirection(dir) + // inst.setViewDirection(direction.mutable) + inst.setWalkDirection(direction.mutable.multLocal(50f)) + } + override def jump(inst: BetterCharacterControl): Unit = inst.jump() + override def rotate( + inst: BetterCharacterControl, + rotateDir: RotateDir + ): Unit = { + val q = + rotateDir match { + case RotateDir.Left => + new Quaternion() + .fromAngleAxis(-10f * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y) + case RotateDir.Right => + new Quaternion() + .fromAngleAxis(10 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y) + } - implicit val implCanMoveForGeom = new CanMove[Spatial] { + val tmp = new Vector3f() + inst.getViewDirection(tmp) + inst.setViewDirection(q.mult(tmp)) + } + override def stop(inst: BetterCharacterControl) = + inst.setWalkDirection(Vector3f.ZERO) + } + implicit val implCanMoveForGeom = new CanMove[Spatial] with LazyLogging { override def move(inst: Spatial, direction: ImVector3f): Unit = { - // val v = inst.getLocalTranslation() - // inst match { - // case n: Node => println(n.getChildren()) - // case _ => - // } inst.move(direction.mutable) } - - override def getDirection( - cam: Camera, - cardinalDir: CardinalDirection - ): ImVector3f = { - // val camDir = - // cam.getDirection().immutable * 0.6f - // val camLeft = cam.getLeft().immutable * 0.4f - - // val zero = ImVector3f.ZERO - // val dir = cardinalDir - // val walkDir = { - // val mutWalkDir = new Vector3f() - // if (dir.left) { - // // ctx.log.trace("left") - // mutWalkDir += (zero + camLeft).mutable - // } - // if (dir.right) { - // // ctx.log.trace("right") - // mutWalkDir += (zero + -camLeft).mutable - // } - // if (dir.up) { - // // ctx.log.trace("up") - // mutWalkDir += (zero + camDir).mutable - // } - // if (dir.down) { - // // ctx.log.trace("down") - // mutWalkDir += (zero + -camDir).mutable - // } - // mutWalkDir.immutable - // } - // walkDir - - // val camDir = - // cam.getDirection().immutable * 0.6f - // val camLeft = cam.getLeft().immutable * 0.4f - - val zero = ImVector3f.ZERO - val dir = cardinalDir - val walkDir = { - val mutWalkDir = new Vector3f() - if (dir.left) { - // ctx.log.trace("left") - mutWalkDir += (zero + ImVector3f(-1, 0, 0)).mutable - } - if (dir.right) { - // ctx.log.trace("right") - mutWalkDir += (zero + ImVector3f(1, 0, 0)).mutable - } - if (dir.up) { - // ctx.log.trace("up") - mutWalkDir += (zero + ImVector3f(0, 0, -1)).mutable - } - if (dir.down) { - // ctx.log.trace("down") - mutWalkDir += (zero + ImVector3f(0, 0, 1)).mutable - } - mutWalkDir.immutable + override def jump(inst: Spatial): Unit = + logger.warn("`Jump` is not implemented for type `Spatial`") + override def rotate(inst: Spatial, rotateDir: RotateDir): Unit = { + rotateDir match { + case RotateDir.Left => inst.rotate(0, -0.01f, 0) + case RotateDir.Right => inst.rotate(0, 0.01f, 0) } - walkDir } + override def stop(inst: Spatial) = {} } implicit val implJFXConsoleStreamForTextArea = 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 2bfde95..90eddd9 100644 --- a/src/main/scala/wow/doge/mygame/subsystems/events/EventBus.scala +++ b/src/main/scala/wow/doge/mygame/subsystems/events/EventBus.scala @@ -1,102 +1,22 @@ package wow.doge.mygame.events -// import akka.event.ActorEventBus -// import akka.event.ManagedActorClassification -// import akka.event.ActorClassifier import akka.actor.typed.ActorRef -// import akka.actor.ActorSystem -// import akka.event.EventBus -// import akka.util.Subclassification -// import java.util.concurrent.atomic.AtomicReference import akka.actor.typed.Behavior import akka.actor.typed.scaladsl.Behaviors import scala.reflect.ClassTag import akka.event.EventStream -// private[events] final case class ClassificationMessage(ref: ActorRef, id: Int) - -// class ActorBusImpl(val system: ActorSystem, val busSize: Int) -// extends ActorEventBus -// with ActorClassifier -// with ManagedActorClassification { -// type Event = ClassificationMessage - -// // is used for extracting the classifier from the incoming events -// override protected def classify(event: Event): ActorRef = event.ref - -// // determines the initial size of the index data structure -// // used internally (i.e. the expected number of different classifiers) -// override protected def mapSize: Int = busSize -// } - -// class StartsWithSubclassification extends Subclassification[String] { -// override def isEqual(x: String, y: String): Boolean = -// x == y - -// override def isSubclass(x: String, y: String): Boolean = -// x.startsWith(y) -// } - -// import akka.event.SubchannelClassification - -// final case class MsgEnvelope(topic: String, payload: Any) -// import akka.actor.typed.scaladsl.adapter._ - -// /** -// * Publishes the payload of the MsgEnvelope when the topic of the -// * MsgEnvelope starts with the String specified when subscribing. -// */ -// class SubchannelBusImpl extends EventBus with SubchannelClassification { -// type Event = Any -// type Classifier = Class[_] -// type Subscriber = ActorRef - -// // Subclassification is an object providing `isEqual` and `isSubclass` -// // to be consumed by the other methods of this classifier -// // override protected val subclassification: Subclassification[Classifier] = -// // new StartsWithSubclassification - -// private val initiallySubscribedOrUnsubscriber = -// new AtomicReference[Either[Set[ActorRef], ActorRef]](Left(Set.empty)) - -// override protected implicit val subclassification -// : Subclassification[Classifier] = new Subclassification[Class[_]] { -// def isEqual(x: Class[_], y: Class[_]) = x == y -// def isSubclass(x: Class[_], y: Class[_]) = y.isAssignableFrom(x) -// } - -// // is used for extracting the classifier from the incoming events -// override protected def classify(event: Event): Classifier = event.getClass() - -// // will be invoked for each event for all subscribers which registered -// // themselves for the event’s classifier -// override protected def publish(event: Event, subscriber: Subscriber): Unit = { -// // subscriber ! event.payload -// subscriber ! event -// } -// } - +/** + * A (typed) event bus + * Copied (and repurposed) from Akka's EventStream + */ object EventBus { - sealed trait Command[A] - final case class Publish[A, E <: A](event: E, publisher: ActorRef[_]) - extends Command[A] + sealed trait Command[-A] + final case class Publish[A, E <: A]( + event: E, + publisherName: String + ) extends Command[A] - /** - * Subscribe a typed actor to listen for types or subtypes of E - * by sending this command to the [[akka.actor.typed.ActorSystem.eventStream]]. - * - * ==Simple example== - * {{{ - * sealed trait A - * case object A1 extends A - * //listen for all As - * def subscribe(actorSystem: ActorSystem[_], actorRef: ActorRef[A]) = - * actorSystem.eventStream ! EventStream.Subscribe(actorRef) - * //listen for A1s only - * def subscribe(actorSystem: ActorSystem[_], actorRef: ActorRef[A]) = - * actorSystem.eventStream ! EventStream.Subscribe[A1](actorRef) - * }}} - */ final case class Subscribe[A, E <: A](subscriber: ActorRef[E])(implicit classTag: ClassTag[E] ) extends Command[A] { @@ -104,10 +24,6 @@ object EventBus { def topic: Class[_] = classTag.runtimeClass } - /** - * Unsubscribe an actor ref from the event stream - * by sending this command to the [[akka.actor.typed.ActorSystem.eventStream]]. - */ final case class Unsubscribe[A, E <: A](subscriber: ActorRef[E]) extends Command[A] @@ -126,7 +42,7 @@ class EventBus[B] { eventStream: akka.event.EventStream ): Behavior[EventBus.Command[B]] = Behaviors.receiveMessage { - case EventBus.Publish(event, publisher) => + case EventBus.Publish(event, name) => eventStream.publish(event) Behaviors.same case s @ EventBus.Subscribe(subscriber) => 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 1544159..37517f7 100644 --- a/src/main/scala/wow/doge/mygame/subsystems/events/Events.scala +++ b/src/main/scala/wow/doge/mygame/subsystems/events/Events.scala @@ -1,17 +1,14 @@ package wow.doge.mygame.events -// object Test { - -// Events.BulletFired -// } - object Events { sealed trait Event - case object BulletFired extends Event + final case object BulletFired extends Event // type BulletFired = BulletFired.type - case class EventWithData(data: Int) extends Event + final case class EventWithData(data: Int) extends Event sealed trait Tick extends Event - case object RenderTick extends Tick - case object PhysicsTick extends Tick + object Tick { + final case object RenderTick extends Tick + final case object PhysicsTick extends Tick + } } 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 9b6853e..35a479c 100644 --- a/src/main/scala/wow/doge/mygame/subsystems/events/EventsModule.scala +++ b/src/main/scala/wow/doge/mygame/subsystems/events/EventsModule.scala @@ -1,3 +1,82 @@ package wow.doge.mygame.events -trait EventsModule {} +import akka.actor.typed.ActorRef +import akka.actor.typed.SpawnProtocol +import wow.doge.mygame.implicits._ +import akka.actor.typed.scaladsl.AskPattern._ +import akka.actor.typed.Props +import akka.util.Timeout +import akka.actor.typed.Scheduler +import akka.actor.typed.LogOptions +import com.typesafe.scalalogging.{Logger => SLLogger} +import wow.doge.mygame.events.EventBus +import akka.actor.typed.scaladsl.Behaviors +import wow.doge.mygame.subsystems.events.MovementEvent.PlayerMovementEvent +import akka.actor.typed.SupervisorStrategy + +trait EventsModule { + def spawnProtocol: ActorRef[SpawnProtocol.Command] + implicit def akkaScheduler: Scheduler + implicit def timeout: Timeout + def eventBusLogger = SLLogger[EventBus[_]] + +// val subscribingActor = +// spawnProtocol.askT( +// SpawnProtocol.Spawn[Events.PhysicsTick.type]( +// SubscribingActor(), +// "subscriber-1", +// Props.empty, +// _ +// ) +// ) + + lazy val tickEventBusTask = createEventBus[Events.Tick]("tickEventBus") + + // spawnProtocol.askL( + // SpawnProtocol.Spawn[EventBus.Command[Events.Tick]]( + // Behaviors.logMessages( + // logOptions = LogOptions().withLogger(eventBusLogger.underlying), + // EventBus[Events.Tick]() + // ), + // "tickEventBus", + // Props.empty, + // _ + // ) + // ) + + lazy val playerMovementEventBusTask = + createEventBus[PlayerMovementEvent]("movementEventBus") + + // spawnProtocol.askL( + // SpawnProtocol.Spawn[EventBus.Command[Events.Movement.PlayerMovement]]( + // Behaviors.logMessages( + // logOptions = LogOptions().withLogger(eventBusLogger.underlying), + // EventBus[Events.Movement.PlayerMovement]() + // ), + // "movementEventBus", + // Props.empty, + // _ + // ) + // ) + +// tickEventBus ! EventBus.Subscribe(subscribingActor) + +// tickEventBus ! EventBus.Publish(Events.PhysicsTick, ctx.self) + def createEventBus[T](busName: String) = + spawnProtocol.askL( + SpawnProtocol.Spawn[EventBus.Command[T]]( + Behaviors.logMessages( + logOptions = LogOptions().withLogger(eventBusLogger.underlying), + Behaviors + .supervise(EventBus[T]()) + .onFailure[Exception](SupervisorStrategy.restart) + ), + busName, + Props.empty, + _ + ) + ) +} +object EventTypes { + type EventBus[T] = ActorRef[EventBus.Command[T]] +} diff --git a/src/main/scala/wow/doge/mygame/subsystems/events/EventsModule2.scala b/src/main/scala/wow/doge/mygame/subsystems/events/EventsModule2.scala new file mode 100644 index 0000000..1396c82 --- /dev/null +++ b/src/main/scala/wow/doge/mygame/subsystems/events/EventsModule2.scala @@ -0,0 +1,59 @@ +// package wow.doge.mygame.subsystems.events + +// import akka.actor.typed.ActorRef +// import akka.actor.typed.SpawnProtocol +// import wow.doge.mygame.implicits._ +// import akka.actor.typed.Props +// import akka.actor.typed.LogOptions +// import com.typesafe.scalalogging.{Logger => SLLogger} +// import wow.doge.mygame.events.EventBus +// import akka.actor.typed.scaladsl.Behaviors +// import wow.doge.mygame.events.Events +// import cats.effect.Resource +// import monix.bio.Task +// import akka.actor.typed.ActorSystem +// import scala.concurrent.duration._ + +// trait EventsModule2 { +// def eventBusesResource( +// spawnProtocol: ActorSystem[SpawnProtocol.Command], +// eventBusLogger: com.typesafe.scalalogging.Logger = SLLogger[EventBus[_]] +// ): Resource[ +// Task, +// ( +// ActorRef[EventBus.Command[Events.Tick]], +// ActorRef[EventBus.Command[Events.Movement.PlayerMovement]] +// ) +// ] = { +// def createEventBus[T](busName: String) = +// spawnProtocol.askL( +// SpawnProtocol.Spawn[EventBus.Command[T]]( +// Behaviors.logMessages( +// logOptions = LogOptions().withLogger(eventBusLogger.underlying), +// EventBus[T]() +// ), +// busName, +// Props.empty, +// _ +// ) + +// )(1.second, spawnProtocol.scheduler) + +// Resource.liftF { +// { +// lazy val tickEventBusTask = createEventBus[Events.Tick]("tickEventBus") + +// lazy val playerMovementEventBusTask = +// createEventBus[Events.Movement.PlayerMovement]("movementEventBus") + +// // val r = (tickEventBusTask, playerMovementEventBusTask) +// // Task(r) +// for { +// tickEventBus <- tickEventBusTask +// playerMovementEventBus <- playerMovementEventBusTask +// } yield (tickEventBus, playerMovementEventBus) +// } +// } +// } + +// } diff --git a/src/main/scala/wow/doge/mygame/subsystems/events/MovementEvents.scala b/src/main/scala/wow/doge/mygame/subsystems/events/MovementEvents.scala new file mode 100644 index 0000000..e4f39d3 --- /dev/null +++ b/src/main/scala/wow/doge/mygame/subsystems/events/MovementEvents.scala @@ -0,0 +1,33 @@ +package wow.doge.mygame.subsystems.events + +import wow.doge.mygame.subsystems.movement.CanMove + +sealed trait MovementEvent + +object MovementEvent { + final case class MovedLeft[T: CanMove](pressed: Boolean, movable: T) + extends MovementEvent + final case class MovedUp[T: CanMove](pressed: Boolean, movable: T) + extends MovementEvent + final case class MovedRight[T: CanMove](pressed: Boolean, movable: T) + extends MovementEvent + final case class MovedDown[T: CanMove](pressed: Boolean, movable: T) + extends MovementEvent + + sealed trait PlayerMovementEvent extends MovementEvent + object PlayerMovementEvent { + final case class PlayerMovedLeft(pressed: Boolean) + extends PlayerMovementEvent + final case class PlayerMovedRight(pressed: Boolean) + extends PlayerMovementEvent + final case class PlayerMovedForward(pressed: Boolean) + extends PlayerMovementEvent + final case class PlayerMovedBackward(pressed: Boolean) + extends PlayerMovementEvent + final case object PlayerJumped extends PlayerMovementEvent + final case object PlayerRotatedRight extends PlayerMovementEvent + final case object PlayerRotatedLeft extends PlayerMovementEvent + final case object PlayerCameraUp extends PlayerMovementEvent + final case object PlayerCameraDown extends PlayerMovementEvent + } +} diff --git a/src/main/scala/wow/doge/mygame/subsystems/moddingsystem/ModdingSystem.scala b/src/main/scala/wow/doge/mygame/subsystems/moddingsystem/ModdingSystem.scala index 1b26c24..983477a 100644 --- a/src/main/scala/wow/doge/mygame/subsystems/moddingsystem/ModdingSystem.scala +++ b/src/main/scala/wow/doge/mygame/subsystems/moddingsystem/ModdingSystem.scala @@ -15,19 +15,19 @@ import java.nio.file.NoSuchFileException import io.circe.generic.JsonCodec @JsonCodec -case class Test1(hello1: String, hello2: String) +final case class Test1(hello1: String, hello2: String) @JsonCodec -case class Test2(hello1: String) -case class Plugin(name: String, priority: Int) +final case class Test2(hello1: String) +final case class Plugin(name: String, priority: Int) object Plugin { implicit val pluginFormat: Decoder[Plugin] = deriveDecoder } object ModdingSystem { sealed trait Error extends Serializable with Product - case class CouldNotDecode(cause: String) extends Error - case class ParseFailure(cause: String) extends Error - case class FileNotFound(name: String) extends Error + final case class CouldNotDecode(cause: String) extends Error + final case class ParseFailure(cause: String) extends Error + final case class FileNotFound(fileName: String) extends Error case object GenericError extends Error def readPluginsList(dir: os.Path): Try[Either[Error, ArraySeq[Plugin]]] = @@ -119,7 +119,7 @@ object ModdingSystem { (readFailures, readSuccesses) <- UIO(findAndReadPluginFiles(wd, plugins)) (parseFailures, parseSuccesses) <- UIO(parsePluginFiles(readSuccesses)) res <- UIO(mergePluginData(parseSuccesses)) - f <- UIO { + _ <- UIO { println(s"Read Successes = ${readSuccesses.to(Seq)}") println(s"Read Failures = ${readFailures.to(Seq)}") println(s"Parse Successes = ${parseSuccesses.to(Seq)}") 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 30f9f17..3d114e3 100644 --- a/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptActor.scala +++ b/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptActor.scala @@ -12,21 +12,23 @@ import javax.script.ScriptEngine import javax.script.ScriptEngineManager import groovy.util.GroovyScriptEngine import cats.implicits._ +import akka.actor.typed.LogOptions +import org.slf4j.event.Level +import com.typesafe.scalalogging.Logger +import com.softwaremill.tagging._ object ScriptActor { - type Kotlin - type MyScriptEngine[T] = ScriptEngine - type KotlinScriptEngine = MyScriptEngine[Kotlin] + trait Kotlin + type KotlinScriptEngine = ScriptEngine @@ Kotlin final case class Error(reason: String) - sealed trait Command final case class CompileAny( path: os.Path, result: ActorRef[Either[Error, Any]] ) extends Command - def defaultScalaRunner() = + lazy val defaultScalaRunner = ammonite .Main( storageBackend = new Folder( @@ -36,28 +38,34 @@ object ScriptActor { ) ) - def defaultKotlinRunner(): KotlinScriptEngine = { + lazy val defaultKotlinRunner: KotlinScriptEngine = { val manager = new ScriptEngineManager() val engine = manager.getEngineByExtension("main.kts") - engine + engine.taggedWith[Kotlin] } - def defaultGroovyRunner(): GroovyScriptEngine = - new GroovyScriptEngine(os.pwd.toString()) + lazy val defaultGroovyRunner: GroovyScriptEngine = + new GroovyScriptEngine(os.pwd.toString) def apply( - scalaRunner: Main = defaultScalaRunner(), - kotlinRunner: KotlinScriptEngine = defaultKotlinRunner(), - groovyRunner: GroovyScriptEngine = defaultGroovyRunner() - // parent: ActorRef[ScriptStoringActor.Command] + scalaRunner: Main = defaultScalaRunner, + kotlinRunner: KotlinScriptEngine = defaultKotlinRunner, + groovyRunner: GroovyScriptEngine = defaultGroovyRunner ): Behavior[ScriptActor.Command] = - Behaviors.setup(ctx => - new ScriptActor( - scalaRunner, - kotlinRunner, - groovyRunner, - ctx - ).receiveMessage + Behaviors.logMessages( + LogOptions() + .withLevel(Level.TRACE) + .withLogger( + Logger[ScriptActor].underlying + ), + Behaviors.setup(ctx => + new ScriptActor( + scalaRunner, + kotlinRunner, + groovyRunner, + ctx + ).receiveMessage + ) ) sealed trait ScriptType 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 f884c0e..6205320 100644 --- a/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptCachingActor.scala +++ b/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptCachingActor.scala @@ -11,6 +11,9 @@ import akka.util.Timeout import scala.util.Success import scala.util.Failure import akka.actor.typed.SupervisorStrategy +import akka.actor.typed.LogOptions +import org.slf4j.event.Level +import com.typesafe.scalalogging.Logger object ScriptCachingActor { @@ -22,9 +25,16 @@ object ScriptCachingActor { type ScriptResult = Either[ScriptActor.Error, ScriptObject] sealed trait Command + + /** + * @param scriptPath path of the script to compile + * @param requester return address of the asking actor + * @param force if true, forces script compilation even if a previous cached version exists + */ final case class Get( scriptPath: os.Path, - requester: ActorRef[ScriptResult] + requester: ActorRef[ScriptResult], + force: Boolean = false ) extends Command final case class GetMap(requester: ActorRef[ScriptsMap]) extends Command final case class Put(scriptPath: os.Path, script: ScriptObject) @@ -37,37 +47,58 @@ object ScriptCachingActor { requester: ActorRef[ScriptResult] ) extends Command - final case class Props( - ctx: ActorContext[Command], - scriptActor: ActorRef[ScriptActor.Command] - ) + // final case class Props( + // ctx: ActorContext[Command], + // scriptActor: ActorRef[ScriptActor.Command] + // ) { + // def create(state: State = State(Map.empty)): Behavior[Command] = + // Behaviors.logMessages { + // Behaviors.setup { ctx => + // val pool = ScriptActorPool(4) + // val scriptsRouter = ctx.spawn(pool, "script-actors-pool") + // new ScriptCachingActor(this) + // .receiveMessage(state) + // } + // } + // } final case class State(scriptsMap: ScriptsMap) def apply(state: State = State(Map.empty)): Behavior[Command] = - Behaviors.logMessages { + Behaviors.logMessages( + LogOptions() + .withLevel(Level.TRACE) + .withLogger( + Logger[ScriptCachingActor].underlying + ), Behaviors.setup { ctx => val pool = ScriptActorPool(4) val scriptsRouter = ctx.spawn(pool, "script-actors-pool") - new ScriptCachingActor(Props(ctx, scriptsRouter)).receiveMessage(state) + new ScriptCachingActor(ctx, scriptsRouter).receiveMessage(state) } - } + ) } -class ScriptCachingActor(props: ScriptCachingActor.Props) { +class ScriptCachingActor( + ctx: ActorContext[ScriptCachingActor.Command], + scriptActor: ActorRef[ScriptActor.Command] +) { import com.softwaremill.quicklens._ import ScriptCachingActor._ import Methods._ def receiveMessage(state: State): Behavior[Command] = Behaviors.receiveMessage { msg => msg match { - case Get(scriptPath, requester) => - getOrCompileScript( - props.ctx, - scriptPath, - state.scriptsMap, - props.scriptActor, - requester - ) + case Get(scriptPath, requester, force) => + if (force) + ctx.self ! DelegateToChild(scriptActor, scriptPath, requester) + else + getOrCompileScript( + ctx, + scriptPath, + state.scriptsMap, + scriptActor, + requester + ) Behaviors.same case DelegateToChild(scriptActor, scriptPath, requester) => @@ -75,7 +106,7 @@ class ScriptCachingActor(props: ScriptCachingActor.Props) { implicit val timeout = Timeout(15.seconds) // child ! ScriptActor.CompileAny(scriptPath, requester) askChildForScriptCompilation( - props.ctx, + ctx, scriptActor, scriptPath, requester @@ -87,10 +118,10 @@ class ScriptCachingActor(props: ScriptCachingActor.Props) { Behaviors.same case Put(scriptPath, script) => - props.ctx.log.debug(s"Putting $script at path $scriptPath") + ctx.log.debug(s"Putting $script at path $scriptPath") val newState = state.modify(_.scriptsMap).using(_ + (scriptPath -> script)) - props.ctx.log.trace(newState.toString()) + ctx.log.trace(newState.toString()) receiveMessage(state = newState) case NoOp => Behaviors.same diff --git a/src/main/scala/wow/doge/mygame/utils/AkkaUtils.scala b/src/main/scala/wow/doge/mygame/utils/AkkaUtils.scala new file mode 100644 index 0000000..df41940 --- /dev/null +++ b/src/main/scala/wow/doge/mygame/utils/AkkaUtils.scala @@ -0,0 +1,25 @@ +package wow.doge.mygame.utils + +import akka.actor.typed.Props +import akka.util.Timeout +import akka.actor.typed.Scheduler +import akka.actor.typed.ActorRef +import akka.actor.typed.SpawnProtocol +import akka.actor.typed.Behavior +import wow.doge.mygame.implicits._ + +object AkkaUtils { + def spawnActorL[T]( + spawnProtocol: ActorRef[SpawnProtocol.Command], + actorName: String, + behavior: Behavior[T] + )(implicit timeout: Timeout, scheduler: Scheduler) = + spawnProtocol.askL[ActorRef[T]]( + SpawnProtocol.Spawn( + behavior, + actorName, + Props.empty, + _ + ) + ) +} diff --git a/src/main/scala/wow/doge/mygame/utils/IOUtils.scala b/src/main/scala/wow/doge/mygame/utils/IOUtils.scala new file mode 100644 index 0000000..e4c8b67 --- /dev/null +++ b/src/main/scala/wow/doge/mygame/utils/IOUtils.scala @@ -0,0 +1,12 @@ +package wow.doge.mygame.utils + +import monix.bio.IO + +object IOUtils { + def toTask[T](bio: monix.bio.IO[Throwable, T]) = + monix.eval.Task.deferAction(implicit s => bio.to[monix.eval.Task]) + + def toIO[T](task: monix.eval.Task[T]) = + IO.deferAction(implicit s => IO.from(task)) + +} diff --git a/src/main/scala/wow/doge/mygame/utils/Settings.scala b/src/main/scala/wow/doge/mygame/utils/Settings.scala new file mode 100644 index 0000000..00bfadc --- /dev/null +++ b/src/main/scala/wow/doge/mygame/utils/Settings.scala @@ -0,0 +1,14 @@ +package wow.doge.mygame.utils + +case class Display( + width: Int = 640, + height: Int = 480, + title: String = "JME-Game", + fullScren: Boolean = false, + vsync: Boolean = false, + frameRate: Int = -1 +) +object Display { + val default = Display() +} +case class GlobalSettings(display: Display = Display.default)