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")
lazy val root = (project in file(".")).settings(
inThisBuild(
List(
scalaVersion := scalaVersion.value, // 2.11.12, or 2.13.3
semanticdbEnabled := true, // enable SemanticDB
semanticdbVersion := "4.3.24" // use Scalafix compatible version
)
),
name := "mygame",
organization := "wow.doge",
version := "1.0-SNAPSHOT",
@ -71,7 +64,9 @@ lazy val root = (project in file(".")).settings(
"org.recast4j" % "recast" % "1.2.5",
"org.recast4j" % "detour" % "1.2.5",
"com.lihaoyi" %% "pprint" % "0.6.0",
"org.scalatest" %% "scalatest" % "3.2.2" % "test"
"org.scalatest" %% "scalatest" % "3.2.2" % "test",
"org.typelevel" %% "cats-mtl" % "1.1.1",
"io.estatico" %% "newtype" % "0.4.4"
),
// Determine OS version of JavaFX binaries
@ -135,4 +130,11 @@ addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1")
addCompilerPlugin(
"org.typelevel" %% "kind-projector" % "0.11.1" cross CrossVersion.full
)
inThisBuild(
List(
scalaVersion := scalaVersion.value, // 2.11.12, or 2.13.3
semanticdbEnabled := true, // enable SemanticDB
semanticdbVersion := "4.3.24" // use Scalafix compatible version
)
)
ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.4.3"

View File

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

View File

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

View File

@ -1,7 +1,5 @@
package wow.doge.mygame
import java.util.concurrent.TimeoutException
import scala.concurrent.duration._
import akka.actor.typed.ActorRef
@ -19,10 +17,8 @@ import com.jme3.material.MaterialDef
import com.jme3.math.ColorRGBA
import com.jme3.math.FastMath
import com.jme3.renderer.Camera
import com.jme3.renderer.RenderManager
import com.jme3.renderer.ViewPort
import com.jme3.scene.Node
import com.jme3.scene.control.AbstractControl
import com.softwaremill.macwire._
import com.softwaremill.tagging._
import io.odin.Logger
@ -33,38 +29,44 @@ import monix.bio.UIO
import monix.eval.Coeval
import monix.reactive.Observable
import scalafx.scene.control.TextArea
import wow.doge.mygame.AppError.TimeoutError
import wow.doge.mygame.executors.Schedulers
import wow.doge.mygame.game.GameApp
import wow.doge.mygame.game.GameAppActor
import wow.doge.mygame.game.GameAppResource
import wow.doge.mygame.game.GameAppTags
import wow.doge.mygame.game.entities.EntityIds
import wow.doge.mygame.game.entities.NpcActorSupervisor
import wow.doge.mygame.game.entities.NpcMovementActor
import wow.doge.mygame.game.entities.PlayerActorSupervisor
import wow.doge.mygame.game.entities.PlayerController
import wow.doge.mygame.game.entities.PlayerControllerTags
import wow.doge.mygame.game.subsystems.input.GameInputHandler
import wow.doge.mygame.game.subsystems.level.DefaultGameLevel
import wow.doge.mygame.implicits._
import wow.doge.mygame.launcher.Launcher
import wow.doge.mygame.launcher.Launcher.LauncherResult
import wow.doge.mygame.math.ImVector3f
import wow.doge.mygame.subsystems.events.Event
import wow.doge.mygame.subsystems.events.EventBus
import wow.doge.mygame.subsystems.events.EventBus.ObservableSubscription
import wow.doge.mygame.subsystems.events.EventsModule
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
import wow.doge.mygame.subsystems.events.PlayerEvent
import wow.doge.mygame.subsystems.events.PlayerMovementEvent
import wow.doge.mygame.subsystems.events.StatsEvent.DamageEvent
import wow.doge.mygame.subsystems.events.TickEvent
import wow.doge.mygame.subsystems.scriptsystem.ScriptInitMode
import wow.doge.mygame.subsystems.scriptsystem.ScriptSystemResource
import wow.doge.mygame.utils.AkkaUtils
import wow.doge.mygame.utils.GenericConsoleStream
import wow.doge.mygame.utils.IOUtils
import wow.doge.mygame.utils.wrappers.jme.AppNode2
import wow.doge.mygame.utils.wrappers.jme.AssetManager
import wow.doge.mygame.utils.wrappers.jme.PhysicsSpace
import wow.doge.mygame.subsystems.events.PlayerCameraEvent
import com.jme3.math.Quaternion
import com.jme3.math.Vector3f
import wow.doge.mygame.types._
import wow.doge.mygame.game.controls.FollowControl
import wow.doge.mygame.game.subsystems.input.PlayerCameraInput
class MainApp(
logger: Logger[Task],
jmeThread: monix.execution.Scheduler,
@ -94,13 +96,8 @@ class MainApp(
tickEventBus <- eventsModule.tickEventBus
obs <-
playerEventBus
.askL[Observable[PlayerMovementEvent]](
ObservableSubscription(_)
)
.onErrorHandleWith {
case ex: TimeoutException =>
IO.raiseError(AppError.TimeoutError(ex.getMessage()))
}
.askL[Observable[PlayerMovementEvent]](ObservableSubscription(_))
.onErrorHandleWith(TimeoutError.from)
_ <-
IOUtils
.toIO(
@ -121,7 +118,7 @@ class MainApp(
// jfxUI <- gameApp.jfxUI
gameAppActor <- gameApp.spawnGameActor(
GameAppActor.Props(tickEventBus).behavior,
"gameAppActor"
Some("gameAppActor")
)
_ <- gameAppActor !! GameAppActor.Start
consoleTextArea <- UIO(new TextArea {
@ -138,7 +135,7 @@ class MainApp(
_ <-
wire[MainAppDelegate]
.init()
.executeOn(gameApp.scheduler)
.executeOn(gameApp.scheduler.value)
} yield fib
def gameInit: Resource[UIO, Either[AppError, Fiber[Nothing, Unit]]] =
@ -184,6 +181,7 @@ class MainApp(
class MainAppDelegate(
gameApp: GameApp,
loggerL: Logger[Task],
mainEventBus: GameEventBus[Event],
playerEventBus: GameEventBus[PlayerEvent],
tickEventBus: GameEventBus[TickEvent],
inputManager: InputManager,
@ -192,7 +190,8 @@ class MainAppDelegate(
camera: Camera,
viewPort: ViewPort,
enqueueR: Function1[() => Unit, Unit],
rootNode: AppNode2 @@ GameAppTags.RootNode
rootNode: RootNode,
schedulers: Schedulers
)(implicit
spawnProtocol: ActorRef[SpawnProtocol.Command],
timeout: Timeout,
@ -213,17 +212,104 @@ class MainAppDelegate(
// _ <- Task(consoleStream.println("text"))
level <- DefaultGameLevel(assetManager, viewPort)
_ <- level.addToGame(rootNode, physicsSpace)
_ <- createPlayerController()
playerActor <- createPlayerController()
// .onErrorRestart(3)
_ <- wire[GameInputHandler.Props].begin
// .onErrorRestart(3)
johnActor <- createTestNpc("John")
// _ <- johnActor !! NpcActorSupervisor.Move(ImVector3f(0, 0, 20))
// _ <-
// johnActor
// .tellL(NpcActorSupervisor.Move(ImVector3f(-30, 0, 10)))
// .delayExecution(2.seconds)
_ <-
johnActor
.tellL(NpcActorSupervisor.Move(ImVector3f(-30, 0, 10)))
.delayExecution(2.seconds)
rootNode.depthFirstTraversal
.doOnNextF(spat => loggerL.debug(spat.getName).toTask)
.completedL
.toIO
.hideErrors
damageObs <-
mainEventBus
.askL[Observable[DamageEvent]](ObservableSubscription(_))
.onErrorHandleWith(TimeoutError.from)
_ <-
damageObs
.doOnNextF(event =>
(loggerL.debug(s"Received Damage Event $event") >>
IO(
playerActor ! PlayerActorSupervisor.TakeDamage(event.amount)
)).toTask
)
.completedL
.toIO
.hideErrors
.startAndForget
_ <-
Observable
.interval(1.second)
.doOnNextF(_ =>
playerActor
.askL(PlayerActorSupervisor.GetStatus)
.flatMap(s =>
loggerL.debug(s"Player actor status: $s") >> UIO.pure(s)
)
.void
// .flatMap(s =>
// if (s == Status.Alive)
// playerActor
// .askL(PlayerActorSupervisor.CurrentStats )
// .flatMap(s => loggerL.debug(s"Got state $s"))
// else IO.unit
// )
.toTask
)
// .doOnNextF(_ =>
// playerActor
// .askL(PlayerActorSupervisor.GetStatus )
// .flatMap(s => loggerL.debug(s"Player actor status: $s"))
// .toTask
// )
.completedL
.toIO
.hideErrors
.startAndForget
_ <-
physicsSpace.collisionObservable
.filter(event =>
(for {
nodeA <- event.nodeA
nodeB <- event.nodeB
} yield nodeA.getName === "PlayerNode" && nodeB.getName === "John" ||
nodeB.getName === "PlayerNode" && nodeA.getName === "John")
.getOrElse(false)
)
.doOnNextF(event =>
loggerL
.debug(s"$event ${event.appliedImpulse()}")
.toTask
)
.doOnNextF(event =>
(for {
victim <- Coeval(for {
nodeA <- event.nodeA
nodeB <- event.nodeB
} yield if (nodeB.getName === "John") nodeA else nodeB)
_ <- Coeval(
victim.foreach { v =>
pprint.log(s"emitted event ${v.getName}")
mainEventBus ! EventBus.Publish(
DamageEvent("John", v.getName, 10),
"damageHandler"
)
}
)
} yield ()).void
)
.completedL
.toIO
.hideErrors
.startAndForget
// _ <-
// IOUtils
// .toIO(
@ -238,12 +324,12 @@ class MainAppDelegate(
def createPlayerController(
// appScheduler: monix.execution.Scheduler
): IO[AppError, ActorRef[PlayerActorSupervisor.Command]] = {
): IO[AppError, PlayerActorSupervisor.Ref] = {
val playerPos = ImVector3f.Zero
val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o"
val playerPhysicsControl =
PlayerController.Defaults.defaultPlayerPhysicsControl
.taggedWith[PlayerControllerTags.PlayerTag]
for {
playerModel <-
assetManager
@ -260,42 +346,115 @@ class MainAppDelegate(
)
cameraPivotNode <- UIO(
new Node(EntityIds.CameraPivot.value)
.withControl(new FollowControl(playerNode))
.taggedWith[PlayerControllerTags.PlayerCameraPivotNode]
.withControl(
new FollowControl(playerNode)
)
.taggedWith[PlayerController.Tags.PlayerCameraPivotNode]
)
camNode <- UIO(
PlayerController.Defaults
.defaultCamerNode(camera, playerPos)
.taggedWith[PlayerControllerTags.PlayerCameraNode]
.taggedWith[PlayerController.Tags.PlayerCameraNode]
)
playerCameraEvents <-
playerEventBus
.askL[Observable[PlayerCameraEvent]](ObservableSubscription(_))
.onErrorHandleWith(TimeoutError.from)
_ <-
inputManager
.enumAnalogObservable(PlayerCameraInput)
.sample(1.millis)
.scan(new Quaternion) {
case (rotationBuf, action) =>
action.binding match {
// case PlayerCameraEvent.CameraLeft =>
case PlayerCameraInput.CameraRotateLeft =>
// me.Task {
val rot = rotationBuf
.fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y)
cameraPivotNode.rotate(rot)
rotationBuf
// }
// case PlayerCameraEvent.CameraRight =>
case PlayerCameraInput.CameraRotateRight =>
// me.Task {
val rot = rotationBuf
.fromAngleAxis(-1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y)
cameraPivotNode.rotate(rot)
rotationBuf
// }
// case PlayerCameraEvent.CameraMovedUp =>
case PlayerCameraInput.CameraRotateUp =>
// me.Task {
val rot = rotationBuf
.fromAngleAxis(-1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X)
cameraPivotNode.rotate(rot)
rotationBuf
// }
// case PlayerCameraEvent.CameraMovedDown =>
case PlayerCameraInput.CameraRotateDown =>
// me.Task {
val rot = rotationBuf
.fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X)
cameraPivotNode.rotate(rot)
rotationBuf
// }
}
}
.completedL
.toIO
.hideErrors
.startAndForget
_ <-
Observable
.interval(10.millis)
.doOnNextF(_ =>
Coeval {
val location = playerNode.getWorldTranslation()
cameraPivotNode.setLocalTranslation(location)
}
)
.completedL
.toIO
.hideErrors
.startAndForget
sched <- UIO.pure(schedulers.async)
playerActor <- wire[PlayerController.Props].create
obs <-
playerActor
.askL(PlayerActorSupervisor.GetStatsObservable)
.onErrorHandleWith(TimeoutError.from)
_ <-
obs
.doOnNext(s => loggerL.debug(s"Got state $s").toTask)
.completedL
.toIO
.hideErrors
.startAndForget
} yield playerActor
}
def createTestNpc(
// appScheduler: monix.execution.Scheduler,
npcName: String
): IO[AppError, ActorRef[NpcActorSupervisor.Command]] = {
): IO[AppError, NpcActorSupervisor.Ref] = {
val initialPos = ImVector3f(50, 5, 0)
val npcPhysicsControl = new BetterCharacterControl(1.5f, 6f, 1f)
// (1f, 2.1f, 10f)
.withJumpForce(ImVector3f(0, 5f, 0))
val npcActorTask = AkkaUtils.spawnActorL(
NpcActorSupervisor
.Props(
new NpcActorSupervisor.Props(
new NpcMovementActor.Props(
enqueueR,
initialPos,
// tickEventBus,
npcName,
npcPhysicsControl
).behavior,
npcName,
initialPos
)
.behavior,
s"${npcName}-npcActorSupervisor"
).behavior,
actorName = Some(s"${npcName}-npcActorSupervisor")
)
(for {
@ -325,12 +484,3 @@ class MainAppDelegate(
}
}
class FollowControl(playerNode: Node) extends AbstractControl {
override def controlUpdate(tpf: Float): Unit =
this.spatial.setLocalTranslation(playerNode.getLocalTranslation())
override def controlRender(
rm: RenderManager,
vp: ViewPort
): Unit = {}
}

View File

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

View File

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

View File

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

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

View File

@ -1,13 +1,17 @@
package wow.doge.mygame.game.entities
import scala.concurrent.duration._
import akka.actor.typed.ActorRef
import akka.actor.typed.Behavior
import akka.actor.typed.LogOptions
import akka.actor.typed.PostStop
import akka.actor.typed.SupervisorStrategy
import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.scaladsl.Behaviors
import com.typesafe.scalalogging.Logger
import org.slf4j.event.Level
import wow.doge.mygame.Dispatchers
import wow.doge.mygame.implicits._
import wow.doge.mygame.subsystems.events.EventBus
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
@ -15,125 +19,250 @@ import wow.doge.mygame.subsystems.events.PlayerEvent
import wow.doge.mygame.subsystems.events.TickEvent
import wow.doge.mygame.subsystems.events.TickEvent.RenderTick
import wow.doge.mygame.subsystems.movement.ImMovementActor
import scala.util.Success
import scala.util.Failure
import akka.util.Timeout
import monix.reactive.Observable
import monix.reactive.subjects.ConcurrentSubject
import monix.execution.Scheduler
import monix.reactive.OverflowStrategy
object PlayerActorSupervisor {
type Ref = ActorRef[PlayerActorSupervisor.Command]
sealed trait Status
object Status {
case object Alive extends Status
case object Dead extends Status
}
sealed trait Command
final case class Props(
playerEventBus: GameEventBus[PlayerEvent],
tickEventBus: GameEventBus[TickEvent],
imMovementActorBehavior: Behavior[ImMovementActor.Command],
playerCameraActorBehavior: Behavior[PlayerCameraActor.Command]
case class TakeDamage(value: Int) extends Command
case class Heal(value: Int) extends Command
case class CurrentStats(replyTo: ActorRef[StatsActor.State]) extends Command
case class GetStatus(replyTo: ActorRef[Status]) extends Command
case class GetStatsObservable(replyTo: ActorRef[Observable[StatsActor.State]])
extends Command
private case object Die extends Command
private case class DamageResponse(response: (Boolean, StatsActor.State))
extends Command
// private case class InternalTakeDamage(old: Int, value: Int) extends Command
private case class LogError(ex: Throwable) extends Command
class Props(
val playerEventBus: GameEventBus[PlayerEvent],
val tickEventBus: GameEventBus[TickEvent],
val imMovementActorBehavior: Behavior[ImMovementActor.Command],
val scheduler: Scheduler
) {
def behavior =
Behaviors.logMessages(
LogOptions()
.withLevel(Level.TRACE)
.withLevel(Level.DEBUG)
.withLogger(
Logger[PlayerActorSupervisor].underlying
),
Behaviors.setup[Command] { ctx =>
Behaviors
.setup[Command] { ctx =>
ctx.log.infoP("Starting PlayerActor")
// spawn children actors
val movementActor =
ctx.spawn(
val playerMovementActor =
ctx.spawnN(
Behaviors
.supervise(imMovementActorBehavior)
.onFailure[Exception](SupervisorStrategy.restart),
"playerMovementActor"
.onFailure[Exception](
SupervisorStrategy.restart.withLimit(2, 100.millis)
),
Dispatchers.jmeDispatcher
)
val playerCameraActor =
ctx.spawn(playerCameraActorBehavior, "playerCameraActor")
val playerStatsActor =
ctx.spawnN(new StatsActor.Props(100, 100).behavior)
val playerCameraEl = ctx.spawn(
PlayerCameraEventListener(playerCameraActor),
"playerCameraActorEl"
)
val playerMovementEl = ctx.spawn(
val playerMovementEl = ctx.spawnN(
Behaviors
.supervise(PlayerMovementEventListener(movementActor))
.onFailure[Exception](SupervisorStrategy.restart),
"playerMovementEventHandler"
.supervise(PlayerMovementEventListener(playerMovementActor))
.onFailure[Exception](
SupervisorStrategy.restart.withLimit(2, 100.millis)
)
)
val renderTickEl = {
val behavior =
Behaviors.receiveMessage[RenderTick.type] {
val behavior: Behavior[RenderTick.type] =
Behaviors.setup(ctx =>
Behaviors
.receiveMessage[RenderTick.type] {
case RenderTick =>
movementActor ! ImMovementActor.Tick
playerMovementActor ! ImMovementActor.Tick
// playerCameraActor ! PlayerCameraActor.Tick
Behaviors.same
}
.receiveSignal {
case (_, PostStop) =>
ctx.log.infoP("stopped")
Behaviors.same
}
)
ctx.spawn(behavior, "playerMovementTickListener")
}
//init listeners
playerEventBus ! EventBus.Subscribe(playerMovementEl)
tickEventBus ! EventBus.Subscribe(renderTickEl)
playerEventBus ! EventBus.Subscribe(playerCameraEl)
new PlayerActorSupervisor(
ctx,
this,
Children(movementActor)
).receive
Children(playerMovementActor, playerStatsActor),
ConcurrentSubject.publish(OverflowStrategy.DropOld(50))(scheduler)
).aliveState
}
)
}
case class Children(
movementActor: ActorRef[ImMovementActor.Command]
movementActor: ActorRef[ImMovementActor.Command],
statsActor: ActorRef[StatsActor.Command]
)
}
class PlayerActorSupervisor(
ctx: ActorContext[PlayerActorSupervisor.Command],
props: PlayerActorSupervisor.Props,
children: PlayerActorSupervisor.Children
children: PlayerActorSupervisor.Children,
statsSubject: ConcurrentSubject[StatsActor.State, StatsActor.State]
) {
import PlayerActorSupervisor._
def receive =
implicit val timeout = Timeout(1.second)
val aliveState =
Behaviors
.receiveMessage[Command] {
case _ =>
case TakeDamage(value) =>
// children.movementActor ! ImMovementActor.MovedDown(true)
// ctx.ask(children.statsActor, StatsActor.CurrentStats(_)) {
// case Success(status) => InternalTakeDamage(status.hp, value)
// case Failure(ex) => LogError(ex)
// }
ctx.ask(children.statsActor, StatsActor.TakeDamageResult(value, _)) {
case Success(response) => DamageResponse(response)
case Failure(ex) => LogError(ex)
}
Behaviors.same
case CurrentStats(replyTo) =>
// ctx.ask(children.statsActor, StatsActor.CurrentStats())
children.statsActor ! StatsActor.CurrentStats(replyTo)
Behaviors.same
case Heal(value) =>
children.statsActor ! StatsActor.Heal(value)
Behaviors.same
case GetStatus(replyTo) =>
replyTo ! Status.Alive
Behaviors.same
// case _ => Behaviors.unhandled
// case InternalTakeDamage(hp, damage) =>
// if (hp - damage <= 0) dead
// else {
// children.statsActor ! StatsActor.TakeDamage(damage)
// Behaviors.same
// }
case GetStatsObservable(replyTo) =>
replyTo ! statsSubject
Behaviors.same
case DamageResponse(response) =>
response match {
case (dead, state) =>
if (dead) ctx.self ! Die
statsSubject.onNext(state)
}
Behaviors.same
case Die => deadState
case LogError(ex) =>
ctx.log.error(ex.getMessage)
Behaviors.same
}
.receiveSignal {
case (_, PostStop) =>
ctx.log.infoP("stopped")
statsSubject.onComplete()
Behaviors.same
}
val deadState = Behaviors
.receiveMessage[Command] {
// case TakeDamage(value) =>
// children.statsActor ! StatsActor.TakeDamage(value)
// // children.movementActor ! ImMovementActor.MovedDown(true)
// Behaviors.same
// case CurrentStats(replyTo) =>
// // ctx.ask(children.statsActor, StatsActor.CurrentStats())
// children.statsActor ! StatsActor.CurrentStats(replyTo)
// Behaviors.same
// case Heal(_) =>
// Behaviors.same
case CurrentStats(replyTo) =>
// ctx.ask(children.statsActor, StatsActor.CurrentStats())
children.statsActor ! StatsActor.CurrentStats(replyTo)
Behaviors.same
case GetStatus(replyTo) =>
replyTo ! Status.Dead
Behaviors.same
case _ => Behaviors.unhandled
}
.receiveSignal {
case (_, PostStop) =>
ctx.log.infoP("stopped")
statsSubject.onComplete()
Behaviors.same
}
}
object PlayerMovementActor {
object StatsActor {
sealed trait Command
final case class Props(
movementActor: ActorRef[ImMovementActor.Command],
playerCameraActor: ActorRef[PlayerCameraActor.Command],
playerEventBus: GameEventBus[PlayerEvent],
tickEventBus: GameEventBus[TickEvent]
case class TakeDamage(value: Int) extends Command
case class TakeDamageResult(value: Int, replyTo: ActorRef[(Boolean, State)])
extends Command
case class Heal(value: Int) extends Command
case class CurrentStats(replyTo: ActorRef[State]) extends Command
class Props(startingHealth: Int, startingStamina: Int) {
def behavior =
Behaviors.setup[Command] { ctx =>
new StatsActor(ctx, this)
.receive(State(startingHealth, startingStamina))
}
}
case class State(hp: Int, stamina: Int)
}
class StatsActor(
ctx: ActorContext[StatsActor.Command],
props: StatsActor.Props
) {
def behavior: Behavior[Command] =
Behaviors.setup { ctx =>
val playerMovementEl = ctx.spawn(
Behaviors
.supervise(PlayerMovementEventListener(movementActor))
.onFailure[Exception](SupervisorStrategy.restart),
"playerMovementEventHandler"
)
val renderTickEl = {
val behavior =
Behaviors.receiveMessage[RenderTick.type] {
case RenderTick =>
movementActor ! ImMovementActor.Tick
// playerCameraActor ! PlayerCameraActor.Tick
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
}
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 =>
val rot = rotationBuf
.fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y)
props.enqueueR(() => props.cameraPivotNode.rotate(rot))
props.cameraPivotNode.rotate(rot)
Behaviors.same
case RotateRight =>
val rot = rotationBuf
.fromAngleAxis(-1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y)
props.enqueueR(() => props.cameraPivotNode.rotate(rot))
props.cameraPivotNode.rotate(rot)
Behaviors.same
case RotateUp =>
val rot = rotationBuf
.fromAngleAxis(-1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X)
props.enqueueR(() => props.cameraPivotNode.rotate(rot))
props.cameraPivotNode.rotate(rot)
Behaviors.same
case RotateDown =>
val rot = rotationBuf
.fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X)
props.enqueueR(() => props.cameraPivotNode.rotate(rot))
props.cameraPivotNode.rotate(rot)
Behaviors.same
case Tick =>
props.enqueueR(() => {
val location = props.getPlayerLocation()
props.cameraPivotNode.setLocalTranslation(location)
})
Behaviors.same
}
}

View File

@ -1,12 +1,8 @@
package wow.doge.mygame.game.entities
import akka.actor.typed.ActorRef
import akka.actor.typed.Scheduler
import akka.actor.typed.SpawnProtocol
import akka.util.Timeout
import com.jme3.bullet.BulletAppState
import akka.actor.typed.DispatcherSelector
import com.jme3.bullet.control.BetterCharacterControl
import com.jme3.math.FastMath
import com.jme3.renderer.Camera
import com.jme3.scene.CameraNode
import com.jme3.scene.Geometry
@ -18,67 +14,76 @@ import io.odin.Logger
import monix.bio.IO
import monix.bio.Task
import wow.doge.mygame.AppError
import wow.doge.mygame.game.GameAppTags
import wow.doge.mygame.game.SimpleAppExt
import wow.doge.mygame.game.GameApp
import wow.doge.mygame.implicits._
import wow.doge.mygame.math.ImVector3f
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
import wow.doge.mygame.subsystems.events.PlayerEvent
import wow.doge.mygame.subsystems.events.TickEvent
import wow.doge.mygame.subsystems.movement.ImMovementActor
import wow.doge.mygame.utils.AkkaUtils
import wow.doge.mygame.utils.wrappers.jme._
object PlayerControllerTags {
sealed trait PlayerTag
sealed trait PlayerCameraNode
sealed trait PlayerCameraPivotNode
}
import wow.doge.mygame.types._
object PlayerController {
sealed trait Error
case class CouldNotCreatePlayerModel(reason: AssetManager.Error) extends Error
object Tags {
sealed trait PlayerNode
sealed trait PlayerCameraNode
sealed trait PlayerCameraPivotNode
}
type PlayerNode = Node @@ Tags.PlayerNode
type PlayerCameraNode = CameraNode @@ Tags.PlayerCameraNode
type PlayerCameraPivotNode =
Node @@ Tags.PlayerCameraPivotNode
class Props(
gameApp: GameApp,
enqueueR: Function1[() => Unit, Unit],
rootNode: AppNode2 @@ GameAppTags.RootNode,
rootNode: RootNode,
loggerL: Logger[Task],
// physicsSpace: com.jme3.bullet.PhysicsSpace,
physicsSpace: PhysicsSpace,
initialPlayerPos: ImVector3f = ImVector3f.Zero,
initialPlayerPos: ImVector3f,
playerEventBus: GameEventBus[PlayerEvent],
playerPhysicsControl: BetterCharacterControl,
// appScheduler: monix.execution.Scheduler,
playerNode: Node @@ PlayerControllerTags.PlayerTag,
cameraNode: CameraNode @@ PlayerControllerTags.PlayerCameraNode,
cameraPivotNode: Node @@ PlayerControllerTags.PlayerCameraPivotNode,
scheduler: monix.execution.Scheduler,
playerNode: PlayerNode,
cameraNode: PlayerCameraNode,
cameraPivotNode: PlayerCameraPivotNode,
tickEventBus: GameEventBus[TickEvent],
camera: Camera
)(implicit
spawnProtocol: ActorRef[SpawnProtocol.Command],
timeout: Timeout,
scheduler: Scheduler
) {
val playerActorBehavior = {
val movementActorBeh = new ImMovementActor.Props(
enqueueR,
camera
).behavior(playerPhysicsControl)
val cameraActorBeh = new PlayerCameraActor.Props(
cameraPivotNode,
enqueueR,
playerNode.getWorldTranslation _
).behavior
new PlayerActorSupervisor.Props(
playerEventBus,
tickEventBus,
movementActorBeh,
cameraActorBeh
scheduler
).behavior
}
val playerActor =
gameApp.spawnGameActor(
playerActorBehavior,
// Some("playerActorSupervisor"),
props = DispatcherSelector.default()
)
val create: IO[AppError, ActorRef[PlayerActorSupervisor.Command]] =
(for {
playerActor <-
AkkaUtils.spawnActorL(playerActorBehavior, "playerActorSupervisor")
// playerActor <-
// // AkkaUtils.spawnActorL(playerActorBehavior, "playerActorSupervisor")
// gameApp.spawnGameActor(
// playerActorBehavior,
// Some("playerActorSupervisor"),
// props = DispatcherSelector.default()
// )
pa <- playerActor
_ <- (for {
_ <- rootNode += playerNode
_ <- physicsSpace += playerNode
@ -86,61 +91,16 @@ object PlayerController {
_ = cameraPivotNode += cameraNode
_ <- rootNode += cameraPivotNode
} 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 {
def defaultMesh = {
val b = Box(1, 1, 1)
val geom = Geometry("playerGeom", b)
geom
}
// def defaultTexture(assetManager: AssetManager) =
// MyMaterial(
// assetManager = assetManager,
// path = os.rel / "Common" / "MatDefs" / "Misc" / "Unshaded.j3md"
// )
// new CameraControl(cam) {
// override def controlUpdate(tpf: Float) = {
// this.getCamera().setRotation(spatial.getWorldRotation())
// cameraPivotNode.setLocalTranslation(
// playerNode.getWorldTranslation()
// )
// this.getCamera().setLocation(spatial.getWorldTranslation())
// }
// }
def defaultCamerNode(
cam: Camera,
@ -163,7 +123,7 @@ object PlayerController {
.withChildren(playerModel)
.withLocalTranslation(playerPos)
.withControl(playerPhysicsControl)
.taggedWith[PlayerControllerTags.PlayerTag]
.taggedWith[Tags.PlayerNode]
def defaultNpcNode(
// assetManager: AssetManager,

View File

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

View File

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

View File

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

View File

@ -1,15 +1,23 @@
package wow.doge.mygame.subsystems.movement
import akka.actor.typed.Behavior
import akka.actor.typed.PostStop
import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.scaladsl.Behaviors
import com.jme3.math.Vector3f
import com.jme3.renderer.Camera
import com.jme3.scene.Geometry
import com.softwaremill.quicklens._
import wow.doge.mygame.game.subsystems.movement.CanMove
import wow.doge.mygame.implicits._
import wow.doge.mygame.math.ImVector3f
import wow.doge.mygame.state.CardinalDirection
final case class CardinalDirection(
left: Boolean = false,
right: Boolean = false,
up: Boolean = false,
down: Boolean = false
)
sealed trait RotateDir
object RotateDir {
@ -19,19 +27,16 @@ object RotateDir {
object ImMovementActor {
sealed trait Command
// final case class Tick(tpf: Float) extends Command
final case object Tick extends Command
case object Tick extends Command
sealed trait Movement extends Command
final case class MovedLeft(pressed: Boolean) extends Movement
final case class MovedUp(pressed: Boolean) extends Movement
final case class MovedRight(pressed: Boolean) extends Movement
final case class MovedDown(pressed: Boolean) extends Movement
final case object Jump extends Movement
// final case object RotateRight extends Movement
// final case object RotateLeft extends Movement
final case class MoveLeft(pressed: Boolean) extends Movement
final case class MoveUp(pressed: Boolean) extends Movement
final case class MoveRight(pressed: Boolean) extends Movement
final case class MoveDown(pressed: Boolean) extends Movement
case object Jump extends Movement
final class Props(
class Props(
val enqueueR: Function1[() => Unit, Unit],
// playerMovementEventBus: ActorRef[
// EventBus.Command[PlayerMovementEvent]
@ -40,7 +45,7 @@ object ImMovementActor {
) {
def behavior[T: CanMove](movable: T): Behavior[Command] =
Behaviors.setup(ctx =>
new ImMovementActor(ctx, this, movable).receive(State())
new ImMovementActor(ctx, this, movable).receive(State(), new Vector3f)
)
}
@ -57,48 +62,84 @@ class ImMovementActor[T](
ctx: ActorContext[ImMovementActor.Command],
props: ImMovementActor.Props,
val movable: T
) {
)(implicit cm: CanMove[T]) {
import ImMovementActor._
import Methods._
def receive(
state: ImMovementActor.State
)(implicit cm: CanMove[T]): Behavior[Command] =
Behaviors.receiveMessage {
state: ImMovementActor.State,
walkDirBuf: Vector3f
): Behavior[Command] =
Behaviors
.receiveMessage[Command] {
case m: Movement =>
m match {
case MovedLeft(pressed) =>
props.enqueueR(() => stopIfNotPressed(pressed, movable))
receive(state = state.modify(_.cardinalDir.left).setTo(pressed))
case MovedUp(pressed) =>
props.enqueueR(() => stopIfNotPressed(pressed, movable))
receive(state = state.modify(_.cardinalDir.up).setTo(pressed))
case MovedRight(pressed) =>
props.enqueueR(() => stopIfNotPressed(pressed, movable))
receive(state = state.modify(_.cardinalDir.right).setTo(pressed))
case MovedDown(pressed) =>
props.enqueueR(() => stopIfNotPressed(pressed, movable))
receive(state = state.modify(_.cardinalDir.down).setTo(pressed))
case MoveLeft(pressed) =>
stopIfNotPressed(pressed)
receive(
state = state.modify(_.cardinalDir.left).setTo(pressed),
walkDirBuf
)
case MoveUp(pressed) =>
stopIfNotPressed(pressed)
receive(
state = state.modify(_.cardinalDir.up).setTo(pressed),
walkDirBuf
)
case MoveRight(pressed) =>
stopIfNotPressed(pressed)
receive(
state = state.modify(_.cardinalDir.right).setTo(pressed),
walkDirBuf
)
case MoveDown(pressed) =>
stopIfNotPressed(pressed)
receive(
state = state.modify(_.cardinalDir.down).setTo(pressed),
walkDirBuf
)
case Jump =>
props.enqueueR(() => cm.jump(movable))
cm.jump(movable)
Behaviors.same
}
case Tick =>
val walkDir =
getDirection2(state.cardinalDir, ctx.log.traceP)
// if (walkDir != ImVector3f.ZERO) {
val tmp = walkDir * 25f * (1f / 144)
props.enqueueR { () =>
cm.move(movable, tmp)
val camDir =
props.camera.getDirection().clone().normalizeLocal.multLocal(0.6f)
val camLeft =
props.camera.getLeft().clone().normalizeLocal.multLocal(0.4f)
val dir = state.cardinalDir
walkDirBuf.set(0, 0, 0)
if (dir.up) {
ctx.log.traceP("up")
walkDirBuf += camDir
}
// }
if (dir.left) {
ctx.log.traceP("left")
walkDirBuf += camLeft
}
if (dir.right) {
ctx.log.traceP("right")
walkDirBuf += -camLeft
}
if (dir.down) {
ctx.log.traceP("down")
walkDirBuf += -camDir
}
walkDirBuf *= 25f *= (1f / 144)
cm.move(movable, walkDirBuf.immutable, 20f)
Behaviors.same
}
.receiveSignal {
case (_, PostStop) =>
ctx.log.debugP("Stopped")
Behaviors.same
}
def stopIfNotPressed(pressed: Boolean) = if (!pressed) cm.stop(movable)
def getDirection2(
cardinalDir: CardinalDirection,
debug: sourcecode.Text[String] => sourcecode.Text[String]
cardinalDir: CardinalDirection
// debug: sourcecode.Text[String] => sourcecode.Text[String]
) = {
val camDir =
props.camera.getDirection().clone().normalizeLocal.multLocal(0.6f)
@ -107,19 +148,19 @@ class ImMovementActor[T](
val walkDir = {
val mutWalkDir = new Vector3f()
if (dir.up) {
debug("up")
ctx.log.traceP("up")
mutWalkDir += camDir
}
if (dir.left) {
debug("left")
ctx.log.traceP("left")
mutWalkDir += camLeft
}
if (dir.right) {
debug("right")
ctx.log.traceP("right")
mutWalkDir += -camLeft
}
if (dir.down) {
debug("down")
ctx.log.traceP("down")
mutWalkDir += -camDir
}
mutWalkDir.immutable
@ -127,50 +168,111 @@ class ImMovementActor[T](
walkDir
}
}
object Methods {
def getDirection(cardinalDir: CardinalDirection, trace: String => Unit) = {
val zero = ImVector3f.Zero
val dir = cardinalDir
val walkDir = {
val mutWalkDir = new Vector3f()
// old/unused
object MovementActor {
sealed trait Command
case object Tick extends Command
sealed trait Movement extends Command
final case class MovedLeft(pressed: Boolean) extends Movement
final case class MovedUp(pressed: Boolean) extends Movement
final case class MovedRight(pressed: Boolean) extends Movement
final case class MovedDown(pressed: Boolean) extends Movement
class Props(val app: com.jme3.app.Application, val geom: Geometry)
/**
* Internal state of the actor
*
* @param cardinalDir Immutable, can be shared as is
* @param walkDirection scratch space to avoid allocations on every tick. Do not share outside the actor
*/
final case class State(
cardinalDir: CardinalDirection = CardinalDirection(),
walkDirection: Vector3f = Vector3f.UNIT_X
)
def apply(props: Props): Behavior[Command] =
Behaviors.setup(ctx => new MovementActor(ctx, props).receive(State()))
}
class MovementActor(
ctx: ActorContext[MovementActor.Command],
props: MovementActor.Props
) {
import MovementActor._
import com.softwaremill.quicklens._
def receive(state: MovementActor.State): Behavior[Command] =
Behaviors.receiveMessage { msg =>
msg match {
case m: Movement =>
m match {
case MovedLeft(pressed) =>
receive(state = state.modify(_.cardinalDir.left).setTo(pressed))
case MovedUp(pressed) =>
receive(state = state.modify(_.cardinalDir.up).setTo(pressed))
case MovedRight(pressed) =>
receive(state = state.modify(_.cardinalDir.right).setTo(pressed))
case MovedDown(pressed) =>
receive(state = state.modify(_.cardinalDir.down).setTo(pressed))
}
case Tick =>
val camDir =
props.app.getCamera.getDirection().clone().multLocal(0.6f)
val camLeft = props.app.getCamera.getLeft().clone().multLocal(0.4f)
val walkDir = state.walkDirection.set(0, 0, 0)
// val walkDir = new Vector3f
val dir = state.cardinalDir
if (dir.up) {
ctx.log.debugP("up")
// ctx.log.debugP(Thread.currentThread().getName())
// walkDir.addLocal(0, 0, -1)
walkDir += camDir
}
if (dir.left) {
trace("left")
mutWalkDir += zero +=: new Vector3f(-1, 0, 0)
ctx.log.debugP("left")
// walkDir.addLocal(-1, 0, 0)
walkDir.addLocal(camLeft)
}
if (dir.right) {
trace("right")
mutWalkDir += zero +=: new Vector3f(1, 0, 0)
}
if (dir.up) {
trace("up")
mutWalkDir += zero +=: new Vector3f(0, 0, -1)
ctx.log.debugP("right")
// walkDir.addLocal(1, 0, 0)
walkDir.addLocal(camLeft.negateLocal())
}
if (dir.down) {
trace("down")
mutWalkDir += zero +=: new Vector3f(0, 0, 1)
ctx.log.debugP("down")
walkDir.addLocal(camDir.negateLocal())
// walkDir.addLocal(0, 0, 1)
}
mutWalkDir.immutable
}
walkDir
}
def stopIfNotPressed[T](pressed: Boolean, movable: T)(implicit
cm: CanMove[T]
) =
if (!pressed) cm.stop(movable)
}
// props.app
// .enqueueScala { () =>
// 1
// }
// .map(println)(scala.concurrent.ExecutionContext.global)
// monix.eval.Task
// .fromFuture(
// props.app
// .enqueueScala { () =>
// 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)
// )
// .flatMap(i => monix.eval.Task(println(i)))
// .runToFuture(monix.execution.Scheduler.Implicits.global)
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))
}
}
}

View File

@ -4,7 +4,10 @@ import scala.jdk.CollectionConverters._
import scala.reflect.ClassTag
import akka.actor.typed.ActorRef
import akka.actor.typed.Behavior
import akka.actor.typed.Props
import akka.actor.typed.Scheduler
import akka.actor.typed.scaladsl.ActorContext
import akka.util.Timeout
import com.jayfella.jme.jfx.JavaFxUI
import com.jme3.app.Application
@ -14,10 +17,12 @@ import com.jme3.app.state.AppStateManager
import com.jme3.asset.AssetLocator
import com.jme3.asset.AssetManager
import com.jme3.bullet.BulletAppState
import com.jme3.bullet.PhysicsSpace
import com.jme3.bullet.PhysicsTickListener
import com.jme3.bullet.collision.PhysicsCollisionEvent
import com.jme3.bullet.collision.PhysicsCollisionListener
import com.jme3.bullet.collision.PhysicsCollisionObject
import com.jme3.bullet.collision.{
PhysicsCollisionEvent => jmePhysicsCollisionEvent
}
import com.jme3.bullet.control.BetterCharacterControl
import com.jme3.input.Action
import com.jme3.input.InputManager
@ -35,14 +40,17 @@ import com.jme3.scene.SceneGraphVisitor
import com.jme3.scene.Spatial
import com.jme3.scene.control.CameraControl.ControlDirection
import com.jme3.scene.control.Control
import com.jme3.{bullet => jmeb}
import com.simsilica.es.EntityComponent
import com.simsilica.es.EntityData
import com.simsilica.es.EntityId
import enumeratum._
import io.odin.meta.Position
import io.odin.meta.Render
import monix.bio.IO
import monix.bio.Task
import monix.bio.UIO
import monix.eval.Coeval
import monix.execution.Ack
import monix.execution.Ack.Continue
import monix.execution.Ack.Stop
@ -54,6 +62,7 @@ import monix.reactive.observers.Subscriber
import org.slf4j.Logger
import wow.doge.mygame.math.ImVector3f
import wow.doge.mygame.state.MyBaseState
import wow.doge.mygame.utils.wrappers.jme.PhysicsSpace
final case class ActionEvent(binding: Action, value: Boolean, tpf: Float)
final case class EnumActionEvent[T <: EnumEntry](
@ -68,8 +77,15 @@ final case class EnumAnalogEvent[T <: EnumEntry](
value: Float,
tpf: Float
)
final class PrePhysicsTickEvent(val space: PhysicsSpace) extends AnyVal
final class PhysicsTickEvent(val space: PhysicsSpace) extends AnyVal
final case class PrePhysicsTickEvent(space: PhysicsSpace)
final case class PhysicsTickEvent(space: PhysicsSpace)
final case class CollisionEvent(
nodeA: Option[Spatial],
nodeB: Option[Spatial],
objectA: PhysicsCollisionObject,
objectB: PhysicsCollisionObject,
appliedImpulse: Function0[Float]
)
package object implicits {
type PrePhysicsTickEvent = PhysicsTickEvent
@ -112,10 +128,9 @@ package object implicits {
override def init(): Unit = {}
override def update(tpf: Float) = {
override def update(tpf: Float) =
if (sub.onNext(tpf) == Ack.Stop)
c.cancel()
}
override def stop(): Unit = {}
@ -133,14 +148,12 @@ package object implicits {
def registerLocator(
assetPath: os.RelPath,
locator: Class[_ <: AssetLocator]
): Unit = {
): Unit =
am.registerLocator(assetPath.toString(), locator)
}
def loadModel(assetPath: os.RelPath): Spatial = {
def loadModel(assetPath: os.RelPath): Spatial =
am.loadModel(assetPath.toString())
}
}
implicit final class BulletAppStateExt(private val bas: BulletAppState)
extends AnyVal {
@ -251,7 +264,7 @@ package object implicits {
}
// case _ => loop(subscriber, tail)
}
case LazyList() => Task.unit
case LazyList() => Task(subscriber.onComplete())
}
Observable.create(OverflowStrategy.Unbounded) { sub =>
@ -297,7 +310,7 @@ package object implicits {
Task(subscriber.onComplete())
}
}
case LazyList() => Task.unit
case LazyList() => Task(subscriber.onComplete())
}
Observable.create(OverflowStrategy.Unbounded) { sub =>
@ -372,9 +385,8 @@ package object implicits {
*/
def askL[Res](
replyTo: ActorRef[Res] => Req
)(implicit timeout: Timeout, scheduler: Scheduler): Task[Res] = {
)(implicit timeout: Timeout, scheduler: Scheduler): Task[Res] =
Task.deferFuture(a.ask(replyTo)(timeout, scheduler))
}
def ??[Res](
replyTo: ActorRef[Res] => Req
)(implicit timeout: Timeout, scheduler: Scheduler): Task[Res] =
@ -481,8 +493,7 @@ package object implicits {
* @see [[ActionEvent]]
* @see [[enumObservableAction]]
*/
def observableAction(mappingNames: String*): Observable[ActionEvent] = {
def observableAction(mappingNames: String*): Observable[ActionEvent] =
Observable.create(OverflowStrategy.DropOld(10)) { sub =>
val c = SingleAssignCancelable()
val al = new ActionListener {
@ -490,7 +501,7 @@ package object implicits {
binding: String,
value: Boolean,
tpf: Float
): Unit = {
): Unit =
if (
sub.onNext(ActionEvent(Action(binding), value, tpf)) == Ack.Stop
) {
@ -498,14 +509,12 @@ package object implicits {
c.cancel()
}
}
}
inputManager.addListener(al, mappingNames: _*)
c := Cancelable(() => inputManager.removeListener(al))
c
}
}
/**
* <p>
@ -530,8 +539,7 @@ package object implicits {
*/
def enumObservableAction[T <: EnumEntry](
mappingEnum: Enum[T]
): Observable[EnumActionEvent[T]] = {
): Observable[EnumActionEvent[T]] =
Observable.create(OverflowStrategy.DropOld(10)) { sub =>
val c = SingleAssignCancelable()
val entryNames = mappingEnum.values.map(_.entryName)
@ -540,7 +548,7 @@ package object implicits {
binding: String,
value: Boolean,
tpf: Float
): Unit = {
): Unit =
mappingEnum.withNameOption(binding).foreach { b =>
if (sub.onNext(EnumActionEvent(b, value, tpf)) == Ack.Stop) {
sub.onComplete()
@ -548,19 +556,16 @@ package object implicits {
}
}
}
}
inputManager.addListener(al, entryNames: _*)
c := Cancelable(() => inputManager.removeListener(al))
c
}
}
def enumEntryObservableAction[T <: EnumEntry](
mappingEnumEntry: T
): Observable[EnumActionEvent[T]] = {
): Observable[EnumActionEvent[T]] =
Observable.create(OverflowStrategy.DropOld(10)) { sub =>
val c = SingleAssignCancelable()
val al = new ActionListener {
@ -568,7 +573,7 @@ package object implicits {
binding: String,
value: Boolean,
tpf: Float
): Unit = {
): Unit =
if (
sub.onNext(
EnumActionEvent(mappingEnumEntry, value, tpf)
@ -578,17 +583,14 @@ package object implicits {
c.cancel()
}
}
}
inputManager.addListener(al, mappingEnumEntry.entryName)
c := Cancelable(() => inputManager.removeListener(al))
c
}
}
def analogObservable(mappingNames: String*): Observable[AnalogEvent] = {
def analogObservable(mappingNames: String*): Observable[AnalogEvent] =
Observable.create(OverflowStrategy.DropOld(50)) { sub =>
val c = SingleAssignCancelable()
val al = new AnalogListener {
@ -596,7 +598,7 @@ package object implicits {
binding: String,
value: Float,
tpf: Float
): Unit = {
): Unit =
if (
sub.onNext(AnalogEvent(Action(binding), value, tpf)) == Ack.Stop
) {
@ -604,19 +606,16 @@ package object implicits {
c.cancel()
}
}
}
inputManager.addListener(al, mappingNames: _*)
c := Cancelable(() => inputManager.removeListener(al))
c
}
}
def enumAnalogObservable[T <: EnumEntry](
mappingEnum: Enum[T]
): Observable[EnumAnalogEvent[T]] = {
): Observable[EnumAnalogEvent[T]] =
Observable.create(OverflowStrategy.DropOld(50)) { sub =>
val c = SingleAssignCancelable()
val entryNames = mappingEnum.values.map(_.entryName)
@ -625,7 +624,7 @@ package object implicits {
binding: String,
value: Float,
tpf: Float
): Unit = {
): Unit =
mappingEnum.withNameOption(binding).foreach { b =>
if (sub.onNext(EnumAnalogEvent(b, value, tpf)) == Ack.Stop) {
sub.onComplete()
@ -633,7 +632,6 @@ package object implicits {
}
}
}
}
inputManager.addListener(al, entryNames: _*)
@ -641,71 +639,81 @@ package object implicits {
c
}
}
}
implicit final class PhysicsSpaceExt(private val space: PhysicsSpace)
implicit final class PhysicsSpaceExt(private val space: jmeb.PhysicsSpace)
extends AnyVal {
def collisionObservable(): Observable[PhysicsCollisionEvent] = {
def collisionObservable(): Observable[CollisionEvent] =
Observable.create(OverflowStrategy.DropOld(50)) { sub =>
val c = SingleAssignCancelable()
val cl = new PhysicsCollisionListener {
override def collision(event: PhysicsCollisionEvent): Unit = {
if (sub.onNext(event) == Ack.Stop) {
sub.onComplete()
c.cancel()
}
override def collision(event: jmePhysicsCollisionEvent): Unit =
if (
sub.onNext(
CollisionEvent(
Option(event.getNodeA),
Option(event.getNodeB),
event.getObjectA,
event.getObjectB,
event.getAppliedImpulse _
)
) == Ack.Stop
) c.cancel()
}
}
space.addCollisionListener(cl)
c := Cancelable(() => space.removeCollisionListener(cl))
c := Cancelable { () =>
pprint.log("stopped")
space.removeCollisionListener(cl)
}
c
}
}
def prePhysicsTickObservable(): Observable[PrePhysicsTickEvent] = {
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 =>
val c = SingleAssignCancelable()
val cl = new PhysicsTickListener {
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
): 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) {
sub.onComplete()
c.cancel()
@ -719,7 +727,6 @@ package object implicits {
c := Cancelable(() => space.removeTickListener(cl))
c
}
}
//TODO Consider creating a typeclass for this
def +=(anyObject: Any) = space.add(anyObject)
@ -757,6 +764,7 @@ package object implicits {
def +=:(that: ImVector3f) = v += that
def *=(that: Vector3f) = v.multLocal(that)
def *=(that: ImVector3f) = v.multLocal(that.x, that.y, that.z)
def *=(f: Float) = v.multLocal(f, f, f)
def *=:(that: ImVector3f) = v *= that
def -=(that: Vector3f) = v.subtractLocal(that)
def -=(that: ImVector3f) = v.subtractLocal(that.x, that.y, that.z)
@ -766,6 +774,7 @@ package object implicits {
def /=:(that: ImVector3f) = v *= that
def unary_- = v.negateLocal()
def immutable = ImVector3f(v.x, v.y, v.z)
// def transformImmutable(f: Vector3f => Vector3f) = f(v).immutable
}
implicit final class ImVector3fExt(private val v: ImVector3f) extends AnyVal {
@ -858,7 +867,7 @@ package object implicits {
}
implicit class odinLoggerExt(private val logger: io.odin.Logger[Task])
implicit class OdinLoggerExt(private val logger: io.odin.Logger[Task])
extends AnyVal {
def debugU[M](msg: => M)(implicit render: Render[M], position: Position) =
logger.debug(msg).hideErrors
@ -871,4 +880,28 @@ package object implicits {
def errorU[M](msg: => M)(implicit render: Render[M], position: Position) =
logger.error(msg).hideErrors
}
implicit class TypedActorContextExt[T](private val ctx: ActorContext[T])
extends AnyVal {
def spawnN[U](behavior: Behavior[U], props: Props = Props.empty)(implicit
name: sourcecode.Name
): ActorRef[U] =
ctx.spawn(behavior, name.value, props)
}
implicit class MonixEvalTaskExt[T](private val task: monix.eval.Task[T])
extends AnyVal {
def toIO = IO.deferAction(implicit s => IO.from(task))
}
implicit class MonixBioTaskExt[T](private val task: monix.bio.Task[T])
extends AnyVal {
def toTask =
monix.eval.Task.deferAction(implicit s => monix.eval.Task.from(task))
}
implicit class CoevalEitherExt[L, R](private val coeval: Coeval[Either[L, R]])
extends AnyVal {
def toIO = coeval.to[Task].hideErrors.rethrow
}
}

View File

@ -14,9 +14,9 @@ object ImVector3f {
val UnitY = ImVector3f(0, 1, 0)
val UnitZ = ImVector3f(0, 0, 1)
val Unit = ImVector3f(1, 1, 1)
//format: on
val Max = ImVector3f(Float.MaxValue, Float.MaxValue, Float.MaxValue)
val Min = ImVector3f(Float.MinValue, Float.MinValue, Float.MinValue)
//format: on
implicit val show = new Show[ImVector3f] {
def format(f: Float) = f.formatted("%.2f")
@ -39,6 +39,6 @@ object ImVector3f {
sqrt(total)
}
def manhattanDst(v1: ImVector3f, v2: ImVector3f) =
abs(v1.x - v2.x) + abs(v1.y - v2.y) + abs(v1.z - v2.z)
if (v1 === v2) 0 else abs(v1.x - v2.x) + abs(v1.y - v2.y) + abs(v1.z - v2.z)
}

View File

@ -1,10 +1,10 @@
package wow.doge.mygame.subsystems.events
import scala.reflect.ClassTag
import scala.util.Random
import akka.actor.typed.ActorRef
import akka.actor.typed.Behavior
import akka.actor.typed.PostStop
import akka.actor.typed.Scheduler
import akka.actor.typed.SpawnProtocol
import akka.actor.typed.scaladsl.AskPattern._
@ -59,42 +59,7 @@ object EventBus {
new EventBus().eventStreamBehavior(eventStream)
}
def observable[E](eventBus: ActorRef[EventBus.Command[E]])(implicit
timeout: Timeout,
scheduler: Scheduler,
spawnProtocol: ActorRef[SpawnProtocol.Command],
ct: ClassTag[E]
) =
Observable.create[E](OverflowStrategy.DropOld(50)) { sub =>
implicit val s = sub.scheduler
val c = SingleAssignCancelable()
val behavior = Behaviors.receive[E] { (ctx, msg) =>
if (sub.onNext(msg) == Ack.Stop) {
c.cancel()
Behaviors.stopped
} else Behaviors.same
}
val actor =
AkkaUtils
.spawnActorL(
behavior,
s"eventBusObservable-${ct.toString}-${Random.nextLong()}"
)
.mapError(err => new Exception(err.toString))
.tapError {
case ex => UIO(sub.onError(ex))
}
(for {
a <- actor
_ <- eventBus !! Subscribe(a)
_ <- UIO(c := Cancelable(() => eventBus ! Unsubscribe(a)))
} yield ()).runToFuture
c
}
def observable2[A, B <: A](eventBus: ActorRef[EventBus.Command[A]])(implicit
def observable[A, B <: A](eventBus: ActorRef[EventBus.Command[A]])(implicit
timeout: Timeout,
scheduler: Scheduler,
spawnProtocol: ActorRef[SpawnProtocol.Command],
@ -104,18 +69,25 @@ object EventBus {
Observable.create[B](OverflowStrategy.DropOld(50)) { sub =>
implicit val s = sub.scheduler
val c = SingleAssignCancelable()
val behavior = Behaviors.receive[B] { (ctx, msg) =>
val behavior = Behaviors
.receive[B] { (ctx, msg) =>
ctx.log.traceP(s"Emitted $msg")
if (sub.onNext(msg) == Ack.Stop) {
c.cancel()
Behaviors.stopped
} else Behaviors.same
}
.receiveSignal {
case (_, PostStop) =>
sub.onComplete()
Behaviors.same
}
val actor =
AkkaUtils
.spawnActorL(
behavior,
s"eventBusObservable-${ct.toString}-${math.abs(Random.nextLong())}"
actorName =
Some(s"eventBusObservable-${ct.toString.split("""\.""").last}")
)
.mapError(err => new Throwable(err.toString))
.tapError {
@ -155,7 +127,7 @@ class EventBus[A] {
eventStream.unsubscribe(subscriber.toClassic)
Behaviors.same
case s @ ObservableSubscription(replyTo) =>
val obs = EventBus.observable2(
val obs = EventBus.observable(
ctx.self
)(timeout, scheduler, spawnProtocol, ct, s.ct)
replyTo ! obs

View File

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

View File

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

View File

@ -3,7 +3,7 @@ package wow.doge.mygame.subsystems.events
sealed trait PlayerEvent
sealed trait PlayerMovementEvent extends PlayerEvent
final object PlayerMovementEvent {
object PlayerMovementEvent {
final case class PlayerMovedLeft(pressed: Boolean) extends PlayerMovementEvent
final case class PlayerMovedRight(pressed: Boolean)
extends PlayerMovementEvent
@ -11,16 +11,16 @@ final object PlayerMovementEvent {
extends PlayerMovementEvent
final case class PlayerMovedBackward(pressed: Boolean)
extends PlayerMovementEvent
final case object PlayerJumped extends PlayerMovementEvent
// final case object PlayerTurnedRight extends PlayerMovementEvent
// final case object PlayerTurnedLeft extends PlayerMovementEvent
case object PlayerJumped extends PlayerMovementEvent
// case object PlayerTurnedRight extends PlayerMovementEvent
// case object PlayerTurnedLeft extends PlayerMovementEvent
}
sealed trait PlayerCameraEvent extends PlayerEvent
final object PlayerCameraEvent {
final case object CameraLeft extends PlayerCameraEvent
final case object CameraRight extends PlayerCameraEvent
final case object CameraMovedUp extends PlayerCameraEvent
final case object CameraMovedDown extends PlayerCameraEvent
object PlayerCameraEvent {
case object CameraLeft extends PlayerCameraEvent
case object CameraRight extends PlayerCameraEvent
case object CameraMovedUp extends PlayerCameraEvent
case object CameraMovedDown extends PlayerCameraEvent
}

View File

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

View File

@ -31,8 +31,8 @@ class ScriptSystemResource(
findScriptFiles(os.pwd / "assets" / "scripts")
).hideErrors
scriptCacheActor <- AkkaUtils.spawnActorL(
ScriptCachingActor(),
"scriptCachingActor"
ScriptCachingActor()
// "scriptCachingActor"
)
} 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](
behavior: Behavior[T],
actorName: String,
actorName: Option[String] = None,
props: Props = Props.empty
)(implicit
timeout: Timeout,
scheduler: Scheduler,
spawnProtocol: ActorRef[SpawnProtocol.Command]
spawnProtocol: ActorRef[SpawnProtocol.Command],
name: sourcecode.Name
) =
spawnProtocol
.askL[ActorRef[T]](
SpawnProtocol.Spawn(
behavior,
actorName,
actorName.fold(name.value)(identity),
props,
_
)

View File

@ -12,8 +12,8 @@ import wow.doge.mygame.implicits._
object GenericTimerActor {
sealed trait Command
final case object Start extends Command
final case object Stop extends Command
case object Start extends Command
case object Stop extends Command
private case object Tick extends Command
case class TimerKey(seed: Long)

View File

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

View File

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

View File

@ -1,57 +1,91 @@
package wow.doge.mygame.utils.wrappers.jme
import com.jme3.bullet.control.PhysicsControl
import com.jme3.bullet.joints.PhysicsJoint
import com.jme3.{bullet => jmeb}
import com.jme3.{scene => jmes}
import monix.bio.UIO
import wow.doge.mygame.implicits._
final class PhysicsSpace(space: jmeb.PhysicsSpace) {
def add(anyObject: Any) = UIO(space.add(anyObject))
def add[T](addable: T)(implicit P: PhysicsSpaceAddable[T]) =
UIO(P.addToSpace(addable, space))
def remove(anyObject: Any) =
UIO {
space.remove(anyObject)
space
}
def remove(anyObject: Any) = UIO(space.remove(anyObject))
def addAll(spatial: jmes.Spatial) = UIO(space.addAll(spatial))
def removeAll(spatial: jmes.Spatial) =
UIO {
space.removeAll(spatial)
space
}
def removeAll(spatial: jmes.Spatial) = UIO(space.removeAll(spatial))
def collisionObservable = space.collisionObservable()
// space.enqueue(() => ())
def physicsTickObservable = space.physicsTickObservable()
def prePhysicsTickObservable = space.prePhysicsTickObservable()
}
object PhysicsSpace {
implicit final class PhysicsSpaceOps(private val space: PhysicsSpace)
extends AnyVal {
def +=(anyObject: Any) = space.add(anyObject)
def +=[T: PhysicsSpaceAddable](addable: T) = space.add(addable)
def :+(anyObject: Any) = {
space.add(anyObject)
space
}
def -(anyObject: Any) = {
space.remove(anyObject)
space
}
def :+[T: PhysicsSpaceAddable](addable: T) =
for {
_ <- space.add(addable)
} yield space
def -(anyObject: Any) =
for {
_ <- space.remove(anyObject)
} yield space
def -=(anyObject: Any) = space.remove(anyObject)
def +=(spatial: jmes.Spatial) = space.addAll(spatial)
def :+(spatial: jmes.Spatial) = {
space.addAll(spatial)
space
}
// def :+(spatial: jmes.Spatial) = {
// space.addAll(spatial)
// space
// }
def -(spatial: jmes.Spatial) = {
space.removeAll(spatial)
space
}
// def -(spatial: jmes.Spatial) = {
// space.removeAll(spatial)
// space
// }
def -=(spatial: jmes.Spatial) = space.removeAll(spatial)
}
}
trait PhysicsSpaceAddable[-T] {
def addToSpace(inst: T, space: jmeb.PhysicsSpace): Unit
}
object PhysicsSpaceAddable {
implicit val physicsSpaceAddableForPhysControl =
new PhysicsSpaceAddable[PhysicsControl] {
override def addToSpace(
inst: PhysicsControl,
space: jmeb.PhysicsSpace
): Unit = space.add(inst)
}
implicit val physicsSpaceAddableForSpatial =
new PhysicsSpaceAddable[jmes.Spatial] {
override def addToSpace(
inst: jmes.Spatial,
space: jmeb.PhysicsSpace
): Unit = space.add(inst)
}
implicit val physicsSpaceAddableForPhysJoint =
new PhysicsSpaceAddable[PhysicsJoint] {
override def addToSpace(
inst: PhysicsJoint,
space: jmeb.PhysicsSpace
): Unit = space.add(inst)
}
}

View File

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

View File

@ -5,6 +5,12 @@ import monix.bio.UIO
import org.scalatest.funsuite.AnyFunSuite
import monix.execution.Scheduler.Implicits.global
import monix.bio.IO
import cats.mtl.Ask
import cats.mtl.implicits._
import cats.effect.LiftIO
import monix.bio.Task
import cats.data.Kleisli
import monix.bio.IOLift
class ReaderT_Test extends AnyFunSuite {
@ -33,8 +39,36 @@ class ReaderT_Test extends AnyFunSuite {
// uio.runSyncUnsafe()
// }
implicit val L =
new LiftIO[ReaderT[IO[String, ?], Environment, *]] {
override def liftIO[A](
ioa: cats.effect.IO[A]
): ReaderT[IO[String, ?], Environment, A] =
ReaderT(_ => IO.from(ioa).onErrorHandleWith(_ => IO.raiseError("whew")))
// override def liftIO[A](ioa: cats.effect.IO[A]): monix.bio.Task[A] =
// Task.from(ioa)
}
implicit val L2 = new IOLift[ReaderT[IO[String, ?], Environment, *]] {
override def apply[A](
task: monix.bio.Task[A]
): ReaderT[IO[String, ?], Environment, A] =
ReaderT(_ => IO.from(task).onErrorHandleWith(_ => IO.raiseError("whew")))
}
sealed trait AppError
type RIO[S, E, A] = ReaderT[IO[E, ?], S, A]
type EIO[S, A] = RIO[S, AppError, A]
type AppIO[A] = RIO[Environment, AppError, A]
test("2") {
def fun1: ReaderT[IO[String, ?], String, Unit] =
def fun1: RIO[String, String, Unit] =
ReaderT(s => IO(println(s)).onErrorHandleWith(_ => IO.raiseError("wow")))
def fun2: ReaderT[IO[String, ?], Int, Unit] =
ReaderT(num =>
@ -44,6 +78,8 @@ class ReaderT_Test extends AnyFunSuite {
for {
env <- ReaderT.ask[IO[String, ?], Environment]
} yield ()
def test[F[_]]()(implicit A: Ask[F, Int]) = A.ask[Int]
// def synctest[F[_]]
def fun4: ReaderT[IO[String, ?], Environment, Unit] =
for {
env <- ReaderT.ask[IO[String, ?], Environment]
@ -52,12 +88,15 @@ class ReaderT_Test extends AnyFunSuite {
)
_ <- fun3
} yield ()
val topkek =
test[ReaderT[IO[String, ?], Int, *]]().local[Environment](_.num)
def app: ReaderT[IO[String, ?], Environment, Unit] =
for {
_ <- fun1.local[Environment](_.str)
_ <- fun2.local[Environment](_.num)
_ <- fun3
_ <- fun4
_ <- topkek
} yield ()
val io: IO[String, Unit] = app.run(Environment("hello", 50))