diff --git a/build.sbt b/build.sbt index ad79381..6e517fa 100644 --- a/build.sbt +++ b/build.sbt @@ -19,13 +19,6 @@ lazy val javaFXModules = Seq("base", "controls", "fxml", "graphics", "media", "swing", "web") lazy val root = (project in file(".")).settings( - inThisBuild( - List( - scalaVersion := scalaVersion.value, // 2.11.12, or 2.13.3 - semanticdbEnabled := true, // enable SemanticDB - semanticdbVersion := "4.3.24" // use Scalafix compatible version - ) - ), name := "mygame", organization := "wow.doge", version := "1.0-SNAPSHOT", @@ -71,7 +64,9 @@ lazy val root = (project in file(".")).settings( "org.recast4j" % "recast" % "1.2.5", "org.recast4j" % "detour" % "1.2.5", "com.lihaoyi" %% "pprint" % "0.6.0", - "org.scalatest" %% "scalatest" % "3.2.2" % "test" + "org.scalatest" %% "scalatest" % "3.2.2" % "test", + "org.typelevel" %% "cats-mtl" % "1.1.1", + "io.estatico" %% "newtype" % "0.4.4" ), // Determine OS version of JavaFX binaries @@ -135,4 +130,11 @@ addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1") addCompilerPlugin( "org.typelevel" %% "kind-projector" % "0.11.1" cross CrossVersion.full ) +inThisBuild( + List( + scalaVersion := scalaVersion.value, // 2.11.12, or 2.13.3 + semanticdbEnabled := true, // enable SemanticDB + semanticdbVersion := "4.3.24" // use Scalafix compatible version + ) +) ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.4.3" diff --git a/project/plugins.sbt b/project/plugins.sbt index fac4060..eb7d2e4 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,3 +1,2 @@ addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.0") addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.23") -addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1") diff --git a/src/main/scala/wow/doge/mygame/AppError.scala b/src/main/scala/wow/doge/mygame/AppError.scala index 90b49b9..0abc428 100644 --- a/src/main/scala/wow/doge/mygame/AppError.scala +++ b/src/main/scala/wow/doge/mygame/AppError.scala @@ -2,7 +2,8 @@ package wow.doge.mygame import java.util.concurrent.TimeoutException -import cats.data.Reader +import cats.Show +import cats.kernel.Eq import monix.bio.IO import wow.doge.mygame.utils.wrappers.jme.AssetManager import wow.doge.mygame.utils.wrappers.jme.CollisionShapeFactory @@ -12,21 +13,10 @@ sealed trait AppError object AppError { final case class TimeoutError(reason: String) extends AppError object TimeoutError { - def reader = - Reader[Throwable, TimeoutError] { - case ex: TimeoutException => TimeoutError(ex.getMessage) - } def from: PartialFunction[Throwable, IO[TimeoutError, Nothing]] = { case ex: TimeoutException => IO.raiseError(TimeoutError(ex.getMessage)) } } - final case class DummyError(reason: String) extends AppError - object DummyError { - def reader = - Reader[Throwable, DummyError] { - case ex: NullPointerException => DummyError(ex.getMessage) - } - } final case class AssetManagerError(error: AssetManager.Error) extends AppError final case class AppNodeError(error: NodeWrapper2.Error) extends AppError final case class CollisionShapeCreationFailed( @@ -37,4 +27,7 @@ object AppError { case ex: TimeoutException => IO.raiseError(TimeoutError(ex.getMessage)) } + + implicit val show = Show.fromToString[AppError] + implicit val eq = Eq.fromUniversalEquals[AppError] } diff --git a/src/main/scala/wow/doge/mygame/MainApp.scala b/src/main/scala/wow/doge/mygame/MainApp.scala index cebfe23..b75f720 100644 --- a/src/main/scala/wow/doge/mygame/MainApp.scala +++ b/src/main/scala/wow/doge/mygame/MainApp.scala @@ -1,7 +1,5 @@ package wow.doge.mygame -import java.util.concurrent.TimeoutException - import scala.concurrent.duration._ import akka.actor.typed.ActorRef @@ -19,10 +17,8 @@ import com.jme3.material.MaterialDef import com.jme3.math.ColorRGBA import com.jme3.math.FastMath import com.jme3.renderer.Camera -import com.jme3.renderer.RenderManager import com.jme3.renderer.ViewPort import com.jme3.scene.Node -import com.jme3.scene.control.AbstractControl import com.softwaremill.macwire._ import com.softwaremill.tagging._ import io.odin.Logger @@ -33,38 +29,44 @@ import monix.bio.UIO import monix.eval.Coeval import monix.reactive.Observable import scalafx.scene.control.TextArea +import wow.doge.mygame.AppError.TimeoutError import wow.doge.mygame.executors.Schedulers import wow.doge.mygame.game.GameApp import wow.doge.mygame.game.GameAppActor import wow.doge.mygame.game.GameAppResource -import wow.doge.mygame.game.GameAppTags import wow.doge.mygame.game.entities.EntityIds import wow.doge.mygame.game.entities.NpcActorSupervisor import wow.doge.mygame.game.entities.NpcMovementActor import wow.doge.mygame.game.entities.PlayerActorSupervisor import wow.doge.mygame.game.entities.PlayerController -import wow.doge.mygame.game.entities.PlayerControllerTags 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.launcher.Launcher import wow.doge.mygame.launcher.Launcher.LauncherResult import wow.doge.mygame.math.ImVector3f +import wow.doge.mygame.subsystems.events.Event +import wow.doge.mygame.subsystems.events.EventBus import wow.doge.mygame.subsystems.events.EventBus.ObservableSubscription import wow.doge.mygame.subsystems.events.EventsModule import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus import wow.doge.mygame.subsystems.events.PlayerEvent import wow.doge.mygame.subsystems.events.PlayerMovementEvent +import wow.doge.mygame.subsystems.events.StatsEvent.DamageEvent import wow.doge.mygame.subsystems.events.TickEvent import wow.doge.mygame.subsystems.scriptsystem.ScriptInitMode import wow.doge.mygame.subsystems.scriptsystem.ScriptSystemResource import wow.doge.mygame.utils.AkkaUtils import wow.doge.mygame.utils.GenericConsoleStream import wow.doge.mygame.utils.IOUtils -import wow.doge.mygame.utils.wrappers.jme.AppNode2 import wow.doge.mygame.utils.wrappers.jme.AssetManager import wow.doge.mygame.utils.wrappers.jme.PhysicsSpace - +import wow.doge.mygame.subsystems.events.PlayerCameraEvent +import com.jme3.math.Quaternion +import com.jme3.math.Vector3f +import wow.doge.mygame.types._ +import wow.doge.mygame.game.controls.FollowControl +import wow.doge.mygame.game.subsystems.input.PlayerCameraInput class MainApp( logger: Logger[Task], jmeThread: monix.execution.Scheduler, @@ -94,13 +96,8 @@ class MainApp( tickEventBus <- eventsModule.tickEventBus obs <- playerEventBus - .askL[Observable[PlayerMovementEvent]]( - ObservableSubscription(_) - ) - .onErrorHandleWith { - case ex: TimeoutException => - IO.raiseError(AppError.TimeoutError(ex.getMessage())) - } + .askL[Observable[PlayerMovementEvent]](ObservableSubscription(_)) + .onErrorHandleWith(TimeoutError.from) _ <- IOUtils .toIO( @@ -121,7 +118,7 @@ class MainApp( // jfxUI <- gameApp.jfxUI gameAppActor <- gameApp.spawnGameActor( GameAppActor.Props(tickEventBus).behavior, - "gameAppActor" + Some("gameAppActor") ) _ <- gameAppActor !! GameAppActor.Start consoleTextArea <- UIO(new TextArea { @@ -138,7 +135,7 @@ class MainApp( _ <- wire[MainAppDelegate] .init() - .executeOn(gameApp.scheduler) + .executeOn(gameApp.scheduler.value) } yield fib def gameInit: Resource[UIO, Either[AppError, Fiber[Nothing, Unit]]] = @@ -184,6 +181,7 @@ class MainApp( class MainAppDelegate( gameApp: GameApp, loggerL: Logger[Task], + mainEventBus: GameEventBus[Event], playerEventBus: GameEventBus[PlayerEvent], tickEventBus: GameEventBus[TickEvent], inputManager: InputManager, @@ -192,7 +190,8 @@ class MainAppDelegate( camera: Camera, viewPort: ViewPort, enqueueR: Function1[() => Unit, Unit], - rootNode: AppNode2 @@ GameAppTags.RootNode + rootNode: RootNode, + schedulers: Schedulers )(implicit spawnProtocol: ActorRef[SpawnProtocol.Command], timeout: Timeout, @@ -213,17 +212,104 @@ class MainAppDelegate( // _ <- Task(consoleStream.println("text")) level <- DefaultGameLevel(assetManager, viewPort) _ <- level.addToGame(rootNode, physicsSpace) - _ <- createPlayerController() + playerActor <- createPlayerController() // .onErrorRestart(3) _ <- wire[GameInputHandler.Props].begin // .onErrorRestart(3) johnActor <- createTestNpc("John") // _ <- johnActor !! NpcActorSupervisor.Move(ImVector3f(0, 0, 20)) + // _ <- + // johnActor + // .tellL(NpcActorSupervisor.Move(ImVector3f(-30, 0, 10))) + // .delayExecution(2.seconds) + _ <- + rootNode.depthFirstTraversal + .doOnNextF(spat => loggerL.debug(spat.getName).toTask) + .completedL + .toIO + .hideErrors + damageObs <- + mainEventBus + .askL[Observable[DamageEvent]](ObservableSubscription(_)) + .onErrorHandleWith(TimeoutError.from) + _ <- + damageObs + .doOnNextF(event => + (loggerL.debug(s"Received Damage Event $event") >> + IO( + playerActor ! PlayerActorSupervisor.TakeDamage(event.amount) + )).toTask + ) + .completedL + .toIO + .hideErrors + .startAndForget + _ <- + Observable + .interval(1.second) + .doOnNextF(_ => + playerActor + .askL(PlayerActorSupervisor.GetStatus) + .flatMap(s => + loggerL.debug(s"Player actor status: $s") >> UIO.pure(s) + ) + .void + // .flatMap(s => + // if (s == Status.Alive) + // playerActor + // .askL(PlayerActorSupervisor.CurrentStats ) + // .flatMap(s => loggerL.debug(s"Got state $s")) + // else IO.unit + // ) + .toTask + ) + // .doOnNextF(_ => + // playerActor + // .askL(PlayerActorSupervisor.GetStatus ) + // .flatMap(s => loggerL.debug(s"Player actor status: $s")) + // .toTask + // ) + .completedL + .toIO + .hideErrors + .startAndForget _ <- - johnActor - .tellL(NpcActorSupervisor.Move(ImVector3f(-30, 0, 10))) - .delayExecution(2.seconds) + physicsSpace.collisionObservable + .filter(event => + (for { + nodeA <- event.nodeA + nodeB <- event.nodeB + } yield nodeA.getName === "PlayerNode" && nodeB.getName === "John" || + nodeB.getName === "PlayerNode" && nodeA.getName === "John") + .getOrElse(false) + ) + .doOnNextF(event => + loggerL + .debug(s"$event ${event.appliedImpulse()}") + .toTask + ) + .doOnNextF(event => + (for { + victim <- Coeval(for { + nodeA <- event.nodeA + nodeB <- event.nodeB + } yield if (nodeB.getName === "John") nodeA else nodeB) + _ <- Coeval( + victim.foreach { v => + pprint.log(s"emitted event ${v.getName}") + mainEventBus ! EventBus.Publish( + DamageEvent("John", v.getName, 10), + "damageHandler" + ) + } + ) + } yield ()).void + ) + .completedL + .toIO + .hideErrors + .startAndForget // _ <- // IOUtils // .toIO( @@ -238,12 +324,12 @@ class MainAppDelegate( def createPlayerController( // appScheduler: monix.execution.Scheduler - ): IO[AppError, ActorRef[PlayerActorSupervisor.Command]] = { + ): IO[AppError, PlayerActorSupervisor.Ref] = { val playerPos = ImVector3f.Zero val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o" val playerPhysicsControl = PlayerController.Defaults.defaultPlayerPhysicsControl - .taggedWith[PlayerControllerTags.PlayerTag] + for { playerModel <- assetManager @@ -260,42 +346,115 @@ class MainAppDelegate( ) cameraPivotNode <- UIO( new Node(EntityIds.CameraPivot.value) - .withControl(new FollowControl(playerNode)) - .taggedWith[PlayerControllerTags.PlayerCameraPivotNode] + .withControl( + new FollowControl(playerNode) + ) + .taggedWith[PlayerController.Tags.PlayerCameraPivotNode] ) camNode <- UIO( PlayerController.Defaults .defaultCamerNode(camera, playerPos) - .taggedWith[PlayerControllerTags.PlayerCameraNode] + .taggedWith[PlayerController.Tags.PlayerCameraNode] ) + playerCameraEvents <- + playerEventBus + .askL[Observable[PlayerCameraEvent]](ObservableSubscription(_)) + .onErrorHandleWith(TimeoutError.from) + _ <- + inputManager + .enumAnalogObservable(PlayerCameraInput) + .sample(1.millis) + .scan(new Quaternion) { + case (rotationBuf, action) => + action.binding match { + // case PlayerCameraEvent.CameraLeft => + case PlayerCameraInput.CameraRotateLeft => + // me.Task { + val rot = rotationBuf + .fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y) + cameraPivotNode.rotate(rot) + rotationBuf + // } + // case PlayerCameraEvent.CameraRight => + case PlayerCameraInput.CameraRotateRight => + // me.Task { + val rot = rotationBuf + .fromAngleAxis(-1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y) + cameraPivotNode.rotate(rot) + rotationBuf + // } + // case PlayerCameraEvent.CameraMovedUp => + case PlayerCameraInput.CameraRotateUp => + // me.Task { + val rot = rotationBuf + .fromAngleAxis(-1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X) + cameraPivotNode.rotate(rot) + rotationBuf + // } + // case PlayerCameraEvent.CameraMovedDown => + case PlayerCameraInput.CameraRotateDown => + // me.Task { + val rot = rotationBuf + .fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X) + cameraPivotNode.rotate(rot) + rotationBuf + // } + } + } + .completedL + .toIO + .hideErrors + .startAndForget + _ <- + Observable + .interval(10.millis) + .doOnNextF(_ => + Coeval { + val location = playerNode.getWorldTranslation() + cameraPivotNode.setLocalTranslation(location) + } + ) + .completedL + .toIO + .hideErrors + .startAndForget + sched <- UIO.pure(schedulers.async) playerActor <- wire[PlayerController.Props].create + obs <- + playerActor + .askL(PlayerActorSupervisor.GetStatsObservable) + .onErrorHandleWith(TimeoutError.from) + _ <- + obs + .doOnNext(s => loggerL.debug(s"Got state $s").toTask) + .completedL + .toIO + .hideErrors + .startAndForget } yield playerActor } def createTestNpc( // appScheduler: monix.execution.Scheduler, npcName: String - ): IO[AppError, ActorRef[NpcActorSupervisor.Command]] = { + ): IO[AppError, NpcActorSupervisor.Ref] = { val initialPos = ImVector3f(50, 5, 0) val npcPhysicsControl = new BetterCharacterControl(1.5f, 6f, 1f) // (1f, 2.1f, 10f) .withJumpForce(ImVector3f(0, 5f, 0)) val npcActorTask = AkkaUtils.spawnActorL( - NpcActorSupervisor - .Props( - new NpcMovementActor.Props( - enqueueR, - initialPos, - // tickEventBus, - npcName, - npcPhysicsControl - ).behavior, + new NpcActorSupervisor.Props( + new NpcMovementActor.Props( + enqueueR, + initialPos, npcName, - initialPos - ) - .behavior, - s"${npcName}-npcActorSupervisor" + npcPhysicsControl + ).behavior, + npcName, + initialPos + ).behavior, + actorName = Some(s"${npcName}-npcActorSupervisor") ) (for { @@ -325,12 +484,3 @@ class MainAppDelegate( } } - -class FollowControl(playerNode: Node) extends AbstractControl { - override def controlUpdate(tpf: Float): Unit = - this.spatial.setLocalTranslation(playerNode.getLocalTranslation()) - override def controlRender( - rm: RenderManager, - vp: ViewPort - ): Unit = {} -} diff --git a/src/main/scala/wow/doge/mygame/game/GameApp.scala b/src/main/scala/wow/doge/mygame/game/GameApp.scala index cef597a..8383073 100644 --- a/src/main/scala/wow/doge/mygame/game/GameApp.scala +++ b/src/main/scala/wow/doge/mygame/game/GameApp.scala @@ -38,10 +38,9 @@ import wow.doge.mygame.implicits._ import wow.doge.mygame.utils.AkkaUtils import wow.doge.mygame.utils.GenericTimerActor import wow.doge.mygame.utils.wrappers.jme._ - +import wow.doge.mygame.types._ object GameAppTags { sealed trait RootNode - sealed trait GuiNode } @@ -49,34 +48,39 @@ class GameApp private[game] ( logger: Logger[Task], app: SimpleAppExt, gameActor: ActorRef[TestGameActor.Command], - gameSpawnProtocol: ActorRef[SpawnProtocol.Command] + gameSpawnProtocol: ActorRef[SpawnProtocol.Command], + scheduler: akka.actor.typed.Scheduler ) { def inputManager: UIO[InputManager] = UIO(app.getInputManager()) - def assetManager = new AssetManager(app.getAssetManager()) - def guiNode = UIO(app.getGuiNode().taggedWith[GameAppTags.GuiNode]) - val guiNode2 = AppNode2(app.getGuiNode()).taggedWith[GameAppTags.GuiNode] - def flyCam = Option(app.getFlyByCamera()) + val assetManager = new AssetManager(app.getAssetManager()) + val guiNode: GuiNode = + AppNode2(app.getGuiNode()).taggedWith[GameAppTags.GuiNode] + // def flyCam = Option(app.getFlyByCamera()) def camera = UIO(app.getCamera()) def viewPort = UIO(app.getViewPort()) - // def rootNode = UIO(app.getRootNode().taggedWith[GameAppTags.RootNode]) - val rootNode = + val rootNode: RootNode = AppNode2(app.getRootNode()).taggedWith[GameAppTags.RootNode] - val physicsSpace = new PhysicsSpace(app.bulletAppState.physicsSpace) + val physicsSpace = + new PhysicsSpace(app.bulletAppState.physicsSpace) def enqueue(cb: () => Unit) = app.enqueueR(() => cb()) def enqueueL[T](cb: () => T): Task[T] = app.enqueueL(cb) + def whenTerminated: IO[AppError, Unit] = + IO.deferFuture(app.whenTerminated).onErrorHandleWith(TimeoutError.from) + def spawnGameActor[T]( behavior: Behavior[T], - actorName: String, + actorName: Option[String] = None, props: Props = Dispatchers.jmeDispatcher - )(implicit scheduler: akka.actor.typed.Scheduler) = + )(implicit name: sourcecode.Name) = AkkaUtils.spawnActorL(behavior, actorName, props)( 2.seconds, scheduler, - gameSpawnProtocol + gameSpawnProtocol, + name ) - def scheduler = app.scheduler + def scheduler = JmeScheduler(app.scheduler) def jfxUI = JFxUI(app) } @@ -92,10 +96,9 @@ class GameAppResource( ) { def resource : Resource[UIO, Either[AppError, (GameApp, Fiber[Nothing, Unit])]] = - Resource.make { - lazy val bullet = new BulletAppState + Resource.make( (for { - app <- UIO(new SimpleAppExt(schedulers, bullet)) + app <- UIO(new SimpleAppExt(schedulers, new BulletAppState)) _ <- UIO(JMERunner.runner = Some(app.enqueue _)) _ <- UIO { val settings = new AppSettings(true) @@ -113,23 +116,22 @@ class GameAppResource( _ <- Task.deferFuture(app.started).onErrorHandleWith(TimeoutError.from) testGameActor <- AkkaUtils.spawnActorL( new TestGameActor.Props().create, - "testGameActor", - Dispatchers.jmeDispatcher + Some("testGameActor"), + props = Dispatchers.jmeDispatcher ) sp <- testGameActor - .askL(TestGameActor.GetSpawnProtocol(_)) + .askL(TestGameActor.GetSpawnProtocol) .onErrorHandleWith(TimeoutError.from) - gameApp <- UIO(new GameApp(logger, app, testGameActor, sp)) + gameApp <- UIO(new GameApp(logger, app, testGameActor, sp, scheduler)) _ <- UIO { val fut = () => testGameActor.ask(TestGameActor.Stop).flatten app.cancelToken = Some(fut) } } yield (gameApp, fib)).attempt - } { - case Right(gameApp -> fib) => - fib.cancel >> UIO(JMERunner.runner = None) - case Left(error) => IO.terminate(new Exception(error.toString)) + ) { + case Right(gameApp -> fib) => fib.cancel + case Left(error) => IO.terminate(new Exception(error.toString)) } } @@ -213,10 +215,10 @@ object Ops { object SpawnSystem { sealed trait Result - final case object Ok extends Result + case object Ok extends Result sealed trait Complete - final case object Complete extends Complete + case object Complete extends Complete sealed trait SpawnRequest final case class SpawnSpatial(nodeTask: Task[Node]) extends SpawnRequest diff --git a/src/main/scala/wow/doge/mygame/game/GameAppActor.scala b/src/main/scala/wow/doge/mygame/game/GameAppActor.scala index c33f04e..3c873ce 100644 --- a/src/main/scala/wow/doge/mygame/game/GameAppActor.scala +++ b/src/main/scala/wow/doge/mygame/game/GameAppActor.scala @@ -24,18 +24,18 @@ object GameAppActor { Behaviors.setup[Command] { ctx => ctx.log.infoP("Hello from GameAppActor") val renderTickGenerator = - ctx.spawn( + ctx.spawnN( Behaviors .supervise(renderTickGeneratorBehavior) - .onFailure[Exception](SupervisorStrategy.restart), - "tickGeneratorActor" + .onFailure[Exception]( + SupervisorStrategy.restart.withLimit(2, 100.millis) + ) ) - val tickGeneratorTimer = ctx.spawn( + val tickGeneratorTimer = ctx.spawnN( GenericTimerActor .Props(renderTickGenerator, TickGenerator.Send, 10.millis) - .behavior, - "tickGeneratorTimer" + .behavior ) Behaviors.receiveMessage { diff --git a/src/main/scala/wow/doge/mygame/game/SimpleAppExt.scala b/src/main/scala/wow/doge/mygame/game/SimpleAppExt.scala index a15265c..7193802 100644 --- a/src/main/scala/wow/doge/mygame/game/SimpleAppExt.scala +++ b/src/main/scala/wow/doge/mygame/game/SimpleAppExt.scala @@ -14,7 +14,6 @@ import monix.execution.Scheduler import monix.execution.atomic.Atomic import wow.doge.mygame.executors.GUIExecutorService import wow.doge.mygame.executors.Schedulers -// import wow.doge.mygame.implicits._ class SimpleAppExt( schedulers: Schedulers, val bulletAppState: BulletAppState, @@ -28,11 +27,16 @@ class SimpleAppExt( private val taskQueue2 = Atomic(Queue.empty[MyTask[_]]) private val startSignal: CancelablePromise[Unit] = CancelablePromise() + private val terminationSignal: CancelablePromise[Unit] = CancelablePromise() var cancelToken: Option[() => Future[Unit]] = None def started: CancelableFuture[Unit] = startSignal.future + def whenTerminated: CancelableFuture[Unit] = terminationSignal.future + + // override def physicsSpace: PhysicsSpace = ??? + override def simpleInitApp(): Unit = { stateManager.attach(bulletAppState) startSignal.success(()) @@ -40,18 +44,19 @@ class SimpleAppExt( override def simpleUpdate(tpf: Float): Unit = {} - override def stop(waitFor: Boolean): Unit = { + override def stop(waitFor: Boolean): Unit = cancelToken match { case Some(value) => value().foreach { _ => pprint.log("Called cancel in simpleapp") super.stop(true) + terminationSignal.success(()) } case None => pprint.log("Called cancel in simpleapp") super.stop(true) + terminationSignal.success(()) } - } def enqueueFuture[T](cb: () => T): CancelableFuture[T] = { val p = CancelablePromise[T]() diff --git a/src/main/scala/wow/doge/mygame/game/appstates/PlayerMovementState.scala b/src/main/scala/wow/doge/mygame/game/appstates/PlayerMovementState.scala deleted file mode 100644 index 8a68fdb..0000000 --- a/src/main/scala/wow/doge/mygame/game/appstates/PlayerMovementState.scala +++ /dev/null @@ -1,294 +0,0 @@ -package wow.doge.mygame.state - -import scala.concurrent.duration.DurationInt - -import akka.actor.typed.ActorRef -import akka.actor.typed.Behavior -import akka.actor.typed.scaladsl.ActorContext -import akka.actor.typed.scaladsl.Behaviors -import akka.actor.typed.scaladsl.TimerScheduler -import com.jme3.input.InputManager -import com.jme3.input.KeyInput -import com.jme3.input.controls.KeyTrigger -import com.jme3.math.Vector3f -import com.jme3.scene.Geometry -import wow.doge.mygame.implicits._ -import wow.doge.mygame.subsystems.movement.ImMovementActor - -class PlayerMovementState( - // movementActor: ActorRef[MovementActor.Command], - // movementActorTimer: ActorRef[MovementActorTimer.Command], - imMovementActor: ActorRef[ImMovementActor.Command] - // geom: Geometry, - // camNode: CameraNode, - // playerNode: Node - // gameAppActor: ActorRef[GameAppActor.Command] -) extends MyBaseState - // with ActionListener - { - - protected lazy val mat = MyMaterial( - assetManager = assetManager, - path = os.rel / "Common" / "MatDefs" / "Misc" / "Unshaded.j3md" - ) - - override protected def init(): Unit = { - - // setupKeys(inputManager) - // println("playermovementstate " + Thread.currentThread().getName()) - - // geom.setMaterial(mat) - - // camNode.setControlDir(ControlDirection.SpatialToCamera) - // // lazy val camNode = new CameraNode("CameraNode", simpleApp.getCamera()) - // // camNode.setCamera(simpleApp.getCamera()) - // discard { - // playerNode - // .child(camNode) - // .child(geom) - // // playerNode.children(Seq(camNode, geom)) - // } - // discard { rootNode.withChild(playerNode) } - // camNode.setLocalTranslation( - // new Vector3f(0, 1.5f, 10) - // ) - // camNode.lookAt(playerNode.getLocalTranslation(), Vector3f.UNIT_Y) - - // movementActorTimer ! MovementActorTimer.Start(geom, cam) - // movementActorTimer ! MovementActorTimer.Start - } - - override def update(tpf: Float) = { - // movementActor ! MovementActor.Tick(tpf, geom, cam) - // imMovementActor ! ImMovementActor.Tick(tpf) - - // movementActorTimer ! MovementActorTimer.Update(tpf) - } - - override def stop(): Unit = {} - // override protected def cleanup(app: Application): Unit = { - // // gameAppActor ! GameAppActor.Stop - // super.cleanup(app) - // } - - 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( - "Space", - new KeyTrigger(KeyInput.KEY_SPACE), - new KeyTrigger(KeyInput.KEY_H) - ) - .withMapping( - "Reset", - 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") - } - - // 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 = {} - - override protected def onDisable(): Unit = {} - -} - -final case class CardinalDirection( - left: Boolean = false, - right: Boolean = false, - up: Boolean = false, - down: Boolean = false -) - -object MovementActor { - sealed trait Command - // final case class Tick(tpf: Float, geom: Geometry, cam: Camera) extends Command - // final case class Tick(tpf: Float) extends Command - final case object Tick 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(app: com.jme3.app.Application, geom: Geometry) - - /** - * Internal state of the actor - * - * @param cardinalDir Immutable, can be shared as is - * @param walkDirection scratch space to avoid allocations on every tick. Do not share outside the actor - */ - final case class State( - cardinalDir: CardinalDirection = CardinalDirection(), - walkDirection: Vector3f = Vector3f.UNIT_X - ) - - def apply(props: Props): Behavior[Command] = - Behaviors.setup(ctx => new MovementActor(ctx, props).receive(State())) - -} -class MovementActor( - ctx: ActorContext[MovementActor.Command], - props: MovementActor.Props -) { - import MovementActor._ - import com.softwaremill.quicklens._ - def receive(state: MovementActor.State): 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 => - val camDir = - props.app.getCamera.getDirection().clone().multLocal(0.6f) - val camLeft = props.app.getCamera.getLeft().clone().multLocal(0.4f) - val walkDir = state.walkDirection.set(0, 0, 0) - // val walkDir = new Vector3f - val dir = state.cardinalDir - if (dir.up) { - ctx.log.debugP("up") - // ctx.log.debugP(Thread.currentThread().getName()) - // walkDir.addLocal(0, 0, -1) - walkDir += camDir - } - if (dir.left) { - ctx.log.debugP("left") - // walkDir.addLocal(-1, 0, 0) - walkDir.addLocal(camLeft) - } - if (dir.right) { - ctx.log.debugP("right") - // walkDir.addLocal(1, 0, 0) - walkDir.addLocal(camLeft.negateLocal()) - } - if (dir.down) { - ctx.log.debugP("down") - walkDir.addLocal(camDir.negateLocal()) - // walkDir.addLocal(0, 0, 1) - } - // (dir.up, dir.down, dir.left, dir.right) match { - // case (true, false, true, false) => - // case _ => - // } - - walkDir.multLocal(2f) - - // walkDir.multLocal(100f) - // .multLocal(tpf) - - // val v = props.geom.getLocalTranslation() - // props.geom.setLocalTranslation( - // (v += walkDir) - // ) - props.app.enqueue(new Runnable { - override def run(): Unit = { - // geom.setLocalTranslation(walkDir) - - val v = props.geom.getLocalTranslation() - props.geom.setLocalTranslation( - (v += walkDir) - ) - } - }) - Behaviors.same - // receive(state = state.modify(_.walkDirection).setTo(walkDir)) - - } - } -} - -object MovementActorTimer { - sealed trait Command - final case object Start extends Command - final case object Update extends Command - private case object Send extends Command - case object TimerKey - - final case class Props( - timers: TimerScheduler[MovementActorTimer.Command], - target: ActorRef[MovementActor.Command] - ) - final case class State() - def apply(target: ActorRef[MovementActor.Command]) = - Behaviors.withTimers[Command] { timers => - new MovementActorTimer(Props(timers, target)).idle() - } -} -class MovementActorTimer( - props: MovementActorTimer.Props -) { - import MovementActorTimer._ - // import com.softwaremill.quicklens._ - - def idle(): Behavior[Command] = - Behaviors.receiveMessage { msg => - msg match { - case Start => - props.timers.startTimerWithFixedDelay( - Send, - 10.millis - ) - active() - case _ => Behaviors.unhandled - } - } - - def active(): Behavior[Command] = - Behaviors.receiveMessage { msg => - msg match { - case Update => active() - case Send => - props.target ! MovementActor.Tick - Behaviors.same - case _ => Behaviors.unhandled - - } - } -} diff --git a/src/main/scala/wow/doge/mygame/game/controls/CameraMovementControls.scala b/src/main/scala/wow/doge/mygame/game/controls/CameraMovementControls.scala new file mode 100644 index 0000000..e562e66 --- /dev/null +++ b/src/main/scala/wow/doge/mygame/game/controls/CameraMovementControls.scala @@ -0,0 +1,71 @@ +package wow.doge.mygame.game.controls + +import com.jme3.math.Quaternion +import com.jme3.scene.Node +import com.jme3.scene.control.AbstractControl +import com.jme3.renderer.RenderManager +import com.jme3.renderer.ViewPort +import monix.reactive.Observable +import wow.doge.mygame.game.subsystems.input.PlayerCameraInput +import com.jme3.scene.Spatial +import monix.{eval => me} +import com.jme3.math.FastMath +import com.jme3.math.Vector3f +import monix.execution.Cancelable +import monix.execution.Scheduler + +class CameraMovementControl( + rotationBuf: Quaternion, + obs: Observable[PlayerCameraInput], + rotateFn: Quaternion => Unit +)(implicit s: Scheduler) + extends AbstractControl { + private var _event: PlayerCameraInput = null + private var _subscriptionToken: Cancelable = null + + override def controlUpdate(tpf: Float): Unit = + if (_event != null) + _event match { + case PlayerCameraInput.CameraRotateLeft => + val rot = rotationBuf + .fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y) + rotateFn(rot) + case PlayerCameraInput.CameraRotateRight => + val rot = rotationBuf + .fromAngleAxis(-1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y) + rotateFn(rot) + case PlayerCameraInput.CameraRotateUp => + val rot = rotationBuf + .fromAngleAxis(-1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X) + rotateFn(rot) + case PlayerCameraInput.CameraRotateDown => + val rot = rotationBuf + .fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X) + rotateFn(rot) + } + + override def controlRender( + x$1: RenderManager, + x$2: ViewPort + ): Unit = {} + override def setSpatial(spatial: Spatial): Unit = { + super.setSpatial(spatial) + if (this.spatial != null) + _subscriptionToken = + obs.doOnNext(event => me.Task { _event = event }).subscribe() + else { + _subscriptionToken.cancel() + _subscriptionToken = null + } + } + +} + +class FollowControl(playerNode: Node) extends AbstractControl { + override def controlUpdate(tpf: Float): Unit = + this.spatial.setLocalTranslation(playerNode.getLocalTranslation()) + override def controlRender( + rm: RenderManager, + vp: ViewPort + ): Unit = {} +} diff --git a/src/main/scala/wow/doge/mygame/game/entities/NpcActorSupervisor.scala b/src/main/scala/wow/doge/mygame/game/entities/NpcActorSupervisor.scala index 512fd86..1cc598f 100644 --- a/src/main/scala/wow/doge/mygame/game/entities/NpcActorSupervisor.scala +++ b/src/main/scala/wow/doge/mygame/game/entities/NpcActorSupervisor.scala @@ -30,6 +30,8 @@ import wow.doge.mygame.subsystems.movement.ImMovementActor import wow.doge.mygame.utils.GenericTimerActor object NpcActorSupervisor { + type Ref = ActorRef[Command] + sealed trait Command final case class Move(pos: ImVector3f) extends Command private final case class InternalMove( @@ -40,13 +42,13 @@ object NpcActorSupervisor { private case class LogError(err: Throwable) extends Command private case class MovementFailed(err: Throwable) extends Command - final case class Props( - npcMovementActorBehavior: Behavior[NpcMovementActor.Command], - npcName: String, - initialPos: ImVector3f + class Props( + val npcMovementActorBehavior: Behavior[NpcMovementActor.Command], + val npcName: String, + val initialPos: ImVector3f ) { def behavior = - Behaviors.withMdc(Map("actorName" -> npcName))( + Behaviors.withMdc[Command](Map("actorName" -> npcName))( Behaviors.setup[Command] { ctx => val npcMovementActor = ctx.spawn( (npcMovementActorBehavior), @@ -164,7 +166,7 @@ object NpcMovementActor { doneSignal: ActorRef[CancelableFuture[DoneMoving.type]] ) extends Command - final class Props[T: CanMove]( + class Props[T: CanMove]( val enqueueR: Function1[() => Unit, Unit], val initialPos: ImVector3f, // val tickEventBus: GameEventBus[TickEvent], @@ -196,7 +198,7 @@ class NpcMovementActor[T]( target: ImVector3f, replyTo: ActorRef[CancelableFuture[DoneMoving.type]] ) => - props.enqueueR(() => cm.move(props.movable, target - location)) + props.enqueueR(() => cm.move(props.movable, target - location, 20f)) val p = CancelablePromise[DoneMoving.type]() replyTo ! p.future ticking(p, target, state) @@ -221,22 +223,22 @@ class NpcMovementActor[T]( ctx.self ! StopMoving reachDestination.success(DoneMoving) } else { + // format:off ctx.log.traceP( - show"npcActor-${props.npcName}: Difference = " + dst.formatted( - "%.2f" - ) + show"npcActor-${props.npcName}: Difference = ${dst.formatted("%.2f")}" ) ctx.log.traceP( - show"npcActor-${props.npcName}: Current pos = " + location + show"npcActor-${props.npcName}: Current pos = $location" ) } + Behaviors.same } } object NpcMovementActorNotUsed { sealed trait Command - final case class Props( + class Props( imMovementActorBehavior: Behavior[ImMovementActor.Command], npcName: String, // movementActor: ActorRef[ImMovementActor.Command], @@ -251,7 +253,9 @@ object NpcMovementActorNotUsed { val movementActor = ctx.spawn( Behaviors .supervise(imMovementActorBehavior) - .onFailure[Exception](SupervisorStrategy.restart), + .onFailure[Exception]( + SupervisorStrategy.restart.withLimit(2, 100.millis) + ), s"npc-${npcName}-MovementActorChild" ) val npcMovementElBehavior = Behaviors.receiveMessagePartial[Event] { @@ -259,19 +263,19 @@ object NpcMovementActorNotUsed { event match { case MovedLeft(name, pressed) => if (name == npcName) - movementActor ! ImMovementActor.MovedLeft(pressed) + movementActor ! ImMovementActor.MoveLeft(pressed) Behaviors.same case MovedUp(name, pressed) => if (name == npcName) - movementActor ! ImMovementActor.MovedUp(pressed) + movementActor ! ImMovementActor.MoveUp(pressed) Behaviors.same case MovedRight(name, pressed) => if (name == npcName) - movementActor ! ImMovementActor.MovedRight(pressed) + movementActor ! ImMovementActor.MoveRight(pressed) Behaviors.same case MovedDown(name, pressed) => if (name == npcName) - movementActor ! ImMovementActor.MovedDown(pressed) + movementActor ! ImMovementActor.MoveDown(pressed) Behaviors.same } } @@ -279,7 +283,9 @@ object NpcMovementActorNotUsed { val npcMovementEl = ctx.spawn( Behaviors .supervise(npcMovementElBehavior) - .onFailure[Exception](SupervisorStrategy.restart), + .onFailure[Exception]( + SupervisorStrategy.restart.withLimit(2, 100.millis) + ), s"npc-${npcName}-MovementEventHandler" ) val renderTickElBehavior = diff --git a/src/main/scala/wow/doge/mygame/game/entities/player/PlayerActorSupervisor.scala b/src/main/scala/wow/doge/mygame/game/entities/player/PlayerActorSupervisor.scala index 78746b7..30e98de 100644 --- a/src/main/scala/wow/doge/mygame/game/entities/player/PlayerActorSupervisor.scala +++ b/src/main/scala/wow/doge/mygame/game/entities/player/PlayerActorSupervisor.scala @@ -1,13 +1,17 @@ package wow.doge.mygame.game.entities +import scala.concurrent.duration._ + import akka.actor.typed.ActorRef import akka.actor.typed.Behavior import akka.actor.typed.LogOptions +import akka.actor.typed.PostStop import akka.actor.typed.SupervisorStrategy import akka.actor.typed.scaladsl.ActorContext import akka.actor.typed.scaladsl.Behaviors import com.typesafe.scalalogging.Logger import org.slf4j.event.Level +import wow.doge.mygame.Dispatchers import wow.doge.mygame.implicits._ import wow.doge.mygame.subsystems.events.EventBus import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus @@ -15,125 +19,250 @@ import wow.doge.mygame.subsystems.events.PlayerEvent import wow.doge.mygame.subsystems.events.TickEvent import wow.doge.mygame.subsystems.events.TickEvent.RenderTick import wow.doge.mygame.subsystems.movement.ImMovementActor +import scala.util.Success +import scala.util.Failure +import akka.util.Timeout +import monix.reactive.Observable +import monix.reactive.subjects.ConcurrentSubject +import monix.execution.Scheduler +import monix.reactive.OverflowStrategy object PlayerActorSupervisor { + + type Ref = ActorRef[PlayerActorSupervisor.Command] + + sealed trait Status + object Status { + case object Alive extends Status + case object Dead extends Status + } + sealed trait Command - final case class Props( - playerEventBus: GameEventBus[PlayerEvent], - tickEventBus: GameEventBus[TickEvent], - imMovementActorBehavior: Behavior[ImMovementActor.Command], - playerCameraActorBehavior: Behavior[PlayerCameraActor.Command] + case class TakeDamage(value: Int) extends Command + case class Heal(value: Int) extends Command + case class CurrentStats(replyTo: ActorRef[StatsActor.State]) extends Command + case class GetStatus(replyTo: ActorRef[Status]) extends Command + case class GetStatsObservable(replyTo: ActorRef[Observable[StatsActor.State]]) + extends Command + + private case object Die extends Command + private case class DamageResponse(response: (Boolean, StatsActor.State)) + extends Command + // private case class InternalTakeDamage(old: Int, value: Int) extends Command + private case class LogError(ex: Throwable) extends Command + class Props( + val playerEventBus: GameEventBus[PlayerEvent], + val tickEventBus: GameEventBus[TickEvent], + val imMovementActorBehavior: Behavior[ImMovementActor.Command], + val scheduler: Scheduler ) { def behavior = Behaviors.logMessages( LogOptions() - .withLevel(Level.TRACE) + .withLevel(Level.DEBUG) .withLogger( Logger[PlayerActorSupervisor].underlying ), - Behaviors.setup[Command] { ctx => - ctx.log.infoP("Starting PlayerActor") + Behaviors + .setup[Command] { ctx => + ctx.log.infoP("Starting PlayerActor") + + // spawn children actors + val playerMovementActor = + ctx.spawnN( + Behaviors + .supervise(imMovementActorBehavior) + .onFailure[Exception]( + SupervisorStrategy.restart.withLimit(2, 100.millis) + ), + Dispatchers.jmeDispatcher + ) - // spawn children actors - val movementActor = - ctx.spawn( + val playerStatsActor = + ctx.spawnN(new StatsActor.Props(100, 100).behavior) + + val playerMovementEl = ctx.spawnN( Behaviors - .supervise(imMovementActorBehavior) - .onFailure[Exception](SupervisorStrategy.restart), - "playerMovementActor" + .supervise(PlayerMovementEventListener(playerMovementActor)) + .onFailure[Exception]( + SupervisorStrategy.restart.withLimit(2, 100.millis) + ) ) - val playerCameraActor = - ctx.spawn(playerCameraActorBehavior, "playerCameraActor") - - val playerCameraEl = ctx.spawn( - PlayerCameraEventListener(playerCameraActor), - "playerCameraActorEl" - ) - - val playerMovementEl = ctx.spawn( - Behaviors - .supervise(PlayerMovementEventListener(movementActor)) - .onFailure[Exception](SupervisorStrategy.restart), - "playerMovementEventHandler" - ) - - val renderTickEl = { - val behavior = - Behaviors.receiveMessage[RenderTick.type] { - case RenderTick => - movementActor ! ImMovementActor.Tick - // playerCameraActor ! PlayerCameraActor.Tick - Behaviors.same - } - ctx.spawn(behavior, "playerMovementTickListener") - } + val renderTickEl = { + val behavior: Behavior[RenderTick.type] = + Behaviors.setup(ctx => + Behaviors + .receiveMessage[RenderTick.type] { + case RenderTick => + playerMovementActor ! ImMovementActor.Tick + // playerCameraActor ! PlayerCameraActor.Tick + Behaviors.same + } + .receiveSignal { + case (_, PostStop) => + ctx.log.infoP("stopped") + Behaviors.same + } + ) + ctx.spawn(behavior, "playerMovementTickListener") + } - //init listeners - playerEventBus ! EventBus.Subscribe(playerMovementEl) - tickEventBus ! EventBus.Subscribe(renderTickEl) - playerEventBus ! EventBus.Subscribe(playerCameraEl) + //init listeners + playerEventBus ! EventBus.Subscribe(playerMovementEl) + tickEventBus ! EventBus.Subscribe(renderTickEl) - new PlayerActorSupervisor( - ctx, - this, - Children(movementActor) - ).receive - } + new PlayerActorSupervisor( + ctx, + this, + Children(playerMovementActor, playerStatsActor), + ConcurrentSubject.publish(OverflowStrategy.DropOld(50))(scheduler) + ).aliveState + } ) } case class Children( - movementActor: ActorRef[ImMovementActor.Command] + movementActor: ActorRef[ImMovementActor.Command], + statsActor: ActorRef[StatsActor.Command] ) } class PlayerActorSupervisor( ctx: ActorContext[PlayerActorSupervisor.Command], props: PlayerActorSupervisor.Props, - children: PlayerActorSupervisor.Children + children: PlayerActorSupervisor.Children, + statsSubject: ConcurrentSubject[StatsActor.State, StatsActor.State] ) { import PlayerActorSupervisor._ - def receive = + implicit val timeout = Timeout(1.second) + val aliveState = Behaviors .receiveMessage[Command] { - case _ => + case TakeDamage(value) => // children.movementActor ! ImMovementActor.MovedDown(true) + // ctx.ask(children.statsActor, StatsActor.CurrentStats(_)) { + // case Success(status) => InternalTakeDamage(status.hp, value) + // case Failure(ex) => LogError(ex) + // } + ctx.ask(children.statsActor, StatsActor.TakeDamageResult(value, _)) { + case Success(response) => DamageResponse(response) + case Failure(ex) => LogError(ex) + } + Behaviors.same + case CurrentStats(replyTo) => + // ctx.ask(children.statsActor, StatsActor.CurrentStats()) + children.statsActor ! StatsActor.CurrentStats(replyTo) + Behaviors.same + case Heal(value) => + children.statsActor ! StatsActor.Heal(value) + Behaviors.same + case GetStatus(replyTo) => + replyTo ! Status.Alive + Behaviors.same + // case _ => Behaviors.unhandled + // case InternalTakeDamage(hp, damage) => + // if (hp - damage <= 0) dead + // else { + // children.statsActor ! StatsActor.TakeDamage(damage) + // Behaviors.same + // } + case GetStatsObservable(replyTo) => + replyTo ! statsSubject + Behaviors.same + case DamageResponse(response) => + response match { + case (dead, state) => + if (dead) ctx.self ! Die + statsSubject.onNext(state) + } + Behaviors.same + case Die => deadState + case LogError(ex) => + ctx.log.error(ex.getMessage) Behaviors.same } + .receiveSignal { + case (_, PostStop) => + ctx.log.infoP("stopped") + statsSubject.onComplete() + Behaviors.same + } + val deadState = Behaviors + .receiveMessage[Command] { + // case TakeDamage(value) => + // children.statsActor ! StatsActor.TakeDamage(value) + // // children.movementActor ! ImMovementActor.MovedDown(true) + // Behaviors.same + // case CurrentStats(replyTo) => + // // ctx.ask(children.statsActor, StatsActor.CurrentStats()) + // children.statsActor ! StatsActor.CurrentStats(replyTo) + // Behaviors.same + // case Heal(_) => + // Behaviors.same + case CurrentStats(replyTo) => + // ctx.ask(children.statsActor, StatsActor.CurrentStats()) + children.statsActor ! StatsActor.CurrentStats(replyTo) + Behaviors.same + case GetStatus(replyTo) => + replyTo ! Status.Dead + Behaviors.same + case _ => Behaviors.unhandled + } + .receiveSignal { + case (_, PostStop) => + ctx.log.infoP("stopped") + statsSubject.onComplete() + Behaviors.same + } } -object PlayerMovementActor { - sealed trait Command - final case class Props( - movementActor: ActorRef[ImMovementActor.Command], - playerCameraActor: ActorRef[PlayerCameraActor.Command], - playerEventBus: GameEventBus[PlayerEvent], - tickEventBus: GameEventBus[TickEvent] - ) { +object StatsActor { - def behavior: Behavior[Command] = - Behaviors.setup { ctx => - val playerMovementEl = ctx.spawn( - Behaviors - .supervise(PlayerMovementEventListener(movementActor)) - .onFailure[Exception](SupervisorStrategy.restart), - "playerMovementEventHandler" - ) - - val renderTickEl = { - val behavior = - Behaviors.receiveMessage[RenderTick.type] { - case RenderTick => - movementActor ! ImMovementActor.Tick - // playerCameraActor ! PlayerCameraActor.Tick - Behaviors.same - } - ctx.spawn(behavior, "playerMovementTickListener") - } + sealed trait Command + case class TakeDamage(value: Int) extends Command + case class TakeDamageResult(value: Int, replyTo: ActorRef[(Boolean, State)]) + extends Command + case class Heal(value: Int) extends Command + case class CurrentStats(replyTo: ActorRef[State]) extends Command - playerEventBus ! EventBus.Subscribe(playerMovementEl) - tickEventBus ! EventBus.Subscribe(renderTickEl) - Behaviors.receiveMessage { msg => Behaviors.same } + class Props(startingHealth: Int, startingStamina: Int) { + def behavior = + Behaviors.setup[Command] { ctx => + new StatsActor(ctx, this) + .receive(State(startingHealth, startingStamina)) } } + + case class State(hp: Int, stamina: Int) +} +class StatsActor( + ctx: ActorContext[StatsActor.Command], + props: StatsActor.Props +) { + import StatsActor._ + import com.softwaremill.quicklens._ + def receive(state: State): Behavior[Command] = + Behaviors.receiveMessage[Command] { + // Todo add min max values + case TakeDamage(value) => + val nextState = + if (state.hp - value <= 0) + state.modify(_.hp).setTo(0) + else + state.modify(_.hp).using(_ - value) + receive(nextState) + case TakeDamageResult(value, replyTo) => + val nextState = if (state.hp - value <= 0) { + replyTo ! true -> state + state + } else { + replyTo ! false -> state + state.modify(_.hp).using(_ - value) + } + receive(nextState) + case Heal(value) => receive(state.modify(_.hp).using(_ + value)) + case CurrentStats(replyTo) => + replyTo ! state + Behaviors.same + } } diff --git a/src/main/scala/wow/doge/mygame/game/entities/player/PlayerActorSupervisor2.scala b/src/main/scala/wow/doge/mygame/game/entities/player/PlayerActorSupervisor2.scala new file mode 100644 index 0000000..0bcfa80 --- /dev/null +++ b/src/main/scala/wow/doge/mygame/game/entities/player/PlayerActorSupervisor2.scala @@ -0,0 +1,149 @@ +package wow.doge.mygame.game.entities.player + +import scala.concurrent.duration._ + +import akka.actor.typed.ActorRef +import akka.actor.typed.Behavior +import akka.actor.typed.LogOptions +import akka.actor.typed.PostStop +import akka.actor.typed.SupervisorStrategy +import akka.actor.typed.scaladsl.ActorContext +import akka.actor.typed.scaladsl.Behaviors +import com.typesafe.scalalogging.Logger +import org.slf4j.event.Level +import wow.doge.mygame.Dispatchers +import wow.doge.mygame.game.entities.PlayerCameraActor +import wow.doge.mygame.game.entities.PlayerCameraEventListener +import wow.doge.mygame.game.entities.PlayerMovementEventListener +import wow.doge.mygame.implicits._ +import wow.doge.mygame.subsystems.events.EventBus +import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus +import wow.doge.mygame.subsystems.events.PlayerEvent +import wow.doge.mygame.subsystems.events.TickEvent +import wow.doge.mygame.subsystems.events.TickEvent.RenderTick +import wow.doge.mygame.subsystems.movement.ImMovementActor + +object PlayerActorSupervisor2 { + sealed trait Command + case object Tick extends Command + sealed trait Movement extends Command + final case class MoveLeft(pressed: Boolean) extends Movement + final case class MoveUp(pressed: Boolean) extends Movement + final case class MoveRight(pressed: Boolean) extends Movement + final case class MoveDown(pressed: Boolean) extends Movement + case object Jump extends Movement + sealed trait Camera + case object RotateLeft extends Camera + case object RotateRight extends Camera + case object RotateUp extends Camera + case object RotateDown extends Camera + class Props( + val playerEventBus: GameEventBus[PlayerEvent], + val tickEventBus: GameEventBus[TickEvent], + val imMovementActorBehavior: Behavior[ImMovementActor.Command], + val playerCameraActorBehavior: Behavior[PlayerCameraActor.Command] + ) { + def behavior = + Behaviors.logMessages( + LogOptions() + .withLevel(Level.TRACE) + .withLogger( + Logger[PlayerActorSupervisor2].underlying + ), + Behaviors + .setup[Command] { ctx => + ctx.log.infoP("Starting PlayerActor") + + // spawn children actors + val movementActor = + ctx.spawn( + Behaviors + .supervise(imMovementActorBehavior) + .onFailure[Exception]( + SupervisorStrategy.restart.withLimit(2, 100.millis) + ), + "playerMovementActor", + Dispatchers.jmeDispatcher + ) + + val playerCameraActor = + ctx.spawn( + playerCameraActorBehavior, + "playerCameraActor", + Dispatchers.jmeDispatcher + ) + + val playerCameraEl = ctx.spawn( + PlayerCameraEventListener(playerCameraActor), + "playerCameraActorEl" + ) + + val playerMovementEl = ctx.spawn( + Behaviors + .supervise(PlayerMovementEventListener(movementActor)) + .onFailure[Exception]( + SupervisorStrategy.restart.withLimit(2, 100.millis) + ), + "playerMovementEventHandler" + ) + + val renderTickEl = { + val behavior: Behavior[RenderTick.type] = + Behaviors.setup(ctx => + Behaviors + .receiveMessage[RenderTick.type] { + case RenderTick => + movementActor ! ImMovementActor.Tick + // playerCameraActor ! PlayerCameraActor.Tick + Behaviors.same + } + .receiveSignal { + case (_, PostStop) => + ctx.log.infoP("stopped") + Behaviors.same + } + ) + ctx.spawn(behavior, "playerMovementTickListener") + } + + //init listeners + playerEventBus ! EventBus.Subscribe(playerMovementEl) + tickEventBus ! EventBus.Subscribe(renderTickEl) + playerEventBus ! EventBus.Subscribe(playerCameraEl) + + new PlayerActorSupervisor2( + ctx, + this, + Children(movementActor) + ).receive + } + ) + + } + + case class Children( + movementActor: ActorRef[ImMovementActor.Command] + ) +} +class PlayerActorSupervisor2( + ctx: ActorContext[PlayerActorSupervisor2.Command], + props: PlayerActorSupervisor2.Props, + children: PlayerActorSupervisor2.Children +) { + import PlayerActorSupervisor2._ + def receive = + Behaviors + .receiveMessage[Command] { + case m @ MoveDown(pressed) => + // children.movementActor ! m + Behaviors.same + case _ => + // children.movementActor ! ImMovementActor.MovedDown(true) + Behaviors.same + } + .receiveSignal { + case (_, PostStop) => + ctx.log.infoP("stopped") + Behaviors.same + } +} diff --git a/src/main/scala/wow/doge/mygame/game/entities/player/PlayerCameraActor.scala b/src/main/scala/wow/doge/mygame/game/entities/player/PlayerCameraActor.scala index 1993070..2c735c6 100644 --- a/src/main/scala/wow/doge/mygame/game/entities/player/PlayerCameraActor.scala +++ b/src/main/scala/wow/doge/mygame/game/entities/player/PlayerCameraActor.scala @@ -55,28 +55,27 @@ class PlayerCameraActor( case RotateLeft => val rot = rotationBuf .fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y) - props.enqueueR(() => props.cameraPivotNode.rotate(rot)) + props.cameraPivotNode.rotate(rot) Behaviors.same case RotateRight => val rot = rotationBuf .fromAngleAxis(-1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y) - props.enqueueR(() => props.cameraPivotNode.rotate(rot)) + props.cameraPivotNode.rotate(rot) Behaviors.same case RotateUp => val rot = rotationBuf .fromAngleAxis(-1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X) - props.enqueueR(() => props.cameraPivotNode.rotate(rot)) + props.cameraPivotNode.rotate(rot) Behaviors.same case RotateDown => val rot = rotationBuf .fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X) - props.enqueueR(() => props.cameraPivotNode.rotate(rot)) + props.cameraPivotNode.rotate(rot) Behaviors.same case Tick => - props.enqueueR(() => { - val location = props.getPlayerLocation() - props.cameraPivotNode.setLocalTranslation(location) - }) + val location = props.getPlayerLocation() + props.cameraPivotNode.setLocalTranslation(location) + Behaviors.same } } diff --git a/src/main/scala/wow/doge/mygame/game/entities/player/PlayerController.scala b/src/main/scala/wow/doge/mygame/game/entities/player/PlayerController.scala index c941a1a..bdfd199 100644 --- a/src/main/scala/wow/doge/mygame/game/entities/player/PlayerController.scala +++ b/src/main/scala/wow/doge/mygame/game/entities/player/PlayerController.scala @@ -1,12 +1,8 @@ package wow.doge.mygame.game.entities import akka.actor.typed.ActorRef -import akka.actor.typed.Scheduler -import akka.actor.typed.SpawnProtocol -import akka.util.Timeout -import com.jme3.bullet.BulletAppState +import akka.actor.typed.DispatcherSelector import com.jme3.bullet.control.BetterCharacterControl -import com.jme3.math.FastMath import com.jme3.renderer.Camera import com.jme3.scene.CameraNode import com.jme3.scene.Geometry @@ -18,67 +14,76 @@ import io.odin.Logger import monix.bio.IO import monix.bio.Task import wow.doge.mygame.AppError -import wow.doge.mygame.game.GameAppTags -import wow.doge.mygame.game.SimpleAppExt +import wow.doge.mygame.game.GameApp import wow.doge.mygame.implicits._ import wow.doge.mygame.math.ImVector3f import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus import wow.doge.mygame.subsystems.events.PlayerEvent import wow.doge.mygame.subsystems.events.TickEvent import wow.doge.mygame.subsystems.movement.ImMovementActor -import wow.doge.mygame.utils.AkkaUtils import wow.doge.mygame.utils.wrappers.jme._ -object PlayerControllerTags { - sealed trait PlayerTag - sealed trait PlayerCameraNode - sealed trait PlayerCameraPivotNode -} +import wow.doge.mygame.types._ object PlayerController { sealed trait Error case class CouldNotCreatePlayerModel(reason: AssetManager.Error) extends Error + object Tags { + sealed trait PlayerNode + sealed trait PlayerCameraNode + sealed trait PlayerCameraPivotNode + } + + type PlayerNode = Node @@ Tags.PlayerNode + type PlayerCameraNode = CameraNode @@ Tags.PlayerCameraNode + type PlayerCameraPivotNode = + Node @@ Tags.PlayerCameraPivotNode + class Props( + gameApp: GameApp, enqueueR: Function1[() => Unit, Unit], - rootNode: AppNode2 @@ GameAppTags.RootNode, + rootNode: RootNode, loggerL: Logger[Task], - // physicsSpace: com.jme3.bullet.PhysicsSpace, physicsSpace: PhysicsSpace, - initialPlayerPos: ImVector3f = ImVector3f.Zero, + initialPlayerPos: ImVector3f, playerEventBus: GameEventBus[PlayerEvent], playerPhysicsControl: BetterCharacterControl, // appScheduler: monix.execution.Scheduler, - playerNode: Node @@ PlayerControllerTags.PlayerTag, - cameraNode: CameraNode @@ PlayerControllerTags.PlayerCameraNode, - cameraPivotNode: Node @@ PlayerControllerTags.PlayerCameraPivotNode, + scheduler: monix.execution.Scheduler, + playerNode: PlayerNode, + cameraNode: PlayerCameraNode, + cameraPivotNode: PlayerCameraPivotNode, tickEventBus: GameEventBus[TickEvent], camera: Camera - )(implicit - spawnProtocol: ActorRef[SpawnProtocol.Command], - timeout: Timeout, - scheduler: Scheduler ) { val playerActorBehavior = { val movementActorBeh = new ImMovementActor.Props( enqueueR, camera ).behavior(playerPhysicsControl) - val cameraActorBeh = new PlayerCameraActor.Props( - cameraPivotNode, - enqueueR, - playerNode.getWorldTranslation _ - ).behavior new PlayerActorSupervisor.Props( playerEventBus, tickEventBus, movementActorBeh, - cameraActorBeh + scheduler ).behavior } + val playerActor = + gameApp.spawnGameActor( + playerActorBehavior, + // Some("playerActorSupervisor"), + props = DispatcherSelector.default() + ) val create: IO[AppError, ActorRef[PlayerActorSupervisor.Command]] = (for { - playerActor <- - AkkaUtils.spawnActorL(playerActorBehavior, "playerActorSupervisor") + // playerActor <- + // // AkkaUtils.spawnActorL(playerActorBehavior, "playerActorSupervisor") + // gameApp.spawnGameActor( + // playerActorBehavior, + // Some("playerActorSupervisor"), + // props = DispatcherSelector.default() + // ) + pa <- playerActor _ <- (for { _ <- rootNode += playerNode _ <- physicsSpace += playerNode @@ -86,38 +91,8 @@ object PlayerController { _ = cameraPivotNode += cameraNode _ <- rootNode += cameraPivotNode } yield ()).mapError(AppError.AppNodeError) - } yield playerActor) - - } - - def apply( - app: SimpleAppExt, - modelPath: os.RelPath, - cam: Camera - )(assetManager: AssetManager, bulletAppState: BulletAppState) = { - val playerPos = ImVector3f.Zero - val playerPhysicsControl = new BetterCharacterControl(1.5f, 6f, 1f) - .withJumpForce(ImVector3f(0, 5f, 0)) - val playerNode = new Node("PlayerNode") - .withChildren( - assetManager - .loadModel(modelPath) - .asInstanceOf[Node] - .withRotate(0, FastMath.PI, 0) - ) - .withLocalTranslation(playerPos) - .withControl(playerPhysicsControl) - - { - bulletAppState.physicsSpace += playerNode - bulletAppState.physicsSpace += playerPhysicsControl - } - - { - app.rootNode += playerNode - } + } yield pa) - playerPhysicsControl } object Defaults { @@ -126,21 +101,6 @@ object PlayerController { val geom = Geometry("playerGeom", b) geom } - // def defaultTexture(assetManager: AssetManager) = - // MyMaterial( - // assetManager = assetManager, - // path = os.rel / "Common" / "MatDefs" / "Misc" / "Unshaded.j3md" - // ) - - // new CameraControl(cam) { - // override def controlUpdate(tpf: Float) = { - // this.getCamera().setRotation(spatial.getWorldRotation()) - // cameraPivotNode.setLocalTranslation( - // playerNode.getWorldTranslation() - // ) - // this.getCamera().setLocation(spatial.getWorldTranslation()) - // } - // } def defaultCamerNode( cam: Camera, @@ -163,7 +123,7 @@ object PlayerController { .withChildren(playerModel) .withLocalTranslation(playerPos) .withControl(playerPhysicsControl) - .taggedWith[PlayerControllerTags.PlayerTag] + .taggedWith[Tags.PlayerNode] def defaultNpcNode( // assetManager: AssetManager, diff --git a/src/main/scala/wow/doge/mygame/game/entities/player/PlayerEventListeners.scala b/src/main/scala/wow/doge/mygame/game/entities/player/PlayerEventListeners.scala index 123d7da..4506df2 100644 --- a/src/main/scala/wow/doge/mygame/game/entities/player/PlayerEventListeners.scala +++ b/src/main/scala/wow/doge/mygame/game/entities/player/PlayerEventListeners.scala @@ -21,16 +21,16 @@ object PlayerMovementEventListener { Behaviors.setup[PlayerMovementEvent](ctx => Behaviors.receiveMessage { case PlayerMovedLeft(pressed) => - movementActor ! ImMovementActor.MovedLeft(pressed) + movementActor ! ImMovementActor.MoveLeft(pressed) Behaviors.same case PlayerMovedRight(pressed) => - movementActor ! ImMovementActor.MovedRight(pressed) + movementActor ! ImMovementActor.MoveRight(pressed) Behaviors.same case PlayerMovedForward(pressed) => - movementActor ! ImMovementActor.MovedUp(pressed) + movementActor ! ImMovementActor.MoveUp(pressed) Behaviors.same case PlayerMovedBackward(pressed) => - movementActor ! ImMovementActor.MovedDown(pressed) + movementActor ! ImMovementActor.MoveDown(pressed) Behaviors.same case PlayerJumped => movementActor ! ImMovementActor.Jump 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 index 4e9f544..d6bca6c 100644 --- a/src/main/scala/wow/doge/mygame/game/subsystems/input/GameInputHandler.scala +++ b/src/main/scala/wow/doge/mygame/game/subsystems/input/GameInputHandler.scala @@ -43,10 +43,10 @@ object GameInputHandler { // inputManager, // playerEventBus // ).completedL, - generateCameraEvents( - inputManager, - playerEventBus - ).completedL, + // cameraMovementEventsGenerator( + // inputManager, + // playerEventBus + // ).completedL, Ref .of[me.Task, Boolean](false) .flatMap(value => cursorToggle(value)) @@ -170,14 +170,14 @@ object GameInputHandler { ) ) case PlayerMovementInput.Jump => - if (action.value) { + if (action.value) me.Task( playerEventBus ! EventBus.Publish( PlayerMovementEvent.PlayerJumped, name ) ) - } else me.Task.unit + else me.Task.unit } } } @@ -185,36 +185,35 @@ object GameInputHandler { def generateAnalogMovementEvents( inputManager: InputManager, playerEventBus: GameEventBus[PlayerEvent] - ) = { + ) = // val name = "analogMovementEventsGenerator" inputManager .enumAnalogObservable(PlayerAnalogMovementInput) .sample(1.millis) - // .doOnNext(analogEvent => - // analogEvent.binding match { - // case PlayerAnalogMovementInput.TurnRight => - // me.Task( - // playerMovementEventBus ! EventBus.Publish( - // PlayerMovementEvent.PlayerTurnedRight, - // name - // ) - // ) - // case PlayerAnalogMovementInput.TurnLeft => - // me.Task( - // playerMovementEventBus ! EventBus.Publish( - // PlayerMovementEvent.PlayerTurnedLeft, - // name - // ) - // ) - // } - // ) - } + // .doOnNext(analogEvent => + // analogEvent.binding match { + // case PlayerAnalogMovementInput.TurnRight => + // me.Task( + // playerMovementEventBus ! EventBus.Publish( + // PlayerMovementEvent.PlayerTurnedRight, + // name + // ) + // ) + // case PlayerAnalogMovementInput.TurnLeft => + // me.Task( + // playerMovementEventBus ! EventBus.Publish( + // PlayerMovementEvent.PlayerTurnedLeft, + // name + // ) + // ) + // } + // ) - def generateCameraEvents( + def cameraMovementEventsGenerator( inputManager: InputManager, playerEventBus: ActorRef[EventBus.Command[PlayerEvent]] ) = { - val name = "cameraMovementEventsGenerator" + val name = methodName inputManager .enumAnalogObservable(PlayerCameraInput) .sample(1.millis) diff --git a/src/main/scala/wow/doge/mygame/game/subsystems/movement/CanMove.scala b/src/main/scala/wow/doge/mygame/game/subsystems/movement/CanMove.scala index 99d8600..f155eab 100644 --- a/src/main/scala/wow/doge/mygame/game/subsystems/movement/CanMove.scala +++ b/src/main/scala/wow/doge/mygame/game/subsystems/movement/CanMove.scala @@ -12,7 +12,7 @@ import wow.doge.mygame.subsystems.movement.RotateDir trait CanMove[-A] { // def getDirection(cam: Camera, cardinalDir: CardinalDirection): ImVector3f - def move(inst: A, direction: ImVector3f, speedFactor: Float = 20f): Unit + def move(inst: A, direction: ImVector3f, speedFactor: Float): Unit def location(inst: A): ImVector3f def jump(inst: A): Unit def stop(inst: A): Unit @@ -25,7 +25,7 @@ object CanMove { override def move( inst: BetterCharacterControl, direction: ImVector3f, - speedFactor: Float = 20f + speedFactor: Float ): Unit = { val dir = direction.mutable.normalizeLocal() inst.setViewDirection(dir.negate()) @@ -61,19 +61,17 @@ object CanMove { inst: Spatial, direction: ImVector3f, speedFactor: Float = 1f - ): Unit = { + ): Unit = inst.move(direction.mutable multLocal speedFactor) - } override def location(inst: Spatial) = inst.getLocalTranslation().immutable override def jump(inst: Spatial): Unit = logger.warn("`Jump` is not implemented for type `Spatial`") - override def rotate(inst: Spatial, rotateDir: RotateDir): Unit = { + 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) } - } override def stop(inst: Spatial) = { /*not required*/ } } } diff --git a/src/main/scala/wow/doge/mygame/game/subsystems/movement/MovementActor.scala b/src/main/scala/wow/doge/mygame/game/subsystems/movement/MovementActor.scala index faa68a7..8efef1c 100644 --- a/src/main/scala/wow/doge/mygame/game/subsystems/movement/MovementActor.scala +++ b/src/main/scala/wow/doge/mygame/game/subsystems/movement/MovementActor.scala @@ -1,15 +1,23 @@ package wow.doge.mygame.subsystems.movement import akka.actor.typed.Behavior +import akka.actor.typed.PostStop import akka.actor.typed.scaladsl.ActorContext import akka.actor.typed.scaladsl.Behaviors import com.jme3.math.Vector3f import com.jme3.renderer.Camera +import com.jme3.scene.Geometry import com.softwaremill.quicklens._ import wow.doge.mygame.game.subsystems.movement.CanMove import wow.doge.mygame.implicits._ import wow.doge.mygame.math.ImVector3f -import wow.doge.mygame.state.CardinalDirection + +final case class CardinalDirection( + left: Boolean = false, + right: Boolean = false, + up: Boolean = false, + down: Boolean = false +) sealed trait RotateDir object RotateDir { @@ -19,19 +27,16 @@ object RotateDir { object ImMovementActor { sealed trait Command - // final case class Tick(tpf: Float) extends Command - final case object Tick extends Command + case object Tick 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 MoveLeft(pressed: Boolean) extends Movement + final case class MoveUp(pressed: Boolean) extends Movement + final case class MoveRight(pressed: Boolean) extends Movement + final case class MoveDown(pressed: Boolean) extends Movement + case object Jump extends Movement - final class Props( + class Props( val enqueueR: Function1[() => Unit, Unit], // playerMovementEventBus: ActorRef[ // EventBus.Command[PlayerMovementEvent] @@ -40,7 +45,7 @@ object ImMovementActor { ) { def behavior[T: CanMove](movable: T): Behavior[Command] = Behaviors.setup(ctx => - new ImMovementActor(ctx, this, movable).receive(State()) + new ImMovementActor(ctx, this, movable).receive(State(), new Vector3f) ) } @@ -57,48 +62,84 @@ class ImMovementActor[T]( ctx: ActorContext[ImMovementActor.Command], props: ImMovementActor.Props, val movable: T -) { +)(implicit cm: CanMove[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.enqueueR(() => stopIfNotPressed(pressed, movable)) - receive(state = state.modify(_.cardinalDir.left).setTo(pressed)) - case MovedUp(pressed) => - props.enqueueR(() => stopIfNotPressed(pressed, movable)) - receive(state = state.modify(_.cardinalDir.up).setTo(pressed)) - case MovedRight(pressed) => - props.enqueueR(() => stopIfNotPressed(pressed, movable)) - receive(state = state.modify(_.cardinalDir.right).setTo(pressed)) - case MovedDown(pressed) => - props.enqueueR(() => stopIfNotPressed(pressed, movable)) - receive(state = state.modify(_.cardinalDir.down).setTo(pressed)) - case Jump => - props.enqueueR(() => cm.jump(movable)) - Behaviors.same - } - - case Tick => - val walkDir = - getDirection2(state.cardinalDir, ctx.log.traceP) - // if (walkDir != ImVector3f.ZERO) { - val tmp = walkDir * 25f * (1f / 144) - props.enqueueR { () => - cm.move(movable, tmp) - } - // } - Behaviors.same - } + state: ImMovementActor.State, + walkDirBuf: Vector3f + ): Behavior[Command] = + Behaviors + .receiveMessage[Command] { + case m: Movement => + m match { + case MoveLeft(pressed) => + stopIfNotPressed(pressed) + receive( + state = state.modify(_.cardinalDir.left).setTo(pressed), + walkDirBuf + ) + case MoveUp(pressed) => + stopIfNotPressed(pressed) + receive( + state = state.modify(_.cardinalDir.up).setTo(pressed), + walkDirBuf + ) + case MoveRight(pressed) => + stopIfNotPressed(pressed) + receive( + state = state.modify(_.cardinalDir.right).setTo(pressed), + walkDirBuf + ) + case MoveDown(pressed) => + stopIfNotPressed(pressed) + receive( + state = state.modify(_.cardinalDir.down).setTo(pressed), + walkDirBuf + ) + case Jump => + cm.jump(movable) + Behaviors.same + } + + case Tick => + val camDir = + props.camera.getDirection().clone().normalizeLocal.multLocal(0.6f) + val camLeft = + props.camera.getLeft().clone().normalizeLocal.multLocal(0.4f) + val dir = state.cardinalDir + walkDirBuf.set(0, 0, 0) + if (dir.up) { + ctx.log.traceP("up") + walkDirBuf += camDir + } + if (dir.left) { + ctx.log.traceP("left") + walkDirBuf += camLeft + } + if (dir.right) { + ctx.log.traceP("right") + walkDirBuf += -camLeft + } + if (dir.down) { + ctx.log.traceP("down") + walkDirBuf += -camDir + } + walkDirBuf *= 25f *= (1f / 144) + cm.move(movable, walkDirBuf.immutable, 20f) + Behaviors.same + } + .receiveSignal { + case (_, PostStop) => + ctx.log.debugP("Stopped") + Behaviors.same + } + + def stopIfNotPressed(pressed: Boolean) = if (!pressed) cm.stop(movable) def getDirection2( - cardinalDir: CardinalDirection, - debug: sourcecode.Text[String] => sourcecode.Text[String] + cardinalDir: CardinalDirection + // debug: sourcecode.Text[String] => sourcecode.Text[String] ) = { val camDir = props.camera.getDirection().clone().normalizeLocal.multLocal(0.6f) @@ -107,19 +148,19 @@ class ImMovementActor[T]( val walkDir = { val mutWalkDir = new Vector3f() if (dir.up) { - debug("up") + ctx.log.traceP("up") mutWalkDir += camDir } if (dir.left) { - debug("left") + ctx.log.traceP("left") mutWalkDir += camLeft } if (dir.right) { - debug("right") + ctx.log.traceP("right") mutWalkDir += -camLeft } if (dir.down) { - debug("down") + ctx.log.traceP("down") mutWalkDir += -camDir } mutWalkDir.immutable @@ -127,50 +168,111 @@ class ImMovementActor[T]( walkDir } } -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) +// old/unused +object MovementActor { + sealed trait Command + case object Tick 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 + + class Props(val app: com.jme3.app.Application, val geom: Geometry) + + /** + * Internal state of the actor + * + * @param cardinalDir Immutable, can be shared as is + * @param walkDirection scratch space to avoid allocations on every tick. Do not share outside the actor + */ + final case class State( + cardinalDir: CardinalDirection = CardinalDirection(), + walkDirection: Vector3f = Vector3f.UNIT_X + ) + + def apply(props: Props): Behavior[Command] = + Behaviors.setup(ctx => new MovementActor(ctx, props).receive(State())) + } +class MovementActor( + ctx: ActorContext[MovementActor.Command], + props: MovementActor.Props +) { + import MovementActor._ + import com.softwaremill.quicklens._ + def receive(state: MovementActor.State): 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 => + val camDir = + props.app.getCamera.getDirection().clone().multLocal(0.6f) + val camLeft = props.app.getCamera.getLeft().clone().multLocal(0.4f) + val walkDir = state.walkDirection.set(0, 0, 0) + // val walkDir = new Vector3f + val dir = state.cardinalDir + if (dir.up) { + ctx.log.debugP("up") + // ctx.log.debugP(Thread.currentThread().getName()) + // walkDir.addLocal(0, 0, -1) + walkDir += camDir + } + if (dir.left) { + ctx.log.debugP("left") + // walkDir.addLocal(-1, 0, 0) + walkDir.addLocal(camLeft) + } + if (dir.right) { + ctx.log.debugP("right") + // walkDir.addLocal(1, 0, 0) + walkDir.addLocal(camLeft.negateLocal()) + } + if (dir.down) { + ctx.log.debugP("down") + walkDir.addLocal(camDir.negateLocal()) + // walkDir.addLocal(0, 0, 1) + } + // (dir.up, dir.down, dir.left, dir.right) match { + // case (true, false, true, false) => + // case _ => + // } + + walkDir.multLocal(2f) -// 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) + // walkDir.multLocal(100f) + // .multLocal(tpf) + + // val v = props.geom.getLocalTranslation() + // props.geom.setLocalTranslation( + // (v += walkDir) + // ) + props.app.enqueue(new Runnable { + override def run(): Unit = { + // geom.setLocalTranslation(walkDir) + + val v = props.geom.getLocalTranslation() + props.geom.setLocalTranslation( + (v += walkDir) + ) + } + }) + Behaviors.same + // receive(state = state.modify(_.walkDirection).setTo(walkDir)) + + } + } +} diff --git a/src/main/scala/wow/doge/mygame/implicits/package.scala b/src/main/scala/wow/doge/mygame/implicits/package.scala index 8060c52..4b64dac 100644 --- a/src/main/scala/wow/doge/mygame/implicits/package.scala +++ b/src/main/scala/wow/doge/mygame/implicits/package.scala @@ -4,7 +4,10 @@ import scala.jdk.CollectionConverters._ import scala.reflect.ClassTag import akka.actor.typed.ActorRef +import akka.actor.typed.Behavior +import akka.actor.typed.Props import akka.actor.typed.Scheduler +import akka.actor.typed.scaladsl.ActorContext import akka.util.Timeout import com.jayfella.jme.jfx.JavaFxUI import com.jme3.app.Application @@ -14,10 +17,12 @@ import com.jme3.app.state.AppStateManager import com.jme3.asset.AssetLocator import com.jme3.asset.AssetManager import com.jme3.bullet.BulletAppState -import com.jme3.bullet.PhysicsSpace import com.jme3.bullet.PhysicsTickListener -import com.jme3.bullet.collision.PhysicsCollisionEvent import com.jme3.bullet.collision.PhysicsCollisionListener +import com.jme3.bullet.collision.PhysicsCollisionObject +import com.jme3.bullet.collision.{ + PhysicsCollisionEvent => jmePhysicsCollisionEvent +} import com.jme3.bullet.control.BetterCharacterControl import com.jme3.input.Action import com.jme3.input.InputManager @@ -35,14 +40,17 @@ import com.jme3.scene.SceneGraphVisitor import com.jme3.scene.Spatial import com.jme3.scene.control.CameraControl.ControlDirection import com.jme3.scene.control.Control +import com.jme3.{bullet => jmeb} import com.simsilica.es.EntityComponent import com.simsilica.es.EntityData import com.simsilica.es.EntityId import enumeratum._ import io.odin.meta.Position import io.odin.meta.Render +import monix.bio.IO import monix.bio.Task import monix.bio.UIO +import monix.eval.Coeval import monix.execution.Ack import monix.execution.Ack.Continue import monix.execution.Ack.Stop @@ -54,6 +62,7 @@ import monix.reactive.observers.Subscriber import org.slf4j.Logger import wow.doge.mygame.math.ImVector3f import wow.doge.mygame.state.MyBaseState +import wow.doge.mygame.utils.wrappers.jme.PhysicsSpace final case class ActionEvent(binding: Action, value: Boolean, tpf: Float) final case class EnumActionEvent[T <: EnumEntry]( @@ -68,8 +77,15 @@ final case class EnumAnalogEvent[T <: EnumEntry]( value: Float, tpf: Float ) -final class PrePhysicsTickEvent(val space: PhysicsSpace) extends AnyVal -final class PhysicsTickEvent(val space: PhysicsSpace) extends AnyVal +final case class PrePhysicsTickEvent(space: PhysicsSpace) +final case class PhysicsTickEvent(space: PhysicsSpace) +final case class CollisionEvent( + nodeA: Option[Spatial], + nodeB: Option[Spatial], + objectA: PhysicsCollisionObject, + objectB: PhysicsCollisionObject, + appliedImpulse: Function0[Float] +) package object implicits { type PrePhysicsTickEvent = PhysicsTickEvent @@ -112,10 +128,9 @@ package object implicits { override def init(): Unit = {} - override def update(tpf: Float) = { + override def update(tpf: Float) = if (sub.onNext(tpf) == Ack.Stop) c.cancel() - } override def stop(): Unit = {} @@ -133,13 +148,11 @@ package object implicits { def registerLocator( assetPath: os.RelPath, locator: Class[_ <: AssetLocator] - ): Unit = { + ): Unit = am.registerLocator(assetPath.toString(), locator) - } - def loadModel(assetPath: os.RelPath): Spatial = { + def loadModel(assetPath: os.RelPath): Spatial = am.loadModel(assetPath.toString()) - } } implicit final class BulletAppStateExt(private val bas: BulletAppState) @@ -251,7 +264,7 @@ package object implicits { } // case _ => loop(subscriber, tail) } - case LazyList() => Task.unit + case LazyList() => Task(subscriber.onComplete()) } Observable.create(OverflowStrategy.Unbounded) { sub => @@ -297,7 +310,7 @@ package object implicits { Task(subscriber.onComplete()) } } - case LazyList() => Task.unit + case LazyList() => Task(subscriber.onComplete()) } Observable.create(OverflowStrategy.Unbounded) { sub => @@ -372,9 +385,8 @@ package object implicits { */ def askL[Res]( replyTo: ActorRef[Res] => Req - )(implicit timeout: Timeout, scheduler: Scheduler): Task[Res] = { + )(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] = @@ -481,8 +493,7 @@ package object implicits { * @see [[ActionEvent]] * @see [[enumObservableAction]] */ - def observableAction(mappingNames: String*): Observable[ActionEvent] = { - + def observableAction(mappingNames: String*): Observable[ActionEvent] = Observable.create(OverflowStrategy.DropOld(10)) { sub => val c = SingleAssignCancelable() val al = new ActionListener { @@ -490,14 +501,13 @@ package object implicits { binding: String, value: Boolean, tpf: Float - ): Unit = { + ): Unit = if ( sub.onNext(ActionEvent(Action(binding), value, tpf)) == Ack.Stop ) { sub.onComplete() c.cancel() } - } } inputManager.addListener(al, mappingNames: _*) @@ -505,7 +515,6 @@ package object implicits { c := Cancelable(() => inputManager.removeListener(al)) c } - } /** *

@@ -530,8 +539,7 @@ package object implicits { */ def enumObservableAction[T <: EnumEntry]( mappingEnum: Enum[T] - ): Observable[EnumActionEvent[T]] = { - + ): Observable[EnumActionEvent[T]] = Observable.create(OverflowStrategy.DropOld(10)) { sub => val c = SingleAssignCancelable() val entryNames = mappingEnum.values.map(_.entryName) @@ -540,14 +548,13 @@ package object implicits { binding: String, value: Boolean, tpf: Float - ): Unit = { + ): Unit = mappingEnum.withNameOption(binding).foreach { b => if (sub.onNext(EnumActionEvent(b, value, tpf)) == Ack.Stop) { sub.onComplete() c.cancel() } } - } } inputManager.addListener(al, entryNames: _*) @@ -555,12 +562,10 @@ package object implicits { c := Cancelable(() => inputManager.removeListener(al)) c } - } def enumEntryObservableAction[T <: EnumEntry]( mappingEnumEntry: T - ): Observable[EnumActionEvent[T]] = { - + ): Observable[EnumActionEvent[T]] = Observable.create(OverflowStrategy.DropOld(10)) { sub => val c = SingleAssignCancelable() val al = new ActionListener { @@ -568,7 +573,7 @@ package object implicits { binding: String, value: Boolean, tpf: Float - ): Unit = { + ): Unit = if ( sub.onNext( EnumActionEvent(mappingEnumEntry, value, tpf) @@ -577,7 +582,6 @@ package object implicits { sub.onComplete() c.cancel() } - } } inputManager.addListener(al, mappingEnumEntry.entryName) @@ -585,10 +589,8 @@ package object implicits { c := Cancelable(() => inputManager.removeListener(al)) c } - } - - def analogObservable(mappingNames: String*): Observable[AnalogEvent] = { + def analogObservable(mappingNames: String*): Observable[AnalogEvent] = Observable.create(OverflowStrategy.DropOld(50)) { sub => val c = SingleAssignCancelable() val al = new AnalogListener { @@ -596,14 +598,13 @@ package object implicits { binding: String, value: Float, tpf: Float - ): Unit = { + ): Unit = if ( sub.onNext(AnalogEvent(Action(binding), value, tpf)) == Ack.Stop ) { sub.onComplete() c.cancel() } - } } inputManager.addListener(al, mappingNames: _*) @@ -611,12 +612,10 @@ package object implicits { c := Cancelable(() => inputManager.removeListener(al)) c } - } def enumAnalogObservable[T <: EnumEntry]( mappingEnum: Enum[T] - ): Observable[EnumAnalogEvent[T]] = { - + ): Observable[EnumAnalogEvent[T]] = Observable.create(OverflowStrategy.DropOld(50)) { sub => val c = SingleAssignCancelable() val entryNames = mappingEnum.values.map(_.entryName) @@ -625,14 +624,13 @@ package object implicits { binding: String, value: Float, tpf: Float - ): Unit = { + ): Unit = mappingEnum.withNameOption(binding).foreach { b => if (sub.onNext(EnumAnalogEvent(b, value, tpf)) == Ack.Stop) { sub.onComplete() c.cancel() } } - } } inputManager.addListener(al, entryNames: _*) @@ -640,49 +638,58 @@ package object implicits { c := Cancelable(() => inputManager.removeListener(al)) c } - } } - implicit final class PhysicsSpaceExt(private val space: PhysicsSpace) + implicit final class PhysicsSpaceExt(private val space: jmeb.PhysicsSpace) extends AnyVal { - def collisionObservable(): Observable[PhysicsCollisionEvent] = { - + def collisionObservable(): Observable[CollisionEvent] = Observable.create(OverflowStrategy.DropOld(50)) { sub => val c = SingleAssignCancelable() val cl = new PhysicsCollisionListener { - override def collision(event: PhysicsCollisionEvent): Unit = { - - if (sub.onNext(event) == Ack.Stop) { - sub.onComplete() - c.cancel() - } + override def collision(event: jmePhysicsCollisionEvent): Unit = + if ( + sub.onNext( + CollisionEvent( + Option(event.getNodeA), + Option(event.getNodeB), + event.getObjectA, + event.getObjectB, + event.getAppliedImpulse _ + ) + ) == Ack.Stop + ) c.cancel() - } } - space.addCollisionListener(cl) - c := Cancelable(() => space.removeCollisionListener(cl)) + c := Cancelable { () => + pprint.log("stopped") + space.removeCollisionListener(cl) + } c } - } - - def prePhysicsTickObservable(): Observable[PrePhysicsTickEvent] = { + def prePhysicsTickObservable(): Observable[PrePhysicsTickEvent] = Observable.create(OverflowStrategy.DropOld(50)) { sub => val c = SingleAssignCancelable() val cl = new PhysicsTickListener { - override def prePhysicsTick(space: PhysicsSpace, tpf: Float): Unit = { - val event = new PrePhysicsTickEvent(space) + override def prePhysicsTick( + space: jmeb.PhysicsSpace, + tpf: Float + ): Unit = { + val event = new PrePhysicsTickEvent(new PhysicsSpace(space)) if (sub.onNext(event) == Ack.Stop) { sub.onComplete() c.cancel() } } - override def physicsTick(space: PhysicsSpace, tpf: Float): Unit = {} + override def physicsTick( + space: jmeb.PhysicsSpace, + tpf: Float + ): Unit = {} } @@ -691,21 +698,22 @@ package object implicits { c := Cancelable(() => space.removeTickListener(cl)) c } - } - - def physicsTickObservable(): Observable[PhysicsTickEvent] = { + def physicsTickObservable(): Observable[PhysicsTickEvent] = Observable.create(OverflowStrategy.DropOld(50)) { sub => val c = SingleAssignCancelable() val cl = new PhysicsTickListener { override def prePhysicsTick( - space: PhysicsSpace, + space: jmeb.PhysicsSpace, tpf: Float ): Unit = {} - override def physicsTick(space: PhysicsSpace, tpf: Float): Unit = { - val event = new PhysicsTickEvent(space) + override def physicsTick( + space: jmeb.PhysicsSpace, + tpf: Float + ): Unit = { + val event = new PhysicsTickEvent(new PhysicsSpace(space)) if (sub.onNext(event) == Ack.Stop) { sub.onComplete() c.cancel() @@ -719,7 +727,6 @@ package object implicits { c := Cancelable(() => space.removeTickListener(cl)) c } - } //TODO Consider creating a typeclass for this def +=(anyObject: Any) = space.add(anyObject) @@ -757,6 +764,7 @@ package object implicits { def +=:(that: ImVector3f) = v += that def *=(that: Vector3f) = v.multLocal(that) def *=(that: ImVector3f) = v.multLocal(that.x, that.y, that.z) + def *=(f: Float) = v.multLocal(f, f, f) def *=:(that: ImVector3f) = v *= that def -=(that: Vector3f) = v.subtractLocal(that) def -=(that: ImVector3f) = v.subtractLocal(that.x, that.y, that.z) @@ -766,6 +774,7 @@ package object implicits { def /=:(that: ImVector3f) = v *= that def unary_- = v.negateLocal() def immutable = ImVector3f(v.x, v.y, v.z) + // def transformImmutable(f: Vector3f => Vector3f) = f(v).immutable } implicit final class ImVector3fExt(private val v: ImVector3f) extends AnyVal { @@ -858,7 +867,7 @@ package object implicits { } - implicit class odinLoggerExt(private val logger: io.odin.Logger[Task]) + implicit class OdinLoggerExt(private val logger: io.odin.Logger[Task]) extends AnyVal { def debugU[M](msg: => M)(implicit render: Render[M], position: Position) = logger.debug(msg).hideErrors @@ -871,4 +880,28 @@ package object implicits { def errorU[M](msg: => M)(implicit render: Render[M], position: Position) = logger.error(msg).hideErrors } + + implicit class TypedActorContextExt[T](private val ctx: ActorContext[T]) + extends AnyVal { + def spawnN[U](behavior: Behavior[U], props: Props = Props.empty)(implicit + name: sourcecode.Name + ): ActorRef[U] = + ctx.spawn(behavior, name.value, props) + } + + implicit class MonixEvalTaskExt[T](private val task: monix.eval.Task[T]) + extends AnyVal { + def toIO = IO.deferAction(implicit s => IO.from(task)) + } + + implicit class MonixBioTaskExt[T](private val task: monix.bio.Task[T]) + extends AnyVal { + def toTask = + monix.eval.Task.deferAction(implicit s => monix.eval.Task.from(task)) + } + + implicit class CoevalEitherExt[L, R](private val coeval: Coeval[Either[L, R]]) + extends AnyVal { + def toIO = coeval.to[Task].hideErrors.rethrow + } } diff --git a/src/main/scala/wow/doge/mygame/math/ImVector3f.scala b/src/main/scala/wow/doge/mygame/math/ImVector3f.scala index ccf3729..f56ba75 100644 --- a/src/main/scala/wow/doge/mygame/math/ImVector3f.scala +++ b/src/main/scala/wow/doge/mygame/math/ImVector3f.scala @@ -14,9 +14,9 @@ object ImVector3f { val UnitY = ImVector3f(0, 1, 0) val UnitZ = ImVector3f(0, 0, 1) val Unit = ImVector3f(1, 1, 1) + val Max = ImVector3f(Float.MaxValue, Float.MaxValue, Float.MaxValue) + val Min = ImVector3f(Float.MinValue, Float.MinValue, Float.MinValue) //format: on - val Max = ImVector3f(Float.MaxValue, Float.MaxValue, Float.MaxValue) - val Min = ImVector3f(Float.MinValue, Float.MinValue, Float.MinValue) implicit val show = new Show[ImVector3f] { def format(f: Float) = f.formatted("%.2f") @@ -39,6 +39,6 @@ object ImVector3f { sqrt(total) } def manhattanDst(v1: ImVector3f, v2: ImVector3f) = - abs(v1.x - v2.x) + abs(v1.y - v2.y) + abs(v1.z - v2.z) + if (v1 === v2) 0 else abs(v1.x - v2.x) + abs(v1.y - v2.y) + abs(v1.z - v2.z) } 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 3d720c1..91be47f 100644 --- a/src/main/scala/wow/doge/mygame/subsystems/events/EventBus.scala +++ b/src/main/scala/wow/doge/mygame/subsystems/events/EventBus.scala @@ -1,10 +1,10 @@ package wow.doge.mygame.subsystems.events import scala.reflect.ClassTag -import scala.util.Random import akka.actor.typed.ActorRef import akka.actor.typed.Behavior +import akka.actor.typed.PostStop import akka.actor.typed.Scheduler import akka.actor.typed.SpawnProtocol import akka.actor.typed.scaladsl.AskPattern._ @@ -59,42 +59,7 @@ object EventBus { new EventBus().eventStreamBehavior(eventStream) } - def observable[E](eventBus: ActorRef[EventBus.Command[E]])(implicit - timeout: Timeout, - scheduler: Scheduler, - spawnProtocol: ActorRef[SpawnProtocol.Command], - ct: ClassTag[E] - ) = - Observable.create[E](OverflowStrategy.DropOld(50)) { sub => - implicit val s = sub.scheduler - val c = SingleAssignCancelable() - val behavior = Behaviors.receive[E] { (ctx, msg) => - if (sub.onNext(msg) == Ack.Stop) { - c.cancel() - Behaviors.stopped - } else Behaviors.same - - } - val actor = - AkkaUtils - .spawnActorL( - behavior, - s"eventBusObservable-${ct.toString}-${Random.nextLong()}" - ) - .mapError(err => new Exception(err.toString)) - .tapError { - case ex => UIO(sub.onError(ex)) - } - - (for { - a <- actor - _ <- eventBus !! Subscribe(a) - _ <- UIO(c := Cancelable(() => eventBus ! Unsubscribe(a))) - } yield ()).runToFuture - c - } - - def observable2[A, B <: A](eventBus: ActorRef[EventBus.Command[A]])(implicit + def observable[A, B <: A](eventBus: ActorRef[EventBus.Command[A]])(implicit timeout: Timeout, scheduler: Scheduler, spawnProtocol: ActorRef[SpawnProtocol.Command], @@ -104,18 +69,25 @@ object EventBus { Observable.create[B](OverflowStrategy.DropOld(50)) { sub => implicit val s = sub.scheduler val c = SingleAssignCancelable() - val behavior = Behaviors.receive[B] { (ctx, msg) => - if (sub.onNext(msg) == Ack.Stop) { - c.cancel() - Behaviors.stopped - } else Behaviors.same - - } + val behavior = Behaviors + .receive[B] { (ctx, msg) => + ctx.log.traceP(s"Emitted $msg") + if (sub.onNext(msg) == Ack.Stop) { + c.cancel() + Behaviors.stopped + } else Behaviors.same + } + .receiveSignal { + case (_, PostStop) => + sub.onComplete() + Behaviors.same + } val actor = AkkaUtils .spawnActorL( behavior, - s"eventBusObservable-${ct.toString}-${math.abs(Random.nextLong())}" + actorName = + Some(s"eventBusObservable-${ct.toString.split("""\.""").last}") ) .mapError(err => new Throwable(err.toString)) .tapError { @@ -155,7 +127,7 @@ class EventBus[A] { eventStream.unsubscribe(subscriber.toClassic) Behaviors.same case s @ ObservableSubscription(replyTo) => - val obs = EventBus.observable2( + val obs = EventBus.observable( ctx.self )(timeout, scheduler, spawnProtocol, ct, s.ct) replyTo ! obs 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 436e37e..3674019 100644 --- a/src/main/scala/wow/doge/mygame/subsystems/events/Events.scala +++ b/src/main/scala/wow/doge/mygame/subsystems/events/Events.scala @@ -1,14 +1,14 @@ package wow.doge.mygame.subsystems.events sealed trait Event -final case object BulletFired extends Event +case object BulletFired extends Event // type BulletFired = BulletFired.type final case class EventWithData(data: Int) extends Event sealed trait TickEvent extends Event object TickEvent { - final case object RenderTick extends TickEvent - final case object PhysicsTick extends TickEvent + case object RenderTick extends TickEvent + case object PhysicsTick extends TickEvent } sealed trait EntityMovementEvent extends Event @@ -22,3 +22,9 @@ object EntityMovementEvent { final case class MovedDown(name: String, pressed: Boolean) extends EntityMovementEvent } + +sealed trait StatsEvent extends Event +object StatsEvent { + case class DamageEvent(hitBy: String, victimName: String, amount: Int) + extends StatsEvent +} diff --git a/src/main/scala/wow/doge/mygame/subsystems/events/EventsModule.scala b/src/main/scala/wow/doge/mygame/subsystems/events/EventsModule.scala index 6fdbbf1..345f88b 100644 --- a/src/main/scala/wow/doge/mygame/subsystems/events/EventsModule.scala +++ b/src/main/scala/wow/doge/mygame/subsystems/events/EventsModule.scala @@ -1,7 +1,5 @@ package wow.doge.mygame.subsystems.events -import java.util.concurrent.TimeoutException - import scala.concurrent.duration._ import scala.reflect.ClassTag @@ -17,6 +15,7 @@ import com.typesafe.scalalogging.{Logger => SLogger} import monix.bio.IO import org.slf4j.event.Level import wow.doge.mygame.AppError +import wow.doge.mygame.AppError.TimeoutError import wow.doge.mygame.implicits._ import wow.doge.mygame.subsystems.events.Event import wow.doge.mygame.subsystems.events.EventBus @@ -34,21 +33,20 @@ class EventsModule( val eventBusLogger = SLogger[EventBus[_]] val playerEventBus: IO[AppError, GameEventBus[PlayerEvent]] = - createEventBus[PlayerEvent]("playerEventBus") + createEventBus[PlayerEvent]() // val playerCameraEventBusTask = - // createEventBus[PlayerCameraEvent]("playerCameraEventBus", Level.DEBUG) + // createEventBus[PlayerCameraEvent](Level.DEBUG) val tickEventBus: IO[AppError, GameEventBus[TickEvent]] = - createEventBus[TickEvent]("tickEventBus", Level.TRACE) + createEventBus[TickEvent](Level.TRACE) - val mainEventBus: IO[AppError, GameEventBus[Event]] = - createEventBus[Event]("mainEventBus") + val mainEventBus: IO[AppError, GameEventBus[Event]] = createEventBus[Event]() def createEventBus[T: ClassTag]( - busName: String, - logLevel: Level = Level.DEBUG - ) = + logLevel: Level = Level.DEBUG, + busName: Option[String] = None + )(implicit name: sourcecode.Name) = spawnProtocol .askL( SpawnProtocol.Spawn[EventBus.Command[T]]( @@ -58,17 +56,16 @@ class EventsModule( .withLogger(eventBusLogger.underlying), Behaviors .supervise(EventBus[T]()) - .onFailure[Exception](SupervisorStrategy.restart) + .onFailure[Exception]( + SupervisorStrategy.restart.withLimit(2, 100.millis) + ) ), - busName, + busName.fold(name.value)(identity), Props.empty, _ ) ) - .onErrorHandleWith { - case ex: TimeoutException => - IO.raiseError(AppError.TimeoutError(ex.getMessage)) - } + .onErrorHandleWith(TimeoutError.from) } diff --git a/src/main/scala/wow/doge/mygame/subsystems/events/PlayerEvents.scala b/src/main/scala/wow/doge/mygame/subsystems/events/PlayerEvents.scala index 97ef1e5..951041c 100644 --- a/src/main/scala/wow/doge/mygame/subsystems/events/PlayerEvents.scala +++ b/src/main/scala/wow/doge/mygame/subsystems/events/PlayerEvents.scala @@ -3,7 +3,7 @@ package wow.doge.mygame.subsystems.events sealed trait PlayerEvent sealed trait PlayerMovementEvent extends PlayerEvent -final object PlayerMovementEvent { +object PlayerMovementEvent { final case class PlayerMovedLeft(pressed: Boolean) extends PlayerMovementEvent final case class PlayerMovedRight(pressed: Boolean) extends PlayerMovementEvent @@ -11,16 +11,16 @@ final object PlayerMovementEvent { extends PlayerMovementEvent final case class PlayerMovedBackward(pressed: Boolean) extends PlayerMovementEvent - final case object PlayerJumped extends PlayerMovementEvent - // final case object PlayerTurnedRight extends PlayerMovementEvent - // final case object PlayerTurnedLeft extends PlayerMovementEvent + case object PlayerJumped extends PlayerMovementEvent + // case object PlayerTurnedRight extends PlayerMovementEvent + // case object PlayerTurnedLeft extends PlayerMovementEvent } sealed trait PlayerCameraEvent extends PlayerEvent -final object PlayerCameraEvent { - final case object CameraLeft extends PlayerCameraEvent - final case object CameraRight extends PlayerCameraEvent - final case object CameraMovedUp extends PlayerCameraEvent - final case object CameraMovedDown extends PlayerCameraEvent +object PlayerCameraEvent { + case object CameraLeft extends PlayerCameraEvent + case object CameraRight extends PlayerCameraEvent + case object CameraMovedUp extends PlayerCameraEvent + case object CameraMovedDown extends PlayerCameraEvent } 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 27a6e80..4fae7c6 100644 --- a/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptCachingActor.scala +++ b/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptCachingActor.scala @@ -45,7 +45,7 @@ object ScriptCachingActor { final case class GetMap(requester: ActorRef[ScriptsMap]) extends Command final case class Put(scriptPath: os.Path, script: ScriptObject) extends Command - private[scriptsystem] final case object NoOp extends Command + private[scriptsystem] case object NoOp extends Command private[scriptsystem] final case class DelegateToChild( scriptActor: ActorRef[ScriptActor.Command], @@ -62,7 +62,7 @@ object ScriptCachingActor { // requester: ActorRef[Map[os.Path, ScriptResult]] // ) extends Command - // final case class Props( + // class Props( // ctx: ActorContext[Command], // scriptActor: ActorRef[ScriptActor.Command] // ) { @@ -221,7 +221,7 @@ class ScriptCachingActor( scriptActor: ActorRef[ScriptActor.Command], scriptPath: os.Path, requester: ActorRef[ScriptResult] - )(implicit timeout: Timeout) = { + )(implicit timeout: Timeout) = ctx.ask(scriptActor, ScriptActor.CompileAny(scriptPath, _)) { case Success(value) => requester ! value @@ -230,16 +230,12 @@ class ScriptCachingActor( ctx.log.errorP(err.reason) NoOp }, - res => { - Put(scriptPath, res) - } + res => Put(scriptPath, res) ) - case Failure(exception) => { + case Failure(exception) => ctx.log.errorP(exception.getMessage) NoOp - } } - } } @@ -251,6 +247,8 @@ object ScriptActorPool { // make sure the workers are restarted if they fail Behaviors .supervise(ScriptActor()) - .onFailure[Exception](SupervisorStrategy.restart) + .onFailure[Exception]( + SupervisorStrategy.restart.withLimit(2, 100.millis) + ) ) } diff --git a/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptSystemModule.scala b/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptSystemModule.scala index a48f668..e1185d2 100644 --- a/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptSystemModule.scala +++ b/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptSystemModule.scala @@ -31,8 +31,8 @@ class ScriptSystemResource( findScriptFiles(os.pwd / "assets" / "scripts") ).hideErrors scriptCacheActor <- AkkaUtils.spawnActorL( - ScriptCachingActor(), - "scriptCachingActor" + ScriptCachingActor() + // "scriptCachingActor" ) } yield scriptCacheActor diff --git a/src/main/scala/wow/doge/mygame/types/package.scala b/src/main/scala/wow/doge/mygame/types/package.scala new file mode 100644 index 0000000..0ab4e6d --- /dev/null +++ b/src/main/scala/wow/doge/mygame/types/package.scala @@ -0,0 +1,17 @@ +package wow.doge.mygame + +import wow.doge.mygame.utils.wrappers.jme.AppNode2 +import com.softwaremill.tagging._ +import wow.doge.mygame.game.GameAppTags +import monix.execution.Scheduler +import io.estatico.newtype.macros.newtype + +package object types { + type RootNode = AppNode2 @@ GameAppTags.RootNode + type GuiNode = AppNode2 @@ GameAppTags.GuiNode + @newtype case class AsyncScheduler(value: Scheduler) + @newtype case class IoScheduler(value: Scheduler) + @newtype case class FxScheduler(value: Scheduler) + @newtype case class JmeScheduler(value: Scheduler) + @newtype case class AkkaScheduler(value: akka.actor.typed.Scheduler) +} diff --git a/src/main/scala/wow/doge/mygame/utils/AkkaUtils.scala b/src/main/scala/wow/doge/mygame/utils/AkkaUtils.scala index b7d9997..fdc508d 100644 --- a/src/main/scala/wow/doge/mygame/utils/AkkaUtils.scala +++ b/src/main/scala/wow/doge/mygame/utils/AkkaUtils.scala @@ -26,18 +26,19 @@ object AkkaUtils { ) def spawnActorL[T]( behavior: Behavior[T], - actorName: String, + actorName: Option[String] = None, props: Props = Props.empty )(implicit timeout: Timeout, scheduler: Scheduler, - spawnProtocol: ActorRef[SpawnProtocol.Command] + spawnProtocol: ActorRef[SpawnProtocol.Command], + name: sourcecode.Name ) = spawnProtocol .askL[ActorRef[T]]( SpawnProtocol.Spawn( behavior, - actorName, + actorName.fold(name.value)(identity), props, _ ) diff --git a/src/main/scala/wow/doge/mygame/utils/GenericTimerActor.scala b/src/main/scala/wow/doge/mygame/utils/GenericTimerActor.scala index 10e6459..51d8857 100644 --- a/src/main/scala/wow/doge/mygame/utils/GenericTimerActor.scala +++ b/src/main/scala/wow/doge/mygame/utils/GenericTimerActor.scala @@ -12,8 +12,8 @@ import wow.doge.mygame.implicits._ object GenericTimerActor { sealed trait Command - final case object Start extends Command - final case object Stop extends Command + case object Start extends Command + case object Stop extends Command private case object Tick extends Command case class TimerKey(seed: Long) diff --git a/src/main/scala/wow/doge/mygame/utils/IOUtils.scala b/src/main/scala/wow/doge/mygame/utils/IOUtils.scala index e4c8b67..f5f3ff6 100644 --- a/src/main/scala/wow/doge/mygame/utils/IOUtils.scala +++ b/src/main/scala/wow/doge/mygame/utils/IOUtils.scala @@ -1,12 +1,17 @@ package wow.doge.mygame.utils import monix.bio.IO +import monix.bio.Task +import monix.eval.Coeval object IOUtils { - def toTask[T](bio: monix.bio.IO[Throwable, T]) = + def toTask[T](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)) + def fromCoevalEither[L, R](coeval: Coeval[Either[L, R]]) = + coeval.to[Task].hideErrors.rethrow + } diff --git a/src/main/scala/wow/doge/mygame/utils/wrappers/jme/Node.scala b/src/main/scala/wow/doge/mygame/utils/wrappers/jme/Node.scala index 4aa7430..31babdc 100644 --- a/src/main/scala/wow/doge/mygame/utils/wrappers/jme/Node.scala +++ b/src/main/scala/wow/doge/mygame/utils/wrappers/jme/Node.scala @@ -5,7 +5,6 @@ import cats.syntax.eq._ import com.jme3.light.Light import com.jme3.{scene => jmes} import monix.bio.IO -import monix.bio.Task import monix.bio.UIO import monix.execution.annotations.UnsafeBecauseImpure import monix.reactive.Observable @@ -71,6 +70,8 @@ abstract class NodeWrapper2 protected (node: jmes.Node) { import NodeWrapper2._ def name = node.getName() def children: Observable[jmes.Spatial] = node.observableChildren + def breadthFirstTraversal = node.observableBreadthFirst() + def depthFirstTraversal = node.observableDepthFirst() def attachChild(n: jmes.Spatial): IO[AddNodeToItselfError.type, Unit] = IO { node.attachChild(n); () }.onErrorHandleWith { case ex: IllegalArgumentException => @@ -86,17 +87,10 @@ abstract class NodeWrapper2 protected (node: jmes.Node) { else IO.unit } def remove(n: jmes.Spatial) = UIO(node.detachChild(n)) - def remove(wn: Node2) = - UIO(node.detachChild(wn.unsafeDelegate)) - def addLight(light: Light) = - UIO { - node.addLight(light) - } - def removeLight(light: Light) = - UIO { - node.removeLight(light) - } - def asSpatial: Task[jmes.Spatial] = UIO(node) + def remove(wn: Node2) = UIO(node.detachChild(wn.unsafeDelegate)) + def addLight(light: Light) = UIO(node.addLight(light)) + def removeLight(light: Light) = UIO(node.removeLight(light)) + def asSpatial: UIO[jmes.Spatial] = UIO(node) } object NodeWrapper2 { sealed trait Error diff --git a/src/main/scala/wow/doge/mygame/utils/wrappers/jme/PhysicsSpace.scala b/src/main/scala/wow/doge/mygame/utils/wrappers/jme/PhysicsSpace.scala index b689eee..7c9de7a 100644 --- a/src/main/scala/wow/doge/mygame/utils/wrappers/jme/PhysicsSpace.scala +++ b/src/main/scala/wow/doge/mygame/utils/wrappers/jme/PhysicsSpace.scala @@ -1,57 +1,91 @@ package wow.doge.mygame.utils.wrappers.jme +import com.jme3.bullet.control.PhysicsControl +import com.jme3.bullet.joints.PhysicsJoint import com.jme3.{bullet => jmeb} import com.jme3.{scene => jmes} import monix.bio.UIO import wow.doge.mygame.implicits._ final class PhysicsSpace(space: jmeb.PhysicsSpace) { - def add(anyObject: Any) = UIO(space.add(anyObject)) + def add[T](addable: T)(implicit P: PhysicsSpaceAddable[T]) = + UIO(P.addToSpace(addable, space)) - def remove(anyObject: Any) = - UIO { - space.remove(anyObject) - space - } + def remove(anyObject: Any) = UIO(space.remove(anyObject)) def addAll(spatial: jmes.Spatial) = UIO(space.addAll(spatial)) - def removeAll(spatial: jmes.Spatial) = - UIO { - space.removeAll(spatial) - space - } + def removeAll(spatial: jmes.Spatial) = UIO(space.removeAll(spatial)) def collisionObservable = space.collisionObservable() + + // space.enqueue(() => ()) + def physicsTickObservable = space.physicsTickObservable() + + def prePhysicsTickObservable = space.prePhysicsTickObservable() } object PhysicsSpace { implicit final class PhysicsSpaceOps(private val space: PhysicsSpace) extends AnyVal { - def +=(anyObject: Any) = space.add(anyObject) + def +=[T: PhysicsSpaceAddable](addable: T) = space.add(addable) - def :+(anyObject: Any) = { - space.add(anyObject) - space - } - def -(anyObject: Any) = { - space.remove(anyObject) - space - } + def :+[T: PhysicsSpaceAddable](addable: T) = + for { + _ <- space.add(addable) + } yield space + def -(anyObject: Any) = + for { + _ <- space.remove(anyObject) + } yield space def -=(anyObject: Any) = space.remove(anyObject) def +=(spatial: jmes.Spatial) = space.addAll(spatial) - def :+(spatial: jmes.Spatial) = { - space.addAll(spatial) - space - } + // def :+(spatial: jmes.Spatial) = { + // space.addAll(spatial) + // space + // } - def -(spatial: jmes.Spatial) = { - space.removeAll(spatial) - space - } + // def -(spatial: jmes.Spatial) = { + // space.removeAll(spatial) + // space + // } def -=(spatial: jmes.Spatial) = space.removeAll(spatial) } } + +trait PhysicsSpaceAddable[-T] { + def addToSpace(inst: T, space: jmeb.PhysicsSpace): Unit +} + +object PhysicsSpaceAddable { + implicit val physicsSpaceAddableForPhysControl = + new PhysicsSpaceAddable[PhysicsControl] { + + override def addToSpace( + inst: PhysicsControl, + space: jmeb.PhysicsSpace + ): Unit = space.add(inst) + + } + implicit val physicsSpaceAddableForSpatial = + new PhysicsSpaceAddable[jmes.Spatial] { + + override def addToSpace( + inst: jmes.Spatial, + space: jmeb.PhysicsSpace + ): Unit = space.add(inst) + + } + implicit val physicsSpaceAddableForPhysJoint = + new PhysicsSpaceAddable[PhysicsJoint] { + + override def addToSpace( + inst: PhysicsJoint, + space: jmeb.PhysicsSpace + ): Unit = space.add(inst) + + } +} diff --git a/src/test/scala/wow/doge/mygame/ActorTimeoutTest.scala b/src/test/scala/wow/doge/mygame/ActorTimeoutTest.scala index 9d8d673..be68b03 100644 --- a/src/test/scala/wow/doge/mygame/ActorTimeoutTest.scala +++ b/src/test/scala/wow/doge/mygame/ActorTimeoutTest.scala @@ -17,14 +17,13 @@ class ActorTimeoutTest extends AnyFunSuite with BeforeAndAfterAll { implicit val timeout = Timeout(1.millis) test("timeoutTest") { - val fut = as.ask(MyActor.GetInt(_)) + val fut = as.ask(MyActor.GetInt) val res = Await.result(fut, 1.second) assert(res == 1) } - override protected def afterAll(): Unit = { + override protected def afterAll(): Unit = as.terminate() - } } diff --git a/src/test/scala/wow/doge/mygame/ReaderT_Test.scala b/src/test/scala/wow/doge/mygame/ReaderT_Test.scala index 9b8d29a..dc20690 100644 --- a/src/test/scala/wow/doge/mygame/ReaderT_Test.scala +++ b/src/test/scala/wow/doge/mygame/ReaderT_Test.scala @@ -5,6 +5,12 @@ import monix.bio.UIO import org.scalatest.funsuite.AnyFunSuite import monix.execution.Scheduler.Implicits.global import monix.bio.IO +import cats.mtl.Ask +import cats.mtl.implicits._ +import cats.effect.LiftIO +import monix.bio.Task +import cats.data.Kleisli +import monix.bio.IOLift class ReaderT_Test extends AnyFunSuite { @@ -33,8 +39,36 @@ class ReaderT_Test extends AnyFunSuite { // uio.runSyncUnsafe() // } + implicit val L = + new LiftIO[ReaderT[IO[String, ?], Environment, *]] { + + override def liftIO[A]( + ioa: cats.effect.IO[A] + ): ReaderT[IO[String, ?], Environment, A] = + ReaderT(_ => IO.from(ioa).onErrorHandleWith(_ => IO.raiseError("whew"))) + + // override def liftIO[A](ioa: cats.effect.IO[A]): monix.bio.Task[A] = + // Task.from(ioa) + + } + + implicit val L2 = new IOLift[ReaderT[IO[String, ?], Environment, *]] { + + override def apply[A]( + task: monix.bio.Task[A] + ): ReaderT[IO[String, ?], Environment, A] = + ReaderT(_ => IO.from(task).onErrorHandleWith(_ => IO.raiseError("whew"))) + + } + + sealed trait AppError + + type RIO[S, E, A] = ReaderT[IO[E, ?], S, A] + type EIO[S, A] = RIO[S, AppError, A] + type AppIO[A] = RIO[Environment, AppError, A] + test("2") { - def fun1: ReaderT[IO[String, ?], String, Unit] = + def fun1: RIO[String, String, Unit] = ReaderT(s => IO(println(s)).onErrorHandleWith(_ => IO.raiseError("wow"))) def fun2: ReaderT[IO[String, ?], Int, Unit] = ReaderT(num => @@ -44,6 +78,8 @@ class ReaderT_Test extends AnyFunSuite { for { env <- ReaderT.ask[IO[String, ?], Environment] } yield () + def test[F[_]]()(implicit A: Ask[F, Int]) = A.ask[Int] + // def synctest[F[_]] def fun4: ReaderT[IO[String, ?], Environment, Unit] = for { env <- ReaderT.ask[IO[String, ?], Environment] @@ -52,12 +88,15 @@ class ReaderT_Test extends AnyFunSuite { ) _ <- fun3 } yield () + val topkek = + test[ReaderT[IO[String, ?], Int, *]]().local[Environment](_.num) def app: ReaderT[IO[String, ?], Environment, Unit] = for { _ <- fun1.local[Environment](_.str) _ <- fun2.local[Environment](_.num) _ <- fun3 _ <- fun4 + _ <- topkek } yield () val io: IO[String, Unit] = app.run(Environment("hello", 50))