This commit is contained in:
Rohan Sircar 2021-01-20 12:20:20 +05:30
parent 89fad19d99
commit 67a2bc4385
34 changed files with 1308 additions and 943 deletions

View File

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

View File

@ -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")

View File

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

View File

@ -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]]( .askL[Observable[PlayerMovementEvent]](ObservableSubscription(_))
ObservableSubscription(_) .onErrorHandleWith(TimeoutError.from)
)
.onErrorHandleWith {
case ex: TimeoutException =>
IO.raiseError(AppError.TimeoutError(ex.getMessage()))
}
_ <- _ <-
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)
_ <- _ <-
johnActor rootNode.depthFirstTraversal
.tellL(NpcActorSupervisor.Move(ImVector3f(-30, 0, 10))) .doOnNextF(spat => loggerL.debug(spat.getName).toTask)
.delayExecution(2.seconds) .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
_ <-
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)) .withControl(
.taggedWith[PlayerControllerTags.PlayerCameraPivotNode] 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 new NpcActorSupervisor.Props(
.Props(
new NpcMovementActor.Props( new NpcMovementActor.Props(
enqueueR, enqueueR,
initialPos, initialPos,
// tickEventBus,
npcName, npcName,
npcPhysicsControl npcPhysicsControl
).behavior, ).behavior,
npcName, npcName,
initialPos initialPos
) ).behavior,
.behavior, actorName = Some(s"${npcName}-npcActorSupervisor")
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 = {}
}

View File

@ -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()) val assetManager = new AssetManager(app.getAssetManager())
def guiNode = UIO(app.getGuiNode().taggedWith[GameAppTags.GuiNode]) val guiNode: GuiNode =
val guiNode2 = AppNode2(app.getGuiNode()).taggedWith[GameAppTags.GuiNode] AppNode2(app.getGuiNode()).taggedWith[GameAppTags.GuiNode]
def flyCam = Option(app.getFlyByCamera()) // 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: RootNode =
val 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 { Resource.make(
lazy val bullet = new BulletAppState
(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,22 +116,21 @@ 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", Some("testGameActor"),
Dispatchers.jmeDispatcher 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) => case Right(gameApp -> fib) => fib.cancel
fib.cancel >> UIO(JMERunner.runner = None)
case Left(error) => IO.terminate(new Exception(error.toString)) 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

View File

@ -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), .onFailure[Exception](
"tickGeneratorActor" 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, .behavior
"tickGeneratorTimer"
) )
Behaviors.receiveMessage { Behaviors.receiveMessage {

View File

@ -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,17 +44,18 @@ 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] = {

View File

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

View File

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

View File

@ -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( class Props(
npcMovementActorBehavior: Behavior[NpcMovementActor.Command], val npcMovementActorBehavior: Behavior[NpcMovementActor.Command],
npcName: String, val npcName: String,
initialPos: ImVector3f 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( show"npcActor-${props.npcName}: Difference = ${dst.formatted("%.2f")}"
"%.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 =

View File

@ -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( case class TakeDamage(value: Int) extends Command
playerEventBus: GameEventBus[PlayerEvent], case class Heal(value: Int) extends Command
tickEventBus: GameEventBus[TickEvent], case class CurrentStats(replyTo: ActorRef[StatsActor.State]) extends Command
imMovementActorBehavior: Behavior[ImMovementActor.Command], case class GetStatus(replyTo: ActorRef[Status]) extends Command
playerCameraActorBehavior: Behavior[PlayerCameraActor.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 => Behaviors
.setup[Command] { ctx =>
ctx.log.infoP("Starting PlayerActor") ctx.log.infoP("Starting PlayerActor")
// spawn children actors // spawn children actors
val movementActor = val playerMovementActor =
ctx.spawn( ctx.spawnN(
Behaviors Behaviors
.supervise(imMovementActorBehavior) .supervise(imMovementActorBehavior)
.onFailure[Exception](SupervisorStrategy.restart), .onFailure[Exception](
"playerMovementActor" SupervisorStrategy.restart.withLimit(2, 100.millis)
),
Dispatchers.jmeDispatcher
) )
val playerCameraActor = val playerStatsActor =
ctx.spawn(playerCameraActorBehavior, "playerCameraActor") ctx.spawnN(new StatsActor.Props(100, 100).behavior)
val playerCameraEl = ctx.spawn( val playerMovementEl = ctx.spawnN(
PlayerCameraEventListener(playerCameraActor),
"playerCameraActorEl"
)
val playerMovementEl = ctx.spawn(
Behaviors Behaviors
.supervise(PlayerMovementEventListener(movementActor)) .supervise(PlayerMovementEventListener(playerMovementActor))
.onFailure[Exception](SupervisorStrategy.restart), .onFailure[Exception](
"playerMovementEventHandler" SupervisorStrategy.restart.withLimit(2, 100.millis)
)
) )
val renderTickEl = { val renderTickEl = {
val behavior = val behavior: Behavior[RenderTick.type] =
Behaviors.receiveMessage[RenderTick.type] { Behaviors.setup(ctx =>
Behaviors
.receiveMessage[RenderTick.type] {
case RenderTick => case RenderTick =>
movementActor ! ImMovementActor.Tick playerMovementActor ! ImMovementActor.Tick
// playerCameraActor ! PlayerCameraActor.Tick // playerCameraActor ! PlayerCameraActor.Tick
Behaviors.same Behaviors.same
} }
.receiveSignal {
case (_, PostStop) =>
ctx.log.infoP("stopped")
Behaviors.same
}
)
ctx.spawn(behavior, "playerMovementTickListener") ctx.spawn(behavior, "playerMovementTickListener")
} }
//init listeners //init listeners
playerEventBus ! EventBus.Subscribe(playerMovementEl) playerEventBus ! EventBus.Subscribe(playerMovementEl)
tickEventBus ! EventBus.Subscribe(renderTickEl) tickEventBus ! EventBus.Subscribe(renderTickEl)
playerEventBus ! EventBus.Subscribe(playerCameraEl)
new PlayerActorSupervisor( new PlayerActorSupervisor(
ctx, ctx,
this, this,
Children(movementActor) Children(playerMovementActor, playerStatsActor),
).receive 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
}
.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 Behaviors.same
} }
} }
object PlayerMovementActor { object StatsActor {
sealed trait Command sealed trait Command
final case class Props( case class TakeDamage(value: Int) extends Command
movementActor: ActorRef[ImMovementActor.Command], case class TakeDamageResult(value: Int, replyTo: ActorRef[(Boolean, State)])
playerCameraActor: ActorRef[PlayerCameraActor.Command], extends Command
playerEventBus: GameEventBus[PlayerEvent], case class Heal(value: Int) extends Command
tickEventBus: GameEventBus[TickEvent] case class CurrentStats(replyTo: ActorRef[State]) extends Command
) {
def behavior: Behavior[Command] = class Props(startingHealth: Int, startingStamina: Int) {
Behaviors.setup { ctx => def behavior =
val playerMovementEl = ctx.spawn( Behaviors.setup[Command] { ctx =>
Behaviors new StatsActor(ctx, this)
.supervise(PlayerMovementEventListener(movementActor)) .receive(State(startingHealth, startingStamina))
.onFailure[Exception](SupervisorStrategy.restart), }
"playerMovementEventHandler" }
)
val renderTickEl = { case class State(hp: Int, stamina: Int)
val behavior = }
Behaviors.receiveMessage[RenderTick.type] { class StatsActor(
case RenderTick => ctx: ActorContext[StatsActor.Command],
movementActor ! ImMovementActor.Tick props: StatsActor.Props
// playerCameraActor ! PlayerCameraActor.Tick ) {
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 Behaviors.same
} }
ctx.spawn(behavior, "playerMovementTickListener")
}
playerEventBus ! EventBus.Subscribe(playerMovementEl)
tickEventBus ! EventBus.Subscribe(renderTickEl)
Behaviors.receiveMessage { msg => Behaviors.same }
}
}
} }

View File

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

View File

@ -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() val location = props.getPlayerLocation()
props.cameraPivotNode.setLocalTranslation(location) props.cameraPivotNode.setLocalTranslation(location)
})
Behaviors.same Behaviors.same
} }
} }

View File

@ -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.DispatcherSelector
import akka.actor.typed.SpawnProtocol
import akka.util.Timeout
import com.jme3.bullet.BulletAppState
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.GameApp
import wow.doge.mygame.game.SimpleAppExt
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 { import wow.doge.mygame.types._
sealed trait PlayerTag
sealed trait PlayerCameraNode
sealed trait PlayerCameraPivotNode
}
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, scheduler: monix.execution.Scheduler,
cameraNode: CameraNode @@ PlayerControllerTags.PlayerCameraNode, playerNode: PlayerNode,
cameraPivotNode: Node @@ PlayerControllerTags.PlayerCameraPivotNode, 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 <- // playerActor <-
AkkaUtils.spawnActorL(playerActorBehavior, "playerActorSupervisor") // // 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,61 +91,16 @@ object PlayerController {
_ = cameraPivotNode += cameraNode _ = cameraPivotNode += cameraNode
_ <- rootNode += cameraPivotNode _ <- rootNode += cameraPivotNode
} yield ()).mapError(AppError.AppNodeError) } yield ()).mapError(AppError.AppNodeError)
} yield playerActor) } yield pa)
} }
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
}
playerPhysicsControl
}
object Defaults { object Defaults {
def defaultMesh = { def defaultMesh = {
val b = Box(1, 1, 1) val b = Box(1, 1, 1)
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,

View File

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

View File

@ -43,10 +43,10 @@ object GameInputHandler {
// inputManager, // inputManager,
// playerEventBus // playerEventBus
// ).completedL, // ).completedL,
generateCameraEvents( // cameraMovementEventsGenerator(
inputManager, // inputManager,
playerEventBus // playerEventBus
).completedL, // ).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,7 +185,7 @@ 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)
@ -208,13 +208,12 @@ object GameInputHandler {
// ) // )
// } // }
// ) // )
}
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)

View File

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

View File

@ -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 case object Tick extends Command
final 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 MoveLeft(pressed: Boolean) extends Movement
final case class MovedUp(pressed: Boolean) extends Movement final case class MoveUp(pressed: Boolean) extends Movement
final case class MovedRight(pressed: Boolean) extends Movement final case class MoveRight(pressed: Boolean) extends Movement
final case class MovedDown(pressed: Boolean) extends Movement final case class MoveDown(pressed: Boolean) extends Movement
final case object Jump extends Movement case object Jump extends Movement
// final case object RotateRight extends Movement
// final case object RotateLeft 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 state: ImMovementActor.State,
)(implicit cm: CanMove[T]): Behavior[Command] = walkDirBuf: Vector3f
Behaviors.receiveMessage { ): Behavior[Command] =
Behaviors
.receiveMessage[Command] {
case m: Movement => case m: Movement =>
m match { m match {
case MovedLeft(pressed) => case MoveLeft(pressed) =>
props.enqueueR(() => stopIfNotPressed(pressed, movable)) stopIfNotPressed(pressed)
receive(state = state.modify(_.cardinalDir.left).setTo(pressed)) receive(
case MovedUp(pressed) => state = state.modify(_.cardinalDir.left).setTo(pressed),
props.enqueueR(() => stopIfNotPressed(pressed, movable)) walkDirBuf
receive(state = state.modify(_.cardinalDir.up).setTo(pressed)) )
case MovedRight(pressed) => case MoveUp(pressed) =>
props.enqueueR(() => stopIfNotPressed(pressed, movable)) stopIfNotPressed(pressed)
receive(state = state.modify(_.cardinalDir.right).setTo(pressed)) receive(
case MovedDown(pressed) => state = state.modify(_.cardinalDir.up).setTo(pressed),
props.enqueueR(() => stopIfNotPressed(pressed, movable)) walkDirBuf
receive(state = state.modify(_.cardinalDir.down).setTo(pressed)) )
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 => case Jump =>
props.enqueueR(() => cm.jump(movable)) cm.jump(movable)
Behaviors.same Behaviors.same
} }
case Tick => case Tick =>
val walkDir = val camDir =
getDirection2(state.cardinalDir, ctx.log.traceP) props.camera.getDirection().clone().normalizeLocal.multLocal(0.6f)
// if (walkDir != ImVector3f.ZERO) { val camLeft =
val tmp = walkDir * 25f * (1f / 144) props.camera.getLeft().clone().normalizeLocal.multLocal(0.4f)
props.enqueueR { () => val dir = state.cardinalDir
cm.move(movable, tmp) 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 Behaviors.same
} }
def stopIfNotPressed(pressed: Boolean) = if (!pressed) cm.stop(movable)
def getDirection2( def getDirection2(
cardinalDir: CardinalDirection, cardinalDir: CardinalDirection
debug: sourcecode.Text[String] => sourcecode.Text[String] // 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) = { // old/unused
val zero = ImVector3f.Zero object MovementActor {
val dir = cardinalDir sealed trait Command
val walkDir = { case object Tick extends Command
val mutWalkDir = new Vector3f()
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) { if (dir.left) {
trace("left") ctx.log.debugP("left")
mutWalkDir += zero +=: new Vector3f(-1, 0, 0) // walkDir.addLocal(-1, 0, 0)
walkDir.addLocal(camLeft)
} }
if (dir.right) { if (dir.right) {
trace("right") ctx.log.debugP("right")
mutWalkDir += zero +=: new Vector3f(1, 0, 0) // walkDir.addLocal(1, 0, 0)
} walkDir.addLocal(camLeft.negateLocal())
if (dir.up) {
trace("up")
mutWalkDir += zero +=: new Vector3f(0, 0, -1)
} }
if (dir.down) { if (dir.down) {
trace("down") ctx.log.debugP("down")
mutWalkDir += zero +=: new Vector3f(0, 0, 1) walkDir.addLocal(camDir.negateLocal())
} // walkDir.addLocal(0, 0, 1)
mutWalkDir.immutable
}
walkDir
} }
// (dir.up, dir.down, dir.left, dir.right) match {
// case (true, false, true, false) =>
// case _ =>
// }
def stopIfNotPressed[T](pressed: Boolean, movable: T)(implicit walkDir.multLocal(2f)
cm: CanMove[T]
) = // walkDir.multLocal(100f)
if (!pressed) cm.stop(movable) // .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))
}
}
} }
// 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)

View File

@ -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 case class PrePhysicsTickEvent(space: PhysicsSpace)
final class PhysicsTickEvent(val space: PhysicsSpace) extends AnyVal 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,14 +148,12 @@ 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)
extends AnyVal { extends AnyVal {
@ -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,7 +501,7 @@ 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
) { ) {
@ -498,14 +509,12 @@ package object implicits {
c.cancel() c.cancel()
} }
} }
}
inputManager.addListener(al, mappingNames: _*) inputManager.addListener(al, mappingNames: _*)
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,7 +548,7 @@ 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()
@ -548,19 +556,16 @@ package object implicits {
} }
} }
} }
}
inputManager.addListener(al, entryNames: _*) inputManager.addListener(al, entryNames: _*)
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)
@ -578,17 +583,14 @@ package object implicits {
c.cancel() c.cancel()
} }
} }
}
inputManager.addListener(al, mappingEnumEntry.entryName) inputManager.addListener(al, mappingEnumEntry.entryName)
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,7 +598,7 @@ 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
) { ) {
@ -604,19 +606,16 @@ package object implicits {
c.cancel() c.cancel()
} }
} }
}
inputManager.addListener(al, mappingNames: _*) inputManager.addListener(al, mappingNames: _*)
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,7 +624,7 @@ 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()
@ -633,7 +632,6 @@ package object implicits {
} }
} }
} }
}
inputManager.addListener(al, entryNames: _*) inputManager.addListener(al, entryNames: _*)
@ -641,71 +639,81 @@ package object implicits {
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 = { override def collision(event: jmePhysicsCollisionEvent): Unit =
if (
if (sub.onNext(event) == Ack.Stop) { sub.onNext(
sub.onComplete() CollisionEvent(
c.cancel() 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] = {
Observable.create(OverflowStrategy.DropOld(50)) { sub =>
val c = SingleAssignCancelable()
val cl = new PhysicsTickListener {
override def prePhysicsTick(space: PhysicsSpace, tpf: Float): Unit = {
val event = new PrePhysicsTickEvent(space)
if (sub.onNext(event) == Ack.Stop) {
sub.onComplete()
c.cancel()
}
}
override def physicsTick(space: PhysicsSpace, tpf: Float): Unit = {}
}
space.addTickListener(cl)
c := Cancelable(() => space.removeTickListener(cl))
c
}
}
def physicsTickObservable(): Observable[PhysicsTickEvent] = {
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( override def prePhysicsTick(
space: PhysicsSpace, space: jmeb.PhysicsSpace,
tpf: Float
): Unit = {
val event = new PrePhysicsTickEvent(new PhysicsSpace(space))
if (sub.onNext(event) == Ack.Stop) {
sub.onComplete()
c.cancel()
}
}
override def physicsTick(
space: jmeb.PhysicsSpace,
tpf: Float tpf: Float
): Unit = {} ): Unit = {}
override def physicsTick(space: PhysicsSpace, tpf: Float): Unit = { }
val event = new PhysicsTickEvent(space)
space.addTickListener(cl)
c := Cancelable(() => space.removeTickListener(cl))
c
}
def physicsTickObservable(): Observable[PhysicsTickEvent] =
Observable.create(OverflowStrategy.DropOld(50)) { sub =>
val c = SingleAssignCancelable()
val cl = new PhysicsTickListener {
override def prePhysicsTick(
space: jmeb.PhysicsSpace,
tpf: Float
): Unit = {}
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
}
} }

View File

@ -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)
//format: on
val Max = ImVector3f(Float.MaxValue, Float.MaxValue, Float.MaxValue) val Max = ImVector3f(Float.MaxValue, Float.MaxValue, Float.MaxValue)
val Min = ImVector3f(Float.MinValue, Float.MinValue, Float.MinValue) val Min = ImVector3f(Float.MinValue, Float.MinValue, Float.MinValue)
//format: on
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)
} }

View File

@ -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 def observable[A, B <: A](eventBus: ActorRef[EventBus.Command[A]])(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
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) => val behavior = Behaviors
.receive[B] { (ctx, msg) =>
ctx.log.traceP(s"Emitted $msg")
if (sub.onNext(msg) == Ack.Stop) { if (sub.onNext(msg) == Ack.Stop) {
c.cancel() c.cancel()
Behaviors.stopped Behaviors.stopped
} else Behaviors.same } 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

View File

@ -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 case object RenderTick extends TickEvent
final case object PhysicsTick 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
}

View File

@ -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]] = val mainEventBus: IO[AppError, GameEventBus[Event]] = createEventBus[Event]()
createEventBus[Event]("mainEventBus")
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 { .onErrorHandleWith(TimeoutError.from)
case ex: TimeoutException =>
IO.raiseError(AppError.TimeoutError(ex.getMessage))
}
} }

View File

@ -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 case object PlayerJumped extends PlayerMovementEvent
// final case object PlayerTurnedRight extends PlayerMovementEvent // case object PlayerTurnedRight extends PlayerMovementEvent
// final case object PlayerTurnedLeft extends PlayerMovementEvent // case object PlayerTurnedLeft extends PlayerMovementEvent
} }
sealed trait PlayerCameraEvent extends PlayerEvent sealed trait PlayerCameraEvent extends PlayerEvent
final object PlayerCameraEvent { object PlayerCameraEvent {
final case object CameraLeft extends PlayerCameraEvent case object CameraLeft extends PlayerCameraEvent
final case object CameraRight extends PlayerCameraEvent case object CameraRight extends PlayerCameraEvent
final case object CameraMovedUp extends PlayerCameraEvent case object CameraMovedUp extends PlayerCameraEvent
final case object CameraMovedDown extends PlayerCameraEvent case object CameraMovedDown extends PlayerCameraEvent
} }

View File

@ -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 => { res => Put(scriptPath, 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)
)
) )
} }

View File

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

View File

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

View File

@ -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,
_ _
) )

View File

@ -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 case object Start extends Command
final case object Stop 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)

View File

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

View File

@ -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) = def remove(wn: Node2) = UIO(node.detachChild(wn.unsafeDelegate))
UIO(node.detachChild(wn.unsafeDelegate)) def addLight(light: Light) = UIO(node.addLight(light))
def addLight(light: Light) = def removeLight(light: Light) = UIO(node.removeLight(light))
UIO { def asSpatial: UIO[jmes.Spatial] = UIO(node)
node.addLight(light)
}
def removeLight(light: Light) =
UIO {
node.removeLight(light)
}
def asSpatial: Task[jmes.Spatial] = UIO(node)
} }
object NodeWrapper2 { object NodeWrapper2 {
sealed trait Error sealed trait Error

View File

@ -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) = def remove(anyObject: Any) = UIO(space.remove(anyObject))
UIO {
space.remove(anyObject)
space
}
def addAll(spatial: jmes.Spatial) = UIO(space.addAll(spatial)) def addAll(spatial: jmes.Spatial) = UIO(space.addAll(spatial))
def removeAll(spatial: jmes.Spatial) = def removeAll(spatial: jmes.Spatial) = UIO(space.removeAll(spatial))
UIO {
space.removeAll(spatial)
space
}
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) = { def :+[T: PhysicsSpaceAddable](addable: T) =
space.add(anyObject) for {
space _ <- space.add(addable)
} } yield space
def -(anyObject: Any) = { def -(anyObject: Any) =
space.remove(anyObject) for {
space _ <- 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) = { // def :+(spatial: jmes.Spatial) = {
space.addAll(spatial) // space.addAll(spatial)
space // space
} // }
def -(spatial: jmes.Spatial) = { // def -(spatial: jmes.Spatial) = {
space.removeAll(spatial) // space.removeAll(spatial)
space // 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)
}
}

View File

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

View File

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