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"
rewrite {
rules = [SortImports, RedundantBraces]
}

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,16 @@
package wow.doge.mygame.game
import scala.concurrent.duration._
import akka.actor.typed.ActorRef
import akka.actor.typed.Behavior
import akka.actor.typed.Props
import akka.actor.typed.SpawnProtocol
import akka.actor.typed.scaladsl.AskPattern._
import akka.util.Timeout
import cats.effect.Resource
import cats.effect.concurrent.Deferred
import com.jme3.app.state.AppStateManager
import com.jme3.asset.AssetManager
import com.jme3.bullet.BulletAppState
import com.jme3.input.InputManager
import com.jme3.scene.Node
@ -12,20 +19,23 @@ import com.jme3.system.AppSettings
import com.softwaremill.tagging._
import com.typesafe.scalalogging.{Logger => SLogger}
import io.odin.Logger
import monix.bio.IO
import monix.bio.Fiber
import monix.bio.Task
import monix.bio.UIO
import monix.catnap.ConcurrentChannel
import monix.catnap.ConsumerF
import monix.catnap.Semaphore
import monix.eval.Coeval
import monix.execution.CancelableFuture
import monix.execution.CancelablePromise
import monix.execution.Scheduler
import wow.doge.mygame.executors.JMERunner
import wow.doge.mygame.executors.Schedulers
import wow.doge.mygame.utils.GenericTimerActor
import wow.doge.mygame.game.subsystems.ui.JFxUI
import wow.doge.mygame.implicits._
import wow.doge.mygame.utils.AkkaUtils
import wow.doge.mygame.utils.wrappers.jme._
sealed trait Error
case object FlyCamNotExists extends Error
import wow.doge.mygame.Dispatchers
object GameAppTags {
sealed trait RootNode
@ -33,47 +43,58 @@ object GameAppTags {
sealed trait GuiNode
}
class GameApp private (logger: Logger[Task], app: SimpleAppExt) {
class GameApp private[game] (
logger: Logger[Task],
app: SimpleAppExt,
gameActor: ActorRef[TestGameActor.Command],
gameSpawnProtocol: ActorRef[SpawnProtocol.Command]
) {
def stateManager: Task[AppStateManager] = Task(app.getStateManager())
def inputManager: Task[InputManager] = Task(app.getInputManager())
def assetManager: Task[AssetManager] = Task(app.getAssetManager())
def assetManager = new AssetManager(app.getAssetManager())
def guiNode = Task(app.getGuiNode().taggedWith[GameAppTags.GuiNode])
def guiNode2 = AppNode[Task](app.getGuiNode()).taggedWith[GameAppTags.GuiNode]
def flyCam =
IO(app.getFlyByCamera()).onErrorHandleWith(_ =>
IO.raiseError(FlyCamNotExists)
)
val guiNode2 = AppNode[Task](app.getGuiNode()).taggedWith[GameAppTags.GuiNode]
def flyCam = Option(app.getFlyByCamera())
def camera = Task(app.getCamera())
def viewPort = Task(app.getViewPort())
// def rootNode = Task(app.getRootNode().taggedWith[GameAppTags.RootNode])
val rootNode =
AppNode[Task](app.getRootNode()).taggedWith[GameAppTags.RootNode]
val physicsSpace = new PhysicsSpace[Task](app.bulletAppState.physicsSpace)
def enqueue(cb: () => Unit) =
app.enqueue(new Runnable {
override def run() = cb()
})
def enqueue(cb: () => Unit) = app.enqueueR(() => cb())
def enqueueL[T](cb: () => T): Task[T] = app.enqueueL(cb)
def spawnGameActor[T](
behavior: Behavior[T],
actorName: String,
props: Props = Dispatchers.jmeDispatcher
)(implicit scheduler: akka.actor.typed.Scheduler) =
AkkaUtils.spawnActorL(behavior, actorName, props)(
2.seconds,
scheduler,
gameSpawnProtocol
)
def scheduler = app.scheduler
def jfxUI = JFxUI(app)
}
object GameApp {
def resource(
class GameAppResource(
logger: Logger[Task],
jmeThread: Scheduler,
schedulers: Schedulers
) =
)(implicit
timeout: Timeout,
scheduler: akka.actor.typed.Scheduler,
spawnProtocol: ActorRef[SpawnProtocol.Command]
) {
def resource: Resource[Task, (GameApp, Fiber[Throwable, Unit])] =
Resource.make {
lazy val bullet = new BulletAppState
for {
// bullet <- Task(new BulletAppState)
// startSignal <- Task(CancelablePromise[Unit]())
app <- Task(new SimpleAppExt(schedulers, bullet))
_ <- UIO(JMERunner.runner = Some(app.enqueue _))
_ <- Task {
val settings = new AppSettings(true)
settings.setVSync(true)
@ -88,34 +109,76 @@ object GameApp {
fib <- Task(app.start).executeOn(jmeThread).start
_ <- Task.deferFuture(app.started)
// _ <- Task.fromCancelablePromise(startSignal)
_ <- Task(pprint.log(bullet.toString()))
_ <- Task(println(bullet.physicsSpace.toString()))
gameApp <- Task(new GameApp(logger, app))
} yield gameApp -> fib
}(_._2.cancel)
/**
* Synchronization wrapper for a mutable object
*
* @param obj the mutable object
* @param lock lock for synchronization
*/
class SynchedObject[A](obj: A, lock: Semaphore[Task]) {
def modify(f: A => Unit): Task[Unit] =
lock.withPermit(Task(f(obj)))
def flatModify(f: A => Task[Unit]): Task[Unit] =
lock.withPermit(f(obj))
def get: Task[A] = lock.withPermit(Task(obj))
testGameActor <- AkkaUtils.spawnActorL(
new TestGameActor.Props().create,
"testGameActor",
Dispatchers.jmeDispatcher
)
sp <- testGameActor.askL(TestGameActor.GetSpawnProtocol(_))
gameApp <- Task(new GameApp(logger, app, testGameActor, sp))
_ <- Task {
val fut = () => testGameActor.ask(TestGameActor.Stop).flatten
app.cancelToken = Some(fut)
}
} yield (gameApp, fib)
} {
case (gameApp, fib) =>
fib.cancel >> UIO(JMERunner.runner = None)
}
}
object SynchedObject {
def apply[A](obj: A) =
Semaphore[Task](1).flatMap(lock => Task(new SynchedObject(obj, lock)))
}
object GameApp {}
import akka.actor.typed.scaladsl.Behaviors
import akka.actor.typed.scaladsl.ActorContext
object TestGameActor {
sealed trait Command
case object Ping extends Command
case class Stop(stopSignal: ActorRef[CancelableFuture[Unit]]) extends Command
case class GetSpawnProtocol(
replyTo: ActorRef[ActorRef[SpawnProtocol.Command]]
) extends Command
import scala.concurrent.duration._
class Props() {
def create =
Behaviors.setup[Command] { ctx =>
ctx.spawn(
GenericTimerActor
.Props(ctx.self, Ping, 1000.millis)
.behavior,
"pingTimer"
) ! GenericTimerActor.Start
new TestGameActor(ctx, this).receive
}
}
}
class TestGameActor(
ctx: ActorContext[TestGameActor.Command],
props: TestGameActor.Props
) {
import TestGameActor._
val stopPromise = CancelablePromise[Unit]()
def receive =
Behaviors
.receiveMessage[Command] {
case Stop(replyTo) =>
ctx.log.infoP("stopping")
replyTo ! stopPromise.future
Behaviors.stopped
case Ping =>
ctx.log.debugP("ping")
Behaviors.same
case GetSpawnProtocol(replyTo) =>
val sp = ctx.spawn(SpawnProtocol(), "gameSpawnProtocol")
replyTo ! sp
Behaviors.same
}
.receiveSignal {
case (_, akka.actor.typed.PostStop) =>
stopPromise.success(())
Behaviors.same
}
}
object Ops {

View File

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

View File

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

View File

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

View File

@ -1,15 +1,11 @@
package wow.doge.mygame.game.entities
import scala.concurrent.duration._
import scala.util.Random
import akka.actor.typed.ActorRef
import akka.actor.typed.Behavior
import akka.actor.typed.LogOptions
import akka.actor.typed.SupervisorStrategy
import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.scaladsl.Behaviors
import akka.actor.typed.scaladsl.TimerScheduler
import com.typesafe.scalalogging.Logger
import org.slf4j.event.Level
import wow.doge.mygame.game.subsystems.movement.CanMove
@ -19,7 +15,7 @@ import wow.doge.mygame.subsystems.events.PlayerEvent
import wow.doge.mygame.subsystems.events.TickEvent
import wow.doge.mygame.subsystems.events.TickEvent.RenderTick
import wow.doge.mygame.subsystems.movement.ImMovementActor
import wow.doge.mygame.implicits._
object PlayerActorSupervisor {
sealed trait Command
final case class Props(
@ -28,15 +24,15 @@ object PlayerActorSupervisor {
imMovementActorBehavior: Behavior[ImMovementActor.Command],
playerCameraActorBehavior: Behavior[PlayerCameraActor.Command]
) {
def behavior[T: CanMove](movable: T) =
def behavior =
Behaviors.logMessages(
LogOptions()
.withLevel(Level.TRACE)
.withLogger(
Logger[PlayerActorSupervisor[T]].underlying
Logger[PlayerActorSupervisor].underlying
),
Behaviors.setup[Command] { ctx =>
ctx.log.info("Hello from PlayerActor")
ctx.log.infoP("Starting PlayerActor")
// spawn children actors
val movementActor =
@ -44,7 +40,7 @@ object PlayerActorSupervisor {
Behaviors
.supervise(imMovementActorBehavior)
.onFailure[Exception](SupervisorStrategy.restart),
"playerMovementActorChild"
"playerMovementActor"
)
val playerCameraActor =
@ -55,19 +51,27 @@ object PlayerActorSupervisor {
"playerCameraActorEl"
)
ctx.spawn(
PlayerMovementActor
.Props(
movementActor,
playerCameraActor,
playerEventBus,
tickEventBus
)
.behavior,
"playerMovementAcor"
val playerMovementEl = ctx.spawn(
Behaviors
.supervise(PlayerMovementEventListener(movementActor))
.onFailure[Exception](SupervisorStrategy.restart),
"playerMovementEventHandler"
)
//init actors
val renderTickEl = {
val behavior =
Behaviors.receiveMessage[RenderTick.type] {
case RenderTick =>
movementActor ! ImMovementActor.Tick
// playerCameraActor ! PlayerCameraActor.Tick
Behaviors.same
}
ctx.spawn(behavior, "playerMovementTickListener")
}
//init listeners
playerEventBus ! EventBus.Subscribe(playerMovementEl)
tickEventBus ! EventBus.Subscribe(renderTickEl)
playerEventBus ! EventBus.Subscribe(playerCameraEl)
new PlayerActorSupervisor(
@ -84,14 +88,15 @@ object PlayerActorSupervisor {
movementActor: ActorRef[ImMovementActor.Command]
)
}
class PlayerActorSupervisor[T: CanMove](
class PlayerActorSupervisor(
ctx: ActorContext[PlayerActorSupervisor.Command],
props: PlayerActorSupervisor.Props,
children: PlayerActorSupervisor.Children
) {
import PlayerActorSupervisor._
def receive =
Behaviors.receiveMessage[Command] {
Behaviors
.receiveMessage[Command] {
case _ =>
// children.movementActor ! ImMovementActor.MovedDown(true)
Behaviors.same
@ -115,15 +120,17 @@ object PlayerMovementActor {
.onFailure[Exception](SupervisorStrategy.restart),
"playerMovementEventHandler"
)
val renderTickElBehavior =
val renderTickEl = {
val behavior =
Behaviors.receiveMessage[RenderTick.type] {
case RenderTick =>
movementActor ! ImMovementActor.Tick
// playerCameraActor ! PlayerCameraActor.Tick
Behaviors.same
}
val renderTickEl =
ctx.spawn(renderTickElBehavior, "playerMovementTickListener")
ctx.spawn(behavior, "playerMovementTickListener")
}
playerEventBus ! EventBus.Subscribe(playerMovementEl)
tickEventBus ! EventBus.Subscribe(renderTickEl)
@ -131,47 +138,3 @@ object PlayerMovementActor {
}
}
}
object GenericTimerActor {
sealed trait Command
final case object Start extends Command
final case object Stop extends Command
private case object Send extends Command
case class TimerKey(seed: Long)
case class Props[T](
target: ActorRef[T],
messageToSend: T,
timeInterval: FiniteDuration
) {
val behavior = Behaviors.withTimers[Command] { timers =>
new GenericTimerActor(timers, TimerKey(Random.nextLong()), this).idle
}
}
}
class GenericTimerActor[T](
timers: TimerScheduler[GenericTimerActor.Command],
timerKey: GenericTimerActor.TimerKey,
props: GenericTimerActor.Props[T]
) {
import GenericTimerActor._
val idle: Behavior[Command] =
Behaviors.receiveMessage {
case Start =>
timers.startTimerWithFixedDelay(timerKey, Send, props.timeInterval)
active
case _ => Behaviors.unhandled
}
val active: Behavior[Command] =
Behaviors.receiveMessagePartial {
case Send =>
props.target ! props.messageToSend
Behaviors.same
case Stop =>
timers.cancel(timerKey)
idle
}
}

View File

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

View File

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

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.PlayerMovementEvent
import wow.doge.mygame.utils.IOUtils._
import monix.bio.UIO
object GameInputHandler {
@ -29,13 +30,13 @@ object GameInputHandler {
) {
def begin =
for {
_ <- Task(setupMovementKeys(inputManager))
_ <- UIO(setupMovementKeys(inputManager))
// _ <- UIO(setupAnalogMovementKeys)
_ <- Task(setupCameraKeys())
_ <- UIO(setupCameraKeys())
_ <- toIO(
me.Task.parSequence(
Seq(
generateMovementInputEvents(
playerMovementInputEventsGenerator(
inputManager,
playerEventBus
).completedL,
@ -128,11 +129,14 @@ object GameInputHandler {
}
def generateMovementInputEvents(
def methodName(implicit enclosing: sourcecode.Enclosing) =
enclosing.value.split(" ")(0).split("""\.""").last
def playerMovementInputEventsGenerator(
inputManager: InputManager,
playerEventBus: GameEventBus[PlayerEvent]
) = {
val name = "playerMovementInputEventsGenerator"
val name = methodName
inputManager
.enumObservableAction(PlayerMovementInput)
// .dump("O")

View File

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

View File

@ -8,6 +8,7 @@ import com.jme3.math.ColorRGBA
import com.jme3.math.Vector3f
import com.jme3.renderer.ViewPort
import com.jme3.scene.Spatial
import monix.bio.UIO
object DefaultGameLevel {
def apply(
@ -22,8 +23,7 @@ object DefaultGameLevel {
throw new NotImplementedError("No fallback sceneshape")
}
)
val landscape: RigidBodyControl =
new RigidBodyControl(sceneShape, 0)
val landscape: RigidBodyControl = new RigidBodyControl(sceneShape, 0)
viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f))
sceneModel.setLocalScale(2f)
@ -47,4 +47,58 @@ object DefaultGameLevel {
directionalLight = dl
)
}
def apply(
assetManager: wow.doge.mygame.utils.wrappers.jme.AssetManager,
viewPort: ViewPort
) =
// for {
// sceneModel <- assetManager.loadModelAs[Node](os.rel / "main.scene")
// sceneShape <- UIO(CollisionShapeFactory.createMeshShape(sceneModel))
// landscape <- UIO(new RigidBodyControl(sceneShape, 0))
// _ <- UIO {
// viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f))
// sceneModel.setLocalScale(2f)
// sceneModel.addControl(landscape)
// }
// al = {
// val al = new AmbientLight()
// al.setColor(ColorRGBA.White.mult(1.3f))
// al
// }
// dl = {
// val dl = new DirectionalLight()
// dl.setColor(ColorRGBA.White)
// dl.setDirection(new Vector3f(2.8f, -2.8f, -2.8f).normalizeLocal())
// dl
// }
// } yield new GameLevel(
// model = sceneModel,
// physicsControl = landscape,
// ambientLight = al,
// directionalLight = dl
// )
GameLevel(
os.rel / "main.scene", {
val al = new AmbientLight()
al.setColor(ColorRGBA.White.mult(1.3f))
al
}, {
val dl = new DirectionalLight()
dl.setColor(ColorRGBA.White)
dl.setDirection(new Vector3f(2.8f, -2.8f, -2.8f).normalizeLocal())
dl
}
)(assetManager).flatMap(gameLevel =>
UIO {
viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f))
gameLevel.model.setLocalScale(2f)
gameLevel.model.addControl(gameLevel.physicsControl)
gameLevel
}
)
}

View File

@ -1,21 +1,26 @@
package wow.doge.mygame.game.subsystems.level
import cats.effect.Resource
import com.jme3.bullet.control.RigidBodyControl
import com.jme3.light.AmbientLight
import com.jme3.light.DirectionalLight
import com.jme3.scene.Node
import com.jme3.scene.Spatial
import com.softwaremill.tagging._
import monix.bio.IO
import monix.bio.Task
import monix.bio.UIO
import wow.doge.mygame.game.GameAppTags
// import wow.doge.mygame.implicits._
import wow.doge.mygame.utils.wrappers.jme.AppNode
import wow.doge.mygame.utils.wrappers.jme.AssetManager
import wow.doge.mygame.utils.wrappers.jme.CollisionShapeFactory
import wow.doge.mygame.utils.wrappers.jme.PhysicsSpace
class GameLevel(
model: Spatial,
physicsControl: RigidBodyControl,
ambientLight: AmbientLight,
directionalLight: DirectionalLight
val model: Spatial,
val physicsControl: RigidBodyControl,
val ambientLight: AmbientLight,
val directionalLight: DirectionalLight
) {
def addToGame(
rootNode: AppNode[Task] @@ GameAppTags.RootNode,
@ -29,4 +34,56 @@ class GameLevel(
_ <- physicsSpace += physicsControl
} yield ()
}
def removeFromGame(
rootNode: AppNode[Task] @@ GameAppTags.RootNode,
physicsSpace: PhysicsSpace[Task]
) = {
for {
_ <- rootNode -= model
_ <- rootNode -= ambientLight
_ <- rootNode -= directionalLight
_ <- physicsSpace -= model
_ <- physicsSpace -= physicsControl
} yield ()
}
def resource(
rootNode: AppNode[Task] @@ GameAppTags.RootNode,
physicsSpace: PhysicsSpace[Task]
) =
Resource.make(this.addToGame(rootNode, physicsSpace))(_ =>
this.removeFromGame(rootNode, physicsSpace)
)
}
object GameLevel {
sealed trait Error
case class AssetLoadError(err: AssetManager.Error) extends Error
case class CollisionShapeCreationFailed(err: CollisionShapeFactory.Error)
extends Error
def apply(
modelPath: os.RelPath,
al: AmbientLight,
dl: DirectionalLight
)(implicit
assetManager: wow.doge.mygame.utils.wrappers.jme.AssetManager
): IO[Error, GameLevel] =
for {
sceneModel <-
assetManager
.loadModelAs[Node](modelPath)
.mapError(AssetLoadError)
sceneShape <-
CollisionShapeFactory
.createMeshShape(sceneModel)
.mapError(CollisionShapeCreationFailed)
landscape <- UIO(new RigidBodyControl(sceneShape, 0))
} yield new GameLevel(
model = sceneModel,
physicsControl = landscape,
ambientLight = al,
directionalLight = dl
)
}

View File

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

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.EntityId
import enumeratum._
import io.odin.meta.Position
import io.odin.meta.Render
import monix.bio.Task
import monix.bio.UIO
import monix.execution.Ack
@ -797,7 +799,7 @@ package object implicits {
}
implicit class AkkaLoggerExt(private val logger: Logger) extends AnyVal {
def logP[T](
private def logP[T](
x: sourcecode.Text[T],
tag: String = "",
width: Int = 100,
@ -806,12 +808,10 @@ package object implicits {
initialOffset: Int = 0
)(implicit line: sourcecode.Line, fileName: sourcecode.FileName) = {
// def joinSeq[T](seq: Seq[T], sep: T): Seq[T] = {
// seq.flatMap(x => Seq(x, sep)).dropRight(1)
// }
val tagStrs =
if (tag.isEmpty) Seq.empty
else Seq(fansi.Color.Cyan(tag), fansi.Str(" "))
// "".slice(1, -1)
val prefix = Seq(
fansi.Color.Magenta(fileName.value),
fansi.Str(":"),
@ -823,7 +823,6 @@ package object implicits {
fansi.Str.join(
prefix ++ pprint.tokenize(x.value, width, height, indent).toSeq: _*
)
// x.value
}
def warnP[T](
@ -858,4 +857,18 @@ package object implicits {
}
}
implicit class odinLoggerExt(private val logger: io.odin.Logger[Task])
extends AnyVal {
def debugU[M](msg: => M)(implicit render: Render[M], position: Position) =
logger.debug(msg).hideErrors
def infoU[M](msg: => M)(implicit render: Render[M], position: Position) =
logger.info(msg).hideErrors
def traceU[M](msg: => M)(implicit render: Render[M], position: Position) =
logger.trace(msg).hideErrors
def warnU[M](msg: => M)(implicit render: Render[M], position: Position) =
logger.warn(msg).hideErrors
def errorU[M](msg: => M)(implicit render: Render[M], position: Position) =
logger.error(msg).hideErrors
}
}

View File

@ -4,7 +4,6 @@ import cats.effect.Resource
import cats.effect.concurrent.Deferred
import cats.kernel.Eq
import javafx.application.Platform
import javafx.beans.value.ObservableValue
import monix.bio.Task
import monix.catnap.CancelableF
import monix.execution.CancelablePromise
@ -13,7 +12,6 @@ import monix.{eval => me}
import scalafx.Includes._
import scalafx.application.JFXApp
import scalafx.application.JFXApp.PrimaryStage
import scalafx.beans.property.StringProperty
import scalafx.scene.control.Button
import scalafx.stage.StageStyle
import wow.doge.mygame.executors.Schedulers
@ -47,23 +45,10 @@ class Launcher private (props: Launcher.Props) {
.observableAction()
.doOnNext(_ => toTask(props.signal.complete(LauncherResult.LaunchGame)))
def testChangeObs(
obs: Observable[(ObservableValue[_ <: String], String, String)]
) =
obs
.doOnNext {
case (x, y, z) => monix.eval.Task.unit
}
// .subscribe()
private lazy val exitButton = new Button {
text = "Exit"
// text <-- testChangeObs
}
// exitButton.text.bind
StringProperty("") addListener ((_, _, _) => ())
private lazy val exitAction =
exitButton
.observableAction()
@ -103,27 +88,6 @@ class Launcher private (props: Launcher.Props) {
)
}
// import cats.syntax.all._
// def init(delay: FiniteDuration = 2000.millis) =
// for {
// _ <- Task(Platform.setImplicitExit(false))
// x <- (Task.unit.start, Task.unit.start).parTupled
// fxAppStartFib <- Task(internal.main(Array.empty)).start
// _ <- Task.sleep(500.millis)
// sceneDragFib <- toIO(sceneDragObservable.completedL).start
// buttonActionsComposedFib <- toIO(
// Observable(launchAction, exitAction).merge
// .doOnNext(_ =>
// me.Task(internal.stage.close()).executeOn(props.schedulers.fx)
// )
// .completedL
// ).start
// c <- CancelableF[Task](
// fxAppStartFib.cancel >> buttonActionsComposedFib.cancel >> sceneDragFib.cancel
// )
// } yield (c)
def init =
Resource.make(for {
_ <- Task(Platform.setImplicitExit(false))
@ -148,13 +112,7 @@ class Launcher private (props: Launcher.Props) {
)
)
.start
c <- CancelableF[Task](
// Task(println("Cancelling")) >>
// combinedFib.cancel >>
// fxAppStartFib.cancel
// Task.unit
combinedFib.cancel
)
c <- CancelableF[Task](combinedFib.cancel)
} yield c)(_.cancel)
}

View File

@ -1,9 +1,10 @@
package wow.doge.mygame.math;
import Math.{sqrt, pow}
import cats.Show
case class ImVector3f(x: Float = 0f, y: Float = 0f, z: Float = 0f)
import math.{abs, pow, sqrt}
case class ImVector3f(x: Float, y: Float, z: Float)
object ImVector3f {
val ZERO = ImVector3f(0, 0, 0)
val UNIT_X = ImVector3f(1, 0, 0)
@ -11,5 +12,18 @@ object ImVector3f {
val UNIT_Z = ImVector3f(0, 0, 1)
def dst(v1: ImVector3f, v2: ImVector3f) =
sqrt(pow(v1.x - v2.x, 2) + pow(v1.y - v2.y, 2) + pow(v1.z - v2.z, 2))
sqrt(
pow((v1.x - v2.x).toDouble, 2) + pow((v1.y - v2.y).toDouble, 2) + pow(
(v1.z - v2.z).toDouble,
2
)
)
def manhattanDst(v1: ImVector3f, v2: ImVector3f) =
abs(v1.x - v2.x) + abs(v1.y - v2.y) + abs(v1.z - v2.z)
implicit val showForImVector3f = new Show[ImVector3f] {
def format(f: Float) = f.formatted("%.2f")
override def show(t: ImVector3f): String =
s"ImVector3f(${format(t.x)},${format(t.y)},${format(t.z)})"
}
}

View File

@ -6,6 +6,20 @@ import akka.actor.typed.ActorRef
import akka.actor.typed.Behavior
import akka.actor.typed.scaladsl.Behaviors
import akka.event.EventStream
import monix.reactive.Observable
import monix.reactive.OverflowStrategy
import monix.execution.cancelables.SingleAssignCancelable
import monix.execution.Ack
import akka.util.Timeout
import akka.actor.typed.Scheduler
import akka.actor.typed.SpawnProtocol
import scala.util.Random
import akka.actor.typed.scaladsl.AskPattern._
import monix.execution.Cancelable
import wow.doge.mygame.utils.AkkaUtils
import wow.doge.mygame.implicits._
import monix.bio.UIO
import wow.doge.mygame.subsystems.events.EventBus.ObservableSubscription
/**
* A (typed) event bus
@ -21,27 +35,113 @@ object EventBus {
final case class Subscribe[A, E <: A](subscriber: ActorRef[E])(implicit
classTag: ClassTag[E]
) extends Command[A] {
def topic: Class[_] = classTag.runtimeClass
}
final case class Unsubscribe[A, E <: A](subscriber: ActorRef[E])
extends Command[A]
def apply[A](): Behavior[EventBus.Command[A]] =
final case class ObservableSubscription[A, E <: A](
replyTo: ActorRef[Observable[E]]
)(implicit
classTag: ClassTag[E]
) extends Command[A] {
def ct = classTag
}
def apply[A: ClassTag]()(implicit
timeout: Timeout,
spawnProtocol: ActorRef[SpawnProtocol.Command]
): Behavior[EventBus.Command[A]] =
Behaviors.setup { ctx =>
val eventStream = new EventStream(ctx.system.classicSystem)
implicit val scheduler = ctx.system.scheduler
new EventBus().eventStreamBehavior(eventStream)
}
def observable[E](eventBus: ActorRef[EventBus.Command[E]])(implicit
timeout: Timeout,
scheduler: Scheduler,
spawnProtocol: ActorRef[SpawnProtocol.Command],
ct: ClassTag[E]
) =
Observable.create[E](OverflowStrategy.DropOld(50)) { sub =>
implicit val s = sub.scheduler
val c = SingleAssignCancelable()
val behavior = Behaviors.receive[E] { (ctx, msg) =>
if (sub.onNext(msg) == Ack.Stop) {
c.cancel()
Behaviors.stopped
} else Behaviors.same
}
val actor =
AkkaUtils
.spawnActorL(
behavior,
s"eventBusObservable-${ct.toString}-${Random.nextLong()}"
)
.tapError {
case ex => UIO(sub.onError(ex))
}
class EventBus[B] {
(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[A] {
import akka.actor.typed.scaladsl.adapter._
private def eventStreamBehavior(
eventStream: akka.event.EventStream
): Behavior[EventBus.Command[B]] =
eventStream: EventStream
)(implicit
timeout: Timeout,
scheduler: Scheduler,
spawnProtocol: ActorRef[SpawnProtocol.Command],
ct: ClassTag[A]
): Behavior[EventBus.Command[A]] =
Behaviors.setup { ctx =>
Behaviors.receiveMessage {
case EventBus.Publish(event, name) =>
eventStream.publish(event)
@ -52,5 +152,12 @@ class EventBus[B] {
case EventBus.Unsubscribe(subscriber) =>
eventStream.unsubscribe(subscriber.toClassic)
Behaviors.same
case s @ ObservableSubscription(replyTo) =>
val obs = EventBus.observable2(
ctx.self
)(timeout, scheduler, spawnProtocol, ct, s.ct)
replyTo ! obs
Behaviors.same
}
}
}

View File

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

View File

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

View File

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

View File

@ -99,7 +99,6 @@ class ScriptCachingActor(
) {
import com.softwaremill.quicklens._
import ScriptCachingActor._
import Methods._
def receiveMessage(state: State): Behavior[Command] =
Behaviors.receiveMessage { msg =>
msg match {
@ -108,7 +107,6 @@ class ScriptCachingActor(
ctx.self ! DelegateToChild(scriptActor, scriptPath, requester)
else
getOrCompileScript(
ctx,
scriptPath,
state.scriptsMap,
scriptActor,
@ -165,7 +163,6 @@ class ScriptCachingActor(
implicit val timeout = Timeout(15.seconds)
// child ! ScriptActor.CompileAny(scriptPath, requester)
askChildForScriptCompilation(
ctx,
scriptActor,
scriptPath,
requester
@ -200,29 +197,12 @@ class ScriptCachingActor(
}
}
}
object ScriptActorPool {
def apply(
poolSize: Int
): PoolRouter[ScriptActor.Command] =
Routers.pool(poolSize = poolSize)(
// make sure the workers are restarted if they fail
Behaviors
.supervise(ScriptActor())
.onFailure[Exception](SupervisorStrategy.restart)
)
}
private[scriptsystem] object Methods {
import ScriptCachingActor._
def getOrCompileScript(
ctx: ActorContext[Command],
scriptPath: os.Path,
scriptsMap: ScriptsMap,
scriptActor: ActorRef[ScriptActor.Command],
requester: ActorRef[ScriptResult]
) = {
) =
scriptsMap
.get(scriptPath)
.fold {
@ -236,10 +216,8 @@ private[scriptsystem] object Methods {
ctx.log.debugP("Getting script from cache")
requester ! Right(s)
}
}
def askChildForScriptCompilation(
ctx: ActorContext[Command],
scriptActor: ActorRef[ScriptActor.Command],
scriptPath: os.Path,
requester: ActorRef[ScriptResult]
@ -249,7 +227,7 @@ private[scriptsystem] object Methods {
requester ! value
value.fold(
err => {
ctx.log.error(err.reason)
ctx.log.errorP(err.reason)
NoOp
},
res => {
@ -257,9 +235,22 @@ private[scriptsystem] object Methods {
}
)
case Failure(exception) => {
ctx.log.error(exception.getMessage())
ctx.log.errorP(exception.getMessage)
NoOp
}
}
}
}
object ScriptActorPool {
def apply(
poolSize: Int
): PoolRouter[ScriptActor.Command] =
Routers.pool(poolSize = poolSize)(
// make sure the workers are restarted if they fail
Behaviors
.supervise(ScriptActor())
.onFailure[Exception](SupervisorStrategy.restart)
)
}

View File

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

View File

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

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
import cats.effect.Sync
import cats.syntax.eq._
import com.jme3.light.Light
import com.jme3.{scene => jmes}
import monix.bio.IO
import monix.bio.Task
import monix.bio.UIO
import monix.execution.annotations.UnsafeBecauseImpure
import monix.reactive.Observable
import wow.doge.mygame.implicits._
import com.jme3.light.Light
trait NodeDelegate {
/**
* Get the underlying wrapped value
*/
@UnsafeBecauseImpure
def unsafeDelegate: jmes.Node
}
abstract class NodeWrapper[F[_]: Sync] protected (node: jmes.Node) {
def name = node.getName()
def children: Observable[jmes.Spatial] = node.observableChildren
def attachChild(n: jmes.Spatial): F[Unit] = Sync[F].delay(node.attachChild(n))
def add(wn: Node[F]): F[Unit] =
Sync[F].delay(node.attachChild(wn.unsafeDelegate))
def remove(n: jmes.Spatial): F[Unit] =
Sync[F].delay(node.detachChild(n))
def remove(n: jmes.Spatial): F[Unit] = Sync[F].delay(node.detachChild(n))
def remove(wn: Node[F]): F[Unit] =
Sync[F].delay(node.detachChild(wn.unsafeDelegate))
def addLight(light: Light) =
@ -52,8 +47,7 @@ object NodeWrapper {
}
final class Node[F[_]: Sync] private (node: jmes.Node)
extends NodeWrapper[F](node)
with NodeDelegate {
extends NodeWrapper[F](node) {
/**
* Get the underlying wrapped value
@ -74,3 +68,73 @@ object AppNode {
def apply[F[_]: Sync](n: jmes.Node) = new AppNode[F](n)
}
abstract class NodeWrapper2 protected (node: jmes.Node) {
import NodeWrapper2._
def name = node.getName()
def children: Observable[jmes.Spatial] = node.observableChildren
def attachChild(n: jmes.Spatial): IO[AddNodeToItselfError.type, Unit] =
IO { node.attachChild(n); () }.onErrorHandleWith {
case ex: IllegalArgumentException =>
if (ex.getMessage === "Cannot add child to itself")
IO.raiseError(AddNodeToItselfError)
else IO.unit
}
def add(wn: Node2): IO[AddNodeToItselfError.type, Unit] =
IO { node.attachChild(wn.unsafeDelegate); () }.onErrorHandleWith {
case ex: IllegalArgumentException =>
if (ex.getMessage === "Cannot add child to itself")
IO.raiseError(AddNodeToItselfError)
else IO.unit
}
def remove(n: jmes.Spatial) = UIO(node.detachChild(n))
def remove(wn: Node2) =
UIO(node.detachChild(wn.unsafeDelegate))
def addLight(light: Light) =
UIO {
node.addLight(light)
}
def removeLight(light: Light) =
UIO {
node.removeLight(light)
}
def asSpatial: Task[jmes.Spatial] = UIO(node)
}
object NodeWrapper2 {
sealed trait Error
case object AddNodeToItselfError extends Error
implicit class NodeOps[F[_]](private val nw: NodeWrapper2) extends AnyVal {
def +=(n: jmes.Spatial) = nw.attachChild(n)
def +=(n: Node2) = nw.add(n)
def -=(n: jmes.Spatial) = nw.remove(n)
def -=(wn: Node2) = nw.remove(wn)
def +=(light: Light) = {
nw.addLight(light)
}
def -=(light: Light) = {
nw.removeLight(light)
}
}
}
final class Node2 private (node: jmes.Node) extends NodeWrapper2(node) {
/**
* Get the underlying wrapped value
*/
@UnsafeBecauseImpure
def unsafeDelegate = node
}
object Node2 {
def apply(name: String) = new Node2(new jmes.Node(name))
def apply(n: jmes.Node) = new Node2(n)
}
final class AppNode2 private (node: jmes.Node) extends NodeWrapper2(node)
object AppNode2 {
def apply(name: String) = new AppNode2(new jmes.Node(name))
def apply(n: jmes.Node) = new AppNode2(n)
}

View File

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

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