This commit is contained in:
Rohan Sircar 2021-01-15 12:09:43 +05:30
parent 64480e8e03
commit 2e05cb35fe
42 changed files with 1265 additions and 599 deletions

View File

@ -1 +1,4 @@
version = "2.6.4" version = "2.6.4"
rewrite {
rules = [SortImports, RedundantBraces]
}

View File

@ -61,15 +61,15 @@ lazy val root = (project in file(".")).settings(
"org.jmonkeyengine" % "jme3-blender" % jmeVersion, "org.jmonkeyengine" % "jme3-blender" % jmeVersion,
"com.github.stephengold" % "Minie" % "3.0.0", "com.github.stephengold" % "Minie" % "3.0.0",
"com.simsilica" % "zay-es" % "1.2.1", "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, "com.lihaoyi" % "ammonite" % "2.2.0" cross CrossVersion.full,
"org.jetbrains.kotlin" % "kotlin-main-kts" % "1.4.10", "org.jetbrains.kotlin" % "kotlin-main-kts" % "1.4.10",
"org.jetbrains.kotlin" % "kotlin-scripting-jsr223" % "1.4.10", "org.jetbrains.kotlin" % "kotlin-scripting-jsr223" % "1.4.10",
"org.codehaus.groovy" % "groovy-all" % "3.0.6" pomOnly (), "org.codehaus.groovy" % "groovy-all" % "3.0.6" pomOnly (),
"org.scalafx" %% "scalafx" % "14-R19", "org.scalafx" %% "scalafx" % "14-R19",
"com.typesafe.akka" %% "akka-actor-typed" % "2.6.10", "com.typesafe.akka" %% "akka-actor-typed" % "2.6.10",
"org.typelevel" %% "cats-core" % "2.1.1", "org.typelevel" %% "cats-core" % "2.3.0",
"org.typelevel" %% "cats-effect" % "2.1.4", "org.typelevel" %% "cats-effect" % "2.3.0",
"io.monix" %% "monix" % "3.2.2", "io.monix" %% "monix" % "3.2.2",
"io.monix" %% "monix-bio" % "1.1.0", "io.monix" %% "monix-bio" % "1.1.0",
"io.circe" %% "circe-core" % "0.13.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", "com.badlogicgames.gdx" % "gdx-ai" % "1.8.2",
"org.recast4j" % "recast" % "1.2.5", "org.recast4j" % "recast" % "1.2.5",
"org.recast4j" % "detour" % "1.2.5", "org.recast4j" % "detour" % "1.2.5",
"com.lihaoyi" %% "pprint" % "0.6.0" "com.lihaoyi" %% "pprint" % "0.6.0",
"org.scalatest" %% "scalatest" % "3.2.2" % "test"
), ),
// Determine OS version of JavaFX binaries // 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 // To learn more about multi-project builds, head over to the official sbt
// documentation at http://www.scala-sbt.org/documentation.html // documentation at http://www.scala-sbt.org/documentation.html
addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1") 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" ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.4.3"

View File

@ -1,7 +1,7 @@
# jme-dispatcher { jme-dispatcher {
# type = "Dispatcher" type = "Dispatcher"
# name = "JME-Thread" name = "JME-Thread"
# executor = "wow.doge.mygame.executors.JMEThreadExecutorServiceConfigurator" executor = "wow.doge.mygame.executors.JMEThreadExecutorServiceConfigurator"
# throughput = 1 throughput = 1
# } }
# akka.jvm-exit-on-fatal-error = on # akka.jvm-exit-on-fatal-error = on

View File

@ -32,13 +32,13 @@ class StaticLoggerBinder extends OdinLoggerBinder[IO] {
def apply[A](fa: _root_.monix.bio.Task[A]): IO[A] = fa.to[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) consoleLogger[IO](minLevel = Level.Debug)
.withAsync(timeWindow = 1.milliseconds, maxBufferSize = Some(2000)) .withAsync(timeWindow = 1.milliseconds, maxBufferSize = Some(2000))
.allocated .allocated
.unsafeRunSync() .unsafeRunSync()
private lazy val (mainFileLogger, release2) = val (mainFileLogger, release2) =
fileLogger[IO]( fileLogger[IO](
"application-log-2.log", "application-log-2.log",
Formatter.json, Formatter.json,
@ -51,7 +51,7 @@ class StaticLoggerBinder extends OdinLoggerBinder[IO] {
lm.copy(message = lm.message.map(s => fansi.Str(s).plainText)) lm.copy(message = lm.message.map(s => fansi.Str(s).plainText))
) )
private lazy val (eventBusFileLogger, release3) = val (eventBusFileLogger, release3) =
fileLogger[IO]( fileLogger[IO](
"eventbus.log", "eventbus.log",
Formatter.json, Formatter.json,

View File

@ -0,0 +1,6 @@
package wow.doge.mygame
sealed trait AppError
object AppError {
case class TimeoutError(reason: String) extends AppError
}

View File

@ -0,0 +1,7 @@
package wow.doge.mygame
import akka.actor.typed.DispatcherSelector
object Dispatchers {
val jmeDispatcher = DispatcherSelector.fromConfig("jme-dispatcher")
}

View File

@ -5,8 +5,7 @@ import scala.concurrent.duration._
import _root_.monix.bio.BIOApp import _root_.monix.bio.BIOApp
import _root_.monix.bio.Task import _root_.monix.bio.Task
import _root_.monix.bio.UIO import _root_.monix.bio.UIO
import akka.actor.typed.ActorSystem import _root_.monix.execution.Scheduler
import akka.actor.typed.SpawnProtocol
import akka.util.Timeout import akka.util.Timeout
import cats.effect.ExitCode import cats.effect.ExitCode
import cats.effect.Resource import cats.effect.Resource
@ -22,31 +21,24 @@ object Main extends BIOApp with MainModule {
JLogger.getLogger("").setLevel(Level.SEVERE) JLogger.getLogger("").setLevel(Level.SEVERE)
implicit val timeout = Timeout(1.second) implicit val timeout = Timeout(1.second)
override def scheduler: Scheduler = schedulers.async
def appResource(consoleStream: GenericConsoleStream[TextArea]) = def appResource(consoleStream: GenericConsoleStream[TextArea]) =
for { for {
logger <- logger <-
consoleLogger().withAsync( consoleLogger().withAsync(
timeWindow = 1.milliseconds, timeWindow = 1.milliseconds,
maxBufferSize = Some(2000) maxBufferSize = Some(100)
) |+| ) |+|
fileLogger( fileLogger(
"application-log-1.log", "application-log-1.log",
Formatter.json Formatter.json
).withAsync(timeWindow = 1.milliseconds, maxBufferSize = Some(2000)) ).withAsync(timeWindow = 1.milliseconds, maxBufferSize = Some(2000))
jmeScheduler <- jMESchedulerResource jmeScheduler <- jMESchedulerResource
implicit0(actorSystem: ActorSystem[SpawnProtocol.Command]) <- actorSystem <- actorSystemResource(logger, schedulers.async)
actorSystemResource(logger)
// gameApp <- {
// // new BulletAppState()
// // bas.setThreadingType(Thr)
// // gameAppResource(new StatsAppState())
// wire[GameAppResource].get
// }
_ <- Resource.liftF( _ <- Resource.liftF(
new MainApp( new MainApp(
logger, logger,
// gameApp,
// actorSystem,
jmeScheduler, jmeScheduler,
schedulers, schedulers,
consoleStream consoleStream
@ -56,11 +48,11 @@ object Main extends BIOApp with MainModule {
} yield () } yield ()
def run(args: List[String]): UIO[ExitCode] = { def run(args: List[String]): UIO[ExitCode] = {
val consoleStream = GenericConsoleStream.textAreaStream()
lazy val consoleStream = GenericConsoleStream.textAreaStream()
Console.withOut(consoleStream)( Console.withOut(consoleStream)(
appResource(consoleStream) appResource(consoleStream)
.use(_ => Task.unit >> Task(consoleStream.close())) .use(_ => Task.unit)
.flatMap(_ => Task(consoleStream.close()))
.onErrorHandleWith(ex => UIO(ex.printStackTrace())) .onErrorHandleWith(ex => UIO(ex.printStackTrace()))
.as(ExitCode.Success) .as(ExitCode.Success)
) )

View File

@ -1,5 +1,6 @@
package wow.doge.mygame package wow.doge.mygame
import akka.actor.typed.ActorRef
import akka.actor.typed.ActorSystem import akka.actor.typed.ActorSystem
import akka.actor.typed.Scheduler import akka.actor.typed.Scheduler
import akka.actor.typed.SpawnProtocol import akka.actor.typed.SpawnProtocol
@ -8,10 +9,12 @@ import cats.effect.Resource
import cats.effect.concurrent.Deferred import cats.effect.concurrent.Deferred
import cats.syntax.eq._ import cats.syntax.eq._
import com.jme3.app.state.AppStateManager import com.jme3.app.state.AppStateManager
import com.jme3.asset.AssetManager
import com.jme3.asset.plugins.ZipLocator import com.jme3.asset.plugins.ZipLocator
import com.jme3.bullet.control.BetterCharacterControl import com.jme3.bullet.control.BetterCharacterControl
import com.jme3.input.InputManager 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.Camera
import com.jme3.renderer.RenderManager import com.jme3.renderer.RenderManager
import com.jme3.renderer.ViewPort import com.jme3.renderer.ViewPort
@ -23,18 +26,21 @@ import io.odin.Logger
import monix.bio.Fiber import monix.bio.Fiber
import monix.bio.IO import monix.bio.IO
import monix.bio.Task import monix.bio.Task
import monix.execution.exceptions.DummyException import monix.bio.UIO
import scalafx.scene.control.TextArea import scalafx.scene.control.TextArea
import wow.doge.mygame.executors.Schedulers import wow.doge.mygame.executors.Schedulers
import wow.doge.mygame.game.GameApp import wow.doge.mygame.game.GameApp
import wow.doge.mygame.game.GameAppActor import wow.doge.mygame.game.GameAppActor
import wow.doge.mygame.game.GameAppResource
import wow.doge.mygame.game.GameAppTags import wow.doge.mygame.game.GameAppTags
import wow.doge.mygame.game.entities.EntityIds import wow.doge.mygame.game.entities.EntityIds
import wow.doge.mygame.game.entities.NpcActorSupervisor import wow.doge.mygame.game.entities.NpcActorSupervisor
import wow.doge.mygame.game.entities.NpcMovementActor import wow.doge.mygame.game.entities.NpcMovementActor
import wow.doge.mygame.game.entities.PlayerActorSupervisor
import wow.doge.mygame.game.entities.PlayerController import wow.doge.mygame.game.entities.PlayerController
import wow.doge.mygame.game.entities.PlayerControllerTags import wow.doge.mygame.game.entities.PlayerControllerTags
import wow.doge.mygame.game.subsystems.input.GameInputHandler import wow.doge.mygame.game.subsystems.input.GameInputHandler
import wow.doge.mygame.game.subsystems.level.DefaultGameLevel
import wow.doge.mygame.implicits._ import wow.doge.mygame.implicits._
import wow.doge.mygame.launcher.Launcher import wow.doge.mygame.launcher.Launcher
import wow.doge.mygame.launcher.Launcher.LauncherResult import wow.doge.mygame.launcher.Launcher.LauncherResult
@ -48,8 +54,14 @@ import wow.doge.mygame.subsystems.scriptsystem.ScriptSystemResource
import wow.doge.mygame.utils.AkkaUtils import wow.doge.mygame.utils.AkkaUtils
import wow.doge.mygame.utils.GenericConsoleStream import wow.doge.mygame.utils.GenericConsoleStream
import wow.doge.mygame.utils.wrappers.jme.AppNode import wow.doge.mygame.utils.wrappers.jme.AppNode
import wow.doge.mygame.game.subsystems.level.DefaultGameLevel import wow.doge.mygame.utils.wrappers.jme.AssetManager
import com.jme3.math.FastMath 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( class MainApp(
logger: Logger[Task], logger: Logger[Task],
@ -57,37 +69,52 @@ class MainApp(
schedulers: Schedulers, schedulers: Schedulers,
consoleStream: GenericConsoleStream[TextArea] consoleStream: GenericConsoleStream[TextArea]
)(implicit )(implicit
spawnProtocol: ActorSystem[SpawnProtocol.Command], spawnProtocol: ActorRef[SpawnProtocol.Command],
@annotation.unused timeout: Timeout, timeout: Timeout,
@annotation.unused scheduler: Scheduler scheduler: Scheduler
) { ) {
val scriptSystemInit = val scriptSystemInit =
new ScriptSystemResource(os.pwd, ScriptInitMode.Eager).init 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]] = def gameInit: Resource[Task, Fiber[Throwable, Unit]] =
GameApp.resource(logger, jmeThread, schedulers).evalMap { wire[GameAppResource].resource.evalMap {
case gameApp -> gameAppFib => case gameApp -> gameAppFib =>
for { for {
playerEventBus <- eventsModule.playerEventBusTask playerEventBus <- eventsModule.playerEventBusTask
mainEventBus <- eventsModule.mainEventBusTask mainEventBus <- eventsModule.mainEventBusTask
tickEventBus <- eventsModule.tickEventBusTask 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.Props(tickEventBus).behavior,
"gameAppActor" "gameAppActor"
) )
_ <- gameAppActor !! GameAppActor.Start _ <- 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 { consoleTextArea <- Task(new TextArea {
text = "hello \n" text = "hello \n"
editable = false editable = false
@ -98,9 +125,6 @@ class MainApp(
// _ <- Task(consoleStream := consoleTextArea) // _ <- Task(consoleStream := consoleTextArea)
// _ <- Task(jfxUI += consoleTextArea) // _ <- Task(jfxUI += consoleTextArea)
_ <- logger.info("after") _ <- logger.info("after")
// bulletAppState <- Task(new BulletAppState())
// _ <- Task(stateManager.attach(bulletAppState))
// bulletAppState <- Task.pure(gameApp.bulletAppstate)
_ <- logger.info("Initializing console stream") _ <- logger.info("Initializing console stream")
_ <- _ <-
wire[MainAppDelegate] wire[MainAppDelegate]
@ -109,6 +133,13 @@ class MainApp(
} yield gameAppFib } yield gameAppFib
} }
// val x: Task[Unit] = for {
// tickEventBus <- eventsModule.tickEventBusTask
// playerEventBus <- eventsModule.playerEventBusTask
// _ <- UIO(wire[TestClass])
// _ <- gameInit(tickEventBus).use(_.join)
// } yield ()
val program = for { val program = for {
scriptSystem <- scriptSystemInit scriptSystem <- scriptSystemInit
launchSignal <- Deferred[Task, Launcher.LauncherResult] launchSignal <- Deferred[Task, Launcher.LauncherResult]
@ -125,6 +156,7 @@ class MainApp(
*/ */
else else
gameInit.use(_.join) gameInit.use(_.join)
} yield () } yield ()
} }
@ -139,47 +171,44 @@ class MainAppDelegate(
inputManager: InputManager, inputManager: InputManager,
assetManager: AssetManager, assetManager: AssetManager,
stateManager: AppStateManager, stateManager: AppStateManager,
physicsSpace: PhysicsSpace[Task],
camera: Camera, camera: Camera,
viewPort: ViewPort, viewPort: ViewPort,
enqueueR: Function1[() => Unit, Unit], enqueueR: Function1[() => Unit, Unit],
rootNode: AppNode[Task] @@ GameAppTags.RootNode rootNode: AppNode[Task] @@ GameAppTags.RootNode
// bulletAppState: BulletAppState
)(implicit )(implicit
spawnProtocol: ActorSystem[SpawnProtocol.Command], spawnProtocol: ActorRef[SpawnProtocol.Command],
@annotation.unused timeout: Timeout, timeout: Timeout,
@annotation.unused scheduler: Scheduler scheduler: Scheduler
) { ) {
// val physicsSpace = bulletAppState.physicsSpace
val physicsSpace = gameApp.physicsSpace
def init( def init(
// appScheduler: monix.execution.Scheduler // appScheduler: monix.execution.Scheduler
// consoleStream: GenericConsoleStream[TextArea] // consoleStream: GenericConsoleStream[TextArea]
) = ) =
for { for {
_ <- loggerL.info("Initializing Systems") _ <- loggerL.info("Initializing Systems")
_ <- loggerL.debug(physicsSpace.toString()) _ <- assetManager.registerLocator(
_ <- Task( os.rel / "assets" / "town.zip",
assetManager.registerLocator( classOf[ZipLocator]
os.rel / "assets" / "town.zip",
classOf[ZipLocator]
)
) )
_ <- loggerL.info("test") _ <- loggerL.info("test")
// _ <- Task(consoleStream.println("text")) // _ <- Task(consoleStream.println("text"))
_ <- DefaultGameLevel(assetManager, viewPort) _ <- DefaultGameLevel(assetManager, viewPort)
.addToGame(rootNode, physicsSpace) .flatMap(_.addToGame(rootNode, physicsSpace).hideErrors)
.onErrorHandleWith(e => loggerL.error(e.toString))
_ <- createPlayerController() _ <- createPlayerController()
.absorbWith(e => DummyException(e.toString())) .onErrorHandleWith(e => loggerL.error(e.toString))
// .onErrorRestart(3) // .onErrorRestart(3)
_ <- wire[GameInputHandler.Props].begin _ <- wire[GameInputHandler.Props].begin
// .onErrorRestart(3) // .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(0, 0, 20))
// _ <- // _ <-
// (johnActor !! NpcActorSupervisor.Move( // johnActor
// ImVector3f(-30, 0, 10) // .tellL(NpcActorSupervisor.Move(ImVector3f(-30, 0, 10)))
// )).executeAsync
// .delayExecution(2.seconds) // .delayExecution(2.seconds)
// _ <- // _ <-
// IOUtils // IOUtils
@ -195,53 +224,40 @@ class MainAppDelegate(
def createPlayerController( def createPlayerController(
// appScheduler: monix.execution.Scheduler // appScheduler: monix.execution.Scheduler
): IO[PlayerController.Error, Unit] = { ): IO[PlayerController.Error, ActorRef[PlayerActorSupervisor.Command]] = {
val playerPos = ImVector3f.ZERO val playerPos = ImVector3f.ZERO
val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o" val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o"
val playerPhysicsControl = val playerPhysicsControl =
PlayerController.Defaults.defaultPlayerPhysicsControl PlayerController.Defaults.defaultPlayerPhysicsControl
.taggedWith[PlayerControllerTags.PlayerTag] .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
.defaultPlayerNode(
playerPos,
playerModel,
playerPhysicsControl
)
val cameraPivotNode = new Node(EntityIds.CameraPivot.value)
.taggedWith[PlayerControllerTags.PlayerCameraPivotNode]
for { for {
playerNode <- IO.fromEither(mbPlayerNode) playerModel <-
_ <- IO(cameraPivotNode.addControl(new FollowControl(playerNode))) assetManager
.onErrorHandleWith(e => .loadModelAs[Node](modelPath)
IO.raiseError(PlayerController.GenericError(e.getMessage())) .map(_.withRotate(0, FastMath.PI, 0))
) .mapError(PlayerController.CouldNotCreatePlayerModel)
camNode <- IO( playerNode <- UIO(
PlayerController.Defaults PlayerController.Defaults
.defaultCamerNode(camera, cameraPivotNode, playerNode, playerPos) .defaultPlayerNode(
).onErrorHandleWith(e => playerPos,
IO.raiseError(PlayerController.GenericError(e.getMessage())) playerModel,
).map(_.taggedWith[PlayerControllerTags.PlayerCameraNode]) playerPhysicsControl
// _ <- Task { )
// val chaseCam = new ChaseCamera(camera, playerNode, inputManager) )
// chaseCam.setSmoothMotion(false) cameraPivotNode <- UIO(
// chaseCam.setLookAtOffset(new Vector3f(0, 1.5f, 10)) new Node(EntityIds.CameraPivot.value)
// chaseCam .withControl(new FollowControl(playerNode))
// } .taggedWith[PlayerControllerTags.PlayerCameraPivotNode]
// .onErrorHandleWith(e => )
// IO.raiseError(PlayerController.GenericError(e.getMessage())) camNode <- UIO(
// ) PlayerController.Defaults
_ <- wire[PlayerController.Props].create .defaultCamerNode(camera, playerPos)
} yield () .taggedWith[PlayerControllerTags.PlayerCameraNode]
)
playerActor <- wire[PlayerController.Props].create
} yield playerActor
} }
def createTestNpc( def createTestNpc(
// appScheduler: monix.execution.Scheduler, // appScheduler: monix.execution.Scheduler,
npcName: String npcName: String
@ -250,12 +266,7 @@ class MainAppDelegate(
val npcPhysicsControl = new BetterCharacterControl(1.5f, 6f, 1f) val npcPhysicsControl = new BetterCharacterControl(1.5f, 6f, 1f)
// (1f, 2.1f, 10f) // (1f, 2.1f, 10f)
.withJumpForce(ImVector3f(0, 5f, 0)) .withJumpForce(ImVector3f(0, 5f, 0))
val mbNpcNode = PlayerController.Defaults.defaultNpcNode(
assetManager,
initialPos,
npcPhysicsControl,
npcName
)
val npcActorTask = AkkaUtils.spawnActorL( val npcActorTask = AkkaUtils.spawnActorL(
NpcActorSupervisor NpcActorSupervisor
.Props( .Props(
@ -263,6 +274,7 @@ class MainAppDelegate(
enqueueR, enqueueR,
initialPos, initialPos,
// tickEventBus, // tickEventBus,
npcName,
npcPhysicsControl npcPhysicsControl
).behavior, ).behavior,
npcName, npcName,
@ -271,28 +283,35 @@ class MainAppDelegate(
.behavior, .behavior,
s"${npcName}-npcActorSupervisor" s"${npcName}-npcActorSupervisor"
) )
// .taggedWith[PlayerControllerTags.PlayerTag]
for { for {
npcNode <- IO.fromEither(mbNpcNode) materialDef <- assetManager.loadAssetAs[MaterialDef](
npcActor <- npcActorTask os.rel / "Common" / "MatDefs" / "Misc" / "Unshaded.j3md"
// _ <- IO { )
// physicsSpace += npcPhysicsControl material = new Material(materialDef)
// physicsSpace += npcNode _ = material.setColor("Color", ColorRGBA.Blue)
// // rootNode += npcNode mesh = PlayerController.Defaults.defaultMesh.withMaterial(material)
// } npcNode = PlayerController.Defaults.defaultNpcNode(
_ <- physicsSpace += npcPhysicsControl mesh,
_ <- physicsSpace += npcNode initialPos,
_ <- rootNode += npcNode npcPhysicsControl,
npcName
)
npcActor <- npcActorTask.hideErrors
_ <- (for {
_ <- physicsSpace += npcPhysicsControl
_ <- physicsSpace += npcNode
_ <- rootNode += npcNode
} yield ()).hideErrors
} yield npcActor } yield npcActor
} }
} }
class FollowControl(playerNode: Node) extends AbstractControl { class FollowControl(playerNode: Node) extends AbstractControl {
override def controlUpdate(tpf: Float): Unit = { override def controlUpdate(tpf: Float): Unit =
this.spatial.setLocalTranslation(playerNode.getLocalTranslation()) this.spatial.setLocalTranslation(playerNode.getLocalTranslation())
}
override def controlRender( override def controlRender(
rm: RenderManager, rm: RenderManager,
vp: ViewPort vp: ViewPort

View File

@ -5,15 +5,22 @@ import cats.effect.Resource
import io.odin.Logger import io.odin.Logger
import monix.bio.Task import monix.bio.Task
import wow.doge.mygame.executors.ExecutorsModule import wow.doge.mygame.executors.ExecutorsModule
import akka.actor.BootstrapSetup
import monix.execution.Scheduler
trait MainModule extends ExecutorsModule { trait MainModule extends ExecutorsModule {
def actorSystemResource( def actorSystemResource(
logger: Logger[Task] logger: Logger[Task],
scheduler: Scheduler
): Resource[Task, ActorSystem[SpawnProtocol.Command]] = ): Resource[Task, ActorSystem[SpawnProtocol.Command]] =
Resource.make( Resource.make(
logger.info("Creating Actor System") >> Task( logger.info("Creating Actor System") >> Task(
ActorSystem(SpawnProtocol(), name = "GameActorSystem") ActorSystem(
SpawnProtocol(),
name = "GameActorSystem",
BootstrapSetup().withDefaultExecutionContext(scheduler)
)
) )
)(sys => )(sys =>
for { for {

View File

@ -1,23 +1,32 @@
package wow.doge.mygame.executors package wow.doge.mygame.executors
import cats.effect.Resource import cats.effect.Resource
import monix.bio.IO
import monix.bio.Task import monix.bio.Task
import monix.bio.UIO
import monix.execution.Scheduler import monix.execution.Scheduler
trait ExecutorsModule { trait ExecutorsModule {
lazy val schedulers = Schedulers() val schedulers = Schedulers()
// Resource.make( val acquire: UIO[Either[Error, Int]] =
// Task( IO.pure(1).onErrorHandleWith(_ => IO.raiseError(Error)).attempt
// new Schedulers( // : Resource[IO[Error, Unit], Unit]
// jme = Scheduler val res = Resource.make(acquire)(_ => IO.unit)
// .singleThread(name = "JME-Application-Thread", daemonic = false) val x: Task[Either[Error, Unit]] = res.use {
// ) case Right(value) => Task(Right(println(s"got $value")))
// ) case Left(value) => Task(Left(value))
// )(s => Task(s.jme.shutdown())) }
lazy val jMESchedulerResource = Resource.make( val z = x.onErrorHandleWith(ex => UIO(Right(ex.printStackTrace())))
val y: IO[Error, Unit] = z >>
x.hideErrors.rethrow
val jMESchedulerResource = Resource.make(
Task( Task(
Scheduler Scheduler
.singleThread(name = "JME-Application-Thread", daemonic = false) .singleThread(name = "JME-Application-Thread", daemonic = false)
) )
)(e => Task(e.shutdown())) )(e => Task(e.shutdown()))
} }
sealed trait Error
case object Error extends Error

View File

@ -42,13 +42,13 @@ object SwingExecutorService extends GUIExecutorService {
object JMEExecutorService extends GUIExecutorService { object JMEExecutorService extends GUIExecutorService {
override def execute(command: Runnable) = override def execute(command: Runnable) =
JMERunner.runner.enqueue(command) JMERunner.runner.get.apply(command)
// new SingleThreadEventExecutor() // new SingleThreadEventExecutor()
sys.addShutdownHook(JMEExecutorService.shutdown()) sys.addShutdownHook(JMEExecutorService.shutdown())
} }
object JMERunner { object JMERunner {
var runner: com.jme3.app.Application = null var runner: Option[Runnable => Unit] = None
} }

View File

@ -1,9 +1,16 @@
package wow.doge.mygame.game 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.Resource
import cats.effect.concurrent.Deferred import cats.effect.concurrent.Deferred
import com.jme3.app.state.AppStateManager import com.jme3.app.state.AppStateManager
import com.jme3.asset.AssetManager
import com.jme3.bullet.BulletAppState import com.jme3.bullet.BulletAppState
import com.jme3.input.InputManager import com.jme3.input.InputManager
import com.jme3.scene.Node import com.jme3.scene.Node
@ -12,20 +19,23 @@ import com.jme3.system.AppSettings
import com.softwaremill.tagging._ import com.softwaremill.tagging._
import com.typesafe.scalalogging.{Logger => SLogger} import com.typesafe.scalalogging.{Logger => SLogger}
import io.odin.Logger import io.odin.Logger
import monix.bio.IO import monix.bio.Fiber
import monix.bio.Task import monix.bio.Task
import monix.bio.UIO
import monix.catnap.ConcurrentChannel import monix.catnap.ConcurrentChannel
import monix.catnap.ConsumerF import monix.catnap.ConsumerF
import monix.catnap.Semaphore
import monix.eval.Coeval import monix.eval.Coeval
import monix.execution.CancelableFuture
import monix.execution.CancelablePromise
import monix.execution.Scheduler import monix.execution.Scheduler
import wow.doge.mygame.executors.JMERunner
import wow.doge.mygame.executors.Schedulers import wow.doge.mygame.executors.Schedulers
import wow.doge.mygame.utils.GenericTimerActor
import wow.doge.mygame.game.subsystems.ui.JFxUI import wow.doge.mygame.game.subsystems.ui.JFxUI
import wow.doge.mygame.implicits._ import wow.doge.mygame.implicits._
import wow.doge.mygame.utils.AkkaUtils
import wow.doge.mygame.utils.wrappers.jme._ import wow.doge.mygame.utils.wrappers.jme._
import wow.doge.mygame.Dispatchers
sealed trait Error
case object FlyCamNotExists extends Error
object GameAppTags { object GameAppTags {
sealed trait RootNode sealed trait RootNode
@ -33,47 +43,58 @@ object GameAppTags {
sealed trait GuiNode 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 stateManager: Task[AppStateManager] = Task(app.getStateManager())
def inputManager: Task[InputManager] = Task(app.getInputManager()) 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 guiNode = Task(app.getGuiNode().taggedWith[GameAppTags.GuiNode])
def guiNode2 = AppNode[Task](app.getGuiNode()).taggedWith[GameAppTags.GuiNode] val guiNode2 = AppNode[Task](app.getGuiNode()).taggedWith[GameAppTags.GuiNode]
def flyCam = def flyCam = Option(app.getFlyByCamera())
IO(app.getFlyByCamera()).onErrorHandleWith(_ =>
IO.raiseError(FlyCamNotExists)
)
def camera = Task(app.getCamera()) def camera = Task(app.getCamera())
def viewPort = Task(app.getViewPort()) def viewPort = Task(app.getViewPort())
// def rootNode = Task(app.getRootNode().taggedWith[GameAppTags.RootNode]) // def rootNode = Task(app.getRootNode().taggedWith[GameAppTags.RootNode])
val rootNode = val rootNode =
AppNode[Task](app.getRootNode()).taggedWith[GameAppTags.RootNode] AppNode[Task](app.getRootNode()).taggedWith[GameAppTags.RootNode]
val physicsSpace = new PhysicsSpace[Task](app.bulletAppState.physicsSpace) val physicsSpace = new PhysicsSpace[Task](app.bulletAppState.physicsSpace)
def enqueue(cb: () => Unit) = def enqueue(cb: () => Unit) = app.enqueueR(() => cb())
app.enqueue(new Runnable {
override def run() = cb()
})
def enqueueL[T](cb: () => T): Task[T] = app.enqueueL(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 scheduler = app.scheduler
def jfxUI = JFxUI(app) def jfxUI = JFxUI(app)
} }
object GameApp { class GameAppResource(
logger: Logger[Task],
def resource( jmeThread: Scheduler,
logger: Logger[Task], schedulers: Schedulers
jmeThread: Scheduler, )(implicit
schedulers: Schedulers timeout: Timeout,
) = scheduler: akka.actor.typed.Scheduler,
spawnProtocol: ActorRef[SpawnProtocol.Command]
) {
def resource: Resource[Task, (GameApp, Fiber[Throwable, Unit])] =
Resource.make { Resource.make {
lazy val bullet = new BulletAppState lazy val bullet = new BulletAppState
for { for {
// bullet <- Task(new BulletAppState)
// startSignal <- Task(CancelablePromise[Unit]())
app <- Task(new SimpleAppExt(schedulers, bullet)) app <- Task(new SimpleAppExt(schedulers, bullet))
_ <- UIO(JMERunner.runner = Some(app.enqueue _))
_ <- Task { _ <- Task {
val settings = new AppSettings(true) val settings = new AppSettings(true)
settings.setVSync(true) settings.setVSync(true)
@ -88,34 +109,76 @@ object GameApp {
fib <- Task(app.start).executeOn(jmeThread).start fib <- Task(app.start).executeOn(jmeThread).start
_ <- Task.deferFuture(app.started) _ <- Task.deferFuture(app.started)
// _ <- Task.fromCancelablePromise(startSignal) testGameActor <- AkkaUtils.spawnActorL(
_ <- Task(pprint.log(bullet.toString())) new TestGameActor.Props().create,
_ <- Task(println(bullet.physicsSpace.toString())) "testGameActor",
gameApp <- Task(new GameApp(logger, app)) Dispatchers.jmeDispatcher
} yield gameApp -> fib )
}(_._2.cancel) 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)
}
} yield (gameApp, fib)
} {
case (gameApp, fib) =>
fib.cancel >> UIO(JMERunner.runner = None)
}
}
/** object GameApp {}
* 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] = import akka.actor.typed.scaladsl.Behaviors
lock.withPermit(f(obj)) import akka.actor.typed.scaladsl.ActorContext
def get: Task[A] = lock.withPermit(Task(obj)) 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
}
} }
}
object SynchedObject { class TestGameActor(
def apply[A](obj: A) = ctx: ActorContext[TestGameActor.Command],
Semaphore[Task](1).flatMap(lock => Task(new SynchedObject(obj, lock))) 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 { object Ops {

View File

@ -5,7 +5,7 @@ import scala.concurrent.duration._
import akka.actor.typed.SupervisorStrategy import akka.actor.typed.SupervisorStrategy
import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.scaladsl.Behaviors
import wow.doge.mygame.game.TickGenerator.Send 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.implicits._
import wow.doge.mygame.subsystems.events.EventBus import wow.doge.mygame.subsystems.events.EventBus
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
@ -19,14 +19,7 @@ object GameAppActor {
case object Pause extends Command case object Pause extends Command
case object Stop extends Command case object Stop extends Command
case class Props( case class Props(tickEventBus: GameEventBus[TickEvent]) {
// app: SimpleAppExt,
// akkaScheduler: Scheduler,
// schedulers: Schedulers,
// spawnProtocol: ActorRef[SpawnProtocol.Command],
// loggerL: Logger[Task]
tickEventBus: GameEventBus[TickEvent]
) {
def behavior = def behavior =
Behaviors.setup[Command] { ctx => Behaviors.setup[Command] { ctx =>
ctx.log.infoP("Hello from GameAppActor") ctx.log.infoP("Hello from GameAppActor")

View File

@ -1,9 +1,12 @@
package wow.doge.mygame.game package wow.doge.mygame.game
import scala.collection.immutable.Queue import scala.collection.immutable.Queue
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import com.jme3.app.SimpleApplication import com.jme3.app.SimpleApplication
import com.jme3.app.state.AppState import com.jme3.app.state.AppState
import com.jme3.bullet.BulletAppState
import monix.bio.Task import monix.bio.Task
import monix.execution.CancelableFuture import monix.execution.CancelableFuture
import monix.execution.CancelablePromise import monix.execution.CancelablePromise
@ -11,7 +14,6 @@ import monix.execution.Scheduler
import monix.execution.atomic.Atomic import monix.execution.atomic.Atomic
import wow.doge.mygame.executors.GUIExecutorService import wow.doge.mygame.executors.GUIExecutorService
import wow.doge.mygame.executors.Schedulers import wow.doge.mygame.executors.Schedulers
import com.jme3.bullet.BulletAppState
// import wow.doge.mygame.implicits._ // import wow.doge.mygame.implicits._
class SimpleAppExt( class SimpleAppExt(
schedulers: Schedulers, schedulers: Schedulers,
@ -25,27 +27,30 @@ class SimpleAppExt(
*/ */
private val taskQueue2 = Atomic(Queue.empty[MyTask[_]]) 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() private val startSignal: CancelablePromise[Unit] = CancelablePromise()
var cancelToken: Option[() => Future[Unit]] = None
def started: CancelableFuture[Unit] = startSignal.future def started: CancelableFuture[Unit] = startSignal.future
override def simpleInitApp(): Unit = { override def simpleInitApp(): Unit = {
// _bulletAppState = new BulletAppState
stateManager.attach(bulletAppState) stateManager.attach(bulletAppState)
startSignal.success(()) startSignal.success(())
} }
override def simpleUpdate(tpf: Float): Unit = {} override def simpleUpdate(tpf: Float): Unit = {}
override def stop(): Unit = { override def stop(waitFor: Boolean): Unit = {
super.stop() 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] = { def enqueueFuture[T](cb: () => T): CancelableFuture[T] = {
@ -74,7 +79,7 @@ class SimpleAppExt(
enqueue(command) enqueue(command)
} }
lazy val scheduler = Scheduler(JMEExecutorService) val scheduler = Scheduler(JMEExecutorService)
} }
object SimpleAppExt { object SimpleAppExt {
private[game] case class MyTask[T](p: CancelablePromise[T], cb: () => T) private[game] case class MyTask[T](p: CancelablePromise[T], cb: () => T)

View File

@ -10,6 +10,7 @@ import akka.actor.typed.SupervisorStrategy
import akka.actor.typed.scaladsl.ActorContext import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.scaladsl.Behaviors
import akka.util.Timeout import akka.util.Timeout
import cats.syntax.show._
import monix.execution.CancelableFuture import monix.execution.CancelableFuture
import monix.execution.CancelablePromise import monix.execution.CancelablePromise
import wow.doge.mygame.game.subsystems.movement.CanMove 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
import wow.doge.mygame.subsystems.events.TickEvent.RenderTick import wow.doge.mygame.subsystems.events.TickEvent.RenderTick
import wow.doge.mygame.subsystems.movement.ImMovementActor import wow.doge.mygame.subsystems.movement.ImMovementActor
import wow.doge.mygame.utils.GenericTimerActor
object NpcActorSupervisor { object NpcActorSupervisor {
sealed trait Command sealed trait Command
@ -35,10 +37,7 @@ object NpcActorSupervisor {
signal: CancelableFuture[NpcMovementActor.DoneMoving.type] signal: CancelableFuture[NpcMovementActor.DoneMoving.type]
) extends Command ) extends Command
private case object DoneMoving 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 class LogError(err: Throwable) extends Command
private case object NoOp extends Command
private case class MovementFailed(err: Throwable) extends Command private case class MovementFailed(err: Throwable) extends Command
final case class Props( final case class Props(
@ -47,18 +46,19 @@ object NpcActorSupervisor {
initialPos: ImVector3f initialPos: ImVector3f
) { ) {
def behavior = def behavior =
Behaviors.setup[Command] { ctx => Behaviors.withMdc(Map("actorName" -> npcName))(
val npcMovementActor = ctx.spawn( Behaviors.setup[Command] { ctx =>
(npcMovementActorBehavior), val npcMovementActor = ctx.spawn(
s"npc-${npcName}-NpcMovementActor" (npcMovementActorBehavior),
) s"npc-${npcName}-NpcMovementActor"
)
new NpcActorSupervisor(ctx, this, Children(npcMovementActor)) new NpcActorSupervisor(ctx, this, Children(npcMovementActor))
.idle(State()) .idle(State())
} }
)
} }
final case class State( final case class State()
)
final case class Children( final case class Children(
npcMovementActor: ActorRef[NpcMovementActor.Command] npcMovementActor: ActorRef[NpcMovementActor.Command]
) )
@ -79,12 +79,12 @@ class NpcActorSupervisor(
100.millis 100.millis
) )
.behavior, .behavior,
s"npc-John-NpcActorTimer" s"npc-${props.npcName}-NpcActorTimer"
) )
def idle(state: State): Behavior[NpcActorSupervisor.Command] = def idle(state: State): Behavior[NpcActorSupervisor.Command] =
Behaviors.setup { _ => Behaviors.setup { _ =>
ctx.log.debugP("Inside Idle State") ctx.log.debugP(s"npcActor-${props.npcName}: Entered Idle State")
Behaviors.receiveMessage[Command] { Behaviors.receiveMessage[Command] {
case m @ Move(pos) => case m @ Move(pos) =>
ctx.ask( ctx.ask(
@ -99,7 +99,7 @@ class NpcActorSupervisor(
moving(state, move.pos, signal) moving(state, move.pos, signal)
case LogError(err) => case LogError(err) =>
ctx.log.warnP(err.getMessage()) logError(err)
Behaviors.same Behaviors.same
case _ => Behaviors.unhandled case _ => Behaviors.unhandled
} }
@ -111,19 +111,15 @@ class NpcActorSupervisor(
signal: CancelableFuture[NpcMovementActor.DoneMoving.type] signal: CancelableFuture[NpcMovementActor.DoneMoving.type]
): Behavior[NpcActorSupervisor.Command] = ): Behavior[NpcActorSupervisor.Command] =
Behaviors.setup { _ => Behaviors.setup { _ =>
ctx.log.debugP(s"npcActor-${props.npcName}: Entered Moving State")
movementTimer ! GenericTimerActor.Start movementTimer ! GenericTimerActor.Start
// ctx
// .ask(state.npcMovementActor, NpcMovementActor2.MoveTo(targetPos, _))(
// _.fold(LogError(_), MovementResponse(_))
// )
ctx.pipeToSelf(signal) { ctx.pipeToSelf(signal) {
case Success(value) => DoneMoving case Success(value) => DoneMoving
case Failure(exception) => MovementFailed(exception) case Failure(exception) => MovementFailed(exception)
} }
Behaviors.receiveMessagePartial[Command] { Behaviors.receiveMessagePartial[Command] {
case LogError(err) => case LogError(err) =>
ctx.log.error(err.getMessage()) logError(err)
Behaviors.same Behaviors.same
case MovementFailed(err) => case MovementFailed(err) =>
ctx.self ! LogError(err) ctx.self ! LogError(err)
@ -132,6 +128,7 @@ class NpcActorSupervisor(
case m @ Move(pos) => case m @ Move(pos) =>
movementTimer ! GenericTimerActor.Stop movementTimer ! GenericTimerActor.Stop
children.npcMovementActor ! NpcMovementActor.StopMoving children.npcMovementActor ! NpcMovementActor.StopMoving
// new movement request received, cancel previous request
signal.cancel() signal.cancel()
ctx.ask( ctx.ask(
children.npcMovementActor, children.npcMovementActor,
@ -143,15 +140,15 @@ class NpcActorSupervisor(
Behaviors.same Behaviors.same
case InternalMove(move, signal) => case InternalMove(move, signal) =>
moving(state, targetPos, signal) moving(state, targetPos, signal)
case NoOp => Behaviors.same
// case MovementResponse(x: CancelableFuture[_]) =>
// // ctx.pipeToSelf(x)(_.)
case DoneMoving => case DoneMoving =>
movementTimer ! GenericTimerActor.Stop movementTimer ! GenericTimerActor.Stop
idle(state) idle(state)
} }
} }
def logError(err: Throwable) =
ctx.log.errorP(s"npcActor-${props.npcName}: " + err.getMessage)
} }
object NpcMovementActor { object NpcMovementActor {
@ -171,6 +168,7 @@ object NpcMovementActor {
val enqueueR: Function1[() => Unit, Unit], val enqueueR: Function1[() => Unit, Unit],
val initialPos: ImVector3f, val initialPos: ImVector3f,
// val tickEventBus: GameEventBus[TickEvent], // val tickEventBus: GameEventBus[TickEvent],
val npcName: String,
val movable: T val movable: T
) { ) {
def behavior = def behavior =
@ -189,9 +187,7 @@ class NpcMovementActor[T](
def location = cm.location(props.movable) def location = cm.location(props.movable)
def receive( def receive(state: State): Behavior[NpcMovementActor.Command] =
state: State
): Behavior[NpcMovementActor.Command] =
Behaviors.receiveMessagePartial { Behaviors.receiveMessagePartial {
case AskPosition(replyTo) => case AskPosition(replyTo) =>
replyTo ! location replyTo ! location
@ -214,19 +210,25 @@ class NpcMovementActor[T](
Behaviors.receiveMessagePartial { Behaviors.receiveMessagePartial {
case StopMoving => case StopMoving =>
ctx.log.debugP( ctx.log.debugP(
"Position at Stop = " + location.toString show"npcActor-${props.npcName}: Position at Stop = " + location
) )
props.enqueueR(() => cm.stop(props.movable)) props.enqueueR(() => cm.stop(props.movable))
receive(state) receive(state)
case MovementTick => case MovementTick =>
val dst = ImVector3f.dst(targetPos, location) val dst = ImVector3f.manhattanDst(targetPos, location)
if (dst <= 10f) { if (dst <= 10f) {
ctx.self ! StopMoving ctx.self ! StopMoving
reachDestination.success(DoneMoving) reachDestination.success(DoneMoving)
} else { } else {
ctx.log.traceP("Difference = " + dst.toString()) ctx.log.traceP(
ctx.log.traceP("Current pos = " + location.toString()) show"npcActor-${props.npcName}: Difference = " + dst.formatted(
"%.2f"
)
)
ctx.log.traceP(
show"npcActor-${props.npcName}: Current pos = " + location
)
} }
Behaviors.same Behaviors.same
} }
@ -295,7 +297,7 @@ object NpcMovementActorNotUsed {
mainEventBus ! EventBus.Subscribe(npcMovementEl) mainEventBus ! EventBus.Subscribe(npcMovementEl)
tickEventBus ! EventBus.Subscribe(renderTickEl) tickEventBus ! EventBus.Subscribe(renderTickEl)
Behaviors.receiveMessage { msg => Behaviors.same } Behaviors.receiveMessage(msg => Behaviors.same)
} }
} }

View File

@ -1,15 +1,11 @@
package wow.doge.mygame.game.entities package wow.doge.mygame.game.entities
import scala.concurrent.duration._
import scala.util.Random
import akka.actor.typed.ActorRef import akka.actor.typed.ActorRef
import akka.actor.typed.Behavior import akka.actor.typed.Behavior
import akka.actor.typed.LogOptions import akka.actor.typed.LogOptions
import akka.actor.typed.SupervisorStrategy import akka.actor.typed.SupervisorStrategy
import akka.actor.typed.scaladsl.ActorContext import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.scaladsl.Behaviors
import akka.actor.typed.scaladsl.TimerScheduler
import com.typesafe.scalalogging.Logger import com.typesafe.scalalogging.Logger
import org.slf4j.event.Level import org.slf4j.event.Level
import wow.doge.mygame.game.subsystems.movement.CanMove 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
import wow.doge.mygame.subsystems.events.TickEvent.RenderTick import wow.doge.mygame.subsystems.events.TickEvent.RenderTick
import wow.doge.mygame.subsystems.movement.ImMovementActor import wow.doge.mygame.subsystems.movement.ImMovementActor
import wow.doge.mygame.implicits._
object PlayerActorSupervisor { object PlayerActorSupervisor {
sealed trait Command sealed trait Command
final case class Props( final case class Props(
@ -28,15 +24,15 @@ object PlayerActorSupervisor {
imMovementActorBehavior: Behavior[ImMovementActor.Command], imMovementActorBehavior: Behavior[ImMovementActor.Command],
playerCameraActorBehavior: Behavior[PlayerCameraActor.Command] playerCameraActorBehavior: Behavior[PlayerCameraActor.Command]
) { ) {
def behavior[T: CanMove](movable: T) = def behavior =
Behaviors.logMessages( Behaviors.logMessages(
LogOptions() LogOptions()
.withLevel(Level.TRACE) .withLevel(Level.TRACE)
.withLogger( .withLogger(
Logger[PlayerActorSupervisor[T]].underlying Logger[PlayerActorSupervisor].underlying
), ),
Behaviors.setup[Command] { ctx => Behaviors.setup[Command] { ctx =>
ctx.log.info("Hello from PlayerActor") ctx.log.infoP("Starting PlayerActor")
// spawn children actors // spawn children actors
val movementActor = val movementActor =
@ -44,7 +40,7 @@ object PlayerActorSupervisor {
Behaviors Behaviors
.supervise(imMovementActorBehavior) .supervise(imMovementActorBehavior)
.onFailure[Exception](SupervisorStrategy.restart), .onFailure[Exception](SupervisorStrategy.restart),
"playerMovementActorChild" "playerMovementActor"
) )
val playerCameraActor = val playerCameraActor =
@ -55,19 +51,27 @@ object PlayerActorSupervisor {
"playerCameraActorEl" "playerCameraActorEl"
) )
ctx.spawn( val playerMovementEl = ctx.spawn(
PlayerMovementActor Behaviors
.Props( .supervise(PlayerMovementEventListener(movementActor))
movementActor, .onFailure[Exception](SupervisorStrategy.restart),
playerCameraActor, "playerMovementEventHandler"
playerEventBus,
tickEventBus
)
.behavior,
"playerMovementAcor"
) )
//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) playerEventBus ! EventBus.Subscribe(playerCameraEl)
new PlayerActorSupervisor( new PlayerActorSupervisor(
@ -84,18 +88,19 @@ object PlayerActorSupervisor {
movementActor: ActorRef[ImMovementActor.Command] movementActor: ActorRef[ImMovementActor.Command]
) )
} }
class PlayerActorSupervisor[T: CanMove]( class PlayerActorSupervisor(
ctx: ActorContext[PlayerActorSupervisor.Command], ctx: ActorContext[PlayerActorSupervisor.Command],
props: PlayerActorSupervisor.Props, props: PlayerActorSupervisor.Props,
children: PlayerActorSupervisor.Children children: PlayerActorSupervisor.Children
) { ) {
import PlayerActorSupervisor._ import PlayerActorSupervisor._
def receive = def receive =
Behaviors.receiveMessage[Command] { Behaviors
case _ => .receiveMessage[Command] {
// children.movementActor ! ImMovementActor.MovedDown(true) case _ =>
Behaviors.same // children.movementActor ! ImMovementActor.MovedDown(true)
} Behaviors.same
}
} }
object PlayerMovementActor { object PlayerMovementActor {
@ -115,15 +120,17 @@ object PlayerMovementActor {
.onFailure[Exception](SupervisorStrategy.restart), .onFailure[Exception](SupervisorStrategy.restart),
"playerMovementEventHandler" "playerMovementEventHandler"
) )
val renderTickElBehavior =
Behaviors.receiveMessage[RenderTick.type] { val renderTickEl = {
case RenderTick => val behavior =
movementActor ! ImMovementActor.Tick Behaviors.receiveMessage[RenderTick.type] {
// playerCameraActor ! PlayerCameraActor.Tick case RenderTick =>
Behaviors.same movementActor ! ImMovementActor.Tick
} // playerCameraActor ! PlayerCameraActor.Tick
val renderTickEl = Behaviors.same
ctx.spawn(renderTickElBehavior, "playerMovementTickListener") }
ctx.spawn(behavior, "playerMovementTickListener")
}
playerEventBus ! EventBus.Subscribe(playerMovementEl) playerEventBus ! EventBus.Subscribe(playerMovementEl)
tickEventBus ! EventBus.Subscribe(renderTickEl) 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
}
}

View File

@ -4,7 +4,6 @@ import akka.actor.typed.ActorRef
import akka.actor.typed.Scheduler import akka.actor.typed.Scheduler
import akka.actor.typed.SpawnProtocol import akka.actor.typed.SpawnProtocol
import akka.util.Timeout import akka.util.Timeout
import cats.implicits._
import com.jme3.asset.AssetManager import com.jme3.asset.AssetManager
import com.jme3.bullet.BulletAppState import com.jme3.bullet.BulletAppState
import com.jme3.bullet.control.BetterCharacterControl import com.jme3.bullet.control.BetterCharacterControl
@ -13,24 +12,22 @@ import com.jme3.renderer.Camera
import com.jme3.scene.CameraNode import com.jme3.scene.CameraNode
import com.jme3.scene.Geometry import com.jme3.scene.Geometry
import com.jme3.scene.Node import com.jme3.scene.Node
import com.jme3.scene.Spatial
import com.jme3.scene.shape.Box import com.jme3.scene.shape.Box
import com.softwaremill.tagging._ import com.softwaremill.tagging._
import io.odin.Logger import io.odin.Logger
import monix.bio.IO
import monix.bio.Task import monix.bio.Task
import monix.bio.UIO
import wow.doge.mygame.game.GameAppTags import wow.doge.mygame.game.GameAppTags
import wow.doge.mygame.game.SimpleAppExt import wow.doge.mygame.game.SimpleAppExt
import wow.doge.mygame.implicits._ import wow.doge.mygame.implicits._
import wow.doge.mygame.math.ImVector3f import wow.doge.mygame.math.ImVector3f
import wow.doge.mygame.state.MyMaterial
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
import wow.doge.mygame.subsystems.events.PlayerEvent import wow.doge.mygame.subsystems.events.PlayerEvent
import wow.doge.mygame.subsystems.events.TickEvent import wow.doge.mygame.subsystems.events.TickEvent
import wow.doge.mygame.subsystems.movement.ImMovementActor import wow.doge.mygame.subsystems.movement.ImMovementActor
import wow.doge.mygame.utils.AkkaUtils import wow.doge.mygame.utils.AkkaUtils
import wow.doge.mygame.utils.wrappers.jme._ import wow.doge.mygame.utils.wrappers.jme._
import monix.bio.UIO
object PlayerControllerTags { object PlayerControllerTags {
sealed trait PlayerTag sealed trait PlayerTag
sealed trait PlayerCameraNode sealed trait PlayerCameraNode
@ -40,7 +37,9 @@ object PlayerControllerTags {
object PlayerController { object PlayerController {
sealed trait Error sealed trait Error
case class CouldNotCreatePlayerNode(reason: String) extends 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( class Props(
enqueueR: Function1[() => Unit, Unit], enqueueR: Function1[() => Unit, Unit],
@ -65,9 +64,8 @@ object PlayerController {
val playerActorBehavior = { val playerActorBehavior = {
val movementActorBeh = new ImMovementActor.Props( val movementActorBeh = new ImMovementActor.Props(
enqueueR, enqueueR,
playerPhysicsControl,
camera camera
).behavior ).behavior(playerPhysicsControl)
val cameraActorBeh = new PlayerCameraActor.Props( val cameraActorBeh = new PlayerCameraActor.Props(
cameraPivotNode, cameraPivotNode,
enqueueR, enqueueR,
@ -78,36 +76,19 @@ object PlayerController {
tickEventBus, tickEventBus,
movementActorBeh, movementActorBeh,
cameraActorBeh cameraActorBeh
).behavior(playerPhysicsControl) ).behavior
} }
val create: IO[Error, Unit] = val create: UIO[ActorRef[PlayerActorSupervisor.Command]] =
(for { (for {
playerActor <- AkkaUtils.spawnActorL( playerActor <-
playerActorBehavior, AkkaUtils.spawnActorL(playerActorBehavior, "playerActorSupervisor")
"playerActorSupervisor"
)
// _ <- Task(rootNode += playerNode)
// _ <- Task(pprint.log("Physicsspace = " + physicsSpace.toString()))
// _ <- Task(pprint.log("playerNode = " + playerNode.toString()))
_ <- rootNode += playerNode _ <- rootNode += playerNode
_ <- physicsSpace += playerNode _ <- physicsSpace += playerNode
_ <- physicsSpace += playerPhysicsControl _ <- physicsSpace += playerPhysicsControl
_ <- IO { _ = cameraPivotNode += cameraNode
// physicsSpace += playerNode
// physicsSpace += playerPhysicsControl
// rootNode += cameraNode
cameraPivotNode += cameraNode
// playerNode += cameraPivotNode
// rootNode += cameraPivotNode
}
_ <- rootNode += cameraPivotNode _ <- rootNode += cameraPivotNode
} yield ()) } yield playerActor).hideErrors
.onErrorHandleWith(e =>
UIO(e.printStackTrace()) >> IO.raiseError(
GenericError(e.getMessage())
)
)
// .executeOn(appScheduler)
} }
def apply( def apply(
@ -146,11 +127,11 @@ object PlayerController {
val geom = Geometry("playerGeom", b) val geom = Geometry("playerGeom", b)
geom geom
} }
def defaultTexture(assetManager: AssetManager) = // def defaultTexture(assetManager: AssetManager) =
MyMaterial( // MyMaterial(
assetManager = assetManager, // assetManager = assetManager,
path = os.rel / "Common" / "MatDefs" / "Misc" / "Unshaded.j3md" // path = os.rel / "Common" / "MatDefs" / "Misc" / "Unshaded.j3md"
) // )
// new CameraControl(cam) { // new CameraControl(cam) {
// override def controlUpdate(tpf: Float) = { // override def controlUpdate(tpf: Float) = {
@ -164,14 +145,11 @@ object PlayerController {
def defaultCamerNode( def defaultCamerNode(
cam: Camera, cam: Camera,
playerNode: Node, // playerNode: Node,
cameraPivotNode: Node, // cameraPivotNode: Node,
playerPos: ImVector3f playerPos: ImVector3f
) = ) =
new CameraNode( new CameraNode("CameraNode", cam)
"CameraNode",
cam
)
// .withControlDir(ControlDirection.SpatialToCamera) // .withControlDir(ControlDirection.SpatialToCamera)
.withLocalTranslation(ImVector3f(0, 1.5f, 10)) .withLocalTranslation(ImVector3f(0, 1.5f, 10))
.withLookAt(playerPos, ImVector3f.UNIT_Y) .withLookAt(playerPos, ImVector3f.UNIT_Y)
@ -182,66 +160,32 @@ object PlayerController {
// camNode: CameraNode, // camNode: CameraNode,
playerPhysicsControl: BetterCharacterControl playerPhysicsControl: BetterCharacterControl
) = ) =
Either Node("PlayerNode")
.catchNonFatal( .withChildren(playerModel)
Node("PlayerNode") .withLocalTranslation(playerPos)
.withChildren( .withControl(playerPhysicsControl)
// camNode, .taggedWith[PlayerControllerTags.PlayerTag]
playerModel
)
.withLocalTranslation(playerPos)
.withControl(playerPhysicsControl)
)
.map(_.taggedWith[PlayerControllerTags.PlayerTag])
.leftMap(e => PlayerController.CouldNotCreatePlayerNode(e.getMessage()))
def defaultNpcNode( def defaultNpcNode(
assetManager: AssetManager, // assetManager: AssetManager,
// modelPath: os.RelPath, npcModel: Spatial,
initialPos: ImVector3f, initialPos: ImVector3f,
npcPhysicsControl: BetterCharacterControl, npcPhysicsControl: BetterCharacterControl,
npcName: String npcName: String
) = ) =
Either // Either
.catchNonFatal( // .catchNonFatal(
Node(npcName) Node(npcName)
.withChildren( .withChildren(
// assetManager // defaultMesh.withMaterial(defaultTexture(assetManager))
// .loadModel(modelPath) npcModel
// .asInstanceOf[Node]
// .withRotate(0, FastMath.PI, 0)
defaultMesh.withMaterial(defaultTexture(assetManager))
)
.withLocalTranslation(initialPos)
.withControl(npcPhysicsControl)
) )
// .map(_.taggedWith[PlayerControllerTags.PlayerTag]) .withLocalTranslation(initialPos)
// .leftMap(e => PlayerController.CouldNotCreatePlayerNode(e.getMessage())) .withControl(npcPhysicsControl)
// )
def defaultPlayerPhysicsControl = def defaultPlayerPhysicsControl =
new BetterCharacterControl(1.5f, 6f, 1f) new BetterCharacterControl(1.5f, 6f, 1f)
.withJumpForce(ImVector3f(0, 5f, 0)) .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)
// )

View File

@ -11,9 +11,7 @@ import wow.doge.mygame.subsystems.movement.ImMovementActor
object PlayerMovementEventListener { object PlayerMovementEventListener {
import PlayerMovementEvent._ import PlayerMovementEvent._
def apply( def apply(movementActor: ActorRef[ImMovementActor.Command]) =
movementActor: ActorRef[ImMovementActor.Command]
) =
Behaviors.logMessages( Behaviors.logMessages(
LogOptions() LogOptions()
.withLevel(Level.TRACE) .withLevel(Level.TRACE)
@ -50,9 +48,7 @@ object PlayerMovementEventListener {
object PlayerCameraEventListener { object PlayerCameraEventListener {
import PlayerCameraEvent._ import PlayerCameraEvent._
def apply( def apply(playerCameraActor: ActorRef[PlayerCameraActor.Command]) =
playerCameraActor: ActorRef[PlayerCameraActor.Command]
) =
Behaviors.logMessages( Behaviors.logMessages(
LogOptions() LogOptions()
.withLevel(Level.TRACE) .withLevel(Level.TRACE)
@ -63,7 +59,6 @@ object PlayerCameraEventListener {
Behaviors.receiveMessagePartial { Behaviors.receiveMessagePartial {
case CameraMovedUp => case CameraMovedUp =>
playerCameraActor ! PlayerCameraActor.RotateUp playerCameraActor ! PlayerCameraActor.RotateUp
Behaviors.same Behaviors.same
case CameraMovedDown => case CameraMovedDown =>
playerCameraActor ! PlayerCameraActor.RotateDown playerCameraActor ! PlayerCameraActor.RotateDown

View File

@ -18,6 +18,7 @@ import wow.doge.mygame.subsystems.events.PlayerCameraEvent
import wow.doge.mygame.subsystems.events.PlayerEvent import wow.doge.mygame.subsystems.events.PlayerEvent
import wow.doge.mygame.subsystems.events.PlayerMovementEvent import wow.doge.mygame.subsystems.events.PlayerMovementEvent
import wow.doge.mygame.utils.IOUtils._ import wow.doge.mygame.utils.IOUtils._
import monix.bio.UIO
object GameInputHandler { object GameInputHandler {
@ -29,13 +30,13 @@ object GameInputHandler {
) { ) {
def begin = def begin =
for { for {
_ <- Task(setupMovementKeys(inputManager)) _ <- UIO(setupMovementKeys(inputManager))
// _ <- UIO(setupAnalogMovementKeys) // _ <- UIO(setupAnalogMovementKeys)
_ <- Task(setupCameraKeys()) _ <- UIO(setupCameraKeys())
_ <- toIO( _ <- toIO(
me.Task.parSequence( me.Task.parSequence(
Seq( Seq(
generateMovementInputEvents( playerMovementInputEventsGenerator(
inputManager, inputManager,
playerEventBus playerEventBus
).completedL, ).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, inputManager: InputManager,
playerEventBus: GameEventBus[PlayerEvent] playerEventBus: GameEventBus[PlayerEvent]
) = { ) = {
val name = "playerMovementInputEventsGenerator" val name = methodName
inputManager inputManager
.enumObservableAction(PlayerMovementInput) .enumObservableAction(PlayerMovementInput)
// .dump("O") // .dump("O")

View File

@ -3,33 +3,33 @@ import enumeratum.EnumEntry._
import enumeratum._ import enumeratum._
sealed trait PlayerMovementInput extends EnumEntry with UpperSnakecase sealed trait PlayerMovementInput extends EnumEntry with UpperSnakecase
final object PlayerMovementInput extends Enum[PlayerMovementInput] { object PlayerMovementInput extends Enum[PlayerMovementInput] {
val values = findValues val values = findValues
final case object WalkForward extends PlayerMovementInput case object WalkForward extends PlayerMovementInput
final case object WalkRight extends PlayerMovementInput case object WalkRight extends PlayerMovementInput
final case object WalkLeft extends PlayerMovementInput case object WalkLeft extends PlayerMovementInput
final case object WalkBackward extends PlayerMovementInput case object WalkBackward extends PlayerMovementInput
final case object Jump extends PlayerMovementInput case object Jump extends PlayerMovementInput
} }
sealed trait PlayerAnalogMovementInput extends EnumEntry with UpperSnakecase sealed trait PlayerAnalogMovementInput extends EnumEntry with UpperSnakecase
final object PlayerAnalogMovementInput extends Enum[PlayerAnalogMovementInput] { object PlayerAnalogMovementInput extends Enum[PlayerAnalogMovementInput] {
val values = findValues val values = findValues
final case object TurnRight extends PlayerAnalogMovementInput case object TurnRight extends PlayerAnalogMovementInput
final case object TurnLeft extends PlayerAnalogMovementInput case object TurnLeft extends PlayerAnalogMovementInput
} }
sealed trait PlayerCameraInput extends EnumEntry with UpperSnakecase sealed trait PlayerCameraInput extends EnumEntry with UpperSnakecase
final object PlayerCameraInput extends Enum[PlayerCameraInput] { object PlayerCameraInput extends Enum[PlayerCameraInput] {
val values = findValues val values = findValues
final case object CameraRotateLeft extends PlayerCameraInput case object CameraRotateLeft extends PlayerCameraInput
final case object CameraRotateRight extends PlayerCameraInput case object CameraRotateRight extends PlayerCameraInput
final case object CameraRotateUp extends PlayerCameraInput case object CameraRotateUp extends PlayerCameraInput
final case object CameraRotateDown extends PlayerCameraInput case object CameraRotateDown extends PlayerCameraInput
} }
sealed trait MiscInput extends EnumEntry with UpperSnakecase sealed trait MiscInput extends EnumEntry with UpperSnakecase
final object MiscInput extends Enum[MiscInput] { object MiscInput extends Enum[MiscInput] {
val values = findValues val values = findValues
final case object ToggleCursor extends MiscInput case object ToggleCursor extends MiscInput
} }

View File

@ -8,6 +8,7 @@ import com.jme3.math.ColorRGBA
import com.jme3.math.Vector3f import com.jme3.math.Vector3f
import com.jme3.renderer.ViewPort import com.jme3.renderer.ViewPort
import com.jme3.scene.Spatial import com.jme3.scene.Spatial
import monix.bio.UIO
object DefaultGameLevel { object DefaultGameLevel {
def apply( def apply(
@ -22,8 +23,7 @@ object DefaultGameLevel {
throw new NotImplementedError("No fallback sceneshape") throw new NotImplementedError("No fallback sceneshape")
} }
) )
val landscape: RigidBodyControl = val landscape: RigidBodyControl = new RigidBodyControl(sceneShape, 0)
new RigidBodyControl(sceneShape, 0)
viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f)) viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f))
sceneModel.setLocalScale(2f) sceneModel.setLocalScale(2f)
@ -47,4 +47,58 @@ object DefaultGameLevel {
directionalLight = dl 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
}
)
} }

View File

@ -1,21 +1,26 @@
package wow.doge.mygame.game.subsystems.level package wow.doge.mygame.game.subsystems.level
import cats.effect.Resource
import com.jme3.bullet.control.RigidBodyControl import com.jme3.bullet.control.RigidBodyControl
import com.jme3.light.AmbientLight import com.jme3.light.AmbientLight
import com.jme3.light.DirectionalLight import com.jme3.light.DirectionalLight
import com.jme3.scene.Node
import com.jme3.scene.Spatial import com.jme3.scene.Spatial
import com.softwaremill.tagging._ import com.softwaremill.tagging._
import monix.bio.IO
import monix.bio.Task import monix.bio.Task
import monix.bio.UIO
import wow.doge.mygame.game.GameAppTags 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.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 import wow.doge.mygame.utils.wrappers.jme.PhysicsSpace
class GameLevel( class GameLevel(
model: Spatial, val model: Spatial,
physicsControl: RigidBodyControl, val physicsControl: RigidBodyControl,
ambientLight: AmbientLight, val ambientLight: AmbientLight,
directionalLight: DirectionalLight val directionalLight: DirectionalLight
) { ) {
def addToGame( def addToGame(
rootNode: AppNode[Task] @@ GameAppTags.RootNode, rootNode: AppNode[Task] @@ GameAppTags.RootNode,
@ -29,4 +34,56 @@ class GameLevel(
_ <- physicsSpace += physicsControl _ <- physicsSpace += physicsControl
} yield () } 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
)
} }

View File

@ -31,16 +31,17 @@ object ImMovementActor {
// final case object RotateRight extends Movement // final case object RotateRight extends Movement
// final case object RotateLeft extends Movement // final case object RotateLeft extends Movement
final class Props[T: CanMove]( final class Props(
val enqueueR: Function1[() => Unit, Unit], val enqueueR: Function1[() => Unit, Unit],
val movable: T,
// playerMovementEventBus: ActorRef[ // playerMovementEventBus: ActorRef[
// EventBus.Command[PlayerMovementEvent] // EventBus.Command[PlayerMovementEvent]
// ] // ]
val camera: Camera val camera: Camera
) { ) {
def behavior: Behavior[Command] = def behavior[T: CanMove](movable: T): Behavior[Command] =
Behaviors.setup(ctx => new ImMovementActor(ctx, this).receive(State())) Behaviors.setup(ctx =>
new ImMovementActor(ctx, this, movable).receive(State())
)
} }
/** /**
@ -54,7 +55,8 @@ object ImMovementActor {
class ImMovementActor[T]( class ImMovementActor[T](
ctx: ActorContext[ImMovementActor.Command], ctx: ActorContext[ImMovementActor.Command],
props: ImMovementActor.Props[T] props: ImMovementActor.Props,
val movable: T
) { ) {
import ImMovementActor._ import ImMovementActor._
import Methods._ import Methods._
@ -66,19 +68,19 @@ class ImMovementActor[T](
case m: Movement => case m: Movement =>
m match { m match {
case MovedLeft(pressed) => case MovedLeft(pressed) =>
props.enqueueR(() => stopIfNotPressed(pressed, props.movable)) props.enqueueR(() => stopIfNotPressed(pressed, movable))
receive(state = state.modify(_.cardinalDir.left).setTo(pressed)) receive(state = state.modify(_.cardinalDir.left).setTo(pressed))
case MovedUp(pressed) => case MovedUp(pressed) =>
props.enqueueR(() => stopIfNotPressed(pressed, props.movable)) props.enqueueR(() => stopIfNotPressed(pressed, movable))
receive(state = state.modify(_.cardinalDir.up).setTo(pressed)) receive(state = state.modify(_.cardinalDir.up).setTo(pressed))
case MovedRight(pressed) => case MovedRight(pressed) =>
props.enqueueR(() => stopIfNotPressed(pressed, props.movable)) props.enqueueR(() => stopIfNotPressed(pressed, movable))
receive(state = state.modify(_.cardinalDir.right).setTo(pressed)) receive(state = state.modify(_.cardinalDir.right).setTo(pressed))
case MovedDown(pressed) => case MovedDown(pressed) =>
props.enqueueR(() => stopIfNotPressed(pressed, props.movable)) props.enqueueR(() => stopIfNotPressed(pressed, movable))
receive(state = state.modify(_.cardinalDir.down).setTo(pressed)) receive(state = state.modify(_.cardinalDir.down).setTo(pressed))
case Jump => case Jump =>
props.enqueueR(() => cm.jump(props.movable)) props.enqueueR(() => cm.jump(movable))
Behaviors.same Behaviors.same
} }
@ -88,7 +90,7 @@ class ImMovementActor[T](
// if (walkDir != ImVector3f.ZERO) { // if (walkDir != ImVector3f.ZERO) {
val tmp = walkDir * 25f * (1f / 144) val tmp = walkDir * 25f * (1f / 144)
props.enqueueR { () => props.enqueueR { () =>
cm.move(props.movable, tmp) cm.move(movable, tmp)
} }
// } // }
Behaviors.same Behaviors.same

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

View File

@ -39,6 +39,8 @@ import com.simsilica.es.EntityComponent
import com.simsilica.es.EntityData import com.simsilica.es.EntityData
import com.simsilica.es.EntityId import com.simsilica.es.EntityId
import enumeratum._ import enumeratum._
import io.odin.meta.Position
import io.odin.meta.Render
import monix.bio.Task import monix.bio.Task
import monix.bio.UIO import monix.bio.UIO
import monix.execution.Ack import monix.execution.Ack
@ -797,7 +799,7 @@ package object implicits {
} }
implicit class AkkaLoggerExt(private val logger: Logger) extends AnyVal { implicit class AkkaLoggerExt(private val logger: Logger) extends AnyVal {
def logP[T]( private def logP[T](
x: sourcecode.Text[T], x: sourcecode.Text[T],
tag: String = "", tag: String = "",
width: Int = 100, width: Int = 100,
@ -806,12 +808,10 @@ package object implicits {
initialOffset: Int = 0 initialOffset: Int = 0
)(implicit line: sourcecode.Line, fileName: sourcecode.FileName) = { )(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 = val tagStrs =
if (tag.isEmpty) Seq.empty if (tag.isEmpty) Seq.empty
else Seq(fansi.Color.Cyan(tag), fansi.Str(" ")) else Seq(fansi.Color.Cyan(tag), fansi.Str(" "))
// "".slice(1, -1)
val prefix = Seq( val prefix = Seq(
fansi.Color.Magenta(fileName.value), fansi.Color.Magenta(fileName.value),
fansi.Str(":"), fansi.Str(":"),
@ -823,7 +823,6 @@ package object implicits {
fansi.Str.join( fansi.Str.join(
prefix ++ pprint.tokenize(x.value, width, height, indent).toSeq: _* prefix ++ pprint.tokenize(x.value, width, height, indent).toSeq: _*
) )
// x.value
} }
def warnP[T]( 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
}
} }

View File

@ -4,7 +4,6 @@ import cats.effect.Resource
import cats.effect.concurrent.Deferred import cats.effect.concurrent.Deferred
import cats.kernel.Eq import cats.kernel.Eq
import javafx.application.Platform import javafx.application.Platform
import javafx.beans.value.ObservableValue
import monix.bio.Task import monix.bio.Task
import monix.catnap.CancelableF import monix.catnap.CancelableF
import monix.execution.CancelablePromise import monix.execution.CancelablePromise
@ -13,7 +12,6 @@ import monix.{eval => me}
import scalafx.Includes._ import scalafx.Includes._
import scalafx.application.JFXApp import scalafx.application.JFXApp
import scalafx.application.JFXApp.PrimaryStage import scalafx.application.JFXApp.PrimaryStage
import scalafx.beans.property.StringProperty
import scalafx.scene.control.Button import scalafx.scene.control.Button
import scalafx.stage.StageStyle import scalafx.stage.StageStyle
import wow.doge.mygame.executors.Schedulers import wow.doge.mygame.executors.Schedulers
@ -47,23 +45,10 @@ class Launcher private (props: Launcher.Props) {
.observableAction() .observableAction()
.doOnNext(_ => toTask(props.signal.complete(LauncherResult.LaunchGame))) .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 { private lazy val exitButton = new Button {
text = "Exit" text = "Exit"
// text <-- testChangeObs
} }
// exitButton.text.bind
StringProperty("") addListener ((_, _, _) => ())
private lazy val exitAction = private lazy val exitAction =
exitButton exitButton
.observableAction() .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 = def init =
Resource.make(for { Resource.make(for {
_ <- Task(Platform.setImplicitExit(false)) _ <- Task(Platform.setImplicitExit(false))
@ -148,13 +112,7 @@ class Launcher private (props: Launcher.Props) {
) )
) )
.start .start
c <- CancelableF[Task]( c <- CancelableF[Task](combinedFib.cancel)
// Task(println("Cancelling")) >>
// combinedFib.cancel >>
// fxAppStartFib.cancel
// Task.unit
combinedFib.cancel
)
} yield c)(_.cancel) } yield c)(_.cancel)
} }

View File

@ -1,9 +1,10 @@
package wow.doge.mygame.math; 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 { object ImVector3f {
val ZERO = ImVector3f(0, 0, 0) val ZERO = ImVector3f(0, 0, 0)
val UNIT_X = ImVector3f(1, 0, 0) val UNIT_X = ImVector3f(1, 0, 0)
@ -11,5 +12,18 @@ object ImVector3f {
val UNIT_Z = ImVector3f(0, 0, 1) val UNIT_Z = ImVector3f(0, 0, 1)
def dst(v1: ImVector3f, v2: ImVector3f) = 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)})"
}
} }

View File

@ -6,6 +6,20 @@ import akka.actor.typed.ActorRef
import akka.actor.typed.Behavior import akka.actor.typed.Behavior
import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.scaladsl.Behaviors
import akka.event.EventStream 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 * A (typed) event bus
@ -21,36 +35,129 @@ object EventBus {
final case class Subscribe[A, E <: A](subscriber: ActorRef[E])(implicit final case class Subscribe[A, E <: A](subscriber: ActorRef[E])(implicit
classTag: ClassTag[E] classTag: ClassTag[E]
) extends Command[A] { ) extends Command[A] {
def topic: Class[_] = classTag.runtimeClass def topic: Class[_] = classTag.runtimeClass
} }
final case class Unsubscribe[A, E <: A](subscriber: ActorRef[E]) final case class Unsubscribe[A, E <: A](subscriber: ActorRef[E])
extends Command[A] 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 => Behaviors.setup { ctx =>
val eventStream = new EventStream(ctx.system.classicSystem) val eventStream = new EventStream(ctx.system.classicSystem)
implicit val scheduler = ctx.system.scheduler
new EventBus().eventStreamBehavior(eventStream) new EventBus().eventStreamBehavior(eventStream)
} }
def observable[E](eventBus: ActorRef[EventBus.Command[E]])(implicit
timeout: Timeout,
scheduler: Scheduler,
spawnProtocol: ActorRef[SpawnProtocol.Command],
ct: ClassTag[E]
) =
Observable.create[E](OverflowStrategy.DropOld(50)) { sub =>
implicit val s = sub.scheduler
val c = SingleAssignCancelable()
val behavior = Behaviors.receive[E] { (ctx, msg) =>
if (sub.onNext(msg) == Ack.Stop) {
c.cancel()
Behaviors.stopped
} else Behaviors.same
}
val actor =
AkkaUtils
.spawnActorL(
behavior,
s"eventBusObservable-${ct.toString}-${Random.nextLong()}"
)
.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._ import akka.actor.typed.scaladsl.adapter._
private def eventStreamBehavior( private def eventStreamBehavior(
eventStream: akka.event.EventStream eventStream: EventStream
): Behavior[EventBus.Command[B]] = )(implicit
Behaviors.receiveMessage { timeout: Timeout,
case EventBus.Publish(event, name) => scheduler: Scheduler,
eventStream.publish(event) spawnProtocol: ActorRef[SpawnProtocol.Command],
Behaviors.same ct: ClassTag[A]
case s @ EventBus.Subscribe(subscriber) => ): Behavior[EventBus.Command[A]] =
eventStream.subscribe(subscriber.toClassic, s.topic) Behaviors.setup { ctx =>
Behaviors.same Behaviors.receiveMessage {
case EventBus.Unsubscribe(subscriber) => case EventBus.Publish(event, name) =>
eventStream.unsubscribe(subscriber.toClassic) eventStream.publish(event)
Behaviors.same Behaviors.same
case s @ EventBus.Subscribe(subscriber) =>
eventStream.subscribe(subscriber.toClassic, s.topic)
Behaviors.same
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
}
} }
} }

View File

@ -3,7 +3,6 @@ package wow.doge.mygame.subsystems.events
import scala.concurrent.duration._ import scala.concurrent.duration._
import akka.actor.typed.ActorRef import akka.actor.typed.ActorRef
import akka.actor.typed.ActorSystem
import akka.actor.typed.LogOptions import akka.actor.typed.LogOptions
import akka.actor.typed.Props import akka.actor.typed.Props
import akka.actor.typed.SpawnProtocol 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.Event
import wow.doge.mygame.subsystems.events.EventBus import wow.doge.mygame.subsystems.events.EventBus
import wow.doge.mygame.subsystems.events.TickEvent import wow.doge.mygame.subsystems.events.TickEvent
import scala.reflect.ClassTag
import akka.actor.typed.Scheduler
class EventsModule(spawnProtocol: ActorSystem[SpawnProtocol.Command]) { class EventsModule(
implicit val s = spawnProtocol.scheduler scheduler: Scheduler,
spawnProtocol: ActorRef[SpawnProtocol.Command]
) {
implicit val s = scheduler
implicit val sp = spawnProtocol
implicit val timeout = Timeout(1.second) implicit val timeout = Timeout(1.second)
val eventBusLogger = SLogger[EventBus[_]] val eventBusLogger = SLogger[EventBus[_]]
@ -35,7 +39,10 @@ class EventsModule(spawnProtocol: ActorSystem[SpawnProtocol.Command]) {
val mainEventBusTask = createEventBus[Event]("mainEventBus") 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.askL(
SpawnProtocol.Spawn[EventBus.Command[T]]( SpawnProtocol.Spawn[EventBus.Command[T]](
Behaviors.logMessages( Behaviors.logMessages(

View File

@ -27,7 +27,7 @@ object Plugin {
} }
object ModdingSystem { object ModdingSystem {
sealed trait Error extends Serializable with Product sealed trait Error
final case class CouldNotDecode(cause: String) extends Error final case class CouldNotDecode(cause: String) extends Error
final case class ParseFailure(cause: String) extends Error final case class ParseFailure(cause: String) extends Error
final case class FileNotFound(fileName: String) extends Error final case class FileNotFound(fileName: String) extends Error
@ -53,7 +53,7 @@ object ModdingSystem {
def findAndReadPluginFiles( def findAndReadPluginFiles(
dir: os.Path, dir: os.Path,
plugins: ArraySeq[Plugin] plugins: ArraySeq[Plugin]
) = ): (View[(Plugin, Error)], View[(Plugin, String)]) =
plugins plugins
.sortBy(_.priority) .sortBy(_.priority)
.view .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)) filePaths.map(path => os.read(path))
}
def parsePluginFiles(files: View[(Plugin, String)]) = def parsePluginFiles(files: View[(Plugin, String)]) =
files files
@ -99,11 +98,10 @@ object ModdingSystem {
case (json, value) => json.deepMerge(value) case (json, value) => json.deepMerge(value)
} }
def mergePluginData(plugins: View[(Plugin, Json)]) = { def mergePluginData(plugins: View[(Plugin, Json)]) =
foldMerge(plugins.map { foldMerge(plugins.map {
case (p, json) => json case (p, json) => json
}) })
}
def mergePluginDataConsumer = def mergePluginDataConsumer =
Consumer.foldLeft[Json, Json](Json.fromString("empty")) { Consumer.foldLeft[Json, Json](Json.fromString("empty")) {
@ -143,13 +141,13 @@ object ModdingSystem {
.map { case (p, json) => json } .map { case (p, json) => json }
.consumeWith(loadBalancedPluginDataMerger) .consumeWith(loadBalancedPluginDataMerger)
) )
.onErrorHandle(e => GenericError) .hideErrors
_ <- UIO { _ <- UIO {
println(s"Read Successes = ${readSuccesses.to(Seq)}") println(s"Read Successes = ${readSuccesses.to(Seq)}")
println(s"Read Failures = ${readFailures.to(Seq)}") println(s"Read Failures = ${readFailures.to(Seq)}")
println(s"Parse Successes = ${parseSuccesses.to(Seq)}") println(s"Parse Successes = ${parseSuccesses.to(Seq)}")
println(s"Parse Failures = ${parseFailures.to(Seq)}") println(s"Parse Failures = ${parseFailures.to(Seq)}")
println(s"Merged = $res") println(show"Merged = $res")
} }
} yield () } yield ()

View File

@ -181,12 +181,11 @@ class ScriptActor(
os.Path, os.Path,
Either[wow.doge.mygame.state.ScriptActor.Error, Any] Either[wow.doge.mygame.state.ScriptActor.Error, Any]
] ]
): LOL = { ): LOL =
paths match { paths match {
case head :: next => loop(next, scriptsMap + (head -> getScript(head))) case head :: next => loop(next, scriptsMap + (head -> getScript(head)))
case Nil => scriptsMap case Nil => scriptsMap
} }
}
loop(paths, Map.empty) loop(paths, Map.empty)
} }

View File

@ -99,7 +99,6 @@ class ScriptCachingActor(
) { ) {
import com.softwaremill.quicklens._ import com.softwaremill.quicklens._
import ScriptCachingActor._ import ScriptCachingActor._
import Methods._
def receiveMessage(state: State): Behavior[Command] = def receiveMessage(state: State): Behavior[Command] =
Behaviors.receiveMessage { msg => Behaviors.receiveMessage { msg =>
msg match { msg match {
@ -108,7 +107,6 @@ class ScriptCachingActor(
ctx.self ! DelegateToChild(scriptActor, scriptPath, requester) ctx.self ! DelegateToChild(scriptActor, scriptPath, requester)
else else
getOrCompileScript( getOrCompileScript(
ctx,
scriptPath, scriptPath,
state.scriptsMap, state.scriptsMap,
scriptActor, scriptActor,
@ -165,7 +163,6 @@ class ScriptCachingActor(
implicit val timeout = Timeout(15.seconds) implicit val timeout = Timeout(15.seconds)
// child ! ScriptActor.CompileAny(scriptPath, requester) // child ! ScriptActor.CompileAny(scriptPath, requester)
askChildForScriptCompilation( askChildForScriptCompilation(
ctx,
scriptActor, scriptActor,
scriptPath, scriptPath,
requester 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( def getOrCompileScript(
ctx: ActorContext[Command],
scriptPath: os.Path, scriptPath: os.Path,
scriptsMap: ScriptsMap, scriptsMap: ScriptsMap,
scriptActor: ActorRef[ScriptActor.Command], scriptActor: ActorRef[ScriptActor.Command],
requester: ActorRef[ScriptResult] requester: ActorRef[ScriptResult]
) = { ) =
scriptsMap scriptsMap
.get(scriptPath) .get(scriptPath)
.fold { .fold {
@ -236,10 +216,8 @@ private[scriptsystem] object Methods {
ctx.log.debugP("Getting script from cache") ctx.log.debugP("Getting script from cache")
requester ! Right(s) requester ! Right(s)
} }
}
def askChildForScriptCompilation( def askChildForScriptCompilation(
ctx: ActorContext[Command],
scriptActor: ActorRef[ScriptActor.Command], scriptActor: ActorRef[ScriptActor.Command],
scriptPath: os.Path, scriptPath: os.Path,
requester: ActorRef[ScriptResult] requester: ActorRef[ScriptResult]
@ -249,7 +227,7 @@ private[scriptsystem] object Methods {
requester ! value requester ! value
value.fold( value.fold(
err => { err => {
ctx.log.error(err.reason) ctx.log.errorP(err.reason)
NoOp NoOp
}, },
res => { res => {
@ -257,9 +235,22 @@ private[scriptsystem] object Methods {
} }
) )
case Failure(exception) => { case Failure(exception) => {
ctx.log.error(exception.getMessage()) ctx.log.errorP(exception.getMessage)
NoOp 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)
)
} }

View File

@ -7,8 +7,12 @@ import akka.actor.typed.Scheduler
import akka.actor.typed.SpawnProtocol import akka.actor.typed.SpawnProtocol
import akka.util.Timeout import akka.util.Timeout
import wow.doge.mygame.implicits._ import wow.doge.mygame.implicits._
import java.util.concurrent.TimeoutException
import monix.bio.IO
import wow.doge.mygame.AppError.TimeoutError
object AkkaUtils { object AkkaUtils {
def spawnActorOldL[T]( def spawnActorOldL[T](
spawnProtocol: ActorRef[SpawnProtocol.Command], spawnProtocol: ActorRef[SpawnProtocol.Command],
actorName: String, actorName: String,
@ -24,18 +28,23 @@ object AkkaUtils {
) )
def spawnActorL[T]( def spawnActorL[T](
behavior: Behavior[T], behavior: Behavior[T],
actorName: String actorName: String,
props: Props = Props.empty
)(implicit )(implicit
timeout: Timeout, timeout: Timeout,
scheduler: Scheduler, scheduler: Scheduler,
spawnProtocol: ActorRef[SpawnProtocol.Command] spawnProtocol: ActorRef[SpawnProtocol.Command]
) = ) =
spawnProtocol.askL[ActorRef[T]]( spawnProtocol
SpawnProtocol.Spawn( .askL[ActorRef[T]](
behavior, SpawnProtocol.Spawn(
actorName, behavior,
Props.empty, actorName,
_ props,
_
)
) )
) // .onErrorHandleWith {
// case ex: TimeoutException => IO.raiseError(TimeoutError(ex.getMessage))
// }
} }

View File

@ -16,7 +16,7 @@ class GenericConsoleStream[T](
outputStream: OutputStream, outputStream: OutputStream,
val config: GenericConsoleStream.Config = val config: GenericConsoleStream.Config =
GenericConsoleStream.Config.default, GenericConsoleStream.Config.default,
// TODO make this atomic // TODO make this atomic ?
private var _streamable: Option[T] = None private var _streamable: Option[T] = None
)(implicit )(implicit
cs: ConsoleStreamable[T] cs: ConsoleStreamable[T]
@ -27,27 +27,26 @@ class GenericConsoleStream[T](
stble.foreach(s => cs.println(s, text)) stble.foreach(s => cs.println(s, text))
override def println(text: String): Unit = override def println(text: String): Unit =
if (config.exclusive) { if (config.exclusive)
printToStreamable(_streamable, text) printToStreamable(_streamable, text)
} else { else {
defaultOut.println(text) defaultOut.println(text)
printToStreamable(_streamable, text) printToStreamable(_streamable, text)
} }
override def print(text: String): Unit = override def print(text: String): Unit =
if (config.exclusive) { if (config.exclusive)
printToStreamable(_streamable, text) printToStreamable(_streamable, text)
} else { else {
defaultOut.println(text) defaultOut.println(text)
printToStreamable(_streamable, text) printToStreamable(_streamable, text)
} }
def :=(s: T) = { def :=(s: T) =
_streamable match { _streamable match {
case Some(value) => case Some(value) =>
case None => _streamable = Some(s) case None => _streamable = Some(s)
} }
}
} }
object GenericConsoleStream { object GenericConsoleStream {

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

View File

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

View File

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

View File

@ -1,28 +1,23 @@
package wow.doge.mygame.utils.wrappers.jme package wow.doge.mygame.utils.wrappers.jme
import cats.effect.Sync import cats.effect.Sync
import cats.syntax.eq._
import com.jme3.light.Light
import com.jme3.{scene => jmes} import com.jme3.{scene => jmes}
import monix.bio.IO
import monix.bio.Task
import monix.bio.UIO
import monix.execution.annotations.UnsafeBecauseImpure import monix.execution.annotations.UnsafeBecauseImpure
import monix.reactive.Observable import monix.reactive.Observable
import wow.doge.mygame.implicits._ 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) { abstract class NodeWrapper[F[_]: Sync] protected (node: jmes.Node) {
def name = node.getName()
def children: Observable[jmes.Spatial] = node.observableChildren def children: Observable[jmes.Spatial] = node.observableChildren
def attachChild(n: jmes.Spatial): F[Unit] = Sync[F].delay(node.attachChild(n)) def attachChild(n: jmes.Spatial): F[Unit] = Sync[F].delay(node.attachChild(n))
def add(wn: Node[F]): F[Unit] = def add(wn: Node[F]): F[Unit] =
Sync[F].delay(node.attachChild(wn.unsafeDelegate)) Sync[F].delay(node.attachChild(wn.unsafeDelegate))
def remove(n: jmes.Spatial): F[Unit] = def remove(n: jmes.Spatial): F[Unit] = Sync[F].delay(node.detachChild(n))
Sync[F].delay(node.detachChild(n))
def remove(wn: Node[F]): F[Unit] = def remove(wn: Node[F]): F[Unit] =
Sync[F].delay(node.detachChild(wn.unsafeDelegate)) Sync[F].delay(node.detachChild(wn.unsafeDelegate))
def addLight(light: Light) = def addLight(light: Light) =
@ -52,8 +47,7 @@ object NodeWrapper {
} }
final class Node[F[_]: Sync] private (node: jmes.Node) final class Node[F[_]: Sync] private (node: jmes.Node)
extends NodeWrapper[F](node) extends NodeWrapper[F](node) {
with NodeDelegate {
/** /**
* Get the underlying wrapped value * Get the underlying wrapped value
@ -74,3 +68,73 @@ object AppNode {
def apply[F[_]: Sync](n: jmes.Node) = new AppNode[F](n) 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)
}

View File

@ -38,6 +38,8 @@ object PhysicsSpace {
space space
} }
def -=(anyObject: Any) = space.remove(anyObject)
def +=(spatial: jmes.Spatial) = space.addAll(spatial) def +=(spatial: jmes.Spatial) = space.addAll(spatial)
def :+(spatial: jmes.Spatial) = { def :+(spatial: jmes.Spatial) = {
@ -49,5 +51,7 @@ object PhysicsSpace {
space.removeAll(spatial) space.removeAll(spatial)
space space
} }
def -=(spatial: jmes.Spatial) = space.removeAll(spatial)
} }
} }

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

View 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"))
}
}

View File

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