Browse Source

hmph

development
Rohan Sircar 3 years ago
parent
commit
67a2bc4385
  1. 18
      build.sbt
  2. 1
      project/plugins.sbt
  3. 17
      src/main/scala/wow/doge/mygame/AppError.scala
  4. 250
      src/main/scala/wow/doge/mygame/MainApp.scala
  5. 56
      src/main/scala/wow/doge/mygame/game/GameApp.scala
  6. 12
      src/main/scala/wow/doge/mygame/game/GameAppActor.scala
  7. 11
      src/main/scala/wow/doge/mygame/game/SimpleAppExt.scala
  8. 294
      src/main/scala/wow/doge/mygame/game/appstates/PlayerMovementState.scala
  9. 71
      src/main/scala/wow/doge/mygame/game/controls/CameraMovementControls.scala
  10. 42
      src/main/scala/wow/doge/mygame/game/entities/NpcActorSupervisor.scala
  11. 295
      src/main/scala/wow/doge/mygame/game/entities/player/PlayerActorSupervisor.scala
  12. 149
      src/main/scala/wow/doge/mygame/game/entities/player/PlayerActorSupervisor2.scala
  13. 15
      src/main/scala/wow/doge/mygame/game/entities/player/PlayerCameraActor.scala
  14. 116
      src/main/scala/wow/doge/mygame/game/entities/player/PlayerController.scala
  15. 8
      src/main/scala/wow/doge/mygame/game/entities/player/PlayerEventListeners.scala
  16. 55
      src/main/scala/wow/doge/mygame/game/subsystems/input/GameInputHandler.scala
  17. 10
      src/main/scala/wow/doge/mygame/game/subsystems/movement/CanMove.scala
  18. 296
      src/main/scala/wow/doge/mygame/game/subsystems/movement/MovementActor.scala
  19. 163
      src/main/scala/wow/doge/mygame/implicits/package.scala
  20. 6
      src/main/scala/wow/doge/mygame/math/ImVector3f.scala
  21. 64
      src/main/scala/wow/doge/mygame/subsystems/events/EventBus.scala
  22. 12
      src/main/scala/wow/doge/mygame/subsystems/events/Events.scala
  23. 29
      src/main/scala/wow/doge/mygame/subsystems/events/EventsModule.scala
  24. 18
      src/main/scala/wow/doge/mygame/subsystems/events/PlayerEvents.scala
  25. 18
      src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptCachingActor.scala
  26. 4
      src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptSystemModule.scala
  27. 17
      src/main/scala/wow/doge/mygame/types/package.scala
  28. 7
      src/main/scala/wow/doge/mygame/utils/AkkaUtils.scala
  29. 4
      src/main/scala/wow/doge/mygame/utils/GenericTimerActor.scala
  30. 7
      src/main/scala/wow/doge/mygame/utils/IOUtils.scala
  31. 18
      src/main/scala/wow/doge/mygame/utils/wrappers/jme/Node.scala
  32. 90
      src/main/scala/wow/doge/mygame/utils/wrappers/jme/PhysicsSpace.scala
  33. 5
      src/test/scala/wow/doge/mygame/ActorTimeoutTest.scala
  34. 41
      src/test/scala/wow/doge/mygame/ReaderT_Test.scala

18
build.sbt

@ -19,13 +19,6 @@ lazy val javaFXModules =
Seq("base", "controls", "fxml", "graphics", "media", "swing", "web") Seq("base", "controls", "fxml", "graphics", "media", "swing", "web")
lazy val root = (project in file(".")).settings( 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", name := "mygame",
organization := "wow.doge", organization := "wow.doge",
version := "1.0-SNAPSHOT", version := "1.0-SNAPSHOT",
@ -71,7 +64,9 @@ lazy val root = (project in file(".")).settings(
"org.recast4j" % "recast" % "1.2.5", "org.recast4j" % "recast" % "1.2.5",
"org.recast4j" % "detour" % "1.2.5", "org.recast4j" % "detour" % "1.2.5",
"com.lihaoyi" %% "pprint" % "0.6.0", "com.lihaoyi" %% "pprint" % "0.6.0",
"org.scalatest" %% "scalatest" % "3.2.2" % "test"
"org.scalatest" %% "scalatest" % "3.2.2" % "test",
"org.typelevel" %% "cats-mtl" % "1.1.1",
"io.estatico" %% "newtype" % "0.4.4"
), ),
// Determine OS version of JavaFX binaries // Determine OS version of JavaFX binaries
@ -135,4 +130,11 @@ addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1")
addCompilerPlugin( addCompilerPlugin(
"org.typelevel" %% "kind-projector" % "0.11.1" cross CrossVersion.full "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" ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.4.3"

1
project/plugins.sbt

@ -1,3 +1,2 @@
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.0") addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.0")
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.23") addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.23")
addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1")

17
src/main/scala/wow/doge/mygame/AppError.scala

@ -2,7 +2,8 @@ package wow.doge.mygame
import java.util.concurrent.TimeoutException import java.util.concurrent.TimeoutException
import cats.data.Reader
import cats.Show
import cats.kernel.Eq
import monix.bio.IO import monix.bio.IO
import wow.doge.mygame.utils.wrappers.jme.AssetManager import wow.doge.mygame.utils.wrappers.jme.AssetManager
import wow.doge.mygame.utils.wrappers.jme.CollisionShapeFactory import wow.doge.mygame.utils.wrappers.jme.CollisionShapeFactory
@ -12,21 +13,10 @@ sealed trait AppError
object AppError { object AppError {
final case class TimeoutError(reason: String) extends AppError final case class TimeoutError(reason: String) extends AppError
object TimeoutError { object TimeoutError {
def reader =
Reader[Throwable, TimeoutError] {
case ex: TimeoutException => TimeoutError(ex.getMessage)
}
def from: PartialFunction[Throwable, IO[TimeoutError, Nothing]] = { def from: PartialFunction[Throwable, IO[TimeoutError, Nothing]] = {
case ex: TimeoutException => IO.raiseError(TimeoutError(ex.getMessage)) 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 AssetManagerError(error: AssetManager.Error) extends AppError
final case class AppNodeError(error: NodeWrapper2.Error) extends AppError final case class AppNodeError(error: NodeWrapper2.Error) extends AppError
final case class CollisionShapeCreationFailed( final case class CollisionShapeCreationFailed(
@ -37,4 +27,7 @@ object AppError {
case ex: TimeoutException => IO.raiseError(TimeoutError(ex.getMessage)) case ex: TimeoutException => IO.raiseError(TimeoutError(ex.getMessage))
} }
implicit val show = Show.fromToString[AppError]
implicit val eq = Eq.fromUniversalEquals[AppError]
} }

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

@ -1,7 +1,5 @@
package wow.doge.mygame package wow.doge.mygame
import java.util.concurrent.TimeoutException
import scala.concurrent.duration._ import scala.concurrent.duration._
import akka.actor.typed.ActorRef import akka.actor.typed.ActorRef
@ -19,10 +17,8 @@ import com.jme3.material.MaterialDef
import com.jme3.math.ColorRGBA import com.jme3.math.ColorRGBA
import com.jme3.math.FastMath import com.jme3.math.FastMath
import com.jme3.renderer.Camera import com.jme3.renderer.Camera
import com.jme3.renderer.RenderManager
import com.jme3.renderer.ViewPort import com.jme3.renderer.ViewPort
import com.jme3.scene.Node import com.jme3.scene.Node
import com.jme3.scene.control.AbstractControl
import com.softwaremill.macwire._ import com.softwaremill.macwire._
import com.softwaremill.tagging._ import com.softwaremill.tagging._
import io.odin.Logger import io.odin.Logger
@ -33,38 +29,44 @@ import monix.bio.UIO
import monix.eval.Coeval import monix.eval.Coeval
import monix.reactive.Observable import monix.reactive.Observable
import scalafx.scene.control.TextArea import scalafx.scene.control.TextArea
import wow.doge.mygame.AppError.TimeoutError
import wow.doge.mygame.executors.Schedulers import wow.doge.mygame.executors.Schedulers
import wow.doge.mygame.game.GameApp import wow.doge.mygame.game.GameApp
import wow.doge.mygame.game.GameAppActor import wow.doge.mygame.game.GameAppActor
import wow.doge.mygame.game.GameAppResource 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.EntityIds
import wow.doge.mygame.game.entities.NpcActorSupervisor import wow.doge.mygame.game.entities.NpcActorSupervisor
import wow.doge.mygame.game.entities.NpcMovementActor import wow.doge.mygame.game.entities.NpcMovementActor
import wow.doge.mygame.game.entities.PlayerActorSupervisor import wow.doge.mygame.game.entities.PlayerActorSupervisor
import wow.doge.mygame.game.entities.PlayerController import wow.doge.mygame.game.entities.PlayerController
import wow.doge.mygame.game.entities.PlayerControllerTags
import wow.doge.mygame.game.subsystems.input.GameInputHandler import wow.doge.mygame.game.subsystems.input.GameInputHandler
import wow.doge.mygame.game.subsystems.level.DefaultGameLevel import wow.doge.mygame.game.subsystems.level.DefaultGameLevel
import wow.doge.mygame.implicits._ import wow.doge.mygame.implicits._
import wow.doge.mygame.launcher.Launcher import wow.doge.mygame.launcher.Launcher
import wow.doge.mygame.launcher.Launcher.LauncherResult import wow.doge.mygame.launcher.Launcher.LauncherResult
import wow.doge.mygame.math.ImVector3f 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.EventBus.ObservableSubscription
import wow.doge.mygame.subsystems.events.EventsModule import wow.doge.mygame.subsystems.events.EventsModule
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
import wow.doge.mygame.subsystems.events.PlayerEvent import wow.doge.mygame.subsystems.events.PlayerEvent
import wow.doge.mygame.subsystems.events.PlayerMovementEvent import wow.doge.mygame.subsystems.events.PlayerMovementEvent
import wow.doge.mygame.subsystems.events.StatsEvent.DamageEvent
import wow.doge.mygame.subsystems.events.TickEvent import wow.doge.mygame.subsystems.events.TickEvent
import wow.doge.mygame.subsystems.scriptsystem.ScriptInitMode import wow.doge.mygame.subsystems.scriptsystem.ScriptInitMode
import wow.doge.mygame.subsystems.scriptsystem.ScriptSystemResource import wow.doge.mygame.subsystems.scriptsystem.ScriptSystemResource
import wow.doge.mygame.utils.AkkaUtils import wow.doge.mygame.utils.AkkaUtils
import wow.doge.mygame.utils.GenericConsoleStream import wow.doge.mygame.utils.GenericConsoleStream
import wow.doge.mygame.utils.IOUtils import wow.doge.mygame.utils.IOUtils
import wow.doge.mygame.utils.wrappers.jme.AppNode2
import wow.doge.mygame.utils.wrappers.jme.AssetManager import wow.doge.mygame.utils.wrappers.jme.AssetManager
import wow.doge.mygame.utils.wrappers.jme.PhysicsSpace import wow.doge.mygame.utils.wrappers.jme.PhysicsSpace
import wow.doge.mygame.subsystems.events.PlayerCameraEvent
import com.jme3.math.Quaternion
import com.jme3.math.Vector3f
import wow.doge.mygame.types._
import wow.doge.mygame.game.controls.FollowControl
import wow.doge.mygame.game.subsystems.input.PlayerCameraInput
class MainApp( class MainApp(
logger: Logger[Task], logger: Logger[Task],
jmeThread: monix.execution.Scheduler, jmeThread: monix.execution.Scheduler,
@ -94,13 +96,8 @@ class MainApp(
tickEventBus <- eventsModule.tickEventBus tickEventBus <- eventsModule.tickEventBus
obs <- obs <-
playerEventBus playerEventBus
.askL[Observable[PlayerMovementEvent]](
ObservableSubscription(_)
)
.onErrorHandleWith {
case ex: TimeoutException =>
IO.raiseError(AppError.TimeoutError(ex.getMessage()))
}
.askL[Observable[PlayerMovementEvent]](ObservableSubscription(_))
.onErrorHandleWith(TimeoutError.from)
_ <- _ <-
IOUtils IOUtils
.toIO( .toIO(
@ -121,7 +118,7 @@ class MainApp(
// jfxUI <- gameApp.jfxUI // jfxUI <- gameApp.jfxUI
gameAppActor <- gameApp.spawnGameActor( gameAppActor <- gameApp.spawnGameActor(
GameAppActor.Props(tickEventBus).behavior, GameAppActor.Props(tickEventBus).behavior,
"gameAppActor"
Some("gameAppActor")
) )
_ <- gameAppActor !! GameAppActor.Start _ <- gameAppActor !! GameAppActor.Start
consoleTextArea <- UIO(new TextArea { consoleTextArea <- UIO(new TextArea {
@ -138,7 +135,7 @@ class MainApp(
_ <- _ <-
wire[MainAppDelegate] wire[MainAppDelegate]
.init() .init()
.executeOn(gameApp.scheduler)
.executeOn(gameApp.scheduler.value)
} yield fib } yield fib
def gameInit: Resource[UIO, Either[AppError, Fiber[Nothing, Unit]]] = def gameInit: Resource[UIO, Either[AppError, Fiber[Nothing, Unit]]] =
@ -184,6 +181,7 @@ class MainApp(
class MainAppDelegate( class MainAppDelegate(
gameApp: GameApp, gameApp: GameApp,
loggerL: Logger[Task], loggerL: Logger[Task],
mainEventBus: GameEventBus[Event],
playerEventBus: GameEventBus[PlayerEvent], playerEventBus: GameEventBus[PlayerEvent],
tickEventBus: GameEventBus[TickEvent], tickEventBus: GameEventBus[TickEvent],
inputManager: InputManager, inputManager: InputManager,
@ -192,7 +190,8 @@ class MainAppDelegate(
camera: Camera, camera: Camera,
viewPort: ViewPort, viewPort: ViewPort,
enqueueR: Function1[() => Unit, Unit], enqueueR: Function1[() => Unit, Unit],
rootNode: AppNode2 @@ GameAppTags.RootNode
rootNode: RootNode,
schedulers: Schedulers
)(implicit )(implicit
spawnProtocol: ActorRef[SpawnProtocol.Command], spawnProtocol: ActorRef[SpawnProtocol.Command],
timeout: Timeout, timeout: Timeout,
@ -213,17 +212,104 @@ class MainAppDelegate(
// _ <- Task(consoleStream.println("text")) // _ <- Task(consoleStream.println("text"))
level <- DefaultGameLevel(assetManager, viewPort) level <- DefaultGameLevel(assetManager, viewPort)
_ <- level.addToGame(rootNode, physicsSpace) _ <- level.addToGame(rootNode, physicsSpace)
_ <- createPlayerController()
playerActor <- createPlayerController()
// .onErrorRestart(3) // .onErrorRestart(3)
_ <- wire[GameInputHandler.Props].begin _ <- wire[GameInputHandler.Props].begin
// .onErrorRestart(3) // .onErrorRestart(3)
johnActor <- createTestNpc("John") johnActor <- createTestNpc("John")
// _ <- johnActor !! NpcActorSupervisor.Move(ImVector3f(0, 0, 20)) // _ <- 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 // IOUtils
// .toIO( // .toIO(
@ -238,12 +324,12 @@ class MainAppDelegate(
def createPlayerController( def createPlayerController(
// appScheduler: monix.execution.Scheduler // appScheduler: monix.execution.Scheduler
): IO[AppError, ActorRef[PlayerActorSupervisor.Command]] = {
): IO[AppError, PlayerActorSupervisor.Ref] = {
val playerPos = ImVector3f.Zero val playerPos = ImVector3f.Zero
val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o" val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o"
val playerPhysicsControl = val playerPhysicsControl =
PlayerController.Defaults.defaultPlayerPhysicsControl PlayerController.Defaults.defaultPlayerPhysicsControl
.taggedWith[PlayerControllerTags.PlayerTag]
for { for {
playerModel <- playerModel <-
assetManager assetManager
@ -260,42 +346,115 @@ class MainAppDelegate(
) )
cameraPivotNode <- UIO( cameraPivotNode <- UIO(
new Node(EntityIds.CameraPivot.value) new Node(EntityIds.CameraPivot.value)
.withControl(new FollowControl(playerNode))
.taggedWith[PlayerControllerTags.PlayerCameraPivotNode]
.withControl(
new FollowControl(playerNode)
)
.taggedWith[PlayerController.Tags.PlayerCameraPivotNode]
) )
camNode <- UIO( camNode <- UIO(
PlayerController.Defaults PlayerController.Defaults
.defaultCamerNode(camera, playerPos) .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 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 } yield playerActor
} }
def createTestNpc( def createTestNpc(
// appScheduler: monix.execution.Scheduler, // appScheduler: monix.execution.Scheduler,
npcName: String npcName: String
): IO[AppError, ActorRef[NpcActorSupervisor.Command]] = {
): IO[AppError, NpcActorSupervisor.Ref] = {
val initialPos = ImVector3f(50, 5, 0) val initialPos = ImVector3f(50, 5, 0)
val npcPhysicsControl = new BetterCharacterControl(1.5f, 6f, 1f) val npcPhysicsControl = new BetterCharacterControl(1.5f, 6f, 1f)
// (1f, 2.1f, 10f) // (1f, 2.1f, 10f)
.withJumpForce(ImVector3f(0, 5f, 0)) .withJumpForce(ImVector3f(0, 5f, 0))
val npcActorTask = AkkaUtils.spawnActorL( val npcActorTask = AkkaUtils.spawnActorL(
NpcActorSupervisor
.Props(
new NpcMovementActor.Props(
enqueueR,
initialPos,
// tickEventBus,
npcName,
npcPhysicsControl
).behavior,
new NpcActorSupervisor.Props(
new NpcMovementActor.Props(
enqueueR,
initialPos,
npcName, npcName,
initialPos
)
.behavior,
s"${npcName}-npcActorSupervisor"
npcPhysicsControl
).behavior,
npcName,
initialPos
).behavior,
actorName = Some(s"${npcName}-npcActorSupervisor")
) )
(for { (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 = {}
}

56
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.AkkaUtils
import wow.doge.mygame.utils.GenericTimerActor import wow.doge.mygame.utils.GenericTimerActor
import wow.doge.mygame.utils.wrappers.jme._ import wow.doge.mygame.utils.wrappers.jme._
import wow.doge.mygame.types._
object GameAppTags { object GameAppTags {
sealed trait RootNode sealed trait RootNode
sealed trait GuiNode sealed trait GuiNode
} }
@ -49,34 +48,39 @@ class GameApp private[game] (
logger: Logger[Task], logger: Logger[Task],
app: SimpleAppExt, app: SimpleAppExt,
gameActor: ActorRef[TestGameActor.Command], 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 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 camera = UIO(app.getCamera())
def viewPort = UIO(app.getViewPort()) def viewPort = UIO(app.getViewPort())
// def rootNode = UIO(app.getRootNode().taggedWith[GameAppTags.RootNode])
val rootNode =
val rootNode: RootNode =
AppNode2(app.getRootNode()).taggedWith[GameAppTags.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 enqueue(cb: () => Unit) = app.enqueueR(() => cb())
def enqueueL[T](cb: () => T): Task[T] = app.enqueueL(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]( def spawnGameActor[T](
behavior: Behavior[T], behavior: Behavior[T],
actorName: String,
actorName: Option[String] = None,
props: Props = Dispatchers.jmeDispatcher props: Props = Dispatchers.jmeDispatcher
)(implicit scheduler: akka.actor.typed.Scheduler) =
)(implicit name: sourcecode.Name) =
AkkaUtils.spawnActorL(behavior, actorName, props)( AkkaUtils.spawnActorL(behavior, actorName, props)(
2.seconds, 2.seconds,
scheduler, scheduler,
gameSpawnProtocol
gameSpawnProtocol,
name
) )
def scheduler = app.scheduler
def scheduler = JmeScheduler(app.scheduler)
def jfxUI = JFxUI(app) def jfxUI = JFxUI(app)
} }
@ -92,10 +96,9 @@ class GameAppResource(
) { ) {
def resource def resource
: Resource[UIO, Either[AppError, (GameApp, Fiber[Nothing, Unit])]] = : Resource[UIO, Either[AppError, (GameApp, Fiber[Nothing, Unit])]] =
Resource.make {
lazy val bullet = new BulletAppState
Resource.make(
(for { (for {
app <- UIO(new SimpleAppExt(schedulers, bullet))
app <- UIO(new SimpleAppExt(schedulers, new BulletAppState))
_ <- UIO(JMERunner.runner = Some(app.enqueue _)) _ <- UIO(JMERunner.runner = Some(app.enqueue _))
_ <- UIO { _ <- UIO {
val settings = new AppSettings(true) val settings = new AppSettings(true)
@ -113,23 +116,22 @@ class GameAppResource(
_ <- Task.deferFuture(app.started).onErrorHandleWith(TimeoutError.from) _ <- Task.deferFuture(app.started).onErrorHandleWith(TimeoutError.from)
testGameActor <- AkkaUtils.spawnActorL( testGameActor <- AkkaUtils.spawnActorL(
new TestGameActor.Props().create, new TestGameActor.Props().create,
"testGameActor",
Dispatchers.jmeDispatcher
Some("testGameActor"),
props = Dispatchers.jmeDispatcher
) )
sp <- sp <-
testGameActor testGameActor
.askL(TestGameActor.GetSpawnProtocol(_))
.askL(TestGameActor.GetSpawnProtocol)
.onErrorHandleWith(TimeoutError.from) .onErrorHandleWith(TimeoutError.from)
gameApp <- UIO(new GameApp(logger, app, testGameActor, sp))
gameApp <- UIO(new GameApp(logger, app, testGameActor, sp, scheduler))
_ <- UIO { _ <- UIO {
val fut = () => testGameActor.ask(TestGameActor.Stop).flatten val fut = () => testGameActor.ask(TestGameActor.Stop).flatten
app.cancelToken = Some(fut) app.cancelToken = Some(fut)
} }
} yield (gameApp, fib)).attempt } 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 { object SpawnSystem {
sealed trait Result sealed trait Result
final case object Ok extends Result
case object Ok extends Result
sealed trait Complete sealed trait Complete
final case object Complete extends Complete
case object Complete extends Complete
sealed trait SpawnRequest sealed trait SpawnRequest
final case class SpawnSpatial(nodeTask: Task[Node]) extends SpawnRequest final case class SpawnSpatial(nodeTask: Task[Node]) extends SpawnRequest

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

@ -24,18 +24,18 @@ object GameAppActor {
Behaviors.setup[Command] { ctx => Behaviors.setup[Command] { ctx =>
ctx.log.infoP("Hello from GameAppActor") ctx.log.infoP("Hello from GameAppActor")
val renderTickGenerator = val renderTickGenerator =
ctx.spawn(
ctx.spawnN(
Behaviors Behaviors
.supervise(renderTickGeneratorBehavior) .supervise(renderTickGeneratorBehavior)
.onFailure[Exception](SupervisorStrategy.restart),
"tickGeneratorActor"
.onFailure[Exception](
SupervisorStrategy.restart.withLimit(2, 100.millis)
)
) )
val tickGeneratorTimer = ctx.spawn(
val tickGeneratorTimer = ctx.spawnN(
GenericTimerActor GenericTimerActor
.Props(renderTickGenerator, TickGenerator.Send, 10.millis) .Props(renderTickGenerator, TickGenerator.Send, 10.millis)
.behavior,
"tickGeneratorTimer"
.behavior
) )
Behaviors.receiveMessage { Behaviors.receiveMessage {

11
src/main/scala/wow/doge/mygame/game/SimpleAppExt.scala

@ -14,7 +14,6 @@ import monix.execution.Scheduler
import monix.execution.atomic.Atomic import monix.execution.atomic.Atomic
import wow.doge.mygame.executors.GUIExecutorService import wow.doge.mygame.executors.GUIExecutorService
import wow.doge.mygame.executors.Schedulers import wow.doge.mygame.executors.Schedulers
// import wow.doge.mygame.implicits._
class SimpleAppExt( class SimpleAppExt(
schedulers: Schedulers, schedulers: Schedulers,
val bulletAppState: BulletAppState, val bulletAppState: BulletAppState,
@ -28,11 +27,16 @@ class SimpleAppExt(
private val taskQueue2 = Atomic(Queue.empty[MyTask[_]]) private val taskQueue2 = Atomic(Queue.empty[MyTask[_]])
private val startSignal: CancelablePromise[Unit] = CancelablePromise() private val startSignal: CancelablePromise[Unit] = CancelablePromise()
private val terminationSignal: CancelablePromise[Unit] = CancelablePromise()
var cancelToken: Option[() => Future[Unit]] = None var cancelToken: Option[() => Future[Unit]] = None
def started: CancelableFuture[Unit] = startSignal.future def started: CancelableFuture[Unit] = startSignal.future
def whenTerminated: CancelableFuture[Unit] = terminationSignal.future
// override def physicsSpace: PhysicsSpace = ???
override def simpleInitApp(): Unit = { override def simpleInitApp(): Unit = {
stateManager.attach(bulletAppState) stateManager.attach(bulletAppState)
startSignal.success(()) startSignal.success(())
@ -40,18 +44,19 @@ class SimpleAppExt(
override def simpleUpdate(tpf: Float): Unit = {} override def simpleUpdate(tpf: Float): Unit = {}
override def stop(waitFor: Boolean): Unit = {
override def stop(waitFor: Boolean): Unit =
cancelToken match { cancelToken match {
case Some(value) => case Some(value) =>
value().foreach { _ => value().foreach { _ =>
pprint.log("Called cancel in simpleapp") pprint.log("Called cancel in simpleapp")
super.stop(true) super.stop(true)
terminationSignal.success(())
} }
case None => case None =>
pprint.log("Called cancel in simpleapp") pprint.log("Called cancel in simpleapp")
super.stop(true) super.stop(true)
terminationSignal.success(())
} }
}
def enqueueFuture[T](cb: () => T): CancelableFuture[T] = { def enqueueFuture[T](cb: () => T): CancelableFuture[T] = {
val p = CancelablePromise[T]() val p = CancelablePromise[T]()

294
src/main/scala/wow/doge/mygame/game/appstates/PlayerMovementState.scala

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

71
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 = {}
}

42
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 import wow.doge.mygame.utils.GenericTimerActor
object NpcActorSupervisor { object NpcActorSupervisor {
type Ref = ActorRef[Command]
sealed trait Command sealed trait Command
final case class Move(pos: ImVector3f) extends Command final case class Move(pos: ImVector3f) extends Command
private final case class InternalMove( private final case class InternalMove(
@ -40,13 +42,13 @@ object NpcActorSupervisor {
private case class LogError(err: Throwable) extends Command private case class LogError(err: Throwable) extends Command
private case class MovementFailed(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 = def behavior =
Behaviors.withMdc(Map("actorName" -> npcName))(
Behaviors.withMdc[Command](Map("actorName" -> npcName))(
Behaviors.setup[Command] { ctx => Behaviors.setup[Command] { ctx =>
val npcMovementActor = ctx.spawn( val npcMovementActor = ctx.spawn(
(npcMovementActorBehavior), (npcMovementActorBehavior),
@ -164,7 +166,7 @@ object NpcMovementActor {
doneSignal: ActorRef[CancelableFuture[DoneMoving.type]] doneSignal: ActorRef[CancelableFuture[DoneMoving.type]]
) extends Command ) extends Command
final class Props[T: CanMove](
class Props[T: CanMove](
val enqueueR: Function1[() => Unit, Unit], val enqueueR: Function1[() => Unit, Unit],
val initialPos: ImVector3f, val initialPos: ImVector3f,
// val tickEventBus: GameEventBus[TickEvent], // val tickEventBus: GameEventBus[TickEvent],
@ -196,7 +198,7 @@ class NpcMovementActor[T](
target: ImVector3f, target: ImVector3f,
replyTo: ActorRef[CancelableFuture[DoneMoving.type]] 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]() val p = CancelablePromise[DoneMoving.type]()
replyTo ! p.future replyTo ! p.future
ticking(p, target, state) ticking(p, target, state)
@ -221,22 +223,22 @@ class NpcMovementActor[T](
ctx.self ! StopMoving ctx.self ! StopMoving
reachDestination.success(DoneMoving) reachDestination.success(DoneMoving)
} else { } else {
// format:off
ctx.log.traceP( ctx.log.traceP(
show"npcActor-${props.npcName}: Difference = " + dst.formatted(
"%.2f"
)
show"npcActor-${props.npcName}: Difference = ${dst.formatted("%.2f")}"
) )
ctx.log.traceP( ctx.log.traceP(
show"npcActor-${props.npcName}: Current pos = " + location
show"npcActor-${props.npcName}: Current pos = $location"
) )
} }
Behaviors.same Behaviors.same
} }
} }
object NpcMovementActorNotUsed { object NpcMovementActorNotUsed {
sealed trait Command sealed trait Command
final case class Props(
class Props(
imMovementActorBehavior: Behavior[ImMovementActor.Command], imMovementActorBehavior: Behavior[ImMovementActor.Command],
npcName: String, npcName: String,
// movementActor: ActorRef[ImMovementActor.Command], // movementActor: ActorRef[ImMovementActor.Command],
@ -251,7 +253,9 @@ object NpcMovementActorNotUsed {
val movementActor = ctx.spawn( val movementActor = ctx.spawn(
Behaviors Behaviors
.supervise(imMovementActorBehavior) .supervise(imMovementActorBehavior)
.onFailure[Exception](SupervisorStrategy.restart),
.onFailure[Exception](
SupervisorStrategy.restart.withLimit(2, 100.millis)
),
s"npc-${npcName}-MovementActorChild" s"npc-${npcName}-MovementActorChild"
) )
val npcMovementElBehavior = Behaviors.receiveMessagePartial[Event] { val npcMovementElBehavior = Behaviors.receiveMessagePartial[Event] {
@ -259,19 +263,19 @@ object NpcMovementActorNotUsed {
event match { event match {
case MovedLeft(name, pressed) => case MovedLeft(name, pressed) =>
if (name == npcName) if (name == npcName)
movementActor ! ImMovementActor.MovedLeft(pressed)
movementActor ! ImMovementActor.MoveLeft(pressed)
Behaviors.same Behaviors.same
case MovedUp(name, pressed) => case MovedUp(name, pressed) =>
if (name == npcName) if (name == npcName)
movementActor ! ImMovementActor.MovedUp(pressed)
movementActor ! ImMovementActor.MoveUp(pressed)
Behaviors.same Behaviors.same
case MovedRight(name, pressed) => case MovedRight(name, pressed) =>
if (name == npcName) if (name == npcName)
movementActor ! ImMovementActor.MovedRight(pressed)
movementActor ! ImMovementActor.MoveRight(pressed)
Behaviors.same Behaviors.same
case MovedDown(name, pressed) => case MovedDown(name, pressed) =>
if (name == npcName) if (name == npcName)
movementActor ! ImMovementActor.MovedDown(pressed)
movementActor ! ImMovementActor.MoveDown(pressed)
Behaviors.same Behaviors.same
} }
} }
@ -279,7 +283,9 @@ object NpcMovementActorNotUsed {
val npcMovementEl = ctx.spawn( val npcMovementEl = ctx.spawn(
Behaviors Behaviors
.supervise(npcMovementElBehavior) .supervise(npcMovementElBehavior)
.onFailure[Exception](SupervisorStrategy.restart),
.onFailure[Exception](
SupervisorStrategy.restart.withLimit(2, 100.millis)
),
s"npc-${npcName}-MovementEventHandler" s"npc-${npcName}-MovementEventHandler"
) )
val renderTickElBehavior = val renderTickElBehavior =

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

@ -1,13 +1,17 @@
package wow.doge.mygame.game.entities package wow.doge.mygame.game.entities
import scala.concurrent.duration._
import akka.actor.typed.ActorRef import akka.actor.typed.ActorRef
import akka.actor.typed.Behavior import akka.actor.typed.Behavior
import akka.actor.typed.LogOptions import akka.actor.typed.LogOptions
import akka.actor.typed.PostStop
import akka.actor.typed.SupervisorStrategy import akka.actor.typed.SupervisorStrategy
import akka.actor.typed.scaladsl.ActorContext import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.scaladsl.Behaviors
import com.typesafe.scalalogging.Logger import com.typesafe.scalalogging.Logger
import org.slf4j.event.Level import org.slf4j.event.Level
import wow.doge.mygame.Dispatchers
import wow.doge.mygame.implicits._ import wow.doge.mygame.implicits._
import wow.doge.mygame.subsystems.events.EventBus import wow.doge.mygame.subsystems.events.EventBus
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
@ -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
import wow.doge.mygame.subsystems.events.TickEvent.RenderTick import wow.doge.mygame.subsystems.events.TickEvent.RenderTick
import wow.doge.mygame.subsystems.movement.ImMovementActor import wow.doge.mygame.subsystems.movement.ImMovementActor
import scala.util.Success
import scala.util.Failure
import akka.util.Timeout
import monix.reactive.Observable
import monix.reactive.subjects.ConcurrentSubject
import monix.execution.Scheduler
import monix.reactive.OverflowStrategy
object PlayerActorSupervisor { 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 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 = def behavior =
Behaviors.logMessages( Behaviors.logMessages(
LogOptions() LogOptions()
.withLevel(Level.TRACE)
.withLevel(Level.DEBUG)
.withLogger( .withLogger(
Logger[PlayerActorSupervisor].underlying 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 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( case class Children(
movementActor: ActorRef[ImMovementActor.Command]
movementActor: ActorRef[ImMovementActor.Command],
statsActor: ActorRef[StatsActor.Command]
) )
} }
class PlayerActorSupervisor( class PlayerActorSupervisor(
ctx: ActorContext[PlayerActorSupervisor.Command], ctx: ActorContext[PlayerActorSupervisor.Command],
props: PlayerActorSupervisor.Props, props: PlayerActorSupervisor.Props,
children: PlayerActorSupervisor.Children
children: PlayerActorSupervisor.Children,
statsSubject: ConcurrentSubject[StatsActor.State, StatsActor.State]
) { ) {
import PlayerActorSupervisor._ import PlayerActorSupervisor._
def receive =
implicit val timeout = Timeout(1.second)
val aliveState =
Behaviors Behaviors
.receiveMessage[Command] { .receiveMessage[Command] {
case _ =>
case TakeDamage(value) =>
// children.movementActor ! ImMovementActor.MovedDown(true) // 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 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
}
} }

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

15
src/main/scala/wow/doge/mygame/game/entities/player/PlayerCameraActor.scala

@ -55,28 +55,27 @@ class PlayerCameraActor(
case RotateLeft => case RotateLeft =>
val rot = rotationBuf val rot = rotationBuf
.fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y) .fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y)
props.enqueueR(() => props.cameraPivotNode.rotate(rot))
props.cameraPivotNode.rotate(rot)
Behaviors.same Behaviors.same
case RotateRight => case RotateRight =>
val rot = rotationBuf val rot = rotationBuf
.fromAngleAxis(-1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y) .fromAngleAxis(-1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y)
props.enqueueR(() => props.cameraPivotNode.rotate(rot))
props.cameraPivotNode.rotate(rot)
Behaviors.same Behaviors.same
case RotateUp => case RotateUp =>
val rot = rotationBuf val rot = rotationBuf
.fromAngleAxis(-1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X) .fromAngleAxis(-1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X)
props.enqueueR(() => props.cameraPivotNode.rotate(rot))
props.cameraPivotNode.rotate(rot)
Behaviors.same Behaviors.same
case RotateDown => case RotateDown =>
val rot = rotationBuf val rot = rotationBuf
.fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X) .fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X)
props.enqueueR(() => props.cameraPivotNode.rotate(rot))
props.cameraPivotNode.rotate(rot)
Behaviors.same Behaviors.same
case Tick => case Tick =>
props.enqueueR(() => {
val location = props.getPlayerLocation()
props.cameraPivotNode.setLocalTranslation(location)
})
val location = props.getPlayerLocation()
props.cameraPivotNode.setLocalTranslation(location)
Behaviors.same Behaviors.same
} }
} }

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

@ -1,12 +1,8 @@
package wow.doge.mygame.game.entities package wow.doge.mygame.game.entities
import akka.actor.typed.ActorRef 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.bullet.control.BetterCharacterControl
import com.jme3.math.FastMath
import com.jme3.renderer.Camera import com.jme3.renderer.Camera
import com.jme3.scene.CameraNode import com.jme3.scene.CameraNode
import com.jme3.scene.Geometry import com.jme3.scene.Geometry
@ -18,67 +14,76 @@ import io.odin.Logger
import monix.bio.IO import monix.bio.IO
import monix.bio.Task import monix.bio.Task
import wow.doge.mygame.AppError import wow.doge.mygame.AppError
import wow.doge.mygame.game.GameAppTags
import wow.doge.mygame.game.SimpleAppExt
import wow.doge.mygame.game.GameApp
import wow.doge.mygame.implicits._ import wow.doge.mygame.implicits._
import wow.doge.mygame.math.ImVector3f import wow.doge.mygame.math.ImVector3f
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
import wow.doge.mygame.subsystems.events.PlayerEvent import wow.doge.mygame.subsystems.events.PlayerEvent
import wow.doge.mygame.subsystems.events.TickEvent import wow.doge.mygame.subsystems.events.TickEvent
import wow.doge.mygame.subsystems.movement.ImMovementActor import wow.doge.mygame.subsystems.movement.ImMovementActor
import wow.doge.mygame.utils.AkkaUtils
import wow.doge.mygame.utils.wrappers.jme._ 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 { object PlayerController {
sealed trait Error sealed trait Error
case class CouldNotCreatePlayerModel(reason: AssetManager.Error) extends 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( class Props(
gameApp: GameApp,
enqueueR: Function1[() => Unit, Unit], enqueueR: Function1[() => Unit, Unit],
rootNode: AppNode2 @@ GameAppTags.RootNode,
rootNode: RootNode,
loggerL: Logger[Task], loggerL: Logger[Task],
// physicsSpace: com.jme3.bullet.PhysicsSpace,
physicsSpace: PhysicsSpace, physicsSpace: PhysicsSpace,
initialPlayerPos: ImVector3f = ImVector3f.Zero,
initialPlayerPos: ImVector3f,
playerEventBus: GameEventBus[PlayerEvent], playerEventBus: GameEventBus[PlayerEvent],
playerPhysicsControl: BetterCharacterControl, playerPhysicsControl: BetterCharacterControl,
// appScheduler: monix.execution.Scheduler, // 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], tickEventBus: GameEventBus[TickEvent],
camera: Camera camera: Camera
)(implicit
spawnProtocol: ActorRef[SpawnProtocol.Command],
timeout: Timeout,
scheduler: Scheduler
) { ) {
val playerActorBehavior = { val playerActorBehavior = {
val movementActorBeh = new ImMovementActor.Props( val movementActorBeh = new ImMovementActor.Props(
enqueueR, enqueueR,
camera camera
).behavior(playerPhysicsControl) ).behavior(playerPhysicsControl)
val cameraActorBeh = new PlayerCameraActor.Props(
cameraPivotNode,
enqueueR,
playerNode.getWorldTranslation _
).behavior
new PlayerActorSupervisor.Props( new PlayerActorSupervisor.Props(
playerEventBus, playerEventBus,
tickEventBus, tickEventBus,
movementActorBeh, movementActorBeh,
cameraActorBeh
scheduler
).behavior ).behavior
} }
val playerActor =
gameApp.spawnGameActor(
playerActorBehavior,
// Some("playerActorSupervisor"),
props = DispatcherSelector.default()
)
val create: IO[AppError, ActorRef[PlayerActorSupervisor.Command]] = val create: IO[AppError, ActorRef[PlayerActorSupervisor.Command]] =
(for { (for {
playerActor <-
AkkaUtils.spawnActorL(playerActorBehavior, "playerActorSupervisor")
// playerActor <-
// // AkkaUtils.spawnActorL(playerActorBehavior, "playerActorSupervisor")
// gameApp.spawnGameActor(
// playerActorBehavior,
// Some("playerActorSupervisor"),
// props = DispatcherSelector.default()
// )
pa <- playerActor
_ <- (for { _ <- (for {
_ <- rootNode += playerNode _ <- rootNode += playerNode
_ <- physicsSpace += playerNode _ <- physicsSpace += playerNode
@ -86,38 +91,8 @@ object PlayerController {
_ = cameraPivotNode += cameraNode _ = cameraPivotNode += cameraNode
_ <- rootNode += cameraPivotNode _ <- rootNode += cameraPivotNode
} yield ()).mapError(AppError.AppNodeError) } 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 { object Defaults {
@ -126,21 +101,6 @@ object PlayerController {
val geom = Geometry("playerGeom", b) val geom = Geometry("playerGeom", b)
geom 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( def defaultCamerNode(
cam: Camera, cam: Camera,
@ -163,7 +123,7 @@ object PlayerController {
.withChildren(playerModel) .withChildren(playerModel)
.withLocalTranslation(playerPos) .withLocalTranslation(playerPos)
.withControl(playerPhysicsControl) .withControl(playerPhysicsControl)
.taggedWith[PlayerControllerTags.PlayerTag]
.taggedWith[Tags.PlayerNode]
def defaultNpcNode( def defaultNpcNode(
// assetManager: AssetManager, // assetManager: AssetManager,

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

@ -21,16 +21,16 @@ object PlayerMovementEventListener {
Behaviors.setup[PlayerMovementEvent](ctx => Behaviors.setup[PlayerMovementEvent](ctx =>
Behaviors.receiveMessage { Behaviors.receiveMessage {
case PlayerMovedLeft(pressed) => case PlayerMovedLeft(pressed) =>
movementActor ! ImMovementActor.MovedLeft(pressed)
movementActor ! ImMovementActor.MoveLeft(pressed)
Behaviors.same Behaviors.same
case PlayerMovedRight(pressed) => case PlayerMovedRight(pressed) =>
movementActor ! ImMovementActor.MovedRight(pressed)
movementActor ! ImMovementActor.MoveRight(pressed)
Behaviors.same Behaviors.same
case PlayerMovedForward(pressed) => case PlayerMovedForward(pressed) =>
movementActor ! ImMovementActor.MovedUp(pressed)
movementActor ! ImMovementActor.MoveUp(pressed)
Behaviors.same Behaviors.same
case PlayerMovedBackward(pressed) => case PlayerMovedBackward(pressed) =>
movementActor ! ImMovementActor.MovedDown(pressed)
movementActor ! ImMovementActor.MoveDown(pressed)
Behaviors.same Behaviors.same
case PlayerJumped => case PlayerJumped =>
movementActor ! ImMovementActor.Jump movementActor ! ImMovementActor.Jump

55
src/main/scala/wow/doge/mygame/game/subsystems/input/GameInputHandler.scala

@ -43,10 +43,10 @@ object GameInputHandler {
// inputManager, // inputManager,
// playerEventBus // playerEventBus
// ).completedL, // ).completedL,
generateCameraEvents(
inputManager,
playerEventBus
).completedL,
// cameraMovementEventsGenerator(
// inputManager,
// playerEventBus
// ).completedL,
Ref Ref
.of[me.Task, Boolean](false) .of[me.Task, Boolean](false)
.flatMap(value => cursorToggle(value)) .flatMap(value => cursorToggle(value))
@ -170,14 +170,14 @@ object GameInputHandler {
) )
) )
case PlayerMovementInput.Jump => case PlayerMovementInput.Jump =>
if (action.value) {
if (action.value)
me.Task( me.Task(
playerEventBus ! EventBus.Publish( playerEventBus ! EventBus.Publish(
PlayerMovementEvent.PlayerJumped, PlayerMovementEvent.PlayerJumped,
name name
) )
) )
} else me.Task.unit
else me.Task.unit
} }
} }
} }
@ -185,36 +185,35 @@ object GameInputHandler {
def generateAnalogMovementEvents( def generateAnalogMovementEvents(
inputManager: InputManager, inputManager: InputManager,
playerEventBus: GameEventBus[PlayerEvent] playerEventBus: GameEventBus[PlayerEvent]
) = {
) =
// val name = "analogMovementEventsGenerator" // val name = "analogMovementEventsGenerator"
inputManager inputManager
.enumAnalogObservable(PlayerAnalogMovementInput) .enumAnalogObservable(PlayerAnalogMovementInput)
.sample(1.millis) .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, inputManager: InputManager,
playerEventBus: ActorRef[EventBus.Command[PlayerEvent]] playerEventBus: ActorRef[EventBus.Command[PlayerEvent]]
) = { ) = {
val name = "cameraMovementEventsGenerator"
val name = methodName
inputManager inputManager
.enumAnalogObservable(PlayerCameraInput) .enumAnalogObservable(PlayerCameraInput)
.sample(1.millis) .sample(1.millis)

10
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] { trait CanMove[-A] {
// def getDirection(cam: Camera, cardinalDir: CardinalDirection): ImVector3f // 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 location(inst: A): ImVector3f
def jump(inst: A): Unit def jump(inst: A): Unit
def stop(inst: A): Unit def stop(inst: A): Unit
@ -25,7 +25,7 @@ object CanMove {
override def move( override def move(
inst: BetterCharacterControl, inst: BetterCharacterControl,
direction: ImVector3f, direction: ImVector3f,
speedFactor: Float = 20f
speedFactor: Float
): Unit = { ): Unit = {
val dir = direction.mutable.normalizeLocal() val dir = direction.mutable.normalizeLocal()
inst.setViewDirection(dir.negate()) inst.setViewDirection(dir.negate())
@ -61,19 +61,17 @@ object CanMove {
inst: Spatial, inst: Spatial,
direction: ImVector3f, direction: ImVector3f,
speedFactor: Float = 1f speedFactor: Float = 1f
): Unit = {
): Unit =
inst.move(direction.mutable multLocal speedFactor) inst.move(direction.mutable multLocal speedFactor)
}
override def location(inst: Spatial) = override def location(inst: Spatial) =
inst.getLocalTranslation().immutable inst.getLocalTranslation().immutable
override def jump(inst: Spatial): Unit = override def jump(inst: Spatial): Unit =
logger.warn("`Jump` is not implemented for type `Spatial`") 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 { rotateDir match {
case RotateDir.Left => inst.rotate(0, -0.01f, 0) case RotateDir.Left => inst.rotate(0, -0.01f, 0)
case RotateDir.Right => inst.rotate(0, 0.01f, 0) case RotateDir.Right => inst.rotate(0, 0.01f, 0)
} }
}
override def stop(inst: Spatial) = { /*not required*/ } override def stop(inst: Spatial) = { /*not required*/ }
} }
} }

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

@ -1,15 +1,23 @@
package wow.doge.mygame.subsystems.movement package wow.doge.mygame.subsystems.movement
import akka.actor.typed.Behavior import akka.actor.typed.Behavior
import akka.actor.typed.PostStop
import akka.actor.typed.scaladsl.ActorContext import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.scaladsl.Behaviors
import com.jme3.math.Vector3f import com.jme3.math.Vector3f
import com.jme3.renderer.Camera import com.jme3.renderer.Camera
import com.jme3.scene.Geometry
import com.softwaremill.quicklens._ import com.softwaremill.quicklens._
import wow.doge.mygame.game.subsystems.movement.CanMove import wow.doge.mygame.game.subsystems.movement.CanMove
import wow.doge.mygame.implicits._ import wow.doge.mygame.implicits._
import wow.doge.mygame.math.ImVector3f 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 sealed trait RotateDir
object RotateDir { object RotateDir {
@ -19,19 +27,16 @@ object RotateDir {
object ImMovementActor { object ImMovementActor {
sealed trait Command 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 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], val enqueueR: Function1[() => Unit, Unit],
// playerMovementEventBus: ActorRef[ // playerMovementEventBus: ActorRef[
// EventBus.Command[PlayerMovementEvent] // EventBus.Command[PlayerMovementEvent]
@ -40,7 +45,7 @@ object ImMovementActor {
) { ) {
def behavior[T: CanMove](movable: T): Behavior[Command] = def behavior[T: CanMove](movable: T): Behavior[Command] =
Behaviors.setup(ctx => 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], ctx: ActorContext[ImMovementActor.Command],
props: ImMovementActor.Props, props: ImMovementActor.Props,
val movable: T val movable: T
) {
)(implicit cm: CanMove[T]) {
import ImMovementActor._ import ImMovementActor._
import Methods._
def receive( 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( def getDirection2(
cardinalDir: CardinalDirection,
debug: sourcecode.Text[String] => sourcecode.Text[String]
cardinalDir: CardinalDirection
// debug: sourcecode.Text[String] => sourcecode.Text[String]
) = { ) = {
val camDir = val camDir =
props.camera.getDirection().clone().normalizeLocal.multLocal(0.6f) props.camera.getDirection().clone().normalizeLocal.multLocal(0.6f)
@ -107,19 +148,19 @@ class ImMovementActor[T](
val walkDir = { val walkDir = {
val mutWalkDir = new Vector3f() val mutWalkDir = new Vector3f()
if (dir.up) { if (dir.up) {
debug("up")
ctx.log.traceP("up")
mutWalkDir += camDir mutWalkDir += camDir
} }
if (dir.left) { if (dir.left) {
debug("left")
ctx.log.traceP("left")
mutWalkDir += camLeft mutWalkDir += camLeft
} }
if (dir.right) { if (dir.right) {
debug("right")
ctx.log.traceP("right")
mutWalkDir += -camLeft mutWalkDir += -camLeft
} }
if (dir.down) { if (dir.down) {
debug("down")
ctx.log.traceP("down")
mutWalkDir += -camDir mutWalkDir += -camDir
} }
mutWalkDir.immutable mutWalkDir.immutable
@ -127,50 +168,111 @@ class ImMovementActor[T](
walkDir 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))
}
}
}

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

@ -4,7 +4,10 @@ import scala.jdk.CollectionConverters._
import scala.reflect.ClassTag import scala.reflect.ClassTag
import akka.actor.typed.ActorRef import akka.actor.typed.ActorRef
import akka.actor.typed.Behavior
import akka.actor.typed.Props
import akka.actor.typed.Scheduler import akka.actor.typed.Scheduler
import akka.actor.typed.scaladsl.ActorContext
import akka.util.Timeout import akka.util.Timeout
import com.jayfella.jme.jfx.JavaFxUI import com.jayfella.jme.jfx.JavaFxUI
import com.jme3.app.Application import com.jme3.app.Application
@ -14,10 +17,12 @@ import com.jme3.app.state.AppStateManager
import com.jme3.asset.AssetLocator import com.jme3.asset.AssetLocator
import com.jme3.asset.AssetManager import com.jme3.asset.AssetManager
import com.jme3.bullet.BulletAppState import com.jme3.bullet.BulletAppState
import com.jme3.bullet.PhysicsSpace
import com.jme3.bullet.PhysicsTickListener import com.jme3.bullet.PhysicsTickListener
import com.jme3.bullet.collision.PhysicsCollisionEvent
import com.jme3.bullet.collision.PhysicsCollisionListener 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.bullet.control.BetterCharacterControl
import com.jme3.input.Action import com.jme3.input.Action
import com.jme3.input.InputManager import com.jme3.input.InputManager
@ -35,14 +40,17 @@ import com.jme3.scene.SceneGraphVisitor
import com.jme3.scene.Spatial import com.jme3.scene.Spatial
import com.jme3.scene.control.CameraControl.ControlDirection import com.jme3.scene.control.CameraControl.ControlDirection
import com.jme3.scene.control.Control import com.jme3.scene.control.Control
import com.jme3.{bullet => jmeb}
import com.simsilica.es.EntityComponent import com.simsilica.es.EntityComponent
import com.simsilica.es.EntityData import com.simsilica.es.EntityData
import com.simsilica.es.EntityId import com.simsilica.es.EntityId
import enumeratum._ import enumeratum._
import io.odin.meta.Position import io.odin.meta.Position
import io.odin.meta.Render import io.odin.meta.Render
import monix.bio.IO
import monix.bio.Task import monix.bio.Task
import monix.bio.UIO import monix.bio.UIO
import monix.eval.Coeval
import monix.execution.Ack import monix.execution.Ack
import monix.execution.Ack.Continue import monix.execution.Ack.Continue
import monix.execution.Ack.Stop import monix.execution.Ack.Stop
@ -54,6 +62,7 @@ import monix.reactive.observers.Subscriber
import org.slf4j.Logger import org.slf4j.Logger
import wow.doge.mygame.math.ImVector3f import wow.doge.mygame.math.ImVector3f
import wow.doge.mygame.state.MyBaseState 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 ActionEvent(binding: Action, value: Boolean, tpf: Float)
final case class EnumActionEvent[T <: EnumEntry]( final case class EnumActionEvent[T <: EnumEntry](
@ -68,8 +77,15 @@ final case class EnumAnalogEvent[T <: EnumEntry](
value: Float, value: Float,
tpf: 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 { package object implicits {
type PrePhysicsTickEvent = PhysicsTickEvent type PrePhysicsTickEvent = PhysicsTickEvent
@ -112,10 +128,9 @@ package object implicits {
override def init(): Unit = {} override def init(): Unit = {}
override def update(tpf: Float) = {
override def update(tpf: Float) =
if (sub.onNext(tpf) == Ack.Stop) if (sub.onNext(tpf) == Ack.Stop)
c.cancel() c.cancel()
}
override def stop(): Unit = {} override def stop(): Unit = {}
@ -133,13 +148,11 @@ package object implicits {
def registerLocator( def registerLocator(
assetPath: os.RelPath, assetPath: os.RelPath,
locator: Class[_ <: AssetLocator] locator: Class[_ <: AssetLocator]
): Unit = {
): Unit =
am.registerLocator(assetPath.toString(), locator) am.registerLocator(assetPath.toString(), locator)
}
def loadModel(assetPath: os.RelPath): Spatial = {
def loadModel(assetPath: os.RelPath): Spatial =
am.loadModel(assetPath.toString()) am.loadModel(assetPath.toString())
}
} }
implicit final class BulletAppStateExt(private val bas: BulletAppState) implicit final class BulletAppStateExt(private val bas: BulletAppState)
@ -251,7 +264,7 @@ package object implicits {
} }
// case _ => loop(subscriber, tail) // case _ => loop(subscriber, tail)
} }
case LazyList() => Task.unit
case LazyList() => Task(subscriber.onComplete())
} }
Observable.create(OverflowStrategy.Unbounded) { sub => Observable.create(OverflowStrategy.Unbounded) { sub =>
@ -297,7 +310,7 @@ package object implicits {
Task(subscriber.onComplete()) Task(subscriber.onComplete())
} }
} }
case LazyList() => Task.unit
case LazyList() => Task(subscriber.onComplete())
} }
Observable.create(OverflowStrategy.Unbounded) { sub => Observable.create(OverflowStrategy.Unbounded) { sub =>
@ -372,9 +385,8 @@ package object implicits {
*/ */
def askL[Res]( def askL[Res](
replyTo: ActorRef[Res] => Req 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)) Task.deferFuture(a.ask(replyTo)(timeout, scheduler))
}
def ??[Res]( def ??[Res](
replyTo: ActorRef[Res] => Req replyTo: ActorRef[Res] => Req
)(implicit timeout: Timeout, scheduler: Scheduler): Task[Res] = )(implicit timeout: Timeout, scheduler: Scheduler): Task[Res] =
@ -481,8 +493,7 @@ package object implicits {
* @see [[ActionEvent]] * @see [[ActionEvent]]
* @see [[enumObservableAction]] * @see [[enumObservableAction]]
*/ */
def observableAction(mappingNames: String*): Observable[ActionEvent] = {
def observableAction(mappingNames: String*): Observable[ActionEvent] =
Observable.create(OverflowStrategy.DropOld(10)) { sub => Observable.create(OverflowStrategy.DropOld(10)) { sub =>
val c = SingleAssignCancelable() val c = SingleAssignCancelable()
val al = new ActionListener { val al = new ActionListener {
@ -490,14 +501,13 @@ package object implicits {
binding: String, binding: String,
value: Boolean, value: Boolean,
tpf: Float tpf: Float
): Unit = {
): Unit =
if ( if (
sub.onNext(ActionEvent(Action(binding), value, tpf)) == Ack.Stop sub.onNext(ActionEvent(Action(binding), value, tpf)) == Ack.Stop
) { ) {
sub.onComplete() sub.onComplete()
c.cancel() c.cancel()
} }
}
} }
inputManager.addListener(al, mappingNames: _*) inputManager.addListener(al, mappingNames: _*)
@ -505,7 +515,6 @@ package object implicits {
c := Cancelable(() => inputManager.removeListener(al)) c := Cancelable(() => inputManager.removeListener(al))
c c
} }
}
/** /**
* <p> * <p>
@ -530,8 +539,7 @@ package object implicits {
*/ */
def enumObservableAction[T <: EnumEntry]( def enumObservableAction[T <: EnumEntry](
mappingEnum: Enum[T] mappingEnum: Enum[T]
): Observable[EnumActionEvent[T]] = {
): Observable[EnumActionEvent[T]] =
Observable.create(OverflowStrategy.DropOld(10)) { sub => Observable.create(OverflowStrategy.DropOld(10)) { sub =>
val c = SingleAssignCancelable() val c = SingleAssignCancelable()
val entryNames = mappingEnum.values.map(_.entryName) val entryNames = mappingEnum.values.map(_.entryName)
@ -540,14 +548,13 @@ package object implicits {
binding: String, binding: String,
value: Boolean, value: Boolean,
tpf: Float tpf: Float
): Unit = {
): Unit =
mappingEnum.withNameOption(binding).foreach { b => mappingEnum.withNameOption(binding).foreach { b =>
if (sub.onNext(EnumActionEvent(b, value, tpf)) == Ack.Stop) { if (sub.onNext(EnumActionEvent(b, value, tpf)) == Ack.Stop) {
sub.onComplete() sub.onComplete()
c.cancel() c.cancel()
} }
} }
}
} }
inputManager.addListener(al, entryNames: _*) inputManager.addListener(al, entryNames: _*)
@ -555,12 +562,10 @@ package object implicits {
c := Cancelable(() => inputManager.removeListener(al)) c := Cancelable(() => inputManager.removeListener(al))
c c
} }
}
def enumEntryObservableAction[T <: EnumEntry]( def enumEntryObservableAction[T <: EnumEntry](
mappingEnumEntry: T mappingEnumEntry: T
): Observable[EnumActionEvent[T]] = {
): Observable[EnumActionEvent[T]] =
Observable.create(OverflowStrategy.DropOld(10)) { sub => Observable.create(OverflowStrategy.DropOld(10)) { sub =>
val c = SingleAssignCancelable() val c = SingleAssignCancelable()
val al = new ActionListener { val al = new ActionListener {
@ -568,7 +573,7 @@ package object implicits {
binding: String, binding: String,
value: Boolean, value: Boolean,
tpf: Float tpf: Float
): Unit = {
): Unit =
if ( if (
sub.onNext( sub.onNext(
EnumActionEvent(mappingEnumEntry, value, tpf) EnumActionEvent(mappingEnumEntry, value, tpf)
@ -577,7 +582,6 @@ package object implicits {
sub.onComplete() sub.onComplete()
c.cancel() c.cancel()
} }
}
} }
inputManager.addListener(al, mappingEnumEntry.entryName) inputManager.addListener(al, mappingEnumEntry.entryName)
@ -585,10 +589,8 @@ package object implicits {
c := Cancelable(() => inputManager.removeListener(al)) c := Cancelable(() => inputManager.removeListener(al))
c c
} }
}
def analogObservable(mappingNames: String*): Observable[AnalogEvent] = {
def analogObservable(mappingNames: String*): Observable[AnalogEvent] =
Observable.create(OverflowStrategy.DropOld(50)) { sub => Observable.create(OverflowStrategy.DropOld(50)) { sub =>
val c = SingleAssignCancelable() val c = SingleAssignCancelable()
val al = new AnalogListener { val al = new AnalogListener {
@ -596,14 +598,13 @@ package object implicits {
binding: String, binding: String,
value: Float, value: Float,
tpf: Float tpf: Float
): Unit = {
): Unit =
if ( if (
sub.onNext(AnalogEvent(Action(binding), value, tpf)) == Ack.Stop sub.onNext(AnalogEvent(Action(binding), value, tpf)) == Ack.Stop
) { ) {
sub.onComplete() sub.onComplete()
c.cancel() c.cancel()
} }
}
} }
inputManager.addListener(al, mappingNames: _*) inputManager.addListener(al, mappingNames: _*)
@ -611,12 +612,10 @@ package object implicits {
c := Cancelable(() => inputManager.removeListener(al)) c := Cancelable(() => inputManager.removeListener(al))
c c
} }
}
def enumAnalogObservable[T <: EnumEntry]( def enumAnalogObservable[T <: EnumEntry](
mappingEnum: Enum[T] mappingEnum: Enum[T]
): Observable[EnumAnalogEvent[T]] = {
): Observable[EnumAnalogEvent[T]] =
Observable.create(OverflowStrategy.DropOld(50)) { sub => Observable.create(OverflowStrategy.DropOld(50)) { sub =>
val c = SingleAssignCancelable() val c = SingleAssignCancelable()
val entryNames = mappingEnum.values.map(_.entryName) val entryNames = mappingEnum.values.map(_.entryName)
@ -625,14 +624,13 @@ package object implicits {
binding: String, binding: String,
value: Float, value: Float,
tpf: Float tpf: Float
): Unit = {
): Unit =
mappingEnum.withNameOption(binding).foreach { b => mappingEnum.withNameOption(binding).foreach { b =>
if (sub.onNext(EnumAnalogEvent(b, value, tpf)) == Ack.Stop) { if (sub.onNext(EnumAnalogEvent(b, value, tpf)) == Ack.Stop) {
sub.onComplete() sub.onComplete()
c.cancel() c.cancel()
} }
} }
}
} }
inputManager.addListener(al, entryNames: _*) inputManager.addListener(al, entryNames: _*)
@ -640,49 +638,58 @@ package object implicits {
c := Cancelable(() => inputManager.removeListener(al)) c := Cancelable(() => inputManager.removeListener(al))
c c
} }
}
} }
implicit final class PhysicsSpaceExt(private val space: PhysicsSpace)
implicit final class PhysicsSpaceExt(private val space: jmeb.PhysicsSpace)
extends AnyVal { extends AnyVal {
def collisionObservable(): Observable[PhysicsCollisionEvent] = {
def collisionObservable(): Observable[CollisionEvent] =
Observable.create(OverflowStrategy.DropOld(50)) { sub => Observable.create(OverflowStrategy.DropOld(50)) { sub =>
val c = SingleAssignCancelable() val c = SingleAssignCancelable()
val cl = new PhysicsCollisionListener { 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) space.addCollisionListener(cl)
c := Cancelable(() => space.removeCollisionListener(cl))
c := Cancelable { () =>
pprint.log("stopped")
space.removeCollisionListener(cl)
}
c c
} }
}
def prePhysicsTickObservable(): Observable[PrePhysicsTickEvent] = {
def prePhysicsTickObservable(): Observable[PrePhysicsTickEvent] =
Observable.create(OverflowStrategy.DropOld(50)) { sub => Observable.create(OverflowStrategy.DropOld(50)) { sub =>
val c = SingleAssignCancelable() val c = SingleAssignCancelable()
val cl = new PhysicsTickListener { 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) { if (sub.onNext(event) == Ack.Stop) {
sub.onComplete() sub.onComplete()
c.cancel() 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 := Cancelable(() => space.removeTickListener(cl))
c c
} }
}
def physicsTickObservable(): Observable[PhysicsTickEvent] = {
def physicsTickObservable(): Observable[PhysicsTickEvent] =
Observable.create(OverflowStrategy.DropOld(50)) { sub => Observable.create(OverflowStrategy.DropOld(50)) { sub =>
val c = SingleAssignCancelable() val c = SingleAssignCancelable()
val cl = new PhysicsTickListener { val cl = new PhysicsTickListener {
override def prePhysicsTick( override def prePhysicsTick(
space: PhysicsSpace,
space: jmeb.PhysicsSpace,
tpf: Float tpf: Float
): Unit = {} ): 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) { if (sub.onNext(event) == Ack.Stop) {
sub.onComplete() sub.onComplete()
c.cancel() c.cancel()
@ -719,7 +727,6 @@ package object implicits {
c := Cancelable(() => space.removeTickListener(cl)) c := Cancelable(() => space.removeTickListener(cl))
c c
} }
}
//TODO Consider creating a typeclass for this //TODO Consider creating a typeclass for this
def +=(anyObject: Any) = space.add(anyObject) def +=(anyObject: Any) = space.add(anyObject)
@ -757,6 +764,7 @@ package object implicits {
def +=:(that: ImVector3f) = v += that def +=:(that: ImVector3f) = v += that
def *=(that: Vector3f) = v.multLocal(that) def *=(that: Vector3f) = v.multLocal(that)
def *=(that: ImVector3f) = v.multLocal(that.x, that.y, that.z) 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: ImVector3f) = v *= that
def -=(that: Vector3f) = v.subtractLocal(that) def -=(that: Vector3f) = v.subtractLocal(that)
def -=(that: ImVector3f) = v.subtractLocal(that.x, that.y, that.z) def -=(that: ImVector3f) = v.subtractLocal(that.x, that.y, that.z)
@ -766,6 +774,7 @@ package object implicits {
def /=:(that: ImVector3f) = v *= that def /=:(that: ImVector3f) = v *= that
def unary_- = v.negateLocal() def unary_- = v.negateLocal()
def immutable = ImVector3f(v.x, v.y, v.z) 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 { 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 { extends AnyVal {
def debugU[M](msg: => M)(implicit render: Render[M], position: Position) = def debugU[M](msg: => M)(implicit render: Render[M], position: Position) =
logger.debug(msg).hideErrors logger.debug(msg).hideErrors
@ -871,4 +880,28 @@ package object implicits {
def errorU[M](msg: => M)(implicit render: Render[M], position: Position) = def errorU[M](msg: => M)(implicit render: Render[M], position: Position) =
logger.error(msg).hideErrors 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
}
} }

6
src/main/scala/wow/doge/mygame/math/ImVector3f.scala

@ -14,9 +14,9 @@ object ImVector3f {
val UnitY = ImVector3f(0, 1, 0) val UnitY = ImVector3f(0, 1, 0)
val UnitZ = ImVector3f(0, 0, 1) val UnitZ = ImVector3f(0, 0, 1)
val Unit = ImVector3f(1, 1, 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 //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] { implicit val show = new Show[ImVector3f] {
def format(f: Float) = f.formatted("%.2f") def format(f: Float) = f.formatted("%.2f")
@ -39,6 +39,6 @@ object ImVector3f {
sqrt(total) sqrt(total)
} }
def manhattanDst(v1: ImVector3f, v2: ImVector3f) = 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)
} }

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

@ -1,10 +1,10 @@
package wow.doge.mygame.subsystems.events package wow.doge.mygame.subsystems.events
import scala.reflect.ClassTag import scala.reflect.ClassTag
import scala.util.Random
import akka.actor.typed.ActorRef import akka.actor.typed.ActorRef
import akka.actor.typed.Behavior import akka.actor.typed.Behavior
import akka.actor.typed.PostStop
import akka.actor.typed.Scheduler import akka.actor.typed.Scheduler
import akka.actor.typed.SpawnProtocol import akka.actor.typed.SpawnProtocol
import akka.actor.typed.scaladsl.AskPattern._ import akka.actor.typed.scaladsl.AskPattern._
@ -59,42 +59,7 @@ object EventBus {
new EventBus().eventStreamBehavior(eventStream) 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, timeout: Timeout,
scheduler: Scheduler, scheduler: Scheduler,
spawnProtocol: ActorRef[SpawnProtocol.Command], spawnProtocol: ActorRef[SpawnProtocol.Command],
@ -104,18 +69,25 @@ object EventBus {
Observable.create[B](OverflowStrategy.DropOld(50)) { sub => Observable.create[B](OverflowStrategy.DropOld(50)) { sub =>
implicit val s = sub.scheduler implicit val s = sub.scheduler
val c = SingleAssignCancelable() 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 = val actor =
AkkaUtils AkkaUtils
.spawnActorL( .spawnActorL(
behavior, behavior,
s"eventBusObservable-${ct.toString}-${math.abs(Random.nextLong())}"
actorName =
Some(s"eventBusObservable-${ct.toString.split("""\.""").last}")
) )
.mapError(err => new Throwable(err.toString)) .mapError(err => new Throwable(err.toString))
.tapError { .tapError {
@ -155,7 +127,7 @@ class EventBus[A] {
eventStream.unsubscribe(subscriber.toClassic) eventStream.unsubscribe(subscriber.toClassic)
Behaviors.same Behaviors.same
case s @ ObservableSubscription(replyTo) => case s @ ObservableSubscription(replyTo) =>
val obs = EventBus.observable2(
val obs = EventBus.observable(
ctx.self ctx.self
)(timeout, scheduler, spawnProtocol, ct, s.ct) )(timeout, scheduler, spawnProtocol, ct, s.ct)
replyTo ! obs replyTo ! obs

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

@ -1,14 +1,14 @@
package wow.doge.mygame.subsystems.events package wow.doge.mygame.subsystems.events
sealed trait Event sealed trait Event
final case object BulletFired extends Event
case object BulletFired extends Event
// type BulletFired = BulletFired.type // type BulletFired = BulletFired.type
final case class EventWithData(data: Int) extends Event final case class EventWithData(data: Int) extends Event
sealed trait TickEvent extends Event sealed trait TickEvent extends Event
object TickEvent { 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 sealed trait EntityMovementEvent extends Event
@ -22,3 +22,9 @@ object EntityMovementEvent {
final case class MovedDown(name: String, pressed: Boolean) final case class MovedDown(name: String, pressed: Boolean)
extends EntityMovementEvent extends EntityMovementEvent
} }
sealed trait StatsEvent extends Event
object StatsEvent {
case class DamageEvent(hitBy: String, victimName: String, amount: Int)
extends StatsEvent
}

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

@ -1,7 +1,5 @@
package wow.doge.mygame.subsystems.events package wow.doge.mygame.subsystems.events
import java.util.concurrent.TimeoutException
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.reflect.ClassTag import scala.reflect.ClassTag
@ -17,6 +15,7 @@ import com.typesafe.scalalogging.{Logger => SLogger}
import monix.bio.IO import monix.bio.IO
import org.slf4j.event.Level import org.slf4j.event.Level
import wow.doge.mygame.AppError import wow.doge.mygame.AppError
import wow.doge.mygame.AppError.TimeoutError
import wow.doge.mygame.implicits._ import wow.doge.mygame.implicits._
import wow.doge.mygame.subsystems.events.Event import wow.doge.mygame.subsystems.events.Event
import wow.doge.mygame.subsystems.events.EventBus import wow.doge.mygame.subsystems.events.EventBus
@ -34,21 +33,20 @@ class EventsModule(
val eventBusLogger = SLogger[EventBus[_]] val eventBusLogger = SLogger[EventBus[_]]
val playerEventBus: IO[AppError, GameEventBus[PlayerEvent]] = val playerEventBus: IO[AppError, GameEventBus[PlayerEvent]] =
createEventBus[PlayerEvent]("playerEventBus")
createEventBus[PlayerEvent]()
// val playerCameraEventBusTask = // val playerCameraEventBusTask =
// createEventBus[PlayerCameraEvent]("playerCameraEventBus", Level.DEBUG)
// createEventBus[PlayerCameraEvent](Level.DEBUG)
val tickEventBus: IO[AppError, GameEventBus[TickEvent]] = 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]( def createEventBus[T: ClassTag](
busName: String,
logLevel: Level = Level.DEBUG
) =
logLevel: Level = Level.DEBUG,
busName: Option[String] = None
)(implicit name: sourcecode.Name) =
spawnProtocol spawnProtocol
.askL( .askL(
SpawnProtocol.Spawn[EventBus.Command[T]]( SpawnProtocol.Spawn[EventBus.Command[T]](
@ -58,17 +56,16 @@ class EventsModule(
.withLogger(eventBusLogger.underlying), .withLogger(eventBusLogger.underlying),
Behaviors Behaviors
.supervise(EventBus[T]()) .supervise(EventBus[T]())
.onFailure[Exception](SupervisorStrategy.restart)
.onFailure[Exception](
SupervisorStrategy.restart.withLimit(2, 100.millis)
)
), ),
busName,
busName.fold(name.value)(identity),
Props.empty, Props.empty,
_ _
) )
) )
.onErrorHandleWith {
case ex: TimeoutException =>
IO.raiseError(AppError.TimeoutError(ex.getMessage))
}
.onErrorHandleWith(TimeoutError.from)
} }

18
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 PlayerEvent
sealed trait PlayerMovementEvent extends PlayerEvent sealed trait PlayerMovementEvent extends PlayerEvent
final object PlayerMovementEvent {
object PlayerMovementEvent {
final case class PlayerMovedLeft(pressed: Boolean) extends PlayerMovementEvent final case class PlayerMovedLeft(pressed: Boolean) extends PlayerMovementEvent
final case class PlayerMovedRight(pressed: Boolean) final case class PlayerMovedRight(pressed: Boolean)
extends PlayerMovementEvent extends PlayerMovementEvent
@ -11,16 +11,16 @@ final object PlayerMovementEvent {
extends PlayerMovementEvent extends PlayerMovementEvent
final case class PlayerMovedBackward(pressed: Boolean) final case class PlayerMovedBackward(pressed: Boolean)
extends PlayerMovementEvent 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 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
} }

18
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 GetMap(requester: ActorRef[ScriptsMap]) extends Command
final case class Put(scriptPath: os.Path, script: ScriptObject) final case class Put(scriptPath: os.Path, script: ScriptObject)
extends Command extends Command
private[scriptsystem] final case object NoOp extends Command
private[scriptsystem] case object NoOp extends Command
private[scriptsystem] final case class DelegateToChild( private[scriptsystem] final case class DelegateToChild(
scriptActor: ActorRef[ScriptActor.Command], scriptActor: ActorRef[ScriptActor.Command],
@ -62,7 +62,7 @@ object ScriptCachingActor {
// requester: ActorRef[Map[os.Path, ScriptResult]] // requester: ActorRef[Map[os.Path, ScriptResult]]
// ) extends Command // ) extends Command
// final case class Props(
// class Props(
// ctx: ActorContext[Command], // ctx: ActorContext[Command],
// scriptActor: ActorRef[ScriptActor.Command] // scriptActor: ActorRef[ScriptActor.Command]
// ) { // ) {
@ -221,7 +221,7 @@ class ScriptCachingActor(
scriptActor: ActorRef[ScriptActor.Command], scriptActor: ActorRef[ScriptActor.Command],
scriptPath: os.Path, scriptPath: os.Path,
requester: ActorRef[ScriptResult] requester: ActorRef[ScriptResult]
)(implicit timeout: Timeout) = {
)(implicit timeout: Timeout) =
ctx.ask(scriptActor, ScriptActor.CompileAny(scriptPath, _)) { ctx.ask(scriptActor, ScriptActor.CompileAny(scriptPath, _)) {
case Success(value) => case Success(value) =>
requester ! value requester ! value
@ -230,16 +230,12 @@ class ScriptCachingActor(
ctx.log.errorP(err.reason) ctx.log.errorP(err.reason)
NoOp NoOp
}, },
res => {
Put(scriptPath, res)
}
res => Put(scriptPath, res)
) )
case Failure(exception) => {
case Failure(exception) =>
ctx.log.errorP(exception.getMessage) ctx.log.errorP(exception.getMessage)
NoOp NoOp
}
} }
}
} }
@ -251,6 +247,8 @@ object ScriptActorPool {
// make sure the workers are restarted if they fail // make sure the workers are restarted if they fail
Behaviors Behaviors
.supervise(ScriptActor()) .supervise(ScriptActor())
.onFailure[Exception](SupervisorStrategy.restart)
.onFailure[Exception](
SupervisorStrategy.restart.withLimit(2, 100.millis)
)
) )
} }

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

@ -31,8 +31,8 @@ class ScriptSystemResource(
findScriptFiles(os.pwd / "assets" / "scripts") findScriptFiles(os.pwd / "assets" / "scripts")
).hideErrors ).hideErrors
scriptCacheActor <- AkkaUtils.spawnActorL( scriptCacheActor <- AkkaUtils.spawnActorL(
ScriptCachingActor(),
"scriptCachingActor"
ScriptCachingActor()
// "scriptCachingActor"
) )
} yield scriptCacheActor } yield scriptCacheActor

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

7
src/main/scala/wow/doge/mygame/utils/AkkaUtils.scala

@ -26,18 +26,19 @@ object AkkaUtils {
) )
def spawnActorL[T]( def spawnActorL[T](
behavior: Behavior[T], behavior: Behavior[T],
actorName: String,
actorName: Option[String] = None,
props: Props = Props.empty props: Props = Props.empty
)(implicit )(implicit
timeout: Timeout, timeout: Timeout,
scheduler: Scheduler, scheduler: Scheduler,
spawnProtocol: ActorRef[SpawnProtocol.Command]
spawnProtocol: ActorRef[SpawnProtocol.Command],
name: sourcecode.Name
) = ) =
spawnProtocol spawnProtocol
.askL[ActorRef[T]]( .askL[ActorRef[T]](
SpawnProtocol.Spawn( SpawnProtocol.Spawn(
behavior, behavior,
actorName,
actorName.fold(name.value)(identity),
props, props,
_ _
) )

4
src/main/scala/wow/doge/mygame/utils/GenericTimerActor.scala

@ -12,8 +12,8 @@ import wow.doge.mygame.implicits._
object GenericTimerActor { object GenericTimerActor {
sealed trait Command 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 private case object Tick extends Command
case class TimerKey(seed: Long) case class TimerKey(seed: Long)

7
src/main/scala/wow/doge/mygame/utils/IOUtils.scala

@ -1,12 +1,17 @@
package wow.doge.mygame.utils package wow.doge.mygame.utils
import monix.bio.IO import monix.bio.IO
import monix.bio.Task
import monix.eval.Coeval
object IOUtils { 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]) monix.eval.Task.deferAction(implicit s => bio.to[monix.eval.Task])
def toIO[T](task: monix.eval.Task[T]) = def toIO[T](task: monix.eval.Task[T]) =
IO.deferAction(implicit s => IO.from(task)) IO.deferAction(implicit s => IO.from(task))
def fromCoevalEither[L, R](coeval: Coeval[Either[L, R]]) =
coeval.to[Task].hideErrors.rethrow
} }

18
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.light.Light
import com.jme3.{scene => jmes} import com.jme3.{scene => jmes}
import monix.bio.IO import monix.bio.IO
import monix.bio.Task
import monix.bio.UIO import monix.bio.UIO
import monix.execution.annotations.UnsafeBecauseImpure import monix.execution.annotations.UnsafeBecauseImpure
import monix.reactive.Observable import monix.reactive.Observable
@ -71,6 +70,8 @@ abstract class NodeWrapper2 protected (node: jmes.Node) {
import NodeWrapper2._ import NodeWrapper2._
def name = node.getName() def name = node.getName()
def children: Observable[jmes.Spatial] = node.observableChildren def children: Observable[jmes.Spatial] = node.observableChildren
def breadthFirstTraversal = node.observableBreadthFirst()
def depthFirstTraversal = node.observableDepthFirst()
def attachChild(n: jmes.Spatial): IO[AddNodeToItselfError.type, Unit] = def attachChild(n: jmes.Spatial): IO[AddNodeToItselfError.type, Unit] =
IO { node.attachChild(n); () }.onErrorHandleWith { IO { node.attachChild(n); () }.onErrorHandleWith {
case ex: IllegalArgumentException => case ex: IllegalArgumentException =>
@ -86,17 +87,10 @@ abstract class NodeWrapper2 protected (node: jmes.Node) {
else IO.unit else IO.unit
} }
def remove(n: jmes.Spatial) = UIO(node.detachChild(n)) 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 { object NodeWrapper2 {
sealed trait Error sealed trait Error

90
src/main/scala/wow/doge/mygame/utils/wrappers/jme/PhysicsSpace.scala

@ -1,57 +1,91 @@
package wow.doge.mygame.utils.wrappers.jme 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.{bullet => jmeb}
import com.jme3.{scene => jmes} import com.jme3.{scene => jmes}
import monix.bio.UIO import monix.bio.UIO
import wow.doge.mygame.implicits._ import wow.doge.mygame.implicits._
final class PhysicsSpace(space: jmeb.PhysicsSpace) { 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 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() def collisionObservable = space.collisionObservable()
// space.enqueue(() => ())
def physicsTickObservable = space.physicsTickObservable() def physicsTickObservable = space.physicsTickObservable()
def prePhysicsTickObservable = space.prePhysicsTickObservable()
} }
object PhysicsSpace { object PhysicsSpace {
implicit final class PhysicsSpaceOps(private val space: PhysicsSpace) implicit final class PhysicsSpaceOps(private val space: PhysicsSpace)
extends AnyVal { 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 -=(anyObject: Any) = space.remove(anyObject)
def +=(spatial: jmes.Spatial) = space.addAll(spatial) 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) 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)
}
}

5
src/test/scala/wow/doge/mygame/ActorTimeoutTest.scala

@ -17,14 +17,13 @@ class ActorTimeoutTest extends AnyFunSuite with BeforeAndAfterAll {
implicit val timeout = Timeout(1.millis) implicit val timeout = Timeout(1.millis)
test("timeoutTest") { test("timeoutTest") {
val fut = as.ask(MyActor.GetInt(_))
val fut = as.ask(MyActor.GetInt)
val res = Await.result(fut, 1.second) val res = Await.result(fut, 1.second)
assert(res == 1) assert(res == 1)
} }
override protected def afterAll(): Unit = {
override protected def afterAll(): Unit =
as.terminate() as.terminate()
}
} }

41
src/test/scala/wow/doge/mygame/ReaderT_Test.scala

@ -5,6 +5,12 @@ import monix.bio.UIO
import org.scalatest.funsuite.AnyFunSuite import org.scalatest.funsuite.AnyFunSuite
import monix.execution.Scheduler.Implicits.global import monix.execution.Scheduler.Implicits.global
import monix.bio.IO 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 { class ReaderT_Test extends AnyFunSuite {
@ -33,8 +39,36 @@ class ReaderT_Test extends AnyFunSuite {
// uio.runSyncUnsafe() // 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") { test("2") {
def fun1: ReaderT[IO[String, ?], String, Unit] =
def fun1: RIO[String, String, Unit] =
ReaderT(s => IO(println(s)).onErrorHandleWith(_ => IO.raiseError("wow"))) ReaderT(s => IO(println(s)).onErrorHandleWith(_ => IO.raiseError("wow")))
def fun2: ReaderT[IO[String, ?], Int, Unit] = def fun2: ReaderT[IO[String, ?], Int, Unit] =
ReaderT(num => ReaderT(num =>
@ -44,6 +78,8 @@ class ReaderT_Test extends AnyFunSuite {
for { for {
env <- ReaderT.ask[IO[String, ?], Environment] env <- ReaderT.ask[IO[String, ?], Environment]
} yield () } yield ()
def test[F[_]]()(implicit A: Ask[F, Int]) = A.ask[Int]
// def synctest[F[_]]
def fun4: ReaderT[IO[String, ?], Environment, Unit] = def fun4: ReaderT[IO[String, ?], Environment, Unit] =
for { for {
env <- ReaderT.ask[IO[String, ?], Environment] env <- ReaderT.ask[IO[String, ?], Environment]
@ -52,12 +88,15 @@ class ReaderT_Test extends AnyFunSuite {
) )
_ <- fun3 _ <- fun3
} yield () } yield ()
val topkek =
test[ReaderT[IO[String, ?], Int, *]]().local[Environment](_.num)
def app: ReaderT[IO[String, ?], Environment, Unit] = def app: ReaderT[IO[String, ?], Environment, Unit] =
for { for {
_ <- fun1.local[Environment](_.str) _ <- fun1.local[Environment](_.str)
_ <- fun2.local[Environment](_.num) _ <- fun2.local[Environment](_.num)
_ <- fun3 _ <- fun3
_ <- fun4 _ <- fun4
_ <- topkek
} yield () } yield ()
val io: IO[String, Unit] = app.run(Environment("hello", 50)) val io: IO[String, Unit] = app.run(Environment("hello", 50))

Loading…
Cancel
Save