forked from nova/jmonkey-test
blah
This commit is contained in:
parent
64480e8e03
commit
2e05cb35fe
@ -1 +1,4 @@
|
||||
version = "2.6.4"
|
||||
rewrite {
|
||||
rules = [SortImports, RedundantBraces]
|
||||
}
|
||||
|
12
build.sbt
12
build.sbt
@ -61,15 +61,15 @@ lazy val root = (project in file(".")).settings(
|
||||
"org.jmonkeyengine" % "jme3-blender" % jmeVersion,
|
||||
"com.github.stephengold" % "Minie" % "3.0.0",
|
||||
"com.simsilica" % "zay-es" % "1.2.1",
|
||||
"org.typelevel" %% "cats-core" % "2.1.1",
|
||||
"org.typelevel" %% "cats-core" % "2.3.0",
|
||||
"com.lihaoyi" % "ammonite" % "2.2.0" cross CrossVersion.full,
|
||||
"org.jetbrains.kotlin" % "kotlin-main-kts" % "1.4.10",
|
||||
"org.jetbrains.kotlin" % "kotlin-scripting-jsr223" % "1.4.10",
|
||||
"org.codehaus.groovy" % "groovy-all" % "3.0.6" pomOnly (),
|
||||
"org.scalafx" %% "scalafx" % "14-R19",
|
||||
"com.typesafe.akka" %% "akka-actor-typed" % "2.6.10",
|
||||
"org.typelevel" %% "cats-core" % "2.1.1",
|
||||
"org.typelevel" %% "cats-effect" % "2.1.4",
|
||||
"org.typelevel" %% "cats-core" % "2.3.0",
|
||||
"org.typelevel" %% "cats-effect" % "2.3.0",
|
||||
"io.monix" %% "monix" % "3.2.2",
|
||||
"io.monix" %% "monix-bio" % "1.1.0",
|
||||
"io.circe" %% "circe-core" % "0.13.0",
|
||||
@ -96,7 +96,8 @@ lazy val root = (project in file(".")).settings(
|
||||
"com.badlogicgames.gdx" % "gdx-ai" % "1.8.2",
|
||||
"org.recast4j" % "recast" % "1.2.5",
|
||||
"org.recast4j" % "detour" % "1.2.5",
|
||||
"com.lihaoyi" %% "pprint" % "0.6.0"
|
||||
"com.lihaoyi" %% "pprint" % "0.6.0",
|
||||
"org.scalatest" %% "scalatest" % "3.2.2" % "test"
|
||||
),
|
||||
// Determine OS version of JavaFX binaries
|
||||
|
||||
@ -208,4 +209,7 @@ initialCommands in (console) := """ammonite.Main.main(Array.empty)"""
|
||||
// To learn more about multi-project builds, head over to the official sbt
|
||||
// documentation at http://www.scala-sbt.org/documentation.html
|
||||
addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1")
|
||||
addCompilerPlugin(
|
||||
"org.typelevel" %% "kind-projector" % "0.11.1" cross CrossVersion.full
|
||||
)
|
||||
ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.4.3"
|
||||
|
@ -1,7 +1,7 @@
|
||||
# jme-dispatcher {
|
||||
# type = "Dispatcher"
|
||||
# name = "JME-Thread"
|
||||
# executor = "wow.doge.mygame.executors.JMEThreadExecutorServiceConfigurator"
|
||||
# throughput = 1
|
||||
# }
|
||||
jme-dispatcher {
|
||||
type = "Dispatcher"
|
||||
name = "JME-Thread"
|
||||
executor = "wow.doge.mygame.executors.JMEThreadExecutorServiceConfigurator"
|
||||
throughput = 1
|
||||
}
|
||||
# akka.jvm-exit-on-fatal-error = on
|
@ -32,13 +32,13 @@ class StaticLoggerBinder extends OdinLoggerBinder[IO] {
|
||||
def apply[A](fa: _root_.monix.bio.Task[A]): IO[A] = fa.to[IO]
|
||||
}
|
||||
|
||||
private lazy val (defaultConsoleLogger, release1) =
|
||||
val (defaultConsoleLogger, release1) =
|
||||
consoleLogger[IO](minLevel = Level.Debug)
|
||||
.withAsync(timeWindow = 1.milliseconds, maxBufferSize = Some(2000))
|
||||
.allocated
|
||||
.unsafeRunSync()
|
||||
|
||||
private lazy val (mainFileLogger, release2) =
|
||||
val (mainFileLogger, release2) =
|
||||
fileLogger[IO](
|
||||
"application-log-2.log",
|
||||
Formatter.json,
|
||||
@ -51,7 +51,7 @@ class StaticLoggerBinder extends OdinLoggerBinder[IO] {
|
||||
lm.copy(message = lm.message.map(s => fansi.Str(s).plainText))
|
||||
)
|
||||
|
||||
private lazy val (eventBusFileLogger, release3) =
|
||||
val (eventBusFileLogger, release3) =
|
||||
fileLogger[IO](
|
||||
"eventbus.log",
|
||||
Formatter.json,
|
||||
|
6
src/main/scala/wow/doge/mygame/AppError.scala
Normal file
6
src/main/scala/wow/doge/mygame/AppError.scala
Normal file
@ -0,0 +1,6 @@
|
||||
package wow.doge.mygame
|
||||
|
||||
sealed trait AppError
|
||||
object AppError {
|
||||
case class TimeoutError(reason: String) extends AppError
|
||||
}
|
7
src/main/scala/wow/doge/mygame/Dispatchers.scala
Normal file
7
src/main/scala/wow/doge/mygame/Dispatchers.scala
Normal file
@ -0,0 +1,7 @@
|
||||
package wow.doge.mygame
|
||||
|
||||
import akka.actor.typed.DispatcherSelector
|
||||
|
||||
object Dispatchers {
|
||||
val jmeDispatcher = DispatcherSelector.fromConfig("jme-dispatcher")
|
||||
}
|
@ -5,8 +5,7 @@ import scala.concurrent.duration._
|
||||
import _root_.monix.bio.BIOApp
|
||||
import _root_.monix.bio.Task
|
||||
import _root_.monix.bio.UIO
|
||||
import akka.actor.typed.ActorSystem
|
||||
import akka.actor.typed.SpawnProtocol
|
||||
import _root_.monix.execution.Scheduler
|
||||
import akka.util.Timeout
|
||||
import cats.effect.ExitCode
|
||||
import cats.effect.Resource
|
||||
@ -22,31 +21,24 @@ object Main extends BIOApp with MainModule {
|
||||
JLogger.getLogger("").setLevel(Level.SEVERE)
|
||||
implicit val timeout = Timeout(1.second)
|
||||
|
||||
override def scheduler: Scheduler = schedulers.async
|
||||
|
||||
def appResource(consoleStream: GenericConsoleStream[TextArea]) =
|
||||
for {
|
||||
logger <-
|
||||
consoleLogger().withAsync(
|
||||
timeWindow = 1.milliseconds,
|
||||
maxBufferSize = Some(2000)
|
||||
maxBufferSize = Some(100)
|
||||
) |+|
|
||||
fileLogger(
|
||||
"application-log-1.log",
|
||||
Formatter.json
|
||||
).withAsync(timeWindow = 1.milliseconds, maxBufferSize = Some(2000))
|
||||
jmeScheduler <- jMESchedulerResource
|
||||
implicit0(actorSystem: ActorSystem[SpawnProtocol.Command]) <-
|
||||
actorSystemResource(logger)
|
||||
// gameApp <- {
|
||||
// // new BulletAppState()
|
||||
// // bas.setThreadingType(Thr)
|
||||
// // gameAppResource(new StatsAppState())
|
||||
// wire[GameAppResource].get
|
||||
// }
|
||||
actorSystem <- actorSystemResource(logger, schedulers.async)
|
||||
_ <- Resource.liftF(
|
||||
new MainApp(
|
||||
logger,
|
||||
// gameApp,
|
||||
// actorSystem,
|
||||
jmeScheduler,
|
||||
schedulers,
|
||||
consoleStream
|
||||
@ -56,11 +48,11 @@ object Main extends BIOApp with MainModule {
|
||||
} yield ()
|
||||
|
||||
def run(args: List[String]): UIO[ExitCode] = {
|
||||
|
||||
lazy val consoleStream = GenericConsoleStream.textAreaStream()
|
||||
val consoleStream = GenericConsoleStream.textAreaStream()
|
||||
Console.withOut(consoleStream)(
|
||||
appResource(consoleStream)
|
||||
.use(_ => Task.unit >> Task(consoleStream.close()))
|
||||
.use(_ => Task.unit)
|
||||
.flatMap(_ => Task(consoleStream.close()))
|
||||
.onErrorHandleWith(ex => UIO(ex.printStackTrace()))
|
||||
.as(ExitCode.Success)
|
||||
)
|
||||
|
@ -1,5 +1,6 @@
|
||||
package wow.doge.mygame
|
||||
|
||||
import akka.actor.typed.ActorRef
|
||||
import akka.actor.typed.ActorSystem
|
||||
import akka.actor.typed.Scheduler
|
||||
import akka.actor.typed.SpawnProtocol
|
||||
@ -8,10 +9,12 @@ import cats.effect.Resource
|
||||
import cats.effect.concurrent.Deferred
|
||||
import cats.syntax.eq._
|
||||
import com.jme3.app.state.AppStateManager
|
||||
import com.jme3.asset.AssetManager
|
||||
import com.jme3.asset.plugins.ZipLocator
|
||||
import com.jme3.bullet.control.BetterCharacterControl
|
||||
import com.jme3.input.InputManager
|
||||
import com.jme3.material.Material
|
||||
import com.jme3.material.MaterialDef
|
||||
import com.jme3.math.FastMath
|
||||
import com.jme3.renderer.Camera
|
||||
import com.jme3.renderer.RenderManager
|
||||
import com.jme3.renderer.ViewPort
|
||||
@ -23,18 +26,21 @@ import io.odin.Logger
|
||||
import monix.bio.Fiber
|
||||
import monix.bio.IO
|
||||
import monix.bio.Task
|
||||
import monix.execution.exceptions.DummyException
|
||||
import monix.bio.UIO
|
||||
import scalafx.scene.control.TextArea
|
||||
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
|
||||
@ -48,8 +54,14 @@ import wow.doge.mygame.subsystems.scriptsystem.ScriptSystemResource
|
||||
import wow.doge.mygame.utils.AkkaUtils
|
||||
import wow.doge.mygame.utils.GenericConsoleStream
|
||||
import wow.doge.mygame.utils.wrappers.jme.AppNode
|
||||
import wow.doge.mygame.game.subsystems.level.DefaultGameLevel
|
||||
import com.jme3.math.FastMath
|
||||
import wow.doge.mygame.utils.wrappers.jme.AssetManager
|
||||
import wow.doge.mygame.utils.wrappers.jme.PhysicsSpace
|
||||
import wow.doge.mygame.subsystems.events.EventBus.ObservableSubscription
|
||||
import wow.doge.mygame.subsystems.events.PlayerMovementEvent
|
||||
import monix.reactive.Observable
|
||||
import monix.eval.Coeval
|
||||
import wow.doge.mygame.utils.IOUtils
|
||||
import com.jme3.math.ColorRGBA
|
||||
|
||||
class MainApp(
|
||||
logger: Logger[Task],
|
||||
@ -57,37 +69,52 @@ class MainApp(
|
||||
schedulers: Schedulers,
|
||||
consoleStream: GenericConsoleStream[TextArea]
|
||||
)(implicit
|
||||
spawnProtocol: ActorSystem[SpawnProtocol.Command],
|
||||
@annotation.unused timeout: Timeout,
|
||||
@annotation.unused scheduler: Scheduler
|
||||
spawnProtocol: ActorRef[SpawnProtocol.Command],
|
||||
timeout: Timeout,
|
||||
scheduler: Scheduler
|
||||
) {
|
||||
|
||||
val scriptSystemInit =
|
||||
new ScriptSystemResource(os.pwd, ScriptInitMode.Eager).init
|
||||
|
||||
val eventsModule = new EventsModule(spawnProtocol)
|
||||
val eventsModule = new EventsModule(scheduler, spawnProtocol)
|
||||
|
||||
class TestClass(
|
||||
playerEventBus: GameEventBus[PlayerEvent],
|
||||
tickEventBus: GameEventBus[TickEvent]
|
||||
)
|
||||
|
||||
def gameInit: Resource[Task, Fiber[Throwable, Unit]] =
|
||||
GameApp.resource(logger, jmeThread, schedulers).evalMap {
|
||||
wire[GameAppResource].resource.evalMap {
|
||||
case gameApp -> gameAppFib =>
|
||||
for {
|
||||
playerEventBus <- eventsModule.playerEventBusTask
|
||||
mainEventBus <- eventsModule.mainEventBusTask
|
||||
tickEventBus <- eventsModule.tickEventBusTask
|
||||
gameAppActor <- AkkaUtils.spawnActorL(
|
||||
obs <- playerEventBus.askL[Observable[PlayerMovementEvent]](
|
||||
ObservableSubscription(_)
|
||||
)
|
||||
_ <- IOUtils.toIO(
|
||||
obs
|
||||
.doOnNextF(pme => Coeval(pprint.log(s"Received event $pme")).void)
|
||||
.completedL
|
||||
.startAndForget
|
||||
)
|
||||
inputManager <- gameApp.inputManager
|
||||
assetManager <- UIO.pure(gameApp.assetManager)
|
||||
stateManager <- gameApp.stateManager
|
||||
camera <- gameApp.camera
|
||||
rootNode <- UIO.pure(gameApp.rootNode)
|
||||
enqueueR <- UIO(gameApp.enqueue _)
|
||||
viewPort <- gameApp.viewPort
|
||||
physicsSpace <- UIO.pure(gameApp.physicsSpace)
|
||||
_ <- logger.info("before")
|
||||
// jfxUI <- gameApp.jfxUI
|
||||
gameAppActor <- gameApp.spawnGameActor(
|
||||
GameAppActor.Props(tickEventBus).behavior,
|
||||
"gameAppActor"
|
||||
)
|
||||
_ <- gameAppActor !! GameAppActor.Start
|
||||
inputManager <- gameApp.inputManager
|
||||
assetManager <- gameApp.assetManager
|
||||
stateManager <- gameApp.stateManager
|
||||
camera <- gameApp.camera
|
||||
rootNode <- Task.pure(gameApp.rootNode)
|
||||
enqueueR <- Task(gameApp.enqueue _)
|
||||
viewPort <- gameApp.viewPort
|
||||
_ <- logger.info("before")
|
||||
// jfxUI <- gameApp.jfxUI
|
||||
consoleTextArea <- Task(new TextArea {
|
||||
text = "hello \n"
|
||||
editable = false
|
||||
@ -98,9 +125,6 @@ class MainApp(
|
||||
// _ <- Task(consoleStream := consoleTextArea)
|
||||
// _ <- Task(jfxUI += consoleTextArea)
|
||||
_ <- logger.info("after")
|
||||
// bulletAppState <- Task(new BulletAppState())
|
||||
// _ <- Task(stateManager.attach(bulletAppState))
|
||||
// bulletAppState <- Task.pure(gameApp.bulletAppstate)
|
||||
_ <- logger.info("Initializing console stream")
|
||||
_ <-
|
||||
wire[MainAppDelegate]
|
||||
@ -109,6 +133,13 @@ class MainApp(
|
||||
} yield gameAppFib
|
||||
}
|
||||
|
||||
// val x: Task[Unit] = for {
|
||||
// tickEventBus <- eventsModule.tickEventBusTask
|
||||
// playerEventBus <- eventsModule.playerEventBusTask
|
||||
// _ <- UIO(wire[TestClass])
|
||||
// _ <- gameInit(tickEventBus).use(_.join)
|
||||
// } yield ()
|
||||
|
||||
val program = for {
|
||||
scriptSystem <- scriptSystemInit
|
||||
launchSignal <- Deferred[Task, Launcher.LauncherResult]
|
||||
@ -125,6 +156,7 @@ class MainApp(
|
||||
*/
|
||||
else
|
||||
gameInit.use(_.join)
|
||||
|
||||
} yield ()
|
||||
}
|
||||
|
||||
@ -139,47 +171,44 @@ class MainAppDelegate(
|
||||
inputManager: InputManager,
|
||||
assetManager: AssetManager,
|
||||
stateManager: AppStateManager,
|
||||
physicsSpace: PhysicsSpace[Task],
|
||||
camera: Camera,
|
||||
viewPort: ViewPort,
|
||||
enqueueR: Function1[() => Unit, Unit],
|
||||
rootNode: AppNode[Task] @@ GameAppTags.RootNode
|
||||
// bulletAppState: BulletAppState
|
||||
)(implicit
|
||||
spawnProtocol: ActorSystem[SpawnProtocol.Command],
|
||||
@annotation.unused timeout: Timeout,
|
||||
@annotation.unused scheduler: Scheduler
|
||||
spawnProtocol: ActorRef[SpawnProtocol.Command],
|
||||
timeout: Timeout,
|
||||
scheduler: Scheduler
|
||||
) {
|
||||
// val physicsSpace = bulletAppState.physicsSpace
|
||||
val physicsSpace = gameApp.physicsSpace
|
||||
|
||||
def init(
|
||||
// appScheduler: monix.execution.Scheduler
|
||||
// consoleStream: GenericConsoleStream[TextArea]
|
||||
) =
|
||||
for {
|
||||
_ <- loggerL.info("Initializing Systems")
|
||||
_ <- loggerL.debug(physicsSpace.toString())
|
||||
_ <- Task(
|
||||
assetManager.registerLocator(
|
||||
_ <- assetManager.registerLocator(
|
||||
os.rel / "assets" / "town.zip",
|
||||
classOf[ZipLocator]
|
||||
)
|
||||
)
|
||||
_ <- loggerL.info("test")
|
||||
// _ <- Task(consoleStream.println("text"))
|
||||
_ <- DefaultGameLevel(assetManager, viewPort)
|
||||
.addToGame(rootNode, physicsSpace)
|
||||
.flatMap(_.addToGame(rootNode, physicsSpace).hideErrors)
|
||||
.onErrorHandleWith(e => loggerL.error(e.toString))
|
||||
_ <- createPlayerController()
|
||||
.absorbWith(e => DummyException(e.toString()))
|
||||
.onErrorHandleWith(e => loggerL.error(e.toString))
|
||||
// .onErrorRestart(3)
|
||||
_ <- wire[GameInputHandler.Props].begin
|
||||
// .onErrorRestart(3)
|
||||
// johnActor <- createTestNpc(appScheduler, "John").executeOn(appScheduler)
|
||||
johnActor <- createTestNpc("John")
|
||||
.onErrorHandleWith(e => IO.raiseError(new Throwable(e.toString)))
|
||||
// _ <- johnActor !! NpcActorSupervisor.Move(ImVector3f(0, 0, 20))
|
||||
|
||||
// _ <-
|
||||
// (johnActor !! NpcActorSupervisor.Move(
|
||||
// ImVector3f(-30, 0, 10)
|
||||
// )).executeAsync
|
||||
// johnActor
|
||||
// .tellL(NpcActorSupervisor.Move(ImVector3f(-30, 0, 10)))
|
||||
// .delayExecution(2.seconds)
|
||||
// _ <-
|
||||
// IOUtils
|
||||
@ -195,53 +224,40 @@ class MainAppDelegate(
|
||||
|
||||
def createPlayerController(
|
||||
// appScheduler: monix.execution.Scheduler
|
||||
): IO[PlayerController.Error, Unit] = {
|
||||
): IO[PlayerController.Error, ActorRef[PlayerActorSupervisor.Command]] = {
|
||||
val playerPos = ImVector3f.ZERO
|
||||
val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o"
|
||||
val playerPhysicsControl =
|
||||
PlayerController.Defaults.defaultPlayerPhysicsControl
|
||||
.taggedWith[PlayerControllerTags.PlayerTag]
|
||||
// lazy val camNode =
|
||||
// PlayerController.Defaults
|
||||
// .defaultCamerNode(camera, playerPos)
|
||||
// .taggedWith[PlayerControllerTags.PlayerCameraNode]
|
||||
val playerModel = assetManager
|
||||
.loadModel(modelPath)
|
||||
.asInstanceOf[Node]
|
||||
.withRotate(0, FastMath.PI, 0)
|
||||
val mbPlayerNode = PlayerController.Defaults
|
||||
for {
|
||||
playerModel <-
|
||||
assetManager
|
||||
.loadModelAs[Node](modelPath)
|
||||
.map(_.withRotate(0, FastMath.PI, 0))
|
||||
.mapError(PlayerController.CouldNotCreatePlayerModel)
|
||||
playerNode <- UIO(
|
||||
PlayerController.Defaults
|
||||
.defaultPlayerNode(
|
||||
playerPos,
|
||||
playerModel,
|
||||
playerPhysicsControl
|
||||
)
|
||||
val cameraPivotNode = new Node(EntityIds.CameraPivot.value)
|
||||
.taggedWith[PlayerControllerTags.PlayerCameraPivotNode]
|
||||
|
||||
for {
|
||||
playerNode <- IO.fromEither(mbPlayerNode)
|
||||
_ <- IO(cameraPivotNode.addControl(new FollowControl(playerNode)))
|
||||
.onErrorHandleWith(e =>
|
||||
IO.raiseError(PlayerController.GenericError(e.getMessage()))
|
||||
)
|
||||
camNode <- IO(
|
||||
cameraPivotNode <- UIO(
|
||||
new Node(EntityIds.CameraPivot.value)
|
||||
.withControl(new FollowControl(playerNode))
|
||||
.taggedWith[PlayerControllerTags.PlayerCameraPivotNode]
|
||||
)
|
||||
camNode <- UIO(
|
||||
PlayerController.Defaults
|
||||
.defaultCamerNode(camera, cameraPivotNode, playerNode, playerPos)
|
||||
).onErrorHandleWith(e =>
|
||||
IO.raiseError(PlayerController.GenericError(e.getMessage()))
|
||||
).map(_.taggedWith[PlayerControllerTags.PlayerCameraNode])
|
||||
// _ <- Task {
|
||||
// val chaseCam = new ChaseCamera(camera, playerNode, inputManager)
|
||||
// chaseCam.setSmoothMotion(false)
|
||||
// chaseCam.setLookAtOffset(new Vector3f(0, 1.5f, 10))
|
||||
// chaseCam
|
||||
// }
|
||||
// .onErrorHandleWith(e =>
|
||||
// IO.raiseError(PlayerController.GenericError(e.getMessage()))
|
||||
// )
|
||||
_ <- wire[PlayerController.Props].create
|
||||
} yield ()
|
||||
.defaultCamerNode(camera, playerPos)
|
||||
.taggedWith[PlayerControllerTags.PlayerCameraNode]
|
||||
)
|
||||
playerActor <- wire[PlayerController.Props].create
|
||||
} yield playerActor
|
||||
}
|
||||
|
||||
def createTestNpc(
|
||||
// appScheduler: monix.execution.Scheduler,
|
||||
npcName: String
|
||||
@ -250,12 +266,7 @@ class MainAppDelegate(
|
||||
val npcPhysicsControl = new BetterCharacterControl(1.5f, 6f, 1f)
|
||||
// (1f, 2.1f, 10f)
|
||||
.withJumpForce(ImVector3f(0, 5f, 0))
|
||||
val mbNpcNode = PlayerController.Defaults.defaultNpcNode(
|
||||
assetManager,
|
||||
initialPos,
|
||||
npcPhysicsControl,
|
||||
npcName
|
||||
)
|
||||
|
||||
val npcActorTask = AkkaUtils.spawnActorL(
|
||||
NpcActorSupervisor
|
||||
.Props(
|
||||
@ -263,6 +274,7 @@ class MainAppDelegate(
|
||||
enqueueR,
|
||||
initialPos,
|
||||
// tickEventBus,
|
||||
npcName,
|
||||
npcPhysicsControl
|
||||
).behavior,
|
||||
npcName,
|
||||
@ -271,28 +283,35 @@ class MainAppDelegate(
|
||||
.behavior,
|
||||
s"${npcName}-npcActorSupervisor"
|
||||
)
|
||||
// .taggedWith[PlayerControllerTags.PlayerTag]
|
||||
|
||||
for {
|
||||
npcNode <- IO.fromEither(mbNpcNode)
|
||||
npcActor <- npcActorTask
|
||||
// _ <- IO {
|
||||
// physicsSpace += npcPhysicsControl
|
||||
// physicsSpace += npcNode
|
||||
// // rootNode += npcNode
|
||||
// }
|
||||
materialDef <- assetManager.loadAssetAs[MaterialDef](
|
||||
os.rel / "Common" / "MatDefs" / "Misc" / "Unshaded.j3md"
|
||||
)
|
||||
material = new Material(materialDef)
|
||||
_ = material.setColor("Color", ColorRGBA.Blue)
|
||||
mesh = PlayerController.Defaults.defaultMesh.withMaterial(material)
|
||||
npcNode = PlayerController.Defaults.defaultNpcNode(
|
||||
mesh,
|
||||
initialPos,
|
||||
npcPhysicsControl,
|
||||
npcName
|
||||
)
|
||||
npcActor <- npcActorTask.hideErrors
|
||||
_ <- (for {
|
||||
_ <- physicsSpace += npcPhysicsControl
|
||||
_ <- physicsSpace += npcNode
|
||||
_ <- rootNode += npcNode
|
||||
} yield ()).hideErrors
|
||||
} yield npcActor
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class FollowControl(playerNode: Node) extends AbstractControl {
|
||||
override def controlUpdate(tpf: Float): Unit = {
|
||||
override def controlUpdate(tpf: Float): Unit =
|
||||
this.spatial.setLocalTranslation(playerNode.getLocalTranslation())
|
||||
}
|
||||
override def controlRender(
|
||||
rm: RenderManager,
|
||||
vp: ViewPort
|
||||
|
@ -5,15 +5,22 @@ import cats.effect.Resource
|
||||
import io.odin.Logger
|
||||
import monix.bio.Task
|
||||
import wow.doge.mygame.executors.ExecutorsModule
|
||||
import akka.actor.BootstrapSetup
|
||||
import monix.execution.Scheduler
|
||||
|
||||
trait MainModule extends ExecutorsModule {
|
||||
|
||||
def actorSystemResource(
|
||||
logger: Logger[Task]
|
||||
logger: Logger[Task],
|
||||
scheduler: Scheduler
|
||||
): Resource[Task, ActorSystem[SpawnProtocol.Command]] =
|
||||
Resource.make(
|
||||
logger.info("Creating Actor System") >> Task(
|
||||
ActorSystem(SpawnProtocol(), name = "GameActorSystem")
|
||||
ActorSystem(
|
||||
SpawnProtocol(),
|
||||
name = "GameActorSystem",
|
||||
BootstrapSetup().withDefaultExecutionContext(scheduler)
|
||||
)
|
||||
)
|
||||
)(sys =>
|
||||
for {
|
||||
|
@ -1,23 +1,32 @@
|
||||
package wow.doge.mygame.executors
|
||||
|
||||
import cats.effect.Resource
|
||||
import monix.bio.IO
|
||||
import monix.bio.Task
|
||||
import monix.bio.UIO
|
||||
import monix.execution.Scheduler
|
||||
|
||||
trait ExecutorsModule {
|
||||
lazy val schedulers = Schedulers()
|
||||
// Resource.make(
|
||||
// Task(
|
||||
// new Schedulers(
|
||||
// jme = Scheduler
|
||||
// .singleThread(name = "JME-Application-Thread", daemonic = false)
|
||||
// )
|
||||
// )
|
||||
// )(s => Task(s.jme.shutdown()))
|
||||
lazy val jMESchedulerResource = Resource.make(
|
||||
val schedulers = Schedulers()
|
||||
val acquire: UIO[Either[Error, Int]] =
|
||||
IO.pure(1).onErrorHandleWith(_ => IO.raiseError(Error)).attempt
|
||||
// : Resource[IO[Error, Unit], Unit]
|
||||
val res = Resource.make(acquire)(_ => IO.unit)
|
||||
val x: Task[Either[Error, Unit]] = res.use {
|
||||
case Right(value) => Task(Right(println(s"got $value")))
|
||||
case Left(value) => Task(Left(value))
|
||||
}
|
||||
val z = x.onErrorHandleWith(ex => UIO(Right(ex.printStackTrace())))
|
||||
val y: IO[Error, Unit] = z >>
|
||||
x.hideErrors.rethrow
|
||||
|
||||
val jMESchedulerResource = Resource.make(
|
||||
Task(
|
||||
Scheduler
|
||||
.singleThread(name = "JME-Application-Thread", daemonic = false)
|
||||
)
|
||||
)(e => Task(e.shutdown()))
|
||||
}
|
||||
|
||||
sealed trait Error
|
||||
case object Error extends Error
|
||||
|
@ -42,13 +42,13 @@ object SwingExecutorService extends GUIExecutorService {
|
||||
|
||||
object JMEExecutorService extends GUIExecutorService {
|
||||
override def execute(command: Runnable) =
|
||||
JMERunner.runner.enqueue(command)
|
||||
JMERunner.runner.get.apply(command)
|
||||
// new SingleThreadEventExecutor()
|
||||
sys.addShutdownHook(JMEExecutorService.shutdown())
|
||||
}
|
||||
|
||||
object JMERunner {
|
||||
var runner: com.jme3.app.Application = null
|
||||
var runner: Option[Runnable => Unit] = None
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,16 @@
|
||||
package wow.doge.mygame.game
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import akka.actor.typed.ActorRef
|
||||
import akka.actor.typed.Behavior
|
||||
import akka.actor.typed.Props
|
||||
import akka.actor.typed.SpawnProtocol
|
||||
import akka.actor.typed.scaladsl.AskPattern._
|
||||
import akka.util.Timeout
|
||||
import cats.effect.Resource
|
||||
import cats.effect.concurrent.Deferred
|
||||
import com.jme3.app.state.AppStateManager
|
||||
import com.jme3.asset.AssetManager
|
||||
import com.jme3.bullet.BulletAppState
|
||||
import com.jme3.input.InputManager
|
||||
import com.jme3.scene.Node
|
||||
@ -12,20 +19,23 @@ import com.jme3.system.AppSettings
|
||||
import com.softwaremill.tagging._
|
||||
import com.typesafe.scalalogging.{Logger => SLogger}
|
||||
import io.odin.Logger
|
||||
import monix.bio.IO
|
||||
import monix.bio.Fiber
|
||||
import monix.bio.Task
|
||||
import monix.bio.UIO
|
||||
import monix.catnap.ConcurrentChannel
|
||||
import monix.catnap.ConsumerF
|
||||
import monix.catnap.Semaphore
|
||||
import monix.eval.Coeval
|
||||
import monix.execution.CancelableFuture
|
||||
import monix.execution.CancelablePromise
|
||||
import monix.execution.Scheduler
|
||||
import wow.doge.mygame.executors.JMERunner
|
||||
import wow.doge.mygame.executors.Schedulers
|
||||
import wow.doge.mygame.utils.GenericTimerActor
|
||||
import wow.doge.mygame.game.subsystems.ui.JFxUI
|
||||
import wow.doge.mygame.implicits._
|
||||
import wow.doge.mygame.utils.AkkaUtils
|
||||
import wow.doge.mygame.utils.wrappers.jme._
|
||||
|
||||
sealed trait Error
|
||||
case object FlyCamNotExists extends Error
|
||||
import wow.doge.mygame.Dispatchers
|
||||
|
||||
object GameAppTags {
|
||||
sealed trait RootNode
|
||||
@ -33,47 +43,58 @@ object GameAppTags {
|
||||
sealed trait GuiNode
|
||||
}
|
||||
|
||||
class GameApp private (logger: Logger[Task], app: SimpleAppExt) {
|
||||
class GameApp private[game] (
|
||||
logger: Logger[Task],
|
||||
app: SimpleAppExt,
|
||||
gameActor: ActorRef[TestGameActor.Command],
|
||||
gameSpawnProtocol: ActorRef[SpawnProtocol.Command]
|
||||
) {
|
||||
def stateManager: Task[AppStateManager] = Task(app.getStateManager())
|
||||
def inputManager: Task[InputManager] = Task(app.getInputManager())
|
||||
def assetManager: Task[AssetManager] = Task(app.getAssetManager())
|
||||
def assetManager = new AssetManager(app.getAssetManager())
|
||||
def guiNode = Task(app.getGuiNode().taggedWith[GameAppTags.GuiNode])
|
||||
def guiNode2 = AppNode[Task](app.getGuiNode()).taggedWith[GameAppTags.GuiNode]
|
||||
def flyCam =
|
||||
IO(app.getFlyByCamera()).onErrorHandleWith(_ =>
|
||||
IO.raiseError(FlyCamNotExists)
|
||||
)
|
||||
val guiNode2 = AppNode[Task](app.getGuiNode()).taggedWith[GameAppTags.GuiNode]
|
||||
def flyCam = Option(app.getFlyByCamera())
|
||||
def camera = Task(app.getCamera())
|
||||
def viewPort = Task(app.getViewPort())
|
||||
// def rootNode = Task(app.getRootNode().taggedWith[GameAppTags.RootNode])
|
||||
val rootNode =
|
||||
AppNode[Task](app.getRootNode()).taggedWith[GameAppTags.RootNode]
|
||||
|
||||
val physicsSpace = new PhysicsSpace[Task](app.bulletAppState.physicsSpace)
|
||||
def enqueue(cb: () => Unit) =
|
||||
app.enqueue(new Runnable {
|
||||
override def run() = cb()
|
||||
})
|
||||
def enqueue(cb: () => Unit) = app.enqueueR(() => cb())
|
||||
def enqueueL[T](cb: () => T): Task[T] = app.enqueueL(cb)
|
||||
|
||||
def spawnGameActor[T](
|
||||
behavior: Behavior[T],
|
||||
actorName: String,
|
||||
props: Props = Dispatchers.jmeDispatcher
|
||||
)(implicit scheduler: akka.actor.typed.Scheduler) =
|
||||
AkkaUtils.spawnActorL(behavior, actorName, props)(
|
||||
2.seconds,
|
||||
scheduler,
|
||||
gameSpawnProtocol
|
||||
)
|
||||
|
||||
def scheduler = app.scheduler
|
||||
def jfxUI = JFxUI(app)
|
||||
|
||||
}
|
||||
|
||||
object GameApp {
|
||||
|
||||
def resource(
|
||||
class GameAppResource(
|
||||
logger: Logger[Task],
|
||||
jmeThread: Scheduler,
|
||||
schedulers: Schedulers
|
||||
) =
|
||||
)(implicit
|
||||
timeout: Timeout,
|
||||
scheduler: akka.actor.typed.Scheduler,
|
||||
spawnProtocol: ActorRef[SpawnProtocol.Command]
|
||||
) {
|
||||
def resource: Resource[Task, (GameApp, Fiber[Throwable, Unit])] =
|
||||
Resource.make {
|
||||
lazy val bullet = new BulletAppState
|
||||
for {
|
||||
// bullet <- Task(new BulletAppState)
|
||||
// startSignal <- Task(CancelablePromise[Unit]())
|
||||
app <- Task(new SimpleAppExt(schedulers, bullet))
|
||||
_ <- UIO(JMERunner.runner = Some(app.enqueue _))
|
||||
_ <- Task {
|
||||
val settings = new AppSettings(true)
|
||||
settings.setVSync(true)
|
||||
@ -88,34 +109,76 @@ object GameApp {
|
||||
|
||||
fib <- Task(app.start).executeOn(jmeThread).start
|
||||
_ <- Task.deferFuture(app.started)
|
||||
// _ <- Task.fromCancelablePromise(startSignal)
|
||||
_ <- Task(pprint.log(bullet.toString()))
|
||||
_ <- Task(println(bullet.physicsSpace.toString()))
|
||||
gameApp <- Task(new GameApp(logger, app))
|
||||
} yield gameApp -> fib
|
||||
}(_._2.cancel)
|
||||
|
||||
/**
|
||||
* Synchronization wrapper for a mutable object
|
||||
*
|
||||
* @param obj the mutable object
|
||||
* @param lock lock for synchronization
|
||||
*/
|
||||
class SynchedObject[A](obj: A, lock: Semaphore[Task]) {
|
||||
def modify(f: A => Unit): Task[Unit] =
|
||||
lock.withPermit(Task(f(obj)))
|
||||
|
||||
def flatModify(f: A => Task[Unit]): Task[Unit] =
|
||||
lock.withPermit(f(obj))
|
||||
|
||||
def get: Task[A] = lock.withPermit(Task(obj))
|
||||
testGameActor <- AkkaUtils.spawnActorL(
|
||||
new TestGameActor.Props().create,
|
||||
"testGameActor",
|
||||
Dispatchers.jmeDispatcher
|
||||
)
|
||||
sp <- testGameActor.askL(TestGameActor.GetSpawnProtocol(_))
|
||||
gameApp <- Task(new GameApp(logger, app, testGameActor, sp))
|
||||
_ <- Task {
|
||||
val fut = () => testGameActor.ask(TestGameActor.Stop).flatten
|
||||
app.cancelToken = Some(fut)
|
||||
}
|
||||
|
||||
object SynchedObject {
|
||||
def apply[A](obj: A) =
|
||||
Semaphore[Task](1).flatMap(lock => Task(new SynchedObject(obj, lock)))
|
||||
} yield (gameApp, fib)
|
||||
} {
|
||||
case (gameApp, fib) =>
|
||||
fib.cancel >> UIO(JMERunner.runner = None)
|
||||
}
|
||||
}
|
||||
|
||||
object GameApp {}
|
||||
|
||||
import akka.actor.typed.scaladsl.Behaviors
|
||||
import akka.actor.typed.scaladsl.ActorContext
|
||||
|
||||
object TestGameActor {
|
||||
sealed trait Command
|
||||
case object Ping extends Command
|
||||
case class Stop(stopSignal: ActorRef[CancelableFuture[Unit]]) extends Command
|
||||
case class GetSpawnProtocol(
|
||||
replyTo: ActorRef[ActorRef[SpawnProtocol.Command]]
|
||||
) extends Command
|
||||
import scala.concurrent.duration._
|
||||
class Props() {
|
||||
def create =
|
||||
Behaviors.setup[Command] { ctx =>
|
||||
ctx.spawn(
|
||||
GenericTimerActor
|
||||
.Props(ctx.self, Ping, 1000.millis)
|
||||
.behavior,
|
||||
"pingTimer"
|
||||
) ! GenericTimerActor.Start
|
||||
new TestGameActor(ctx, this).receive
|
||||
}
|
||||
}
|
||||
}
|
||||
class TestGameActor(
|
||||
ctx: ActorContext[TestGameActor.Command],
|
||||
props: TestGameActor.Props
|
||||
) {
|
||||
import TestGameActor._
|
||||
val stopPromise = CancelablePromise[Unit]()
|
||||
def receive =
|
||||
Behaviors
|
||||
.receiveMessage[Command] {
|
||||
case Stop(replyTo) =>
|
||||
ctx.log.infoP("stopping")
|
||||
replyTo ! stopPromise.future
|
||||
Behaviors.stopped
|
||||
case Ping =>
|
||||
ctx.log.debugP("ping")
|
||||
Behaviors.same
|
||||
case GetSpawnProtocol(replyTo) =>
|
||||
val sp = ctx.spawn(SpawnProtocol(), "gameSpawnProtocol")
|
||||
replyTo ! sp
|
||||
Behaviors.same
|
||||
}
|
||||
.receiveSignal {
|
||||
case (_, akka.actor.typed.PostStop) =>
|
||||
stopPromise.success(())
|
||||
Behaviors.same
|
||||
}
|
||||
}
|
||||
|
||||
object Ops {
|
||||
|
@ -5,7 +5,7 @@ import scala.concurrent.duration._
|
||||
import akka.actor.typed.SupervisorStrategy
|
||||
import akka.actor.typed.scaladsl.Behaviors
|
||||
import wow.doge.mygame.game.TickGenerator.Send
|
||||
import wow.doge.mygame.game.entities.GenericTimerActor
|
||||
import wow.doge.mygame.utils.GenericTimerActor
|
||||
import wow.doge.mygame.implicits._
|
||||
import wow.doge.mygame.subsystems.events.EventBus
|
||||
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
|
||||
@ -19,14 +19,7 @@ object GameAppActor {
|
||||
case object Pause extends Command
|
||||
case object Stop extends Command
|
||||
|
||||
case class Props(
|
||||
// app: SimpleAppExt,
|
||||
// akkaScheduler: Scheduler,
|
||||
// schedulers: Schedulers,
|
||||
// spawnProtocol: ActorRef[SpawnProtocol.Command],
|
||||
// loggerL: Logger[Task]
|
||||
tickEventBus: GameEventBus[TickEvent]
|
||||
) {
|
||||
case class Props(tickEventBus: GameEventBus[TickEvent]) {
|
||||
def behavior =
|
||||
Behaviors.setup[Command] { ctx =>
|
||||
ctx.log.infoP("Hello from GameAppActor")
|
||||
|
@ -1,9 +1,12 @@
|
||||
package wow.doge.mygame.game
|
||||
|
||||
import scala.collection.immutable.Queue
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.Future
|
||||
|
||||
import com.jme3.app.SimpleApplication
|
||||
import com.jme3.app.state.AppState
|
||||
import com.jme3.bullet.BulletAppState
|
||||
import monix.bio.Task
|
||||
import monix.execution.CancelableFuture
|
||||
import monix.execution.CancelablePromise
|
||||
@ -11,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 com.jme3.bullet.BulletAppState
|
||||
// import wow.doge.mygame.implicits._
|
||||
class SimpleAppExt(
|
||||
schedulers: Schedulers,
|
||||
@ -25,27 +27,30 @@ class SimpleAppExt(
|
||||
*/
|
||||
private val taskQueue2 = Atomic(Queue.empty[MyTask[_]])
|
||||
|
||||
// lazy val bulletAppState: BulletAppState = new BulletAppState
|
||||
|
||||
// def bulletAppState = synchronized(_bulletAppState)
|
||||
|
||||
// def tickObservable: Observable[Float] = tickSubject
|
||||
// lazy val bulletAppState = stateManager.state[BulletAppState]()
|
||||
|
||||
private val startSignal: CancelablePromise[Unit] = CancelablePromise()
|
||||
|
||||
var cancelToken: Option[() => Future[Unit]] = None
|
||||
|
||||
def started: CancelableFuture[Unit] = startSignal.future
|
||||
|
||||
override def simpleInitApp(): Unit = {
|
||||
// _bulletAppState = new BulletAppState
|
||||
stateManager.attach(bulletAppState)
|
||||
startSignal.success(())
|
||||
}
|
||||
|
||||
override def simpleUpdate(tpf: Float): Unit = {}
|
||||
|
||||
override def stop(): Unit = {
|
||||
super.stop()
|
||||
override def stop(waitFor: Boolean): Unit = {
|
||||
cancelToken match {
|
||||
case Some(value) =>
|
||||
value().foreach { _ =>
|
||||
pprint.log("Called cancel in simpleapp")
|
||||
super.stop(true)
|
||||
}
|
||||
case None =>
|
||||
pprint.log("Called cancel in simpleapp")
|
||||
super.stop(true)
|
||||
}
|
||||
}
|
||||
|
||||
def enqueueFuture[T](cb: () => T): CancelableFuture[T] = {
|
||||
@ -74,7 +79,7 @@ class SimpleAppExt(
|
||||
enqueue(command)
|
||||
}
|
||||
|
||||
lazy val scheduler = Scheduler(JMEExecutorService)
|
||||
val scheduler = Scheduler(JMEExecutorService)
|
||||
}
|
||||
object SimpleAppExt {
|
||||
private[game] case class MyTask[T](p: CancelablePromise[T], cb: () => T)
|
||||
|
@ -10,6 +10,7 @@ import akka.actor.typed.SupervisorStrategy
|
||||
import akka.actor.typed.scaladsl.ActorContext
|
||||
import akka.actor.typed.scaladsl.Behaviors
|
||||
import akka.util.Timeout
|
||||
import cats.syntax.show._
|
||||
import monix.execution.CancelableFuture
|
||||
import monix.execution.CancelablePromise
|
||||
import wow.doge.mygame.game.subsystems.movement.CanMove
|
||||
@ -26,6 +27,7 @@ import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
|
||||
import wow.doge.mygame.subsystems.events.TickEvent
|
||||
import wow.doge.mygame.subsystems.events.TickEvent.RenderTick
|
||||
import wow.doge.mygame.subsystems.movement.ImMovementActor
|
||||
import wow.doge.mygame.utils.GenericTimerActor
|
||||
|
||||
object NpcActorSupervisor {
|
||||
sealed trait Command
|
||||
@ -35,10 +37,7 @@ object NpcActorSupervisor {
|
||||
signal: CancelableFuture[NpcMovementActor.DoneMoving.type]
|
||||
) extends Command
|
||||
private case object DoneMoving extends Command
|
||||
// private case class MovementResponse(response: CancelableFuture[_]) extends Command
|
||||
private case class LogError(err: Throwable) extends Command
|
||||
private case object NoOp extends Command
|
||||
|
||||
private case class MovementFailed(err: Throwable) extends Command
|
||||
|
||||
final case class Props(
|
||||
@ -47,6 +46,7 @@ object NpcActorSupervisor {
|
||||
initialPos: ImVector3f
|
||||
) {
|
||||
def behavior =
|
||||
Behaviors.withMdc(Map("actorName" -> npcName))(
|
||||
Behaviors.setup[Command] { ctx =>
|
||||
val npcMovementActor = ctx.spawn(
|
||||
(npcMovementActorBehavior),
|
||||
@ -56,9 +56,9 @@ object NpcActorSupervisor {
|
||||
new NpcActorSupervisor(ctx, this, Children(npcMovementActor))
|
||||
.idle(State())
|
||||
}
|
||||
}
|
||||
final case class State(
|
||||
)
|
||||
}
|
||||
final case class State()
|
||||
final case class Children(
|
||||
npcMovementActor: ActorRef[NpcMovementActor.Command]
|
||||
)
|
||||
@ -79,12 +79,12 @@ class NpcActorSupervisor(
|
||||
100.millis
|
||||
)
|
||||
.behavior,
|
||||
s"npc-John-NpcActorTimer"
|
||||
s"npc-${props.npcName}-NpcActorTimer"
|
||||
)
|
||||
|
||||
def idle(state: State): Behavior[NpcActorSupervisor.Command] =
|
||||
Behaviors.setup { _ =>
|
||||
ctx.log.debugP("Inside Idle State")
|
||||
ctx.log.debugP(s"npcActor-${props.npcName}: Entered Idle State")
|
||||
Behaviors.receiveMessage[Command] {
|
||||
case m @ Move(pos) =>
|
||||
ctx.ask(
|
||||
@ -99,7 +99,7 @@ class NpcActorSupervisor(
|
||||
moving(state, move.pos, signal)
|
||||
|
||||
case LogError(err) =>
|
||||
ctx.log.warnP(err.getMessage())
|
||||
logError(err)
|
||||
Behaviors.same
|
||||
case _ => Behaviors.unhandled
|
||||
}
|
||||
@ -111,19 +111,15 @@ class NpcActorSupervisor(
|
||||
signal: CancelableFuture[NpcMovementActor.DoneMoving.type]
|
||||
): Behavior[NpcActorSupervisor.Command] =
|
||||
Behaviors.setup { _ =>
|
||||
ctx.log.debugP(s"npcActor-${props.npcName}: Entered Moving State")
|
||||
movementTimer ! GenericTimerActor.Start
|
||||
|
||||
// ctx
|
||||
// .ask(state.npcMovementActor, NpcMovementActor2.MoveTo(targetPos, _))(
|
||||
// _.fold(LogError(_), MovementResponse(_))
|
||||
// )
|
||||
ctx.pipeToSelf(signal) {
|
||||
case Success(value) => DoneMoving
|
||||
case Failure(exception) => MovementFailed(exception)
|
||||
}
|
||||
Behaviors.receiveMessagePartial[Command] {
|
||||
case LogError(err) =>
|
||||
ctx.log.error(err.getMessage())
|
||||
logError(err)
|
||||
Behaviors.same
|
||||
case MovementFailed(err) =>
|
||||
ctx.self ! LogError(err)
|
||||
@ -132,6 +128,7 @@ class NpcActorSupervisor(
|
||||
case m @ Move(pos) =>
|
||||
movementTimer ! GenericTimerActor.Stop
|
||||
children.npcMovementActor ! NpcMovementActor.StopMoving
|
||||
// new movement request received, cancel previous request
|
||||
signal.cancel()
|
||||
ctx.ask(
|
||||
children.npcMovementActor,
|
||||
@ -143,15 +140,15 @@ class NpcActorSupervisor(
|
||||
Behaviors.same
|
||||
case InternalMove(move, signal) =>
|
||||
moving(state, targetPos, signal)
|
||||
case NoOp => Behaviors.same
|
||||
// case MovementResponse(x: CancelableFuture[_]) =>
|
||||
// // ctx.pipeToSelf(x)(_.)
|
||||
case DoneMoving =>
|
||||
movementTimer ! GenericTimerActor.Stop
|
||||
idle(state)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
def logError(err: Throwable) =
|
||||
ctx.log.errorP(s"npcActor-${props.npcName}: " + err.getMessage)
|
||||
}
|
||||
|
||||
object NpcMovementActor {
|
||||
@ -171,6 +168,7 @@ object NpcMovementActor {
|
||||
val enqueueR: Function1[() => Unit, Unit],
|
||||
val initialPos: ImVector3f,
|
||||
// val tickEventBus: GameEventBus[TickEvent],
|
||||
val npcName: String,
|
||||
val movable: T
|
||||
) {
|
||||
def behavior =
|
||||
@ -189,9 +187,7 @@ class NpcMovementActor[T](
|
||||
|
||||
def location = cm.location(props.movable)
|
||||
|
||||
def receive(
|
||||
state: State
|
||||
): Behavior[NpcMovementActor.Command] =
|
||||
def receive(state: State): Behavior[NpcMovementActor.Command] =
|
||||
Behaviors.receiveMessagePartial {
|
||||
case AskPosition(replyTo) =>
|
||||
replyTo ! location
|
||||
@ -214,19 +210,25 @@ class NpcMovementActor[T](
|
||||
Behaviors.receiveMessagePartial {
|
||||
case StopMoving =>
|
||||
ctx.log.debugP(
|
||||
"Position at Stop = " + location.toString
|
||||
show"npcActor-${props.npcName}: Position at Stop = " + location
|
||||
)
|
||||
props.enqueueR(() => cm.stop(props.movable))
|
||||
receive(state)
|
||||
|
||||
case MovementTick =>
|
||||
val dst = ImVector3f.dst(targetPos, location)
|
||||
val dst = ImVector3f.manhattanDst(targetPos, location)
|
||||
if (dst <= 10f) {
|
||||
ctx.self ! StopMoving
|
||||
reachDestination.success(DoneMoving)
|
||||
} else {
|
||||
ctx.log.traceP("Difference = " + dst.toString())
|
||||
ctx.log.traceP("Current pos = " + location.toString())
|
||||
ctx.log.traceP(
|
||||
show"npcActor-${props.npcName}: Difference = " + dst.formatted(
|
||||
"%.2f"
|
||||
)
|
||||
)
|
||||
ctx.log.traceP(
|
||||
show"npcActor-${props.npcName}: Current pos = " + location
|
||||
)
|
||||
}
|
||||
Behaviors.same
|
||||
}
|
||||
@ -295,7 +297,7 @@ object NpcMovementActorNotUsed {
|
||||
|
||||
mainEventBus ! EventBus.Subscribe(npcMovementEl)
|
||||
tickEventBus ! EventBus.Subscribe(renderTickEl)
|
||||
Behaviors.receiveMessage { msg => Behaviors.same }
|
||||
Behaviors.receiveMessage(msg => Behaviors.same)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,15 +1,11 @@
|
||||
package wow.doge.mygame.game.entities
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import scala.util.Random
|
||||
|
||||
import akka.actor.typed.ActorRef
|
||||
import akka.actor.typed.Behavior
|
||||
import akka.actor.typed.LogOptions
|
||||
import akka.actor.typed.SupervisorStrategy
|
||||
import akka.actor.typed.scaladsl.ActorContext
|
||||
import akka.actor.typed.scaladsl.Behaviors
|
||||
import akka.actor.typed.scaladsl.TimerScheduler
|
||||
import com.typesafe.scalalogging.Logger
|
||||
import org.slf4j.event.Level
|
||||
import wow.doge.mygame.game.subsystems.movement.CanMove
|
||||
@ -19,7 +15,7 @@ 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 wow.doge.mygame.implicits._
|
||||
object PlayerActorSupervisor {
|
||||
sealed trait Command
|
||||
final case class Props(
|
||||
@ -28,15 +24,15 @@ object PlayerActorSupervisor {
|
||||
imMovementActorBehavior: Behavior[ImMovementActor.Command],
|
||||
playerCameraActorBehavior: Behavior[PlayerCameraActor.Command]
|
||||
) {
|
||||
def behavior[T: CanMove](movable: T) =
|
||||
def behavior =
|
||||
Behaviors.logMessages(
|
||||
LogOptions()
|
||||
.withLevel(Level.TRACE)
|
||||
.withLogger(
|
||||
Logger[PlayerActorSupervisor[T]].underlying
|
||||
Logger[PlayerActorSupervisor].underlying
|
||||
),
|
||||
Behaviors.setup[Command] { ctx =>
|
||||
ctx.log.info("Hello from PlayerActor")
|
||||
ctx.log.infoP("Starting PlayerActor")
|
||||
|
||||
// spawn children actors
|
||||
val movementActor =
|
||||
@ -44,7 +40,7 @@ object PlayerActorSupervisor {
|
||||
Behaviors
|
||||
.supervise(imMovementActorBehavior)
|
||||
.onFailure[Exception](SupervisorStrategy.restart),
|
||||
"playerMovementActorChild"
|
||||
"playerMovementActor"
|
||||
)
|
||||
|
||||
val playerCameraActor =
|
||||
@ -55,19 +51,27 @@ object PlayerActorSupervisor {
|
||||
"playerCameraActorEl"
|
||||
)
|
||||
|
||||
ctx.spawn(
|
||||
PlayerMovementActor
|
||||
.Props(
|
||||
movementActor,
|
||||
playerCameraActor,
|
||||
playerEventBus,
|
||||
tickEventBus
|
||||
)
|
||||
.behavior,
|
||||
"playerMovementAcor"
|
||||
val playerMovementEl = ctx.spawn(
|
||||
Behaviors
|
||||
.supervise(PlayerMovementEventListener(movementActor))
|
||||
.onFailure[Exception](SupervisorStrategy.restart),
|
||||
"playerMovementEventHandler"
|
||||
)
|
||||
|
||||
//init actors
|
||||
val renderTickEl = {
|
||||
val behavior =
|
||||
Behaviors.receiveMessage[RenderTick.type] {
|
||||
case RenderTick =>
|
||||
movementActor ! ImMovementActor.Tick
|
||||
// playerCameraActor ! PlayerCameraActor.Tick
|
||||
Behaviors.same
|
||||
}
|
||||
ctx.spawn(behavior, "playerMovementTickListener")
|
||||
}
|
||||
|
||||
//init listeners
|
||||
playerEventBus ! EventBus.Subscribe(playerMovementEl)
|
||||
tickEventBus ! EventBus.Subscribe(renderTickEl)
|
||||
playerEventBus ! EventBus.Subscribe(playerCameraEl)
|
||||
|
||||
new PlayerActorSupervisor(
|
||||
@ -84,14 +88,15 @@ object PlayerActorSupervisor {
|
||||
movementActor: ActorRef[ImMovementActor.Command]
|
||||
)
|
||||
}
|
||||
class PlayerActorSupervisor[T: CanMove](
|
||||
class PlayerActorSupervisor(
|
||||
ctx: ActorContext[PlayerActorSupervisor.Command],
|
||||
props: PlayerActorSupervisor.Props,
|
||||
children: PlayerActorSupervisor.Children
|
||||
) {
|
||||
import PlayerActorSupervisor._
|
||||
def receive =
|
||||
Behaviors.receiveMessage[Command] {
|
||||
Behaviors
|
||||
.receiveMessage[Command] {
|
||||
case _ =>
|
||||
// children.movementActor ! ImMovementActor.MovedDown(true)
|
||||
Behaviors.same
|
||||
@ -115,15 +120,17 @@ object PlayerMovementActor {
|
||||
.onFailure[Exception](SupervisorStrategy.restart),
|
||||
"playerMovementEventHandler"
|
||||
)
|
||||
val renderTickElBehavior =
|
||||
|
||||
val renderTickEl = {
|
||||
val behavior =
|
||||
Behaviors.receiveMessage[RenderTick.type] {
|
||||
case RenderTick =>
|
||||
movementActor ! ImMovementActor.Tick
|
||||
// playerCameraActor ! PlayerCameraActor.Tick
|
||||
Behaviors.same
|
||||
}
|
||||
val renderTickEl =
|
||||
ctx.spawn(renderTickElBehavior, "playerMovementTickListener")
|
||||
ctx.spawn(behavior, "playerMovementTickListener")
|
||||
}
|
||||
|
||||
playerEventBus ! EventBus.Subscribe(playerMovementEl)
|
||||
tickEventBus ! EventBus.Subscribe(renderTickEl)
|
||||
@ -131,47 +138,3 @@ object PlayerMovementActor {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object GenericTimerActor {
|
||||
sealed trait Command
|
||||
final case object Start extends Command
|
||||
final case object Stop extends Command
|
||||
private case object Send extends Command
|
||||
case class TimerKey(seed: Long)
|
||||
|
||||
case class Props[T](
|
||||
target: ActorRef[T],
|
||||
messageToSend: T,
|
||||
timeInterval: FiniteDuration
|
||||
) {
|
||||
val behavior = Behaviors.withTimers[Command] { timers =>
|
||||
new GenericTimerActor(timers, TimerKey(Random.nextLong()), this).idle
|
||||
}
|
||||
}
|
||||
}
|
||||
class GenericTimerActor[T](
|
||||
timers: TimerScheduler[GenericTimerActor.Command],
|
||||
timerKey: GenericTimerActor.TimerKey,
|
||||
props: GenericTimerActor.Props[T]
|
||||
) {
|
||||
import GenericTimerActor._
|
||||
|
||||
val idle: Behavior[Command] =
|
||||
Behaviors.receiveMessage {
|
||||
case Start =>
|
||||
timers.startTimerWithFixedDelay(timerKey, Send, props.timeInterval)
|
||||
active
|
||||
case _ => Behaviors.unhandled
|
||||
|
||||
}
|
||||
|
||||
val active: Behavior[Command] =
|
||||
Behaviors.receiveMessagePartial {
|
||||
case Send =>
|
||||
props.target ! props.messageToSend
|
||||
Behaviors.same
|
||||
case Stop =>
|
||||
timers.cancel(timerKey)
|
||||
idle
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import akka.actor.typed.ActorRef
|
||||
import akka.actor.typed.Scheduler
|
||||
import akka.actor.typed.SpawnProtocol
|
||||
import akka.util.Timeout
|
||||
import cats.implicits._
|
||||
import com.jme3.asset.AssetManager
|
||||
import com.jme3.bullet.BulletAppState
|
||||
import com.jme3.bullet.control.BetterCharacterControl
|
||||
@ -13,24 +12,22 @@ import com.jme3.renderer.Camera
|
||||
import com.jme3.scene.CameraNode
|
||||
import com.jme3.scene.Geometry
|
||||
import com.jme3.scene.Node
|
||||
import com.jme3.scene.Spatial
|
||||
import com.jme3.scene.shape.Box
|
||||
import com.softwaremill.tagging._
|
||||
import io.odin.Logger
|
||||
import monix.bio.IO
|
||||
import monix.bio.Task
|
||||
import monix.bio.UIO
|
||||
import wow.doge.mygame.game.GameAppTags
|
||||
import wow.doge.mygame.game.SimpleAppExt
|
||||
import wow.doge.mygame.implicits._
|
||||
import wow.doge.mygame.math.ImVector3f
|
||||
import wow.doge.mygame.state.MyMaterial
|
||||
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._
|
||||
import monix.bio.UIO
|
||||
|
||||
object PlayerControllerTags {
|
||||
sealed trait PlayerTag
|
||||
sealed trait PlayerCameraNode
|
||||
@ -40,7 +37,9 @@ object PlayerControllerTags {
|
||||
object PlayerController {
|
||||
sealed trait Error
|
||||
case class CouldNotCreatePlayerNode(reason: String) extends Error
|
||||
case class GenericError(reason: String) extends Error
|
||||
case class CouldNotCreatePlayerModel(
|
||||
reason: wow.doge.mygame.utils.wrappers.jme.AssetManager.Error
|
||||
) extends Error
|
||||
|
||||
class Props(
|
||||
enqueueR: Function1[() => Unit, Unit],
|
||||
@ -65,9 +64,8 @@ object PlayerController {
|
||||
val playerActorBehavior = {
|
||||
val movementActorBeh = new ImMovementActor.Props(
|
||||
enqueueR,
|
||||
playerPhysicsControl,
|
||||
camera
|
||||
).behavior
|
||||
).behavior(playerPhysicsControl)
|
||||
val cameraActorBeh = new PlayerCameraActor.Props(
|
||||
cameraPivotNode,
|
||||
enqueueR,
|
||||
@ -78,36 +76,19 @@ object PlayerController {
|
||||
tickEventBus,
|
||||
movementActorBeh,
|
||||
cameraActorBeh
|
||||
).behavior(playerPhysicsControl)
|
||||
).behavior
|
||||
}
|
||||
val create: IO[Error, Unit] =
|
||||
val create: UIO[ActorRef[PlayerActorSupervisor.Command]] =
|
||||
(for {
|
||||
playerActor <- AkkaUtils.spawnActorL(
|
||||
playerActorBehavior,
|
||||
"playerActorSupervisor"
|
||||
)
|
||||
// _ <- Task(rootNode += playerNode)
|
||||
// _ <- Task(pprint.log("Physicsspace = " + physicsSpace.toString()))
|
||||
// _ <- Task(pprint.log("playerNode = " + playerNode.toString()))
|
||||
playerActor <-
|
||||
AkkaUtils.spawnActorL(playerActorBehavior, "playerActorSupervisor")
|
||||
_ <- rootNode += playerNode
|
||||
_ <- physicsSpace += playerNode
|
||||
_ <- physicsSpace += playerPhysicsControl
|
||||
_ <- IO {
|
||||
// physicsSpace += playerNode
|
||||
// physicsSpace += playerPhysicsControl
|
||||
// rootNode += cameraNode
|
||||
cameraPivotNode += cameraNode
|
||||
// playerNode += cameraPivotNode
|
||||
// rootNode += cameraPivotNode
|
||||
}
|
||||
_ = cameraPivotNode += cameraNode
|
||||
_ <- rootNode += cameraPivotNode
|
||||
} yield ())
|
||||
.onErrorHandleWith(e =>
|
||||
UIO(e.printStackTrace()) >> IO.raiseError(
|
||||
GenericError(e.getMessage())
|
||||
)
|
||||
)
|
||||
// .executeOn(appScheduler)
|
||||
} yield playerActor).hideErrors
|
||||
|
||||
}
|
||||
|
||||
def apply(
|
||||
@ -146,11 +127,11 @@ object PlayerController {
|
||||
val geom = Geometry("playerGeom", b)
|
||||
geom
|
||||
}
|
||||
def defaultTexture(assetManager: AssetManager) =
|
||||
MyMaterial(
|
||||
assetManager = assetManager,
|
||||
path = os.rel / "Common" / "MatDefs" / "Misc" / "Unshaded.j3md"
|
||||
)
|
||||
// def defaultTexture(assetManager: AssetManager) =
|
||||
// MyMaterial(
|
||||
// assetManager = assetManager,
|
||||
// path = os.rel / "Common" / "MatDefs" / "Misc" / "Unshaded.j3md"
|
||||
// )
|
||||
|
||||
// new CameraControl(cam) {
|
||||
// override def controlUpdate(tpf: Float) = {
|
||||
@ -164,14 +145,11 @@ object PlayerController {
|
||||
|
||||
def defaultCamerNode(
|
||||
cam: Camera,
|
||||
playerNode: Node,
|
||||
cameraPivotNode: Node,
|
||||
// playerNode: Node,
|
||||
// cameraPivotNode: Node,
|
||||
playerPos: ImVector3f
|
||||
) =
|
||||
new CameraNode(
|
||||
"CameraNode",
|
||||
cam
|
||||
)
|
||||
new CameraNode("CameraNode", cam)
|
||||
// .withControlDir(ControlDirection.SpatialToCamera)
|
||||
.withLocalTranslation(ImVector3f(0, 1.5f, 10))
|
||||
.withLookAt(playerPos, ImVector3f.UNIT_Y)
|
||||
@ -182,66 +160,32 @@ object PlayerController {
|
||||
// camNode: CameraNode,
|
||||
playerPhysicsControl: BetterCharacterControl
|
||||
) =
|
||||
Either
|
||||
.catchNonFatal(
|
||||
Node("PlayerNode")
|
||||
.withChildren(
|
||||
// camNode,
|
||||
playerModel
|
||||
)
|
||||
.withChildren(playerModel)
|
||||
.withLocalTranslation(playerPos)
|
||||
.withControl(playerPhysicsControl)
|
||||
)
|
||||
.map(_.taggedWith[PlayerControllerTags.PlayerTag])
|
||||
.leftMap(e => PlayerController.CouldNotCreatePlayerNode(e.getMessage()))
|
||||
.taggedWith[PlayerControllerTags.PlayerTag]
|
||||
|
||||
def defaultNpcNode(
|
||||
assetManager: AssetManager,
|
||||
// modelPath: os.RelPath,
|
||||
// assetManager: AssetManager,
|
||||
npcModel: Spatial,
|
||||
initialPos: ImVector3f,
|
||||
npcPhysicsControl: BetterCharacterControl,
|
||||
npcName: String
|
||||
) =
|
||||
Either
|
||||
.catchNonFatal(
|
||||
// Either
|
||||
// .catchNonFatal(
|
||||
Node(npcName)
|
||||
.withChildren(
|
||||
// assetManager
|
||||
// .loadModel(modelPath)
|
||||
// .asInstanceOf[Node]
|
||||
// .withRotate(0, FastMath.PI, 0)
|
||||
defaultMesh.withMaterial(defaultTexture(assetManager))
|
||||
// defaultMesh.withMaterial(defaultTexture(assetManager))
|
||||
npcModel
|
||||
)
|
||||
.withLocalTranslation(initialPos)
|
||||
.withControl(npcPhysicsControl)
|
||||
)
|
||||
// .map(_.taggedWith[PlayerControllerTags.PlayerTag])
|
||||
// .leftMap(e => PlayerController.CouldNotCreatePlayerNode(e.getMessage()))
|
||||
// )
|
||||
|
||||
def defaultPlayerPhysicsControl =
|
||||
new BetterCharacterControl(1.5f, 6f, 1f)
|
||||
.withJumpForce(ImVector3f(0, 5f, 0))
|
||||
}
|
||||
}
|
||||
|
||||
object Methods {}
|
||||
|
||||
// camNode <- IO(
|
||||
// _cameraNode.getOrElse(defaultCamerNode(camera, initialPlayerPos))
|
||||
// )
|
||||
// playerPhysicsControl <- IO(
|
||||
// _playerPhysicsControl
|
||||
// .getOrElse(defaultPlayerPhysicsControl)
|
||||
// .taggedWith[PlayerTag]
|
||||
// )
|
||||
// playerNode <- IO.fromEither(
|
||||
// _playerNode.fold(
|
||||
// defaultPlayerNode(
|
||||
// assetManager,
|
||||
// modelPath,
|
||||
// initialPlayerPos,
|
||||
// camNode,
|
||||
// playerPhysicsControl
|
||||
// )
|
||||
// )(_.asRight)
|
||||
// )
|
||||
|
@ -11,9 +11,7 @@ import wow.doge.mygame.subsystems.movement.ImMovementActor
|
||||
|
||||
object PlayerMovementEventListener {
|
||||
import PlayerMovementEvent._
|
||||
def apply(
|
||||
movementActor: ActorRef[ImMovementActor.Command]
|
||||
) =
|
||||
def apply(movementActor: ActorRef[ImMovementActor.Command]) =
|
||||
Behaviors.logMessages(
|
||||
LogOptions()
|
||||
.withLevel(Level.TRACE)
|
||||
@ -50,9 +48,7 @@ object PlayerMovementEventListener {
|
||||
|
||||
object PlayerCameraEventListener {
|
||||
import PlayerCameraEvent._
|
||||
def apply(
|
||||
playerCameraActor: ActorRef[PlayerCameraActor.Command]
|
||||
) =
|
||||
def apply(playerCameraActor: ActorRef[PlayerCameraActor.Command]) =
|
||||
Behaviors.logMessages(
|
||||
LogOptions()
|
||||
.withLevel(Level.TRACE)
|
||||
@ -63,7 +59,6 @@ object PlayerCameraEventListener {
|
||||
Behaviors.receiveMessagePartial {
|
||||
case CameraMovedUp =>
|
||||
playerCameraActor ! PlayerCameraActor.RotateUp
|
||||
|
||||
Behaviors.same
|
||||
case CameraMovedDown =>
|
||||
playerCameraActor ! PlayerCameraActor.RotateDown
|
||||
|
@ -18,6 +18,7 @@ import wow.doge.mygame.subsystems.events.PlayerCameraEvent
|
||||
import wow.doge.mygame.subsystems.events.PlayerEvent
|
||||
import wow.doge.mygame.subsystems.events.PlayerMovementEvent
|
||||
import wow.doge.mygame.utils.IOUtils._
|
||||
import monix.bio.UIO
|
||||
|
||||
object GameInputHandler {
|
||||
|
||||
@ -29,13 +30,13 @@ object GameInputHandler {
|
||||
) {
|
||||
def begin =
|
||||
for {
|
||||
_ <- Task(setupMovementKeys(inputManager))
|
||||
_ <- UIO(setupMovementKeys(inputManager))
|
||||
// _ <- UIO(setupAnalogMovementKeys)
|
||||
_ <- Task(setupCameraKeys())
|
||||
_ <- UIO(setupCameraKeys())
|
||||
_ <- toIO(
|
||||
me.Task.parSequence(
|
||||
Seq(
|
||||
generateMovementInputEvents(
|
||||
playerMovementInputEventsGenerator(
|
||||
inputManager,
|
||||
playerEventBus
|
||||
).completedL,
|
||||
@ -128,11 +129,14 @@ object GameInputHandler {
|
||||
|
||||
}
|
||||
|
||||
def generateMovementInputEvents(
|
||||
def methodName(implicit enclosing: sourcecode.Enclosing) =
|
||||
enclosing.value.split(" ")(0).split("""\.""").last
|
||||
|
||||
def playerMovementInputEventsGenerator(
|
||||
inputManager: InputManager,
|
||||
playerEventBus: GameEventBus[PlayerEvent]
|
||||
) = {
|
||||
val name = "playerMovementInputEventsGenerator"
|
||||
val name = methodName
|
||||
inputManager
|
||||
.enumObservableAction(PlayerMovementInput)
|
||||
// .dump("O")
|
||||
|
@ -3,33 +3,33 @@ import enumeratum.EnumEntry._
|
||||
import enumeratum._
|
||||
|
||||
sealed trait PlayerMovementInput extends EnumEntry with UpperSnakecase
|
||||
final object PlayerMovementInput extends Enum[PlayerMovementInput] {
|
||||
object PlayerMovementInput extends Enum[PlayerMovementInput] {
|
||||
val values = findValues
|
||||
final case object WalkForward extends PlayerMovementInput
|
||||
final case object WalkRight extends PlayerMovementInput
|
||||
final case object WalkLeft extends PlayerMovementInput
|
||||
final case object WalkBackward extends PlayerMovementInput
|
||||
final case object Jump extends PlayerMovementInput
|
||||
case object WalkForward extends PlayerMovementInput
|
||||
case object WalkRight extends PlayerMovementInput
|
||||
case object WalkLeft extends PlayerMovementInput
|
||||
case object WalkBackward extends PlayerMovementInput
|
||||
case object Jump extends PlayerMovementInput
|
||||
}
|
||||
|
||||
sealed trait PlayerAnalogMovementInput extends EnumEntry with UpperSnakecase
|
||||
final object PlayerAnalogMovementInput extends Enum[PlayerAnalogMovementInput] {
|
||||
object PlayerAnalogMovementInput extends Enum[PlayerAnalogMovementInput] {
|
||||
val values = findValues
|
||||
final case object TurnRight extends PlayerAnalogMovementInput
|
||||
final case object TurnLeft extends PlayerAnalogMovementInput
|
||||
case object TurnRight extends PlayerAnalogMovementInput
|
||||
case object TurnLeft extends PlayerAnalogMovementInput
|
||||
}
|
||||
|
||||
sealed trait PlayerCameraInput extends EnumEntry with UpperSnakecase
|
||||
final object PlayerCameraInput extends Enum[PlayerCameraInput] {
|
||||
object PlayerCameraInput extends Enum[PlayerCameraInput] {
|
||||
val values = findValues
|
||||
final case object CameraRotateLeft extends PlayerCameraInput
|
||||
final case object CameraRotateRight extends PlayerCameraInput
|
||||
final case object CameraRotateUp extends PlayerCameraInput
|
||||
final case object CameraRotateDown extends PlayerCameraInput
|
||||
case object CameraRotateLeft extends PlayerCameraInput
|
||||
case object CameraRotateRight extends PlayerCameraInput
|
||||
case object CameraRotateUp extends PlayerCameraInput
|
||||
case object CameraRotateDown extends PlayerCameraInput
|
||||
}
|
||||
|
||||
sealed trait MiscInput extends EnumEntry with UpperSnakecase
|
||||
final object MiscInput extends Enum[MiscInput] {
|
||||
object MiscInput extends Enum[MiscInput] {
|
||||
val values = findValues
|
||||
final case object ToggleCursor extends MiscInput
|
||||
case object ToggleCursor extends MiscInput
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import com.jme3.math.ColorRGBA
|
||||
import com.jme3.math.Vector3f
|
||||
import com.jme3.renderer.ViewPort
|
||||
import com.jme3.scene.Spatial
|
||||
import monix.bio.UIO
|
||||
object DefaultGameLevel {
|
||||
|
||||
def apply(
|
||||
@ -22,8 +23,7 @@ object DefaultGameLevel {
|
||||
throw new NotImplementedError("No fallback sceneshape")
|
||||
}
|
||||
)
|
||||
val landscape: RigidBodyControl =
|
||||
new RigidBodyControl(sceneShape, 0)
|
||||
val landscape: RigidBodyControl = new RigidBodyControl(sceneShape, 0)
|
||||
|
||||
viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f))
|
||||
sceneModel.setLocalScale(2f)
|
||||
@ -47,4 +47,58 @@ object DefaultGameLevel {
|
||||
directionalLight = dl
|
||||
)
|
||||
}
|
||||
|
||||
def apply(
|
||||
assetManager: wow.doge.mygame.utils.wrappers.jme.AssetManager,
|
||||
viewPort: ViewPort
|
||||
) =
|
||||
// for {
|
||||
// sceneModel <- assetManager.loadModelAs[Node](os.rel / "main.scene")
|
||||
// sceneShape <- UIO(CollisionShapeFactory.createMeshShape(sceneModel))
|
||||
// landscape <- UIO(new RigidBodyControl(sceneShape, 0))
|
||||
|
||||
// _ <- UIO {
|
||||
// viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f))
|
||||
// sceneModel.setLocalScale(2f)
|
||||
// sceneModel.addControl(landscape)
|
||||
// }
|
||||
|
||||
// al = {
|
||||
// val al = new AmbientLight()
|
||||
// al.setColor(ColorRGBA.White.mult(1.3f))
|
||||
// al
|
||||
// }
|
||||
|
||||
// dl = {
|
||||
// val dl = new DirectionalLight()
|
||||
// dl.setColor(ColorRGBA.White)
|
||||
// dl.setDirection(new Vector3f(2.8f, -2.8f, -2.8f).normalizeLocal())
|
||||
// dl
|
||||
// }
|
||||
|
||||
// } yield new GameLevel(
|
||||
// model = sceneModel,
|
||||
// physicsControl = landscape,
|
||||
// ambientLight = al,
|
||||
// directionalLight = dl
|
||||
// )
|
||||
GameLevel(
|
||||
os.rel / "main.scene", {
|
||||
val al = new AmbientLight()
|
||||
al.setColor(ColorRGBA.White.mult(1.3f))
|
||||
al
|
||||
}, {
|
||||
val dl = new DirectionalLight()
|
||||
dl.setColor(ColorRGBA.White)
|
||||
dl.setDirection(new Vector3f(2.8f, -2.8f, -2.8f).normalizeLocal())
|
||||
dl
|
||||
}
|
||||
)(assetManager).flatMap(gameLevel =>
|
||||
UIO {
|
||||
viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f))
|
||||
gameLevel.model.setLocalScale(2f)
|
||||
gameLevel.model.addControl(gameLevel.physicsControl)
|
||||
gameLevel
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -1,21 +1,26 @@
|
||||
package wow.doge.mygame.game.subsystems.level
|
||||
|
||||
import cats.effect.Resource
|
||||
import com.jme3.bullet.control.RigidBodyControl
|
||||
import com.jme3.light.AmbientLight
|
||||
import com.jme3.light.DirectionalLight
|
||||
import com.jme3.scene.Node
|
||||
import com.jme3.scene.Spatial
|
||||
import com.softwaremill.tagging._
|
||||
import monix.bio.IO
|
||||
import monix.bio.Task
|
||||
import monix.bio.UIO
|
||||
import wow.doge.mygame.game.GameAppTags
|
||||
// import wow.doge.mygame.implicits._
|
||||
import wow.doge.mygame.utils.wrappers.jme.AppNode
|
||||
import wow.doge.mygame.utils.wrappers.jme.AssetManager
|
||||
import wow.doge.mygame.utils.wrappers.jme.CollisionShapeFactory
|
||||
import wow.doge.mygame.utils.wrappers.jme.PhysicsSpace
|
||||
|
||||
class GameLevel(
|
||||
model: Spatial,
|
||||
physicsControl: RigidBodyControl,
|
||||
ambientLight: AmbientLight,
|
||||
directionalLight: DirectionalLight
|
||||
val model: Spatial,
|
||||
val physicsControl: RigidBodyControl,
|
||||
val ambientLight: AmbientLight,
|
||||
val directionalLight: DirectionalLight
|
||||
) {
|
||||
def addToGame(
|
||||
rootNode: AppNode[Task] @@ GameAppTags.RootNode,
|
||||
@ -29,4 +34,56 @@ class GameLevel(
|
||||
_ <- physicsSpace += physicsControl
|
||||
} yield ()
|
||||
}
|
||||
def removeFromGame(
|
||||
rootNode: AppNode[Task] @@ GameAppTags.RootNode,
|
||||
physicsSpace: PhysicsSpace[Task]
|
||||
) = {
|
||||
for {
|
||||
_ <- rootNode -= model
|
||||
_ <- rootNode -= ambientLight
|
||||
_ <- rootNode -= directionalLight
|
||||
_ <- physicsSpace -= model
|
||||
_ <- physicsSpace -= physicsControl
|
||||
} yield ()
|
||||
}
|
||||
|
||||
def resource(
|
||||
rootNode: AppNode[Task] @@ GameAppTags.RootNode,
|
||||
physicsSpace: PhysicsSpace[Task]
|
||||
) =
|
||||
Resource.make(this.addToGame(rootNode, physicsSpace))(_ =>
|
||||
this.removeFromGame(rootNode, physicsSpace)
|
||||
)
|
||||
}
|
||||
|
||||
object GameLevel {
|
||||
sealed trait Error
|
||||
case class AssetLoadError(err: AssetManager.Error) extends Error
|
||||
case class CollisionShapeCreationFailed(err: CollisionShapeFactory.Error)
|
||||
extends Error
|
||||
|
||||
def apply(
|
||||
modelPath: os.RelPath,
|
||||
al: AmbientLight,
|
||||
dl: DirectionalLight
|
||||
)(implicit
|
||||
assetManager: wow.doge.mygame.utils.wrappers.jme.AssetManager
|
||||
): IO[Error, GameLevel] =
|
||||
for {
|
||||
sceneModel <-
|
||||
assetManager
|
||||
.loadModelAs[Node](modelPath)
|
||||
.mapError(AssetLoadError)
|
||||
sceneShape <-
|
||||
CollisionShapeFactory
|
||||
.createMeshShape(sceneModel)
|
||||
.mapError(CollisionShapeCreationFailed)
|
||||
landscape <- UIO(new RigidBodyControl(sceneShape, 0))
|
||||
|
||||
} yield new GameLevel(
|
||||
model = sceneModel,
|
||||
physicsControl = landscape,
|
||||
ambientLight = al,
|
||||
directionalLight = dl
|
||||
)
|
||||
}
|
||||
|
@ -31,16 +31,17 @@ object ImMovementActor {
|
||||
// final case object RotateRight extends Movement
|
||||
// final case object RotateLeft extends Movement
|
||||
|
||||
final class Props[T: CanMove](
|
||||
final class Props(
|
||||
val enqueueR: Function1[() => Unit, Unit],
|
||||
val movable: T,
|
||||
// playerMovementEventBus: ActorRef[
|
||||
// EventBus.Command[PlayerMovementEvent]
|
||||
// ]
|
||||
val camera: Camera
|
||||
) {
|
||||
def behavior: Behavior[Command] =
|
||||
Behaviors.setup(ctx => new ImMovementActor(ctx, this).receive(State()))
|
||||
def behavior[T: CanMove](movable: T): Behavior[Command] =
|
||||
Behaviors.setup(ctx =>
|
||||
new ImMovementActor(ctx, this, movable).receive(State())
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -54,7 +55,8 @@ object ImMovementActor {
|
||||
|
||||
class ImMovementActor[T](
|
||||
ctx: ActorContext[ImMovementActor.Command],
|
||||
props: ImMovementActor.Props[T]
|
||||
props: ImMovementActor.Props,
|
||||
val movable: T
|
||||
) {
|
||||
import ImMovementActor._
|
||||
import Methods._
|
||||
@ -66,19 +68,19 @@ class ImMovementActor[T](
|
||||
case m: Movement =>
|
||||
m match {
|
||||
case MovedLeft(pressed) =>
|
||||
props.enqueueR(() => stopIfNotPressed(pressed, props.movable))
|
||||
props.enqueueR(() => stopIfNotPressed(pressed, movable))
|
||||
receive(state = state.modify(_.cardinalDir.left).setTo(pressed))
|
||||
case MovedUp(pressed) =>
|
||||
props.enqueueR(() => stopIfNotPressed(pressed, props.movable))
|
||||
props.enqueueR(() => stopIfNotPressed(pressed, movable))
|
||||
receive(state = state.modify(_.cardinalDir.up).setTo(pressed))
|
||||
case MovedRight(pressed) =>
|
||||
props.enqueueR(() => stopIfNotPressed(pressed, props.movable))
|
||||
props.enqueueR(() => stopIfNotPressed(pressed, movable))
|
||||
receive(state = state.modify(_.cardinalDir.right).setTo(pressed))
|
||||
case MovedDown(pressed) =>
|
||||
props.enqueueR(() => stopIfNotPressed(pressed, props.movable))
|
||||
props.enqueueR(() => stopIfNotPressed(pressed, movable))
|
||||
receive(state = state.modify(_.cardinalDir.down).setTo(pressed))
|
||||
case Jump =>
|
||||
props.enqueueR(() => cm.jump(props.movable))
|
||||
props.enqueueR(() => cm.jump(movable))
|
||||
Behaviors.same
|
||||
}
|
||||
|
||||
@ -88,7 +90,7 @@ class ImMovementActor[T](
|
||||
// if (walkDir != ImVector3f.ZERO) {
|
||||
val tmp = walkDir * 25f * (1f / 144)
|
||||
props.enqueueR { () =>
|
||||
cm.move(props.movable, tmp)
|
||||
cm.move(movable, tmp)
|
||||
}
|
||||
// }
|
||||
Behaviors.same
|
||||
|
62
src/main/scala/wow/doge/mygame/implicits/CatsImplicits.scala
Normal file
62
src/main/scala/wow/doge/mygame/implicits/CatsImplicits.scala
Normal file
@ -0,0 +1,62 @@
|
||||
package wow.doge.mygame.implicits
|
||||
|
||||
import cats.data.Kleisli
|
||||
import monix.bio.IO
|
||||
import monix.bio.UIO
|
||||
import cats.effect.Resource
|
||||
import cats.Functor
|
||||
import cats.effect.Bracket
|
||||
import monix.bio.Task
|
||||
import cats.Show
|
||||
import cats.syntax.show._
|
||||
|
||||
trait CatsExtensions {
|
||||
implicit class KleisliCompanionExt(k: Kleisli.type) {
|
||||
def io[S, E, A](f: S => IO[E, A]): Kleisli[UIO, S, Either[E, A]] =
|
||||
k.apply(s => f(s).attempt)
|
||||
}
|
||||
implicit class KleisliExt[S, E, A](k: Kleisli[UIO, S, Either[E, A]]) {
|
||||
def runIO(s: S) = k.run(s).rethrow
|
||||
}
|
||||
implicit class ResourceCompanionExt(r: Resource.type) {
|
||||
// def ioMake[UIO, R](acquire: )(release: ) = r.make()()
|
||||
def ioMake[E: Show, A](
|
||||
acquire: IO[E, A]
|
||||
)(
|
||||
release: A => UIO[Unit]
|
||||
)(implicit F: Functor[UIO]): Resource[UIO, Either[E, A]] =
|
||||
r.make(acquire.attempt)(a =>
|
||||
IO.fromEither(a)
|
||||
.onErrorHandleWith(err => IO.terminate(new Exception(err.show)))
|
||||
.flatMap(release)
|
||||
)
|
||||
|
||||
val acq = IO(1).onErrorHandleWith(_ => IO.raiseError(""))
|
||||
|
||||
val res =
|
||||
ioMake(acq)(_ => IO.unit)
|
||||
val result = res
|
||||
.use {
|
||||
case Left(value) => Task(Left(value))
|
||||
case Right(value) => Task(Right(value))
|
||||
}
|
||||
.hideErrors
|
||||
.rethrow
|
||||
|
||||
// IO.unit.bracket()
|
||||
}
|
||||
implicit class ResourceExt[E, A](k: Resource[UIO, Either[E, A]]) {
|
||||
// def runIO(s: S) = k.run(s).rethrow
|
||||
// k.use
|
||||
// : IO[E, B]
|
||||
// def useIO[B](f: Either[E, A] => IO[E, B]) =
|
||||
// k.use(f).rethrow
|
||||
// type test[A] = Tuple2[*, Double]
|
||||
type IoResource[X, D] = Resource[IO[X, *], D]
|
||||
val x: Resource[IO[String, *], String] =
|
||||
Resource.make(IO.raiseError(""))(_ => IO.unit)
|
||||
// x.use(s => Task.unit)
|
||||
val x2: IoResource[String, String] =
|
||||
Resource.make(UIO(""))(_ => IO.unit)
|
||||
}
|
||||
}
|
@ -39,6 +39,8 @@ 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.Task
|
||||
import monix.bio.UIO
|
||||
import monix.execution.Ack
|
||||
@ -797,7 +799,7 @@ package object implicits {
|
||||
}
|
||||
|
||||
implicit class AkkaLoggerExt(private val logger: Logger) extends AnyVal {
|
||||
def logP[T](
|
||||
private def logP[T](
|
||||
x: sourcecode.Text[T],
|
||||
tag: String = "",
|
||||
width: Int = 100,
|
||||
@ -806,12 +808,10 @@ package object implicits {
|
||||
initialOffset: Int = 0
|
||||
)(implicit line: sourcecode.Line, fileName: sourcecode.FileName) = {
|
||||
|
||||
// def joinSeq[T](seq: Seq[T], sep: T): Seq[T] = {
|
||||
// seq.flatMap(x => Seq(x, sep)).dropRight(1)
|
||||
// }
|
||||
val tagStrs =
|
||||
if (tag.isEmpty) Seq.empty
|
||||
else Seq(fansi.Color.Cyan(tag), fansi.Str(" "))
|
||||
// "".slice(1, -1)
|
||||
val prefix = Seq(
|
||||
fansi.Color.Magenta(fileName.value),
|
||||
fansi.Str(":"),
|
||||
@ -823,7 +823,6 @@ package object implicits {
|
||||
fansi.Str.join(
|
||||
prefix ++ pprint.tokenize(x.value, width, height, indent).toSeq: _*
|
||||
)
|
||||
// x.value
|
||||
}
|
||||
|
||||
def warnP[T](
|
||||
@ -858,4 +857,18 @@ package object implicits {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
def infoU[M](msg: => M)(implicit render: Render[M], position: Position) =
|
||||
logger.info(msg).hideErrors
|
||||
def traceU[M](msg: => M)(implicit render: Render[M], position: Position) =
|
||||
logger.trace(msg).hideErrors
|
||||
def warnU[M](msg: => M)(implicit render: Render[M], position: Position) =
|
||||
logger.warn(msg).hideErrors
|
||||
def errorU[M](msg: => M)(implicit render: Render[M], position: Position) =
|
||||
logger.error(msg).hideErrors
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import cats.effect.Resource
|
||||
import cats.effect.concurrent.Deferred
|
||||
import cats.kernel.Eq
|
||||
import javafx.application.Platform
|
||||
import javafx.beans.value.ObservableValue
|
||||
import monix.bio.Task
|
||||
import monix.catnap.CancelableF
|
||||
import monix.execution.CancelablePromise
|
||||
@ -13,7 +12,6 @@ import monix.{eval => me}
|
||||
import scalafx.Includes._
|
||||
import scalafx.application.JFXApp
|
||||
import scalafx.application.JFXApp.PrimaryStage
|
||||
import scalafx.beans.property.StringProperty
|
||||
import scalafx.scene.control.Button
|
||||
import scalafx.stage.StageStyle
|
||||
import wow.doge.mygame.executors.Schedulers
|
||||
@ -47,23 +45,10 @@ class Launcher private (props: Launcher.Props) {
|
||||
.observableAction()
|
||||
.doOnNext(_ => toTask(props.signal.complete(LauncherResult.LaunchGame)))
|
||||
|
||||
def testChangeObs(
|
||||
obs: Observable[(ObservableValue[_ <: String], String, String)]
|
||||
) =
|
||||
obs
|
||||
.doOnNext {
|
||||
case (x, y, z) => monix.eval.Task.unit
|
||||
}
|
||||
// .subscribe()
|
||||
|
||||
private lazy val exitButton = new Button {
|
||||
text = "Exit"
|
||||
// text <-- testChangeObs
|
||||
}
|
||||
|
||||
// exitButton.text.bind
|
||||
StringProperty("") addListener ((_, _, _) => ())
|
||||
|
||||
private lazy val exitAction =
|
||||
exitButton
|
||||
.observableAction()
|
||||
@ -103,27 +88,6 @@ class Launcher private (props: Launcher.Props) {
|
||||
)
|
||||
}
|
||||
|
||||
// import cats.syntax.all._
|
||||
|
||||
// def init(delay: FiniteDuration = 2000.millis) =
|
||||
// for {
|
||||
// _ <- Task(Platform.setImplicitExit(false))
|
||||
// x <- (Task.unit.start, Task.unit.start).parTupled
|
||||
// fxAppStartFib <- Task(internal.main(Array.empty)).start
|
||||
// _ <- Task.sleep(500.millis)
|
||||
// sceneDragFib <- toIO(sceneDragObservable.completedL).start
|
||||
// buttonActionsComposedFib <- toIO(
|
||||
// Observable(launchAction, exitAction).merge
|
||||
// .doOnNext(_ =>
|
||||
// me.Task(internal.stage.close()).executeOn(props.schedulers.fx)
|
||||
// )
|
||||
// .completedL
|
||||
// ).start
|
||||
// c <- CancelableF[Task](
|
||||
// fxAppStartFib.cancel >> buttonActionsComposedFib.cancel >> sceneDragFib.cancel
|
||||
// )
|
||||
// } yield (c)
|
||||
|
||||
def init =
|
||||
Resource.make(for {
|
||||
_ <- Task(Platform.setImplicitExit(false))
|
||||
@ -148,13 +112,7 @@ class Launcher private (props: Launcher.Props) {
|
||||
)
|
||||
)
|
||||
.start
|
||||
c <- CancelableF[Task](
|
||||
// Task(println("Cancelling")) >>
|
||||
// combinedFib.cancel >>
|
||||
// fxAppStartFib.cancel
|
||||
// Task.unit
|
||||
combinedFib.cancel
|
||||
)
|
||||
c <- CancelableF[Task](combinedFib.cancel)
|
||||
} yield c)(_.cancel)
|
||||
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
package wow.doge.mygame.math;
|
||||
|
||||
import Math.{sqrt, pow}
|
||||
import cats.Show
|
||||
|
||||
case class ImVector3f(x: Float = 0f, y: Float = 0f, z: Float = 0f)
|
||||
import math.{abs, pow, sqrt}
|
||||
|
||||
case class ImVector3f(x: Float, y: Float, z: Float)
|
||||
object ImVector3f {
|
||||
val ZERO = ImVector3f(0, 0, 0)
|
||||
val UNIT_X = ImVector3f(1, 0, 0)
|
||||
@ -11,5 +12,18 @@ object ImVector3f {
|
||||
val UNIT_Z = ImVector3f(0, 0, 1)
|
||||
|
||||
def dst(v1: ImVector3f, v2: ImVector3f) =
|
||||
sqrt(pow(v1.x - v2.x, 2) + pow(v1.y - v2.y, 2) + pow(v1.z - v2.z, 2))
|
||||
sqrt(
|
||||
pow((v1.x - v2.x).toDouble, 2) + pow((v1.y - v2.y).toDouble, 2) + pow(
|
||||
(v1.z - v2.z).toDouble,
|
||||
2
|
||||
)
|
||||
)
|
||||
def manhattanDst(v1: ImVector3f, v2: ImVector3f) =
|
||||
abs(v1.x - v2.x) + abs(v1.y - v2.y) + abs(v1.z - v2.z)
|
||||
|
||||
implicit val showForImVector3f = new Show[ImVector3f] {
|
||||
def format(f: Float) = f.formatted("%.2f")
|
||||
override def show(t: ImVector3f): String =
|
||||
s"ImVector3f(${format(t.x)},${format(t.y)},${format(t.z)})"
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,20 @@ import akka.actor.typed.ActorRef
|
||||
import akka.actor.typed.Behavior
|
||||
import akka.actor.typed.scaladsl.Behaviors
|
||||
import akka.event.EventStream
|
||||
import monix.reactive.Observable
|
||||
import monix.reactive.OverflowStrategy
|
||||
import monix.execution.cancelables.SingleAssignCancelable
|
||||
import monix.execution.Ack
|
||||
import akka.util.Timeout
|
||||
import akka.actor.typed.Scheduler
|
||||
import akka.actor.typed.SpawnProtocol
|
||||
import scala.util.Random
|
||||
import akka.actor.typed.scaladsl.AskPattern._
|
||||
import monix.execution.Cancelable
|
||||
import wow.doge.mygame.utils.AkkaUtils
|
||||
import wow.doge.mygame.implicits._
|
||||
import monix.bio.UIO
|
||||
import wow.doge.mygame.subsystems.events.EventBus.ObservableSubscription
|
||||
|
||||
/**
|
||||
* A (typed) event bus
|
||||
@ -21,27 +35,113 @@ object EventBus {
|
||||
final case class Subscribe[A, E <: A](subscriber: ActorRef[E])(implicit
|
||||
classTag: ClassTag[E]
|
||||
) extends Command[A] {
|
||||
|
||||
def topic: Class[_] = classTag.runtimeClass
|
||||
}
|
||||
|
||||
final case class Unsubscribe[A, E <: A](subscriber: ActorRef[E])
|
||||
extends Command[A]
|
||||
|
||||
def apply[A](): Behavior[EventBus.Command[A]] =
|
||||
final case class ObservableSubscription[A, E <: A](
|
||||
replyTo: ActorRef[Observable[E]]
|
||||
)(implicit
|
||||
classTag: ClassTag[E]
|
||||
) extends Command[A] {
|
||||
def ct = classTag
|
||||
}
|
||||
|
||||
def apply[A: ClassTag]()(implicit
|
||||
timeout: Timeout,
|
||||
spawnProtocol: ActorRef[SpawnProtocol.Command]
|
||||
): Behavior[EventBus.Command[A]] =
|
||||
Behaviors.setup { ctx =>
|
||||
val eventStream = new EventStream(ctx.system.classicSystem)
|
||||
implicit val scheduler = ctx.system.scheduler
|
||||
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()}"
|
||||
)
|
||||
.tapError {
|
||||
case ex => UIO(sub.onError(ex))
|
||||
}
|
||||
|
||||
(for {
|
||||
a <- actor
|
||||
_ <- eventBus !! Subscribe(a)
|
||||
_ <- UIO(c := Cancelable(() => eventBus ! Unsubscribe(a)))
|
||||
} yield ()).runToFuture
|
||||
c
|
||||
}
|
||||
|
||||
def observable2[A, B <: A](eventBus: ActorRef[EventBus.Command[A]])(implicit
|
||||
timeout: Timeout,
|
||||
scheduler: Scheduler,
|
||||
spawnProtocol: ActorRef[SpawnProtocol.Command],
|
||||
ct: ClassTag[A],
|
||||
ct2: ClassTag[B]
|
||||
) =
|
||||
Observable.create[B](OverflowStrategy.DropOld(50)) { sub =>
|
||||
implicit val s = sub.scheduler
|
||||
val c = SingleAssignCancelable()
|
||||
val behavior = Behaviors.receive[B] { (ctx, msg) =>
|
||||
if (sub.onNext(msg) == Ack.Stop) {
|
||||
c.cancel()
|
||||
Behaviors.stopped
|
||||
} else Behaviors.same
|
||||
|
||||
}
|
||||
val actor =
|
||||
AkkaUtils
|
||||
.spawnActorL(
|
||||
behavior,
|
||||
s"eventBusObservable-${ct.toString}-${math.abs(Random.nextLong())}"
|
||||
)
|
||||
.tapError {
|
||||
case ex => UIO(sub.onError(ex))
|
||||
}
|
||||
|
||||
(for {
|
||||
a <- actor
|
||||
_ <- eventBus !! Subscribe(a)
|
||||
_ <- UIO(c := Cancelable(() => eventBus ! Unsubscribe(a)))
|
||||
} yield ()).runToFuture
|
||||
c
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class EventBus[B] {
|
||||
class EventBus[A] {
|
||||
import akka.actor.typed.scaladsl.adapter._
|
||||
|
||||
private def eventStreamBehavior(
|
||||
eventStream: akka.event.EventStream
|
||||
): Behavior[EventBus.Command[B]] =
|
||||
eventStream: EventStream
|
||||
)(implicit
|
||||
timeout: Timeout,
|
||||
scheduler: Scheduler,
|
||||
spawnProtocol: ActorRef[SpawnProtocol.Command],
|
||||
ct: ClassTag[A]
|
||||
): Behavior[EventBus.Command[A]] =
|
||||
Behaviors.setup { ctx =>
|
||||
Behaviors.receiveMessage {
|
||||
case EventBus.Publish(event, name) =>
|
||||
eventStream.publish(event)
|
||||
@ -52,5 +152,12 @@ class EventBus[B] {
|
||||
case EventBus.Unsubscribe(subscriber) =>
|
||||
eventStream.unsubscribe(subscriber.toClassic)
|
||||
Behaviors.same
|
||||
case s @ ObservableSubscription(replyTo) =>
|
||||
val obs = EventBus.observable2(
|
||||
ctx.self
|
||||
)(timeout, scheduler, spawnProtocol, ct, s.ct)
|
||||
replyTo ! obs
|
||||
Behaviors.same
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package wow.doge.mygame.subsystems.events
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import akka.actor.typed.ActorRef
|
||||
import akka.actor.typed.ActorSystem
|
||||
import akka.actor.typed.LogOptions
|
||||
import akka.actor.typed.Props
|
||||
import akka.actor.typed.SpawnProtocol
|
||||
@ -16,10 +15,15 @@ import wow.doge.mygame.implicits._
|
||||
import wow.doge.mygame.subsystems.events.Event
|
||||
import wow.doge.mygame.subsystems.events.EventBus
|
||||
import wow.doge.mygame.subsystems.events.TickEvent
|
||||
import scala.reflect.ClassTag
|
||||
import akka.actor.typed.Scheduler
|
||||
|
||||
class EventsModule(spawnProtocol: ActorSystem[SpawnProtocol.Command]) {
|
||||
implicit val s = spawnProtocol.scheduler
|
||||
|
||||
class EventsModule(
|
||||
scheduler: Scheduler,
|
||||
spawnProtocol: ActorRef[SpawnProtocol.Command]
|
||||
) {
|
||||
implicit val s = scheduler
|
||||
implicit val sp = spawnProtocol
|
||||
implicit val timeout = Timeout(1.second)
|
||||
|
||||
val eventBusLogger = SLogger[EventBus[_]]
|
||||
@ -35,7 +39,10 @@ class EventsModule(spawnProtocol: ActorSystem[SpawnProtocol.Command]) {
|
||||
|
||||
val mainEventBusTask = createEventBus[Event]("mainEventBus")
|
||||
|
||||
def createEventBus[T](busName: String, logLevel: Level = Level.DEBUG) =
|
||||
def createEventBus[T: ClassTag](
|
||||
busName: String,
|
||||
logLevel: Level = Level.DEBUG
|
||||
) =
|
||||
spawnProtocol.askL(
|
||||
SpawnProtocol.Spawn[EventBus.Command[T]](
|
||||
Behaviors.logMessages(
|
||||
|
@ -27,7 +27,7 @@ object Plugin {
|
||||
}
|
||||
|
||||
object ModdingSystem {
|
||||
sealed trait Error extends Serializable with Product
|
||||
sealed trait Error
|
||||
final case class CouldNotDecode(cause: String) extends Error
|
||||
final case class ParseFailure(cause: String) extends Error
|
||||
final case class FileNotFound(fileName: String) extends Error
|
||||
@ -53,7 +53,7 @@ object ModdingSystem {
|
||||
def findAndReadPluginFiles(
|
||||
dir: os.Path,
|
||||
plugins: ArraySeq[Plugin]
|
||||
) =
|
||||
): (View[(Plugin, Error)], View[(Plugin, String)]) =
|
||||
plugins
|
||||
.sortBy(_.priority)
|
||||
.view
|
||||
@ -79,9 +79,8 @@ object ModdingSystem {
|
||||
}
|
||||
}
|
||||
|
||||
def readPluginFiles(filePaths: View[os.Path]) = {
|
||||
def readPluginFiles(filePaths: View[os.Path]) =
|
||||
filePaths.map(path => os.read(path))
|
||||
}
|
||||
|
||||
def parsePluginFiles(files: View[(Plugin, String)]) =
|
||||
files
|
||||
@ -99,11 +98,10 @@ object ModdingSystem {
|
||||
case (json, value) => json.deepMerge(value)
|
||||
}
|
||||
|
||||
def mergePluginData(plugins: View[(Plugin, Json)]) = {
|
||||
def mergePluginData(plugins: View[(Plugin, Json)]) =
|
||||
foldMerge(plugins.map {
|
||||
case (p, json) => json
|
||||
})
|
||||
}
|
||||
|
||||
def mergePluginDataConsumer =
|
||||
Consumer.foldLeft[Json, Json](Json.fromString("empty")) {
|
||||
@ -143,13 +141,13 @@ object ModdingSystem {
|
||||
.map { case (p, json) => json }
|
||||
.consumeWith(loadBalancedPluginDataMerger)
|
||||
)
|
||||
.onErrorHandle(e => GenericError)
|
||||
.hideErrors
|
||||
_ <- UIO {
|
||||
println(s"Read Successes = ${readSuccesses.to(Seq)}")
|
||||
println(s"Read Failures = ${readFailures.to(Seq)}")
|
||||
println(s"Parse Successes = ${parseSuccesses.to(Seq)}")
|
||||
println(s"Parse Failures = ${parseFailures.to(Seq)}")
|
||||
println(s"Merged = $res")
|
||||
println(show"Merged = $res")
|
||||
}
|
||||
} yield ()
|
||||
|
||||
|
@ -181,12 +181,11 @@ class ScriptActor(
|
||||
os.Path,
|
||||
Either[wow.doge.mygame.state.ScriptActor.Error, Any]
|
||||
]
|
||||
): LOL = {
|
||||
): LOL =
|
||||
paths match {
|
||||
case head :: next => loop(next, scriptsMap + (head -> getScript(head)))
|
||||
case Nil => scriptsMap
|
||||
}
|
||||
}
|
||||
loop(paths, Map.empty)
|
||||
}
|
||||
|
||||
|
@ -99,7 +99,6 @@ class ScriptCachingActor(
|
||||
) {
|
||||
import com.softwaremill.quicklens._
|
||||
import ScriptCachingActor._
|
||||
import Methods._
|
||||
def receiveMessage(state: State): Behavior[Command] =
|
||||
Behaviors.receiveMessage { msg =>
|
||||
msg match {
|
||||
@ -108,7 +107,6 @@ class ScriptCachingActor(
|
||||
ctx.self ! DelegateToChild(scriptActor, scriptPath, requester)
|
||||
else
|
||||
getOrCompileScript(
|
||||
ctx,
|
||||
scriptPath,
|
||||
state.scriptsMap,
|
||||
scriptActor,
|
||||
@ -165,7 +163,6 @@ class ScriptCachingActor(
|
||||
implicit val timeout = Timeout(15.seconds)
|
||||
// child ! ScriptActor.CompileAny(scriptPath, requester)
|
||||
askChildForScriptCompilation(
|
||||
ctx,
|
||||
scriptActor,
|
||||
scriptPath,
|
||||
requester
|
||||
@ -200,29 +197,12 @@ class ScriptCachingActor(
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object ScriptActorPool {
|
||||
def apply(
|
||||
poolSize: Int
|
||||
): PoolRouter[ScriptActor.Command] =
|
||||
Routers.pool(poolSize = poolSize)(
|
||||
// make sure the workers are restarted if they fail
|
||||
Behaviors
|
||||
.supervise(ScriptActor())
|
||||
.onFailure[Exception](SupervisorStrategy.restart)
|
||||
)
|
||||
}
|
||||
|
||||
private[scriptsystem] object Methods {
|
||||
import ScriptCachingActor._
|
||||
def getOrCompileScript(
|
||||
ctx: ActorContext[Command],
|
||||
scriptPath: os.Path,
|
||||
scriptsMap: ScriptsMap,
|
||||
scriptActor: ActorRef[ScriptActor.Command],
|
||||
requester: ActorRef[ScriptResult]
|
||||
) = {
|
||||
) =
|
||||
scriptsMap
|
||||
.get(scriptPath)
|
||||
.fold {
|
||||
@ -236,10 +216,8 @@ private[scriptsystem] object Methods {
|
||||
ctx.log.debugP("Getting script from cache")
|
||||
requester ! Right(s)
|
||||
}
|
||||
}
|
||||
|
||||
def askChildForScriptCompilation(
|
||||
ctx: ActorContext[Command],
|
||||
scriptActor: ActorRef[ScriptActor.Command],
|
||||
scriptPath: os.Path,
|
||||
requester: ActorRef[ScriptResult]
|
||||
@ -249,7 +227,7 @@ private[scriptsystem] object Methods {
|
||||
requester ! value
|
||||
value.fold(
|
||||
err => {
|
||||
ctx.log.error(err.reason)
|
||||
ctx.log.errorP(err.reason)
|
||||
NoOp
|
||||
},
|
||||
res => {
|
||||
@ -257,9 +235,22 @@ private[scriptsystem] object Methods {
|
||||
}
|
||||
)
|
||||
case Failure(exception) => {
|
||||
ctx.log.error(exception.getMessage())
|
||||
ctx.log.errorP(exception.getMessage)
|
||||
NoOp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object ScriptActorPool {
|
||||
def apply(
|
||||
poolSize: Int
|
||||
): PoolRouter[ScriptActor.Command] =
|
||||
Routers.pool(poolSize = poolSize)(
|
||||
// make sure the workers are restarted if they fail
|
||||
Behaviors
|
||||
.supervise(ScriptActor())
|
||||
.onFailure[Exception](SupervisorStrategy.restart)
|
||||
)
|
||||
}
|
||||
|
@ -7,8 +7,12 @@ import akka.actor.typed.Scheduler
|
||||
import akka.actor.typed.SpawnProtocol
|
||||
import akka.util.Timeout
|
||||
import wow.doge.mygame.implicits._
|
||||
import java.util.concurrent.TimeoutException
|
||||
import monix.bio.IO
|
||||
import wow.doge.mygame.AppError.TimeoutError
|
||||
|
||||
object AkkaUtils {
|
||||
|
||||
def spawnActorOldL[T](
|
||||
spawnProtocol: ActorRef[SpawnProtocol.Command],
|
||||
actorName: String,
|
||||
@ -24,18 +28,23 @@ object AkkaUtils {
|
||||
)
|
||||
def spawnActorL[T](
|
||||
behavior: Behavior[T],
|
||||
actorName: String
|
||||
actorName: String,
|
||||
props: Props = Props.empty
|
||||
)(implicit
|
||||
timeout: Timeout,
|
||||
scheduler: Scheduler,
|
||||
spawnProtocol: ActorRef[SpawnProtocol.Command]
|
||||
) =
|
||||
spawnProtocol.askL[ActorRef[T]](
|
||||
spawnProtocol
|
||||
.askL[ActorRef[T]](
|
||||
SpawnProtocol.Spawn(
|
||||
behavior,
|
||||
actorName,
|
||||
Props.empty,
|
||||
props,
|
||||
_
|
||||
)
|
||||
)
|
||||
// .onErrorHandleWith {
|
||||
// case ex: TimeoutException => IO.raiseError(TimeoutError(ex.getMessage))
|
||||
// }
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ class GenericConsoleStream[T](
|
||||
outputStream: OutputStream,
|
||||
val config: GenericConsoleStream.Config =
|
||||
GenericConsoleStream.Config.default,
|
||||
// TODO make this atomic
|
||||
// TODO make this atomic ?
|
||||
private var _streamable: Option[T] = None
|
||||
)(implicit
|
||||
cs: ConsoleStreamable[T]
|
||||
@ -27,27 +27,26 @@ class GenericConsoleStream[T](
|
||||
stble.foreach(s => cs.println(s, text))
|
||||
|
||||
override def println(text: String): Unit =
|
||||
if (config.exclusive) {
|
||||
if (config.exclusive)
|
||||
printToStreamable(_streamable, text)
|
||||
} else {
|
||||
else {
|
||||
defaultOut.println(text)
|
||||
printToStreamable(_streamable, text)
|
||||
}
|
||||
|
||||
override def print(text: String): Unit =
|
||||
if (config.exclusive) {
|
||||
if (config.exclusive)
|
||||
printToStreamable(_streamable, text)
|
||||
} else {
|
||||
else {
|
||||
defaultOut.println(text)
|
||||
printToStreamable(_streamable, text)
|
||||
}
|
||||
|
||||
def :=(s: T) = {
|
||||
def :=(s: T) =
|
||||
_streamable match {
|
||||
case Some(value) =>
|
||||
case None => _streamable = Some(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object GenericConsoleStream {
|
||||
|
66
src/main/scala/wow/doge/mygame/utils/GenericTimerActor.scala
Normal file
66
src/main/scala/wow/doge/mygame/utils/GenericTimerActor.scala
Normal file
@ -0,0 +1,66 @@
|
||||
package wow.doge.mygame.utils
|
||||
|
||||
import akka.actor.typed.scaladsl.Behaviors
|
||||
import scala.concurrent.duration.FiniteDuration
|
||||
import akka.actor.typed.ActorRef
|
||||
import akka.actor.typed.Behavior
|
||||
import akka.actor.typed.scaladsl.TimerScheduler
|
||||
import scala.util.Random
|
||||
import akka.actor.typed.scaladsl.ActorContext
|
||||
import wow.doge.mygame.implicits._
|
||||
|
||||
object GenericTimerActor {
|
||||
sealed trait Command
|
||||
final case object Start extends Command
|
||||
final case object Stop extends Command
|
||||
private case object Tick extends Command
|
||||
case class TimerKey(seed: Long)
|
||||
|
||||
case class Props[T](
|
||||
target: ActorRef[T],
|
||||
messageToSend: T,
|
||||
timeInterval: FiniteDuration
|
||||
) {
|
||||
def behavior =
|
||||
Behaviors.withTimers[Command] { timers =>
|
||||
Behaviors.setup { ctx =>
|
||||
new GenericTimerActor(
|
||||
ctx,
|
||||
timers,
|
||||
TimerKey(Random.nextLong()),
|
||||
this
|
||||
).idle
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
class GenericTimerActor[T](
|
||||
ctx: ActorContext[GenericTimerActor.Command],
|
||||
timers: TimerScheduler[GenericTimerActor.Command],
|
||||
timerKey: GenericTimerActor.TimerKey,
|
||||
props: GenericTimerActor.Props[T]
|
||||
) {
|
||||
import GenericTimerActor._
|
||||
|
||||
val idle: Behavior[Command] =
|
||||
Behaviors.receiveMessage {
|
||||
case Start =>
|
||||
timers.startTimerWithFixedDelay(timerKey, Tick, props.timeInterval)
|
||||
active
|
||||
case _ => Behaviors.unhandled
|
||||
|
||||
}
|
||||
|
||||
val active: Behavior[Command] =
|
||||
Behaviors.receiveMessage {
|
||||
case Start =>
|
||||
ctx.log.warnP(s"Timer-${timerKey.seed}: Timer already started")
|
||||
Behaviors.same
|
||||
case Tick =>
|
||||
props.target ! props.messageToSend
|
||||
Behaviors.same
|
||||
case Stop =>
|
||||
timers.cancel(timerKey)
|
||||
idle
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
package wow.doge.mygame.utils.wrappers.jme
|
||||
import scala.reflect.ClassTag
|
||||
|
||||
import com.jme3.asset.AssetLoadException
|
||||
import com.jme3.asset.AssetLocator
|
||||
import com.jme3.asset.AssetNotFoundException
|
||||
import com.jme3.scene.Spatial
|
||||
import com.jme3.{asset => jmea}
|
||||
import monix.bio.IO
|
||||
import monix.bio.UIO
|
||||
|
||||
class AssetManager(assetManager: jmea.AssetManager) {
|
||||
import AssetManager._
|
||||
def loadModel(path: os.RelPath): IO[Error, Spatial] =
|
||||
IO(assetManager.loadModel(path.toString)).onErrorHandleWith {
|
||||
case ex: AssetNotFoundException =>
|
||||
IO.raiseError(AssetNotFound(ex.getMessage))
|
||||
case ex: AssetLoadException =>
|
||||
IO.raiseError(AssetLoadError(ex.getMessage))
|
||||
}
|
||||
def loadModelAs[T <: Spatial](
|
||||
path: os.RelPath
|
||||
)(implicit ct: ClassTag[T]): IO[Error, T] =
|
||||
loadModel(path).flatMap(model =>
|
||||
if (model.getClass == ct.runtimeClass)
|
||||
UIO(model.asInstanceOf[T])
|
||||
else IO.raiseError(CouldNotCastError)
|
||||
)
|
||||
def loadAssetAs[T](path: os.RelPath)(implicit ct: ClassTag[T]): IO[Error, T] =
|
||||
IO(assetManager.loadAsset(path.toString))
|
||||
.onErrorHandleWith {
|
||||
case ex: AssetNotFoundException =>
|
||||
IO.raiseError(AssetNotFound(ex.getMessage))
|
||||
case ex: AssetLoadException =>
|
||||
IO.raiseError(AssetLoadError(ex.getMessage))
|
||||
}
|
||||
.flatMap(asset =>
|
||||
if (asset.getClass == ct.runtimeClass)
|
||||
UIO(asset.asInstanceOf[T])
|
||||
else IO.raiseError(CouldNotCastError)
|
||||
)
|
||||
def registerLocator(path: os.RelPath, locator: Class[_ <: AssetLocator]) =
|
||||
UIO(assetManager.registerLocator(path.toString, locator))
|
||||
|
||||
}
|
||||
object AssetManager {
|
||||
sealed trait Error
|
||||
case class AssetNotFound(message: String) extends Error
|
||||
case class AssetLoadError(message: String) extends Error
|
||||
case object CouldNotCastError extends Error
|
||||
import cats.data.ReaderT
|
||||
type IoReaderT[S, E, A] = ReaderT[UIO, S, Either[E, A]]
|
||||
val IoReaderT = ReaderT
|
||||
val t =
|
||||
ReaderT[UIO, String, Either[Error, Unit]](s => UIO.unit.attempt)
|
||||
.run("s")
|
||||
.rethrow
|
||||
val r: IoReaderT[String, Error, Unit] = IoReaderT(s => UIO.unit.attempt)
|
||||
val t2 = r.run("s").rethrow
|
||||
// Kleisli[IO, String, Unit](s => IO.unit)
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package wow.doge.mygame.utils.wrappers.jme
|
||||
|
||||
import com.jme3.bullet.collision.shapes.CollisionShape
|
||||
import com.jme3.bullet.{util => jmebu}
|
||||
import com.jme3.scene.Spatial
|
||||
import monix.bio.IO
|
||||
|
||||
object CollisionShapeFactory {
|
||||
sealed trait Error
|
||||
case class WrongArgumentError(reason: String) extends Error
|
||||
|
||||
def createMeshShape(subtree: Spatial): IO[Error, CollisionShape] =
|
||||
IO(jmebu.CollisionShapeFactory.createMeshShape(subtree)).onErrorHandleWith {
|
||||
case ex: IllegalArgumentException
|
||||
if (ex.getMessage.startsWith("The spatial must either be a Node")) =>
|
||||
IO.raiseError(WrongArgumentError(ex.getMessage))
|
||||
}
|
||||
}
|
@ -1,28 +1,23 @@
|
||||
package wow.doge.mygame.utils.wrappers.jme
|
||||
|
||||
import cats.effect.Sync
|
||||
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
|
||||
import wow.doge.mygame.implicits._
|
||||
import com.jme3.light.Light
|
||||
|
||||
trait NodeDelegate {
|
||||
|
||||
/**
|
||||
* Get the underlying wrapped value
|
||||
*/
|
||||
@UnsafeBecauseImpure
|
||||
def unsafeDelegate: jmes.Node
|
||||
}
|
||||
|
||||
abstract class NodeWrapper[F[_]: Sync] protected (node: jmes.Node) {
|
||||
def name = node.getName()
|
||||
def children: Observable[jmes.Spatial] = node.observableChildren
|
||||
def attachChild(n: jmes.Spatial): F[Unit] = Sync[F].delay(node.attachChild(n))
|
||||
def add(wn: Node[F]): F[Unit] =
|
||||
Sync[F].delay(node.attachChild(wn.unsafeDelegate))
|
||||
def remove(n: jmes.Spatial): F[Unit] =
|
||||
Sync[F].delay(node.detachChild(n))
|
||||
def remove(n: jmes.Spatial): F[Unit] = Sync[F].delay(node.detachChild(n))
|
||||
def remove(wn: Node[F]): F[Unit] =
|
||||
Sync[F].delay(node.detachChild(wn.unsafeDelegate))
|
||||
def addLight(light: Light) =
|
||||
@ -52,8 +47,7 @@ object NodeWrapper {
|
||||
}
|
||||
|
||||
final class Node[F[_]: Sync] private (node: jmes.Node)
|
||||
extends NodeWrapper[F](node)
|
||||
with NodeDelegate {
|
||||
extends NodeWrapper[F](node) {
|
||||
|
||||
/**
|
||||
* Get the underlying wrapped value
|
||||
@ -74,3 +68,73 @@ object AppNode {
|
||||
def apply[F[_]: Sync](n: jmes.Node) = new AppNode[F](n)
|
||||
|
||||
}
|
||||
|
||||
abstract class NodeWrapper2 protected (node: jmes.Node) {
|
||||
import NodeWrapper2._
|
||||
def name = node.getName()
|
||||
def children: Observable[jmes.Spatial] = node.observableChildren
|
||||
def attachChild(n: jmes.Spatial): IO[AddNodeToItselfError.type, Unit] =
|
||||
IO { node.attachChild(n); () }.onErrorHandleWith {
|
||||
case ex: IllegalArgumentException =>
|
||||
if (ex.getMessage === "Cannot add child to itself")
|
||||
IO.raiseError(AddNodeToItselfError)
|
||||
else IO.unit
|
||||
}
|
||||
def add(wn: Node2): IO[AddNodeToItselfError.type, Unit] =
|
||||
IO { node.attachChild(wn.unsafeDelegate); () }.onErrorHandleWith {
|
||||
case ex: IllegalArgumentException =>
|
||||
if (ex.getMessage === "Cannot add child to itself")
|
||||
IO.raiseError(AddNodeToItselfError)
|
||||
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)
|
||||
}
|
||||
object NodeWrapper2 {
|
||||
sealed trait Error
|
||||
case object AddNodeToItselfError extends Error
|
||||
implicit class NodeOps[F[_]](private val nw: NodeWrapper2) extends AnyVal {
|
||||
def +=(n: jmes.Spatial) = nw.attachChild(n)
|
||||
def +=(n: Node2) = nw.add(n)
|
||||
def -=(n: jmes.Spatial) = nw.remove(n)
|
||||
def -=(wn: Node2) = nw.remove(wn)
|
||||
def +=(light: Light) = {
|
||||
nw.addLight(light)
|
||||
}
|
||||
|
||||
def -=(light: Light) = {
|
||||
nw.removeLight(light)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class Node2 private (node: jmes.Node) extends NodeWrapper2(node) {
|
||||
|
||||
/**
|
||||
* Get the underlying wrapped value
|
||||
*/
|
||||
@UnsafeBecauseImpure
|
||||
def unsafeDelegate = node
|
||||
}
|
||||
object Node2 {
|
||||
def apply(name: String) = new Node2(new jmes.Node(name))
|
||||
def apply(n: jmes.Node) = new Node2(n)
|
||||
}
|
||||
|
||||
final class AppNode2 private (node: jmes.Node) extends NodeWrapper2(node)
|
||||
object AppNode2 {
|
||||
|
||||
def apply(name: String) = new AppNode2(new jmes.Node(name))
|
||||
def apply(n: jmes.Node) = new AppNode2(n)
|
||||
|
||||
}
|
||||
|
@ -38,6 +38,8 @@ object PhysicsSpace {
|
||||
space
|
||||
}
|
||||
|
||||
def -=(anyObject: Any) = space.remove(anyObject)
|
||||
|
||||
def +=(spatial: jmes.Spatial) = space.addAll(spatial)
|
||||
|
||||
def :+(spatial: jmes.Spatial) = {
|
||||
@ -49,5 +51,7 @@ object PhysicsSpace {
|
||||
space.removeAll(spatial)
|
||||
space
|
||||
}
|
||||
|
||||
def -=(spatial: jmes.Spatial) = space.removeAll(spatial)
|
||||
}
|
||||
}
|
||||
|
57
src/test/scala/wow/doge/mygame/ActorTimeoutTest.scala
Normal file
57
src/test/scala/wow/doge/mygame/ActorTimeoutTest.scala
Normal file
@ -0,0 +1,57 @@
|
||||
package wow.doge.mygame
|
||||
|
||||
import akka.actor.typed.scaladsl.Behaviors
|
||||
import akka.actor.typed.scaladsl.ActorContext
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
import org.scalatest.BeforeAndAfterAll
|
||||
import akka.actor.typed.ActorSystem
|
||||
import scala.concurrent.duration._
|
||||
import akka.actor.typed.ActorRef
|
||||
import akka.actor.typed.scaladsl.AskPattern._
|
||||
import akka.util.Timeout
|
||||
import scala.concurrent.Await
|
||||
|
||||
class ActorTimeoutTest extends AnyFunSuite with BeforeAndAfterAll {
|
||||
import ActorTimeoutTest._
|
||||
implicit val as = ActorSystem(new MyActor.Props().create, "system")
|
||||
implicit val timeout = Timeout(1.millis)
|
||||
|
||||
test("timeoutTest") {
|
||||
val fut = as.ask(MyActor.GetInt(_))
|
||||
val res = Await.result(fut, 1.second)
|
||||
assert(res == 1)
|
||||
}
|
||||
|
||||
override protected def afterAll(): Unit = {
|
||||
as.terminate()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object ActorTimeoutTest {
|
||||
object MyActor {
|
||||
sealed trait Command
|
||||
case class GetInt(replyTo: ActorRef[Int]) extends Command
|
||||
|
||||
class Props() {
|
||||
def create =
|
||||
Behaviors.setup[Command] { ctx =>
|
||||
new MyActor(ctx, this).receive
|
||||
}
|
||||
}
|
||||
}
|
||||
class MyActor(
|
||||
ctx: ActorContext[MyActor.Command],
|
||||
props: MyActor.Props
|
||||
) {
|
||||
import MyActor._
|
||||
def receive =
|
||||
Behaviors.receiveMessage[Command] {
|
||||
case GetInt(replyTo) =>
|
||||
// Thread.sleep(1000)
|
||||
replyTo ! 1
|
||||
Behaviors.same
|
||||
}
|
||||
}
|
||||
|
||||
}
|
59
src/test/scala/wow/doge/mygame/AssetManagerTest.scala
Normal file
59
src/test/scala/wow/doge/mygame/AssetManagerTest.scala
Normal file
@ -0,0 +1,59 @@
|
||||
package wow.doge.mygame
|
||||
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
import monix.execution.Scheduler.Implicits.global
|
||||
import cats.syntax.eq._
|
||||
import com.jme3.{asset => jmea}
|
||||
import com.jme3.asset.DesktopAssetManager
|
||||
import wow.doge.mygame.utils.wrappers.jme.AssetManager
|
||||
import wow.doge.mygame.utils.wrappers.jme.AssetManager.AssetNotFound
|
||||
import com.jme3.scene.Geometry
|
||||
import wow.doge.mygame.utils.wrappers.jme.AssetManager.CouldNotCastError
|
||||
import com.jme3.scene.Node
|
||||
import com.jme3.material.MaterialDef
|
||||
import com.jme3.material.Material
|
||||
|
||||
class AssetManagerTest extends AnyFunSuite {
|
||||
|
||||
val _assetManager: jmea.AssetManager = new DesktopAssetManager(true)
|
||||
val assetManager = new AssetManager(_assetManager)
|
||||
|
||||
test("Test for AssetNotFound error") {
|
||||
val res =
|
||||
assetManager.loadModel(os.rel / "doesnotexist").attempt.runSyncUnsafe()
|
||||
assert(res === Left(AssetNotFound("doesnotexist")))
|
||||
}
|
||||
|
||||
test("Test for Model CouldNotCastError") {
|
||||
val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o"
|
||||
val res1 = assetManager
|
||||
.loadModelAs[Geometry](modelPath)
|
||||
.attempt
|
||||
.runSyncUnsafe()
|
||||
|
||||
assert(res1 === Left(CouldNotCastError))
|
||||
|
||||
val res2 = assetManager
|
||||
.loadModelAs[Node](modelPath)
|
||||
.attempt
|
||||
.runSyncUnsafe()
|
||||
assert(res2.map(_.getName) === Right("JaimeGeom-ogremesh"))
|
||||
}
|
||||
|
||||
test("Test for Asset CouldNotCastError") {
|
||||
val assetPath = os.rel / "Common" / "MatDefs" / "Misc" / "Unshaded.j3md"
|
||||
|
||||
val res1 = assetManager
|
||||
.loadAssetAs[Material](assetPath)
|
||||
.attempt
|
||||
.runSyncUnsafe()
|
||||
|
||||
assert(res1 === Left(CouldNotCastError))
|
||||
|
||||
val res2 = assetManager
|
||||
.loadAssetAs[MaterialDef](assetPath)
|
||||
.attempt
|
||||
.runSyncUnsafe()
|
||||
assert(res2.map(_.getName) === Right("Unshaded"))
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package wow.doge.mygame
|
||||
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
import com.jme3.scene.Spatial
|
||||
import com.jme3.collision.{Collidable, CollisionResults}
|
||||
import com.jme3.bounding.BoundingVolume
|
||||
import com.jme3.scene.Spatial.DFSMode
|
||||
import com.jme3.scene.SceneGraphVisitor
|
||||
import java.util.Queue
|
||||
import wow.doge.mygame.utils.wrappers.jme.CollisionShapeFactory
|
||||
import monix.execution.Scheduler.Implicits.global
|
||||
import cats.syntax.eq._
|
||||
|
||||
class CollisionShapeFactoryTest extends AnyFunSuite {
|
||||
test("Test for WrongArgumentError") {
|
||||
val res = CollisionShapeFactory
|
||||
.createMeshShape(new TestSpatial)
|
||||
.attempt
|
||||
.runSyncUnsafe()
|
||||
|
||||
assert(
|
||||
res === Left(
|
||||
CollisionShapeFactory.WrongArgumentError(
|
||||
"The spatial must either be a Node or a Geometry!"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class TestSpatial extends Spatial {
|
||||
|
||||
override def collideWith(x$1: Collidable, x$2: CollisionResults): Int = ???
|
||||
|
||||
override def updateModelBound(): Unit = ???
|
||||
|
||||
override def setModelBound(x$1: BoundingVolume): Unit = ???
|
||||
|
||||
override def getVertexCount(): Int = ???
|
||||
|
||||
override def getTriangleCount(): Int = ???
|
||||
|
||||
override def depthFirstTraversal(x$1: SceneGraphVisitor, x$2: DFSMode): Unit =
|
||||
???
|
||||
|
||||
override protected def breadthFirstTraversal(
|
||||
x$1: SceneGraphVisitor,
|
||||
x$2: Queue[Spatial]
|
||||
): Unit = ???
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user