Browse Source

Added camera movement, lighting, physics

master
Rohan Sircar 3 years ago
parent
commit
85a28b3c39
  1. 4
      build.sbt
  2. 14
      src/main/resources/application.conf
  3. 4
      src/main/scala/com/jme3/animation/package.scala
  4. 2
      src/main/scala/com/jme3/app/package.scala
  5. 4
      src/main/scala/com/jme3/input/controls/package.scala
  6. 2
      src/main/scala/com/jme3/input/package.scala
  7. 4
      src/main/scala/com/jme3/scene/package.scala
  8. 57
      src/main/scala/org/slf4j/impl/StaticLoggerBuilder.scala
  9. 27
      src/main/scala/wow/doge/mygame/ActorSystemModule.scala
  10. 110
      src/main/scala/wow/doge/mygame/Main.scala
  11. 76
      src/main/scala/wow/doge/mygame/MainModule.scala
  12. 19
      src/main/scala/wow/doge/mygame/executors/Schedulers.scala
  13. 140
      src/main/scala/wow/doge/mygame/game/GameApp.scala
  14. 250
      src/main/scala/wow/doge/mygame/game/GameAppActor.scala
  15. 61
      src/main/scala/wow/doge/mygame/game/GameModule.scala
  16. 106
      src/main/scala/wow/doge/mygame/game/GameSystemsInitializer.scala
  17. 92
      src/main/scala/wow/doge/mygame/game/appstates/MovementActor.scala
  18. 47
      src/main/scala/wow/doge/mygame/game/appstates/PlayerMovementState.scala
  19. 58
      src/main/scala/wow/doge/mygame/game/nodes/PlayerController.scala
  20. 54
      src/main/scala/wow/doge/mygame/game/nodes/PlayerEventListeners.scala
  21. 237
      src/main/scala/wow/doge/mygame/game/subsystems/input/GameInputHandler.scala
  22. 9
      src/main/scala/wow/doge/mygame/game/subsystems/input/InputConstant.scala
  23. 63
      src/main/scala/wow/doge/mygame/game/subsystems/level/DefaultGameLevel.scala
  24. 184
      src/main/scala/wow/doge/mygame/game/subsystems/movement/MovementActor.scala
  25. 392
      src/main/scala/wow/doge/mygame/implicits/package.scala
  26. 104
      src/main/scala/wow/doge/mygame/subsystems/events/EventBus.scala
  27. 15
      src/main/scala/wow/doge/mygame/subsystems/events/Events.scala
  28. 81
      src/main/scala/wow/doge/mygame/subsystems/events/EventsModule.scala
  29. 59
      src/main/scala/wow/doge/mygame/subsystems/events/EventsModule2.scala
  30. 33
      src/main/scala/wow/doge/mygame/subsystems/events/MovementEvents.scala
  31. 14
      src/main/scala/wow/doge/mygame/subsystems/moddingsystem/ModdingSystem.scala
  32. 48
      src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptActor.scala
  33. 71
      src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptCachingActor.scala
  34. 25
      src/main/scala/wow/doge/mygame/utils/AkkaUtils.scala
  35. 12
      src/main/scala/wow/doge/mygame/utils/IOUtils.scala
  36. 14
      src/main/scala/wow/doge/mygame/utils/Settings.scala

4
build.sbt

@ -82,12 +82,14 @@ lazy val root = (project in file(".")).settings(
"com.softwaremill.sttp.client" %% "circe" % "2.2.5",
"com.softwaremill.sttp.client" %% "async-http-client-backend-monix" % "2.2.5",
"com.github.valskalla" %% "odin-monix" % "0.8.1",
"com.github.valskalla" %% "odin-json" % "0.9.1",
"com.softwaremill.macwire" %% "util" % "2.3.7",
"com.softwaremill.macwire" %% "macros" % "2.3.6" % "provided",
"com.softwaremill.macwire" %% "macrosakka" % "2.3.6" % "provided",
"com.github.valskalla" %% "odin-slf4j" % "0.8.1",
"com.softwaremill.quicklens" %% "quicklens" % "1.6.1",
"org.scala-lang.modules" %% "scala-parallel-collections" % "1.0.0-RC1"
"org.scala-lang.modules" %% "scala-parallel-collections" % "1.0.0-RC1",
"com.typesafe.scala-logging" %% "scala-logging" % "3.9.2"
),
// Determine OS version of JavaFX binaries

14
src/main/resources/application.conf

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

4
src/main/scala/com/jme3/animation/package.scala

@ -4,7 +4,7 @@ import com.jme3.input.Action
package object animation {
implicit class AnimChannelWrap(val uval: AnimChannel) extends AnyVal {
implicit class AnimChannelWrap(private val uval: AnimChannel) extends AnyVal {
/**
* Set the current animation that is played by this AnimChannel.
@ -33,7 +33,7 @@ package object animation {
}
implicit class AnimEventListenerWrap(val uval: AnimEventListener) extends AnyVal {
implicit class AnimEventListenerWrap(private val uval: AnimEventListener) extends AnyVal {
/**
* Invoked when an animation "cycle" is done. For non-looping animations,

2
src/main/scala/com/jme3/app/package.scala

@ -5,7 +5,7 @@ package com.jme3
*/
package object app {
implicit class SimpleApplicationWrap(val uval: SimpleApplication) extends AnyVal {
implicit class SimpleApplicationWrap(private val uval: SimpleApplication) extends AnyVal {
//FIXME: proof of concept, remove later
def testWrap: String = uval.hashCode().toString

4
src/main/scala/com/jme3/input/controls/package.scala

@ -5,7 +5,7 @@ package com.jme3.input
*/
package object controls {
implicit class ActionListenerWrap(val uval: ActionListener) extends AnyVal {
implicit class ActionListenerWrap(private val uval: ActionListener) extends AnyVal {
/**
* Called when an input to which this listener is registered to is invoked.
@ -18,7 +18,7 @@ package object controls {
uval.onAction(action.name, keyPressed, tpf)
}
implicit class AnalogListenerWrap(val uval: AnalogListener) extends AnyVal {
implicit class AnalogListenerWrap(private val uval: AnalogListener) extends AnyVal {
/**
* Called to notify the implementation that an analog event has occurred.

2
src/main/scala/com/jme3/input/package.scala

@ -7,7 +7,7 @@ import com.jme3.input.controls.{InputListener, Trigger}
*/
package object input {
implicit class InputManagerWrap(val uval: InputManager) extends AnyVal {
implicit class InputManagerWrap(private val uval: InputManager) extends AnyVal {
def addMapping(action: Action, triggers: Trigger*): Unit =
uval.addMapping(action.name, triggers: _*)
def addListener(listener: InputListener, actions: Action*): Unit =

4
src/main/scala/com/jme3/scene/package.scala

@ -33,14 +33,14 @@ package object scene {
def apply(name: String): Node = new Node(name)
}
implicit class NodeWrap(val uval: Node) extends AnyVal {
implicit class NodeWrap(private val uval: Node) extends AnyVal {
def getControlMaybe[T <: Control](controlType: Class[T]): Option[T] =
Option(uval.getControl(controlType))
}
implicit class SpatialWrap(val uval: Spatial) extends AnyVal {
implicit class SpatialWrap(private val uval: Spatial) extends AnyVal {
def toNode: Either[ClassCastException, Node] =
uval match {

57
src/main/scala/org/slf4j/impl/StaticLoggerBuilder.scala

@ -11,6 +11,8 @@ import cats.arrow.FunctionK
import _root_.monix.execution.Scheduler.Implicits.global
import io.odin.syntax._
import scala.concurrent.duration._
import scala.collection.immutable.ArraySeq
import io.odin.json.Formatter
//effect type should be specified inbefore
//log line will be recorded right after the call with no suspension
@ -26,27 +28,54 @@ class StaticLoggerBinder extends OdinLoggerBinder[IO] {
def apply[A](fa: _root_.monix.bio.Task[A]): IO[A] = fa.to[IO]
}
val (fLogger, release) =
// MainModule.DefaultFileLogger.mapK(monixToCats).allocated.unsafeRunSync()
private lazy val (defaultConsoleLogger, release1) =
consoleLogger[IO](minLevel = Level.Debug)
.withAsync(timeWindow = 1.milliseconds)
.allocated
.unsafeRunSync()
private lazy val (mainFileLogger, release2) =
fileLogger[IO](
"application-log-2.log",
Formatter.json,
minLevel = Level.Debug
).withAsync(timeWindow = 1.milliseconds)
.allocated
.unsafeRunSync()
private lazy val (eventBusFileLogger, release3) =
fileLogger[IO](
"log2.log"
).withAsync(timeWindow = 1.seconds).allocated.unsafeRunSync()
// Runtime
// .getRuntime()
// .addShutdownHook(new Thread {
// release.unsafeRunSync()
// })
scala.sys.addShutdownHook(release.unsafeRunSync())
"eventbus.log",
Formatter.json,
minLevel = Level.Debug
).withAsync(timeWindow = 1.milliseconds)
.allocated
.unsafeRunSync()
{
ArraySeq(release1, release2, release3).foreach(r =>
sys.addShutdownHook(r.unsafeRunSync())
)
}
val loggers: PartialFunction[String, Logger[IO]] = {
case "some.external.package.SpecificClass" =>
consoleLogger[IO](minLevel = Level.Warn) //disable noisy external logs
//disable noisy external logs
defaultConsoleLogger.withMinimalLevel(Level.Warn)
case asyncHttpClient
if asyncHttpClient.startsWith("org.asynchttpclient.netty") =>
consoleLogger[IO](minLevel = Level.Warn)
defaultConsoleLogger.withMinimalLevel(Level.Warn)
// case s
// if s.startsWith(
// "wow.doge.mygame.subsystems.movement.PlayerMovementEventHandler"
// ) =>
// defaultConsoleLogger.withMinimalLevel( Level.Trace) //selectively turn on trace logging for specific classes
case s if s.startsWith("wow.doge.mygame.events.EventBus") =>
defaultConsoleLogger.withMinimalLevel(Level.Debug) |+| eventBusFileLogger
case s if s.startsWith("akka.actor") || s.startsWith("wow.doge.mygame") =>
consoleLogger[IO]() |+| fLogger
defaultConsoleLogger.withMinimalLevel(Level.Debug) |+| mainFileLogger
case _ => //if wildcard case isn't provided, default logger is no-op
consoleLogger[IO]()
defaultConsoleLogger.withMinimalLevel(Level.Debug)
}
}

27
src/main/scala/wow/doge/mygame/ActorSystemModule.scala

@ -0,0 +1,27 @@
package wow.doge.mygame
import akka.actor.typed.ActorSystem
import cats.effect.Resource
import monix.bio.Task
import io.odin.Logger
import wow.doge.mygame.game.GameApp
import wow.doge.mygame.executors.Schedulers
trait ActorSystemModule {
def logger: Logger[Task]
def app: GameApp
def schedulers: Schedulers
lazy val actorsResource =
Resource.make(logger.info("Creating Actor System") >> Task {
ActorSystem(
RootActor(app, schedulers, logger = logger),
name = "GameActorSystem"
)
})(sys =>
logger.info("Shutting down actor system") >> Task(
sys.terminate()
)
)
}

110
src/main/scala/wow/doge/mygame/Main.scala

@ -1,12 +1,9 @@
package wow.doge.mygame
import com.jme3.app.StatsAppState
import monix.bio.Task
import cats.effect.Resource
import io.odin.syntax._
// import io.odin.monix._
import cats.effect.ExitCode
import cats.implicits._
import com.softwaremill.macwire._
@ -15,72 +12,68 @@ import monix.bio.BIOApp
import monix.bio.UIO
import monix.bio.IO
import io.odin._
import wow.doge.mygame.executors.JMERunner
import com.jme3.bullet.BulletAppState
import wow.doge.mygame.implicits._
// import wow.doge.mygame.implicits._
// object Main extends App {
// import java.util.logging.{Logger, Level}
// Logger.getLogger("").setLevel(Level.SEVERE)
// // runner.runCode("""|println("starting scala script engine")""".stripMargin)
// val gameApp = new GameApp(
// // new EntityDataState(),
// // new TestAppState(),
// // new PlayerMovementState(),
// // new FlyCamAppState(),
// new StatsAppState()
// )
// val settings = new AppSettings(true)
// // settings.setVSync(true)
// settings.setFrameRate(144)
// gameApp.setSettings(settings)
// val actorSystem = ActorSystem(RootActor(gameApp), "rootActor")
// // actorSystem.eventStream
// // gameApp.start()
// println("here 1")
// // actorSystem.terminate()
// // JMEExecutorService.shutdown()
// // println("here 2")
// //FIXME remove this
// // System.exit(0)
// }
import wow.doge.mygame.game.GameAppResource
import io.odin.json.Formatter
object Main extends BIOApp with MainModule {
import java.util.logging.{Logger => JLogger, Level}
JLogger.getLogger("").setLevel(Level.SEVERE)
def run(args: List[String]): UIO[ExitCode] = {
lazy val appResource = for {
def appResource =
for {
logger <-
consoleLogger().withAsync(timeWindow = 1.seconds) |+| fileLogger(
"log.log"
).withAsync(timeWindow = 1.seconds)
consoleLogger().withAsync(timeWindow = 1.milliseconds) |+|
fileLogger(
"application-log-1.log",
Formatter.json
).withAsync(timeWindow = 1.milliseconds)
jmeScheduler <- jMESchedulerResource
// consoleTextArea <- Resource.liftF(Task(new TextArea()))
// consoleStream <- wireWith(JFXConsoleStream.textAreaStream _)
gameApp <- {
new BulletAppState()
(gameApp, gameAppFib) <- {
// new BulletAppState()
// bas.setThreadingType(Thr)
gameAppResource(new StatsAppState())
// gameAppResource(new StatsAppState())
wire[GameAppResource].make
}
_ <- Resource.liftF(IO(JMERunner.runner = gameApp))
// _ <- Resource.liftF(IO(JMERunner.runner = gameApp))
// _ <- Resource.liftF(IO {
// new ActorSystemModule {}
// })
actorSystem <- wireWith(actorSystemResource _)
_ <- Resource.liftF(
gameApp.enqueueT(actorSystem ! RootActor.Start(actorSystem.scheduler))
)
// _ <- Resource.liftF {
// Task {
// implicit val sched = actorSystem.scheduler
// implicit val timeout = Timeout(2.seconds)
// // val module = new EventsModule {}
// }
// }
// actorSystem <- wireWith(actorSystemResource2 _)
// (tickEventBus, playerMovementEventBus) <- wireWith(eventBusesResource _)
// rootActor <- wireWith(rootActorResource _)
// inputSystemHandler <- {
// inputHandlerSystemResource(
// GameInputHandler.Props(gameApp.inputManager, playerMovementEventBus)
// )
// }
// gameSystemsInitializer <-
// gameSystemsResource(actorSystem, inputSystemHandler)
// _ <- Resource.liftF(
// Task(gameApp.start()).asyncBoundary
// .executeOn(Scheduler(JMEExecutorService))
// gameApp.enqueueT(rootActor ! RootActor.Start(actorSystem.scheduler))
// )
_ <- Resource.liftF(gameApp.enqueueT(actorSystem ! RootActor.Start))
_ <- Resource.liftF {
IO(gameApp.start())
.executeOn(jmeScheduler)
}
// _ <- Resource.liftF {
// IO(gameApp.start())
// .executeOn(jmeScheduler)
// }
// (_ => IO(gameApp.stop(() => actorSystem ! RootActor.Stop)))
} yield ()
} yield (gameAppFib)
def run(args: List[String]): UIO[ExitCode] = {
// Console.withOut(
// new JFXConsoleStream(
@ -89,16 +82,7 @@ object Main extends BIOApp with MainModule {
// )
// )(())
appResource
.use(_ =>
// Task(gameApp.start())
// .executeOn(Scheduler(JMEExecutorService))
// .asyncBoundary
// Task.never
Task.unit
// >>
// .executeOn(Scheduler(JMEExecutorService))
)
.use(_.join)
.onErrorHandle(_.printStackTrace())
.as(ExitCode.Success)
}

76
src/main/scala/wow/doge/mygame/MainModule.scala

@ -13,6 +13,12 @@ import wow.doge.mygame.executors.ExecutorsModule
import akka.actor.typed.scaladsl.ActorContext
import wow.doge.mygame.executors.Schedulers
import com.softwaremill.macwire._
import akka.actor.typed.SpawnProtocol
import akka.actor.typed.Scheduler
import wow.doge.mygame.utils.AkkaUtils
import scala.concurrent.duration._
import akka.actor.typed.ActorRef
import wow.doge.mygame.implicits._
trait MainModule extends GameModule with ExecutorsModule {
def actorSystemResource(
@ -21,34 +27,74 @@ trait MainModule extends GameModule with ExecutorsModule {
schedulers: Schedulers
): Resource[Task, ActorSystem[RootActor.Command]] =
Resource.make(logger.info("Creating Actor System") >> Task {
ActorSystem(RootActor(app, schedulers), name = "GameActorSystem")
ActorSystem(
wire[RootActor.Props].create(RootActor.State.empty),
name = "GameActorSystem"
)
})(sys =>
logger.info("Shutting down actor system") >> Task(
sys.terminate()
)
)
}
object MainModule {
def actorSystemResource2(
logger: Logger[Task]
): Resource[Task, ActorSystem[SpawnProtocol.Command]] =
Resource.make(logger.info("Creating Actor System") >> Task {
ActorSystem(
SpawnProtocol(),
name = "GameActorSystem2"
)
})(sys =>
logger.info("Shutting down actor system") >> Task(
sys.terminate()
)
)
// import cats.implicits._
import scala.concurrent.duration._
val DefaultFileLogger: Resource[Task, Logger[Task]] =
fileLogger[Task](
"log.log"
).withAsync(timeWindow = 1.seconds)
def rootActorResource(
loggerL: Logger[Task],
app: GameApp,
schedulers: Schedulers,
spawnProtocol: ActorSystem[SpawnProtocol.Command]
): Resource[Task, ActorRef[RootActor.Command]] =
Resource.make(
loggerL.info("Creating Root Actor") >>
AkkaUtils.spawnActorL(
behavior = RootActor(app, schedulers, logger = loggerL),
actorName = "RootActor",
spawnProtocol = spawnProtocol
)(1.seconds, spawnProtocol.scheduler)
)(actor =>
loggerL.info("Shutting down root actor") >> (actor !! RootActor.Stop)
)
}
object RootActor {
sealed trait Command
final case object Start extends Command
final case class Start(akkaScheduler: Scheduler) extends Command
final case object Stop extends Command
final case class Props(
app: GameApp,
schedulers: Schedulers,
logger: Logger[Task]
) {
def create(state: State): Behavior[Command] =
Behaviors.setup { ctx =>
ctx.log.info("Hello from root actor")
wire[RootActor].receive(State.empty)
}
}
final case class State(initialized: Boolean = false)
object State {
val empty = State()
}
def apply(
app: GameApp,
schedulers: Schedulers,
state: State = State()
state: State = State.empty,
logger: Logger[Task]
): Behavior[Command] =
Behaviors.setup { ctx =>
ctx.log.info("Hello from root actor")
@ -59,17 +105,19 @@ object RootActor {
class RootActor(
ctx: ActorContext[RootActor.Command],
app: GameApp,
schedulers: Schedulers
schedulers: Schedulers,
logger: Logger[Task]
) {
import RootActor._
def receive(state: State): Behavior[Command] =
Behaviors.receiveMessage(msg =>
msg match {
case Start =>
case Start(akkaScheduler) =>
if (!state.initialized) {
ctx.log.info("Starting GameAppActor")
val spawnProtocol = ctx.spawn(SpawnProtocol(), "spawnProtocol")
val _ = ctx.spawn(
wireWith(GameAppActor.apply _),
wire[GameAppActor.Props].create,
"gameAppActor"
// DispatcherSelector.fromConfig("jme-dispatcher")
)

19
src/main/scala/wow/doge/mygame/executors/Schedulers.scala

@ -1,10 +1,23 @@
package wow.doge.mygame.executors
import monix.execution.Scheduler
import monix.execution.UncaughtExceptionReporter
import com.typesafe.scalalogging.Logger
final case class Schedulers(
blockingIO: Scheduler = Scheduler.io(),
async: Scheduler = Scheduler.global,
blockingIO: Scheduler = Scheduler
.io()
.withUncaughtExceptionReporter(Schedulers.reporter),
async: Scheduler = Scheduler.global
.withUncaughtExceptionReporter(Schedulers.reporter),
fx: Scheduler = JFXExecutionContexts.fxScheduler
// jme: SchedulerService
.withUncaughtExceptionReporter(Schedulers.reporter)
)
object Schedulers {
val reporter = UncaughtExceptionReporter { ex =>
val logger = Logger[Schedulers]
logger.error("Uncaught exception", ex)
}
}

140
src/main/scala/wow/doge/mygame/game/GameApp.scala

@ -3,62 +3,35 @@ package wow.doge.mygame.game
import com.jme3.app.SimpleApplication
import com.jme3.app.state.AppState
import com.jme3.bullet.BulletAppState
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape
import com.jme3.bullet.control.CharacterControl
import com.jme3.bullet.control.RigidBodyControl
import com.jme3.bullet.util.CollisionShapeFactory
import com.jme3.scene.Spatial
import com.jme3.syntax._
import com.jme3.asset.plugins.ZipLocator
import com.jme3.math.ColorRGBA
import wow.doge.mygame.implicits._
import monix.execution.{CancelablePromise => Promise}
import monix.execution.CancelableFuture
import monix.bio.Task
import monix.execution.Scheduler
import wow.doge.mygame.executors.GUIExecutorService
import monix.reactive.subjects.ConcurrentSubject
import monix.reactive.MulticastStrategy
import monix.reactive.Observable
import monix.execution.atomic.Atomic
import scala.collection.immutable.Queue
class GameApp(
// actorSystem: ActorSystem[SpawnProtocol.Command],
appStates: AppState*
) extends SimpleApplication(appStates: _*) {
import GameApp._
// implicit val timeout = Timeout(10.seconds)
// implicit val scheduler = actorSystem.scheduler
private lazy val sceneModel: Spatial = assetManager.loadModel("main.scene")
private lazy val bulletAppState: BulletAppState = new BulletAppState()
// bulletAppState.setThreadingType(ThreadingType.SEQUENTIAL)
// We set up collision detection for the scene by creating a
// compound collision shape and a static RigidBodyControl with mass zero.
private lazy val sceneShape = CollisionShapeFactory.createMeshShape(
sceneModel.toNode match {
case util.Right(node) => node
case util.Left(ex) =>
throw new NotImplementedError("No fallback sceneshape")
}
)
private lazy val landscape: RigidBodyControl =
new RigidBodyControl(sceneShape, 0)
// We set up collision detection for the player by creating
// a capsule collision shape and a CharacterControl.
// The CharacterControl offers extra settings for
// size, stepheight, jumping, falling, and gravity.
// We also put the player in its starting position.
protected lazy val capsuleShape = new CapsuleCollisionShape(1.5f, 6f, 1)
// private lazy val taskQueueS = new ConcurrentLinkedQueue[MyTask[_]]()
private lazy val taskQueue2 = Atomic(Queue.empty[MyTask[_]])
private lazy val player: CharacterControl =
new CharacterControl(capsuleShape, 0.05f)
private val tickSubject =
ConcurrentSubject[Float](multicast = MulticastStrategy.publish)(
monix.execution.Scheduler.Implicits.global
)
// (scheduler)
override def simpleInitApp(): Unit = {
discard { stateManager.attach(bulletAppState) }
assetManager.registerLocator(
// "src/main/resources/assets/town.zip",
(os.rel / "src" / "main" / "resources" / "assets" / "town.zip"),
classOf[ZipLocator]
)
viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f))
sceneModel.setLocalScale(2f)
sceneModel.addControl(landscape)
discard { rootNode.attachChild(sceneModel) }
bulletAppState.getPhysicsSpace.add(landscape)
bulletAppState.getPhysicsSpace.add(player)
println("gameapp" + Thread.currentThread().getName())
@ -105,6 +78,9 @@ class GameApp(
// wbActor.map(a => a.ask(Greeter.Greet("hello", _)).map(println))
}
def tickObservable: Observable[Float] = tickSubject
override def simpleUpdate(tpf: Float): Unit = {
// val rot2 = rot.fromAngleAxis(FastMath.PI, new Vector3f(0, 0, 1))
// val rotation = geom.getLocalRotation()
@ -113,30 +89,62 @@ class GameApp(
// geom.updateModelBound()
// geom.updateGeometricState()
tickSubject.onNext(tpf)
}
// override def stop(): Unit = {
// actorSystem.terminate()
// super.stop()
// }
// override def start(): Unit = {
// monix.eval.Task(super.start()).runToFuture(Scheduler(JMEExecutorService))
// }
// def start(system: ActorRef[RootActor.Command]) = {
// // system ! RootActor.Start
// super.start()
// }
// override def stop(): Unit = {
// println("stopping")
// }
def stop[T](cb: () => T): Unit = {
println("destroy")
cb()
override def stop(): Unit = {
tickSubject.onComplete()
super.stop()
}
// override def stop(): Unit = {}
def enqueueScala[T](cb: () => T): CancelableFuture[T] = {
val p = Promise[T]()
// p.success(cb())
// taskQueueS.add(MyTask(p, cb))
taskQueue2.transform(_ :+ MyTask(p, cb))
p.future
}
// taskQueue2.transform(_ :+ MyTask(p, cb))
// p
def enqueueL[T](cb: () => T): Task[T] =
// Task(Promise[T]()).flatMap { p =>
// Task(taskQueue2.transform(_ :+ MyTask(p, cb))) >>
// Task.fromCancelablePromise(p)
// }
// Task.fromCancelablePromise {
// val p = Promise[T]()
// taskQueue2.transform(_ :+ MyTask(p, cb))
// p
// }
Task.deferFuture(enqueueScala(cb))
// taskQueueS.add(MyTask(p, cb))
override protected def runQueuedTasks(): Unit = {
// Option(taskQueueS.poll()).foreach {
// case MyTask(p, cb) =>
// p.success(cb())
// }
taskQueue2.transform { current =>
current.dequeueOption.fold(current) {
case (MyTask(p, cb), updated) =>
p.success(cb())
updated
}
}
super.runQueuedTasks()
}
object JMEExecutorService extends GUIExecutorService {
override def execute(command: Runnable) =
enqueue(command)
// new SingleThreadEventExecutor()
// sys.addShutdownHook(JMEExecutorService.shutdown())
}
lazy val scheduler = Scheduler(JMEExecutorService)
}
object GameApp {
private[game] case class MyTask[T](p: Promise[T], cb: () => T)
}

250
src/main/scala/wow/doge/mygame/game/GameAppActor.scala

@ -1,161 +1,86 @@
package wow.doge.mygame.game
import akka.actor.typed.scaladsl.Behaviors
import wow.doge.mygame.state.PlayerMovementState
import com.jme3.scene.Geometry
import wow.doge.mygame.events.EventBus
import wow.doge.mygame.events.Events
import wow.doge.mygame.state.ImMovementActor
import com.jme3.scene.CameraNode
import com.jme3.scene.Node
import com.jme3.renderer.Camera
import wow.doge.mygame.executors.Schedulers
import wow.doge.mygame.game.nodes.PlayerNode
import com.softwaremill.macwire._
import wow.doge.mygame.implicits._
import akka.actor.typed.SpawnProtocol
import akka.actor.typed.ActorRef
import io.odin.Logger
import monix.bio.Task
import akka.actor.typed.Scheduler
import scala.util.Failure
import scala.util.Success
import com.jme3.app.StatsAppState
object GameAppActor {
import Methods._
sealed trait Command
case object XD extends Command
case object ApplicationStarted extends Command
case class ApplicationStartFailed(reason: String) extends Command
case object Stop extends Command
def apply(app: GameApp, schedulers: Schedulers) =
Behaviors.setup[Command] { ctx =>
ctx.log.info("Hello from GameAppActor")
// lazy val b = new Box(1, 1, 1)
// lazy val geom = new Geometry("Box", b)
// lazy val playerNode = new Node("PlayerNode")
// lazy val camNode = new CameraNode("CameraNode", app.getCamera())
// lazy val players = createPlayer(geom, app.getCamera())
// ctx.pipeToSelf(
// app.enqueueF(() => ())(monix.execution.Scheduler.io("aege"))
// ) {
// case x =>
// println("SENDEDEEEEEd")
// XD
// }
// ctx.pipeToSelf(
// createPlayer(
// geom,
// app.getCamera(),
// schedulers.jme
// )
// ) {
// case x =>
// println(x)
// XD
// }
val subscribingActor = ctx.spawn(SubscribingActor(), "subscriber-1")
val tickEventBus =
ctx.spawn(Behaviors.logMessages(EventBus[Events.Tick]()), "eventBus1")
tickEventBus ! EventBus.Subscribe(subscribingActor)
tickEventBus ! EventBus.Publish(Events.PhysicsTick, ctx.self)
// {
// app
// .getInputManager()
// .observableAction("Left")
// .map { action =>
// action.binding.name match {
// case "Left" => Task(println("Pressed left"))
// }
// }
// }
// binding match {
// case "Left" =>
def playerNodeFactory =
PlayerNode(
modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o",
cam = app.camera
) _
// (assetManager = app.assetManager)
{
lazy val playerNode = playerNodeFactory(app.assetManager)
lazy val actor = ctx.spawn(
ImMovementActor(ImMovementActor.Props(app, playerNode)),
"imMovementActor"
)
lazy val state = wire[PlayerMovementState]
app.stateManager.attach(state)
}
Thread.sleep(2000)
app
.getRootNode()
.depthFirst(s =>
// s match {
// case node: Node =>
// println("node" + s.getName() + " children " + node.getChildren())
// case g: Geometry => println(s.getName())
case class Props(
app: GameApp,
akkaScheduler: Scheduler,
schedulers: Schedulers,
spawnProtocol: ActorRef[SpawnProtocol.Command],
loggerL: Logger[Task]
) {
def create =
Behaviors.setup[Command] { ctx =>
ctx.log.info("Hello from GameAppActor")
{
implicit val s = schedulers.async
val initializer: GameSystemsInitializer = wire[GameSystemsInitializer]
schedulers.async.execute(() => initializer.init.runAsyncAndForget)
// ctx.pipeToSelf(application.timed.runToFuture) {
// case Failure(exception) =>
// ApplicationStartFailed(exception.getMessage())
// case Success(value) =>
// println("here applications started")
// ApplicationStarted
// }
println(s.getName())
)
println("----------------")
{
app
.getRootNode()
.observableDepthFirst()
.map(s => s.getName())
// .takeWhileInclusive(_.getName() != "level")
.onErrorHandle(e => e.getMessage())
.foreach(println)(schedulers.async)
}
println("----------------")
{
app
.getRootNode()
.observableBreadthFirst()
.map(s => s.getName())
// .takeWhileInclusive(_.getName() != "level")
.onErrorHandle(e => e.getMessage())
.foreach(println)(schedulers.async)
}
// app.start()
// Behaviors.same
Behaviors.receiveMessage { msg =>
msg match {
case XD =>
ctx.log.info("RECEEEEEIVED")
ctx.log.info(app.camera.toString())
Behaviors.same
case Stop =>
ctx.log.info("Received stop")
Behaviors.stopped
}
Behaviors.receiveMessage { msg =>
msg match {
case Stop =>
ctx.log.info("Received stop")
Behaviors.stopped
case ApplicationStarted =>
ctx.log.info("Application started")
Behaviors.same
case ApplicationStartFailed(reason) =>
ctx.log.error(
s"Failed to start application - $reason"
)
Behaviors.stopped
}
}
}
}
}
object SubscribingActor {
def apply() =
Behaviors.receive[Events.Tick.PhysicsTick.type] { (ctx, msg) =>
ctx.log.debug(s"received event $msg")
Behaviors.same
}
}
object Methods {
def createPlayer(
geom: Geometry,
cam: Camera
): Node = {
val playerNode = new Node("PlayerNode")
lazy val camNode = new CameraNode("CameraNode", cam)
playerNode
.child(camNode)
.child(geom)
playerNode
}
def old() = {
// val movementActor =
@ -170,15 +95,64 @@ object Methods {
// "movementActorTimer"
// )
}
}
object SubscribingActor {
def apply() =
Behaviors.receive[Events.PhysicsTick.type] { (ctx, msg) =>
ctx.log.debug(s"received event $msg")
Behaviors.same
}
def old2() = {
// ctx.log.info("here")
// {
// implicit val s = schedulers.async
// Task
// .parZip2(
// loggerL.info("Test").executeOn(app.scheduler),
// app
// .enqueueL(() => loggerL.info("here 2").executeOn(app.scheduler))
// .flatten
// )
// .runToFuture
// }
// app
// .getRootNode()
// .depthFirst(s =>
// // s match {
// // case node: Node =>
// // println("node" + s.getName() + " children " + node.getChildren())
// // case g: Geometry => println(s.getName())
// // }
// println(s.getName())
// )
// println("----------------")
// {
// app
// .getRootNode()
// .observableDepthFirst()
// .map(s => s.getName())
// // .takeWhileInclusive(_.getName() != "level")
// .onErrorHandle(e => e.getMessage())
// .foreach(println)(schedulers.async)
// }
// println("----------------")
// {
// app
// .getRootNode()
// .observableBreadthFirst()
// .map(s => s.getName())
// // .takeWhileInclusive(_.getName() != "level")
// .onErrorHandle(e => e.getMessage())
// .foreach(println)(schedulers.async)
// }
// app.start()
// Behaviors.same
}
}
// new PlayerMovementState(
// // movementActor,
// // movementActorTimer,

61
src/main/scala/wow/doge/mygame/game/GameModule.scala

@ -1,25 +1,70 @@
package wow.doge.mygame.game
import cats.effect.Resource
import com.jme3.app.state.AppState
import com.jme3.system.AppSettings
import monix.bio.Task
import io.odin.Logger
import akka.actor.typed.ActorRef
import akka.actor.typed.SpawnProtocol
import wow.doge.mygame.game.subsystems.input.GameInputHandler
import monix.bio.IO
import monix.bio.Fiber
import monix.execution.Scheduler
import com.jme3.app.StatsAppState
import com.jme3.app.FlyCamAppState
// import wow.doge.mygame.executors.JMERunner
class GameAppResource(logger: Logger[Task], jmeScheduler: Scheduler) {
def make: Resource[Task, (GameApp, Fiber[Throwable, Unit])] =
Resource.make(
for {
_ <- logger.info("Creating game app")
app <- Task(new GameApp(new StatsAppState()))
_ <- Task {
val settings = new AppSettings(true)
settings.setVSync(true)
settings.setUseInput(true)
// new FlyCamAppState
// settings.setFrameRate(250)
app.setSettings(settings)
// JMERunner.runner = app
app
}
fib <- Task(app.start()).executeOn(jmeScheduler).start
} yield (app -> fib)
)(logger.info("Closing game app") >> _._2.cancel)
}
trait GameModule {
def gameAppResource(appStates: AppState*): Resource[Task, GameApp] =
Resource.liftF {
for {
app <- Task(new GameApp(appStates: _*))
def gameAppResource(
logger: Logger[Task],
jmeScheduler: Scheduler
): Resource[Task, (GameApp, Fiber[Throwable, Unit])] =
Resource.make(
(for {
_ <- logger.info("Creating game app")
app <- Task(new GameApp())
_ <- Task {
val settings = new AppSettings(true)
// settings.setVSync(true)
settings.setFrameRate(144)
settings.setVSync(true)
// settings.setFrameRate(250)
app.setSettings(settings)
// JMERunner.runner = app
app
}
} yield (app)
fib <- Task(app.start()).executeOn(jmeScheduler).start
} yield (app -> fib))
)(_._2.cancel)
def inputHandlerSystemResource(
props: GameInputHandler.Props
): Resource[Task, Task[Unit]] =
Resource.liftF {
Task.evalAsync(props.begin)
}
def gameSystemsResource(
spawnProtocol: ActorRef[SpawnProtocol.Command],
gameSystems: Task[Unit]*
): Resource[Task, List[Unit]] =
Resource.liftF(IO.defer(Task.parSequence(gameSystems)))
}

106
src/main/scala/wow/doge/mygame/game/GameSystemsInitializer.scala

@ -1,13 +1,107 @@
package wow.doge.mygame.game
import wow.doge.mygame.state.MyBaseState
import scala.concurrent.duration._
class GameSystemsInitializer extends MyBaseState {
import akka.actor.typed.ActorRef
import akka.actor.typed.Props
import akka.actor.typed.Scheduler
import akka.actor.typed.SpawnProtocol
import akka.util.Timeout
import com.jme3.asset.plugins.ZipLocator
import com.jme3.bullet.BulletAppState
import com.jme3.bullet.control.BetterCharacterControl
import com.softwaremill.macwire._
import com.softwaremill.tagging._
import io.odin.Logger
import monix.bio.Task
import monix.reactive.Consumer
import wow.doge.mygame.events.EventBus
import wow.doge.mygame.events.EventsModule
import wow.doge.mygame.game.nodes.Player
import wow.doge.mygame.game.nodes.PlayerController
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.subsystems.events.MovementEvent.PlayerMovementEvent
import wow.doge.mygame.subsystems.movement.ImMovementActor
import wow.doge.mygame.utils.IOUtils
override protected def onEnable(): Unit = {}
class GameSystemsInitializer()(
override val spawnProtocol: ActorRef[SpawnProtocol.Command],
override implicit val akkaScheduler: Scheduler,
app: GameApp,
loggerL: Logger[Task]
) extends EventsModule {
override implicit val timeout: Timeout = Timeout(1.second)
override protected def onDisable(): Unit = {}
import GameSystemsInitializer._
override protected def init(): Unit = {}
override def stop(): Unit = {}
def init =
for {
playerMovementEventBus <- playerMovementEventBusTask
inputManager = app.inputManager
bulletAppState = new BulletAppState()
_ <- Task(app.stateManager.attach(bulletAppState))
_ <- Task(
app.assetManager.registerLocator(
// "src/main/resources/assets/town.zip",
(os.rel / "src" / "main" / "resources" / "assets" / "town.zip"),
classOf[ZipLocator]
)
)
_ <- app.enqueueL(() => DefaultGameLevel(app, bulletAppState))
playerController <- app.enqueueL(() =>
PlayerController(
app,
modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o",
cam = app.camera
)(app.assetManager, bulletAppState)
.taggedWith[Player]
)
// _ <- loggerL.debug(playerNode.getName())
// _ <- Task(app.rootNode.attachChild(playerNode))
// playerMovementActor <- wireWith(spawnMovementActor _)
// _ <-
// IOUtils
// .toIO(
// app.tickObservable
// .doOnNext { tpf =>
// IOUtils.toTask(playerMovementActor !! ImMovementActor.Tick(tpf))
// }
// .completedL
// .startAndForget
// .onErrorRestart(3)
// )
_ <- wire[GameInputHandler.Props].begin
} yield ()
}
object GameSystemsInitializer {
def spawnMovementActor(
app: GameApp,
spawnProtocol: ActorRef[SpawnProtocol.Command],
playerNode: BetterCharacterControl @@ Player,
playerMovementEventBus: ActorRef[
EventBus.Command[PlayerMovementEvent]
],
loggerL: Logger[Task]
)(implicit timeout: Timeout, scheduler: Scheduler) =
spawnProtocol.askL[ActorRef[ImMovementActor.Command]](
SpawnProtocol.Spawn(
ImMovementActor.Props(app, playerNode, playerMovementEventBus).create,
"imMovementActor",
Props.empty,
_
)
)
def playerMovementActorTickConsumer(
playerMovementActor: ActorRef[ImMovementActor.Command]
) =
Consumer
.foreachTask[Float](tpf =>
IOUtils.toTask(playerMovementActor !! ImMovementActor.Tick(tpf))
)
// .mapTask()
}

92
src/main/scala/wow/doge/mygame/game/appstates/MovementActor.scala

@ -1,92 +0,0 @@
package wow.doge.mygame.state
import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.Behavior
import akka.actor.typed.scaladsl.Behaviors
import com.softwaremill.quicklens._
import wow.doge.mygame.implicits._
import com.jme3.renderer.Camera
import wow.doge.mygame.math.ImVector3f
trait CanMove[-A] {
def getDirection(cam: Camera, cardinalDir: CardinalDirection): ImVector3f
def move(inst: A, direction: ImVector3f): Unit
}
object ImMovementActor {
sealed trait Command
// final case class Tick(tpf: Float) extends Command
final case class Tick(tpf: Float) extends Command
sealed trait Movement extends Command
final case class MovedLeft(pressed: Boolean) extends Movement
final case class MovedUp(pressed: Boolean) extends Movement
final case class MovedRight(pressed: Boolean) extends Movement
final case class MovedDown(pressed: Boolean) extends Movement
final case class Props[T: CanMove](
app: com.jme3.app.Application,
movable: T
)
/**
* Internal state of the actor
*
* @param cardinalDir Immutable, can be shared as is
* @param walkDirection Immutable
*/
final case class State(
cardinalDir: CardinalDirection = CardinalDirection()
)
def apply[T: CanMove](props: Props[T]): Behavior[Command] =
Behaviors.setup(ctx => {
ctx.log.info("Hello from MovementActor")
new ImMovementActor(ctx, props).receive(State())
})
}
class ImMovementActor[T](
ctx: ActorContext[ImMovementActor.Command],
props: ImMovementActor.Props[T]
) {
import ImMovementActor._
def receive(
state: ImMovementActor.State
)(implicit cm: CanMove[T]): Behavior[Command] =
Behaviors.receiveMessage { msg =>
msg match {
case m: Movement =>
m match {
case MovedLeft(pressed) =>
receive(state = state.modify(_.cardinalDir.left).setTo(pressed))
case MovedUp(pressed) =>
receive(state = state.modify(_.cardinalDir.up).setTo(pressed))
case MovedRight(pressed) =>
receive(state = state.modify(_.cardinalDir.right).setTo(pressed))
case MovedDown(pressed) =>
receive(state = state.modify(_.cardinalDir.down).setTo(pressed))
}
case Tick(tpf) =>
val walkDir =
cm.getDirection(props.app.getCamera(), state.cardinalDir)
if (walkDir != ImVector3f.ZERO) {
val tmp = walkDir * 25f * tpf
// props.app.enqueue(new Runnable {
// override def run(): Unit = {
// cm.move(props.movable, tmp)
// }
// })
props.app.enqueueF {
cm.move(props.movable, tmp)
}
}
Behaviors.same
// receive(state = state.modify(_.walkDirection).setTo(walkDir))
}
}
}

47
src/main/scala/wow/doge/mygame/game/appstates/PlayerMovementState.scala

@ -4,7 +4,6 @@ import scala.concurrent.duration.DurationInt
import com.jme3.input.InputManager
import com.jme3.input.KeyInput
import com.jme3.input.controls.ActionListener
import com.jme3.input.controls.KeyTrigger
import com.jme3.math.Vector3f
@ -16,19 +15,19 @@ import com.jme3.scene.Geometry
import akka.actor.typed.scaladsl.TimerScheduler
import wow.doge.mygame.implicits._
import com.jme3.scene.Node
import com.jme3.syntax._
import wow.doge.mygame.subsystems.movement.ImMovementActor
class PlayerMovementState(
// movementActor: ActorRef[MovementActor.Command],
// movementActorTimer: ActorRef[MovementActorTimer.Command],
imMovementActor: ActorRef[ImMovementActor.Command],
imMovementActor: ActorRef[ImMovementActor.Command]
// geom: Geometry,
// camNode: CameraNode,
playerNode: Node
// playerNode: Node
// gameAppActor: ActorRef[GameAppActor.Command]
) extends MyBaseState
with ActionListener {
// with ActionListener
{
protected lazy val mat = MyMaterial(
assetManager = assetManager,
@ -37,8 +36,8 @@ class PlayerMovementState(
override protected def init(): Unit = {
setupKeys(inputManager)
println("playermovementstate " + Thread.currentThread().getName())
// setupKeys(inputManager)
// println("playermovementstate " + Thread.currentThread().getName())
// geom.setMaterial(mat)
@ -51,7 +50,7 @@ class PlayerMovementState(
// .child(geom)
// // playerNode.children(Seq(camNode, geom))
// }
discard { rootNode.child(playerNode) }
// discard { rootNode.withChild(playerNode) }
// camNode.setLocalTranslation(
// new Vector3f(0, 1.5f, 10)
// )
@ -107,23 +106,23 @@ class PlayerMovementState(
new KeyTrigger(KeyInput.KEY_R),
new KeyTrigger(KeyInput.KEY_RETURN)
)
.withListener(this, "Left")
.withListener(this, "Right")
.withListener(this, "Up")
.withListener(this, "Down")
.withListener(this, "Space")
.withListener(this, "Reset")
// .withListener(this, "Left")
// .withListener(this, "Right")
// .withListener(this, "Up")
// .withListener(this, "Down")
// .withListener(this, "Space")
// .withListener(this, "Reset")
}
def onAction(binding: String, value: Boolean, tpf: Float) =
binding match {
case "Left" => imMovementActor ! ImMovementActor.MovedLeft(value)
case "Right" => imMovementActor ! ImMovementActor.MovedRight(value)
case "Up" => imMovementActor ! ImMovementActor.MovedUp(value)
case "Down" => imMovementActor ! ImMovementActor.MovedDown(value)
case "Space" =>
case _ =>
}
// def onAction(binding: String, value: Boolean, tpf: Float) =
// binding match {
// case "Left" => imMovementActor ! ImMovementActor.MovedLeft(value)
// case "Right" => imMovementActor ! ImMovementActor.MovedRight(value)
// case "Up" => imMovementActor ! ImMovementActor.MovedUp(value)
// case "Down" => imMovementActor ! ImMovementActor.MovedDown(value)
// case "Space" =>
// case _ =>
// }
override protected def onEnable(): Unit = {}

58
src/main/scala/wow/doge/mygame/game/nodes/PlayerNode.scala → src/main/scala/wow/doge/mygame/game/nodes/PlayerController.scala

@ -4,17 +4,23 @@ import com.jme3.scene.Node
import com.jme3.scene.CameraNode
import com.jme3.scene.Geometry
import com.jme3.renderer.Camera
import wow.doge.mygame.implicits._
import com.jme3.asset.AssetManager
import wow.doge.mygame.state.MyMaterial
import com.jme3.math.Vector3f
import com.jme3.scene.control.CameraControl.ControlDirection
import com.jme3.syntax._
import com.jme3.scene.shape.Box
import com.jme3.bullet.control.BetterCharacterControl
import com.jme3.bullet.BulletAppState
import wow.doge.mygame.game.GameApp
import wow.doge.mygame.implicits._
import wow.doge.mygame.math.ImVector3f
import com.jme3.math.FastMath
// class PlayerNode(val name: String) extends Node(name) {}
object PlayerNode {
def defaultMesh() = {
trait Player
object PlayerController {
def defaultMesh = {
lazy val b = new Box(1, 1, 1)
lazy val geom = new Geometry("playerMesh", b)
geom
@ -26,36 +32,40 @@ object PlayerNode {
)
def apply(
app: GameApp,
modelPath: os.RelPath,
cam: Camera
)(assetManager: AssetManager) = {
)(assetManager: AssetManager, bulletAppState: BulletAppState) = {
lazy val playerPos = ImVector3f.ZERO
lazy val playerPhysicsControl = new BetterCharacterControl(1.5f, 6f, 1f)
.withJumpForce(ImVector3f(0, 5f, 0))
lazy val playerNode = new Node("PlayerNode")
lazy val camNode = new CameraNode("CameraNode", cam)
// lazy val camNode = new CameraNode("CameraNode", simpleApp.getCamera())
// camNode.setCamera(simpleApp.getCamera())
.withChildren(
new CameraNode("CameraNode", cam)
.withControlDir(ControlDirection.SpatialToCamera)
.withLocalTranslation(ImVector3f(0, 1.5f, 10))
.withLookAt(playerPos, ImVector3f.UNIT_Y),
assetManager
.loadModel(modelPath)
.asInstanceOf[Node]
.withRotate(0, FastMath.PI, 0)
)
.withLocalTranslation(playerPos)
.withControl(playerPhysicsControl)
val playerModel: Node =
assetManager.loadModel(modelPath).asInstanceOf[Node]
discard {
playerNode
.child(camNode)
.child(playerModel)
// playerNode.children(Seq(camNode, geom))
{
bulletAppState.physicsSpace += playerNode
bulletAppState.physicsSpace += playerPhysicsControl
}
{
camNode.setControlDir(ControlDirection.SpatialToCamera)
camNode.setLocalTranslation(
new Vector3f(0, 1.5f, 10)
)
camNode.lookAt(playerNode.getLocalTranslation(), Vector3f.UNIT_Y)
app.rootNode += playerNode
}
playerNode
playerPhysicsControl
}
def apply(
mesh: Geometry = defaultMesh(),
mesh: Geometry = defaultMesh,
texturePath: os.RelPath =
os.rel / "Common" / "MatDefs" / "Misc" / "Unshaded.j3md",
cam: Camera
@ -76,8 +86,8 @@ object PlayerNode {
// camNode.setCamera(simpleApp.getCamera())
discard {
playerNode
.child(camNode)
.child(mesh)
.withChild(camNode)
.withChild(mesh)
// playerNode.children(Seq(camNode, geom))
}

54
src/main/scala/wow/doge/mygame/game/nodes/PlayerEventListeners.scala

@ -0,0 +1,54 @@
package wow.doge.mygame.game.nodes
import akka.actor.typed.ActorRef
import akka.actor.typed.scaladsl.Behaviors
import wow.doge.mygame.subsystems.movement.ImMovementActor
import org.slf4j.event.Level
import akka.actor.typed.LogOptions
import wow.doge.mygame.subsystems.events.MovementEvent.PlayerMovementEvent
import com.typesafe.scalalogging.Logger
object PlayerMovementEventHandler {
import PlayerMovementEvent._
def apply(movementActor: ActorRef[ImMovementActor.Command]) =
Behaviors.logMessages(
LogOptions()
.withLevel(Level.TRACE)
.withLogger(
Logger[PlayerMovementEventHandler.type].underlying
),
Behaviors.setup[PlayerMovementEvent](ctx =>
Behaviors.receiveMessage {
case PlayerMovedLeft(pressed) =>
movementActor ! ImMovementActor.MovedLeft(pressed)
Behaviors.same
case PlayerMovedRight(pressed) =>
movementActor ! ImMovementActor.MovedRight(pressed)
Behaviors.same
case PlayerMovedForward(pressed) =>
movementActor ! ImMovementActor.MovedUp(pressed)
Behaviors.same
case PlayerMovedBackward(pressed) =>
movementActor ! ImMovementActor.MovedDown(pressed)
Behaviors.same
case PlayerJumped =>
movementActor ! ImMovementActor.Jump
Behaviors.same
case PlayerRotatedRight =>
// ctx.log.warn("right rotate not implemented yet")
movementActor ! ImMovementActor.RotateRight
Behaviors.same
case PlayerRotatedLeft =>
// ctx.log.warn("left rotate not implemented yet")
movementActor ! ImMovementActor.RotateLeft
Behaviors.same
case PlayerCameraUp =>
ctx.log.warn("camera up not implemented yet")
Behaviors.same
case PlayerCameraDown =>
ctx.log.warn("camera down not implemented yet")
Behaviors.same
}
)
)
}

237
src/main/scala/wow/doge/mygame/game/subsystems/input/GameInputHandler.scala

@ -0,0 +1,237 @@
package wow.doge.mygame.game.subsystems.input
import com.jme3.input.InputManager
import wow.doge.mygame.implicits._
import akka.actor.typed.ActorRef
import wow.doge.mygame.events.EventBus
import com.jme3.input.KeyInput
import com.jme3.input.controls.KeyTrigger
import monix.bio.UIO
import wow.doge.mygame.utils.IOUtils._
import wow.doge.mygame.subsystems.events.MovementEvent.PlayerMovementEvent
import scala.concurrent.duration._
import com.jme3.input.controls.MouseAxisTrigger
import com.jme3.input.MouseInput
// class GameInputHandler(
// inputManager: InputManager
// // inputEventBus: InputEventBus
// ) {}
object GameInputHandler {
final case class Props(
inputManager: InputManager,
playerMovementEventBus: ActorRef[
EventBus.Command[PlayerMovementEvent]
]
) {
def begin =
for {
_ <- UIO(setupKeys(inputManager))
_ <- toIO(
generateMovementInputEvents(
inputManager,
playerMovementEventBus
).completedL.startAndForget
)
_ <- toIO(
generateRotateEvents(
inputManager,
playerMovementEventBus
).completedL.startAndForget
)
_ <- toIO(
generateCameraEvents(
inputManager,
playerMovementEventBus
).completedL.startAndForget
)
} yield ()
}
def setupKeys(inputManager: InputManager) =
inputManager
.withMapping(
"Left",
new KeyTrigger(KeyInput.KEY_A)
// new KeyTrigger(KeyInput.KEY_LEFT)
)
.withMapping(
"Right",
new KeyTrigger(KeyInput.KEY_D)
// new KeyTrigger(KeyInput.KEY_RIGHT)
)
.withMapping(
"Up",
new KeyTrigger(KeyInput.KEY_W)
// new KeyTrigger(KeyInput.KEY_UP)
)
.withMapping(
"Down",
new KeyTrigger(KeyInput.KEY_S)
// new KeyTrigger(KeyInput.KEY_DOWN)
)
.withMapping(
"Jump",
new KeyTrigger(KeyInput.KEY_SPACE)
)
.withMapping(
"ROTATE_RIGHT",
new KeyTrigger(KeyInput.KEY_RIGHT),
new MouseAxisTrigger(MouseInput.AXIS_X, true)
)
.withMapping(
"ROTATE_LEFT",
new KeyTrigger(KeyInput.KEY_LEFT),
new MouseAxisTrigger(MouseInput.AXIS_X, false)
)
.withMapping(
"CAMERA_UP",
// new KeyTrigger(KeyInput.KEY_LEFT),
new MouseAxisTrigger(MouseInput.AXIS_Y, false)
)
.withMapping(
"CAMERA_DOWN",
// new KeyTrigger(KeyInput.KEY_LEFT),
new MouseAxisTrigger(MouseInput.AXIS_Y, true)
)
.setCursorVisible(false)
def generateMovementInputEvents(
inputManager: InputManager,
playerMovementEventBus: ActorRef[
EventBus.Command[PlayerMovementEvent]
]
) = {
val name = "movementInputEventsGenerator"
inputManager
.observableAction(
"Left",
"Right",
"Up",
"Down",
"Jump",
"ROTATE_RIGHT",
"ROTATE_LEFT"
)
// .dump("O")
.doOnNext { action =>
action.binding.name match {
case "Left" =>
toTask(
playerMovementEventBus !! EventBus.Publish(
PlayerMovementEvent.PlayerMovedLeft(pressed = action.value),
name
)
)
case "Right" =>
toTask(
playerMovementEventBus !! EventBus.Publish(
PlayerMovementEvent.PlayerMovedRight(pressed = action.value),
name
)
)
case "Up" =>
toTask(
playerMovementEventBus !! EventBus.Publish(
PlayerMovementEvent.PlayerMovedForward(pressed = action.value),
name
)
)
case "Down" =>
toTask(
playerMovementEventBus !! EventBus.Publish(
PlayerMovementEvent.PlayerMovedBackward(pressed = action.value),
name
)
)
case "Jump" if action.value =>
toTask(
playerMovementEventBus !! EventBus.Publish(
PlayerMovementEvent.PlayerJumped,
name
)
)
case _ => monix.eval.Task.unit
}
}
}
def generateRotateEvents(
inputManager: InputManager,
playerMovementEventBus: ActorRef[
EventBus.Command[PlayerMovementEvent]
]
) = {
val name = "rotateMovementEventsGenerator"
inputManager
.analogObservable("ROTATE_RIGHT", "ROTATE_LEFT")
.sample(1.millis)
.mapEval(analogEvent =>
analogEvent.binding.name match {
case "ROTATE_RIGHT" =>
toTask(
playerMovementEventBus !! EventBus.Publish(
PlayerMovementEvent.PlayerRotatedRight,
name
)
)
case "ROTATE_LEFT" =>
toTask(
playerMovementEventBus !! EventBus.Publish(
PlayerMovementEvent.PlayerRotatedLeft,
name
)
)
case _ => monix.eval.Task.unit
}
)
}
def generateCameraEvents(
inputManager: InputManager,
playerMovementEventBus: ActorRef[
EventBus.Command[PlayerMovementEvent]
]
) = {
val name = "cameraMovementEventsGenerator"
inputManager
.analogObservable("CAMERA_UP", "CAMERA_DOWN")
.sample(1.millis)
.mapEval(analogEvent =>
analogEvent.binding.name match {
case "CAMERA_UP" =>
toTask(
playerMovementEventBus !! EventBus.Publish(
PlayerMovementEvent.PlayerCameraUp,
name
)
)
case "CAMERA_DOWN" =>
toTask(
playerMovementEventBus !! EventBus.Publish(
PlayerMovementEvent.PlayerCameraDown,
name
)
)
case _ => monix.eval.Task.unit
}
)
}
// def bindMappings(inputManager: InputManager, mappings: ActionMapping*) = {
// inputManager
// .observableAction(mappings.map(_.name): _*)
// .doOnNext(action =>
// mappings.map(m =>
// if (action.binding.name == m.name) toTask(m.cb(action))
// else monix.eval.Task.unit
// )
// )
// }
}
// case class ActionMapping(name: String, cb: ActionEvent => Task[Unit])

9
src/main/scala/wow/doge/mygame/game/subsystems/input/InputConstant.scala

@ -0,0 +1,9 @@
package wow.doge.mygame.game.subsystems.input
object InputConstants {
val PLAYER_MOVE_LEFT = "PLAYER_MOVE_LEFT"
val PLAYER_MOVE_RIGHT = "PLAYER_MOVE_RIGHT"
val PLAYER_MOVE_FORWARD = "PLAYER_MOVE_FORWARD"
val PLAYER_MOVE_BACKWARD = "PLAYER_MOVE_BACKWARD"
val PLAYER_JUMP = "PLAYER_JUMP "
}

63
src/main/scala/wow/doge/mygame/game/subsystems/level/DefaultGameLevel.scala

@ -0,0 +1,63 @@
package wow.doge.mygame.game.subsystems.level
import com.jme3.bullet.BulletAppState
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape
import com.jme3.bullet.control.CharacterControl
import com.jme3.bullet.control.RigidBodyControl
import com.jme3.bullet.util.CollisionShapeFactory
import com.jme3.scene.Spatial
import wow.doge.mygame.implicits._
import wow.doge.mygame.game.GameApp
import com.jme3.syntax._
import com.jme3.math.ColorRGBA
import com.jme3.light.DirectionalLight
import com.jme3.math.Vector3f
import com.jme3.light.AmbientLight
object DefaultGameLevel {
// lazy valbulletAppState: BulletAppState
// bulletAppState.setThreadingType(ThreadingType.SEQUENTIAL)
// We set up collision detection for the scene by creating a
// compound collision shape and a static RigidBodyControl with mass zero.
// We set up collision detection for the player by creating
// a capsule collision shape and a CharacterControl.
// The CharacterControl offers extra settings for
// size, stepheight, jumping, falling, and gravity.
// We also put the player in its starting position.
lazy val capsuleShape = new CapsuleCollisionShape(1.5f, 6f, 1)
lazy val player: CharacterControl =
new CharacterControl(capsuleShape, 0.05f)
def apply(app: GameApp, bulletAppState: BulletAppState) = {
lazy val sceneModel: Spatial = app.assetManager.loadModel("main.scene")
lazy val sceneShape = CollisionShapeFactory.createMeshShape(
sceneModel.toNode match {
case util.Right(node) => node
case util.Left(ex) =>
throw new NotImplementedError("No fallback sceneshape")
}
)
lazy val landscape: RigidBodyControl =
new RigidBodyControl(sceneShape, 0)
// // discard { app.stateManager.attach(bulletAppState) }
app.viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f))
sceneModel.setLocalScale(2f)
sceneModel.addControl(landscape)
discard { app.rootNode.attachChild(sceneModel) }
bulletAppState.getPhysicsSpace.add(landscape)
bulletAppState.getPhysicsSpace.add(player)
val al = new AmbientLight();
al.setColor(ColorRGBA.White.mult(1.3f));
app.rootNode.addLight(al);
val dl = new DirectionalLight();
dl.setColor(ColorRGBA.White);
dl.setDirection(new Vector3f(2.8f, -2.8f, -2.8f).normalizeLocal());
app.rootNode.addLight(dl);
}
}

184
src/main/scala/wow/doge/mygame/game/subsystems/movement/MovementActor.scala

@ -0,0 +1,184 @@
package wow.doge.mygame.subsystems.movement
import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.Behavior
import akka.actor.typed.scaladsl.Behaviors
import com.softwaremill.quicklens._
import wow.doge.mygame.implicits._
import com.jme3.renderer.Camera
import wow.doge.mygame.math.ImVector3f
import wow.doge.mygame.game.GameApp
import akka.actor.typed.ActorRef
import wow.doge.mygame.events.EventBus
import com.jme3.math.Vector3f
import wow.doge.mygame.state.CardinalDirection
import wow.doge.mygame.subsystems.events.MovementEvent.PlayerMovementEvent
import akka.actor.typed.LogOptions
import com.typesafe.scalalogging.Logger
import org.slf4j.event.Level
sealed trait RotateDir
object RotateDir {
case object Left extends RotateDir
case object Right extends RotateDir
}
trait CanMove[-A] {
// def getDirection(cam: Camera, cardinalDir: CardinalDirection): ImVector3f
def move(inst: A, direction: ImVector3f): Unit
def jump(inst: A): Unit
def stop(inst: A): Unit
def rotate(inst: A, rotateDir: RotateDir): Unit
}
object ImMovementActor {
sealed trait Command
// final case class Tick(tpf: Float) extends Command
final case class Tick(tpf: Float) extends Command
sealed trait Movement extends Command
final case class MovedLeft(pressed: Boolean) extends Movement
final case class MovedUp(pressed: Boolean) extends Movement
final case class MovedRight(pressed: Boolean) extends Movement
final case class MovedDown(pressed: Boolean) extends Movement
final case object Jump extends Movement
final case object RotateRight extends Movement
final case object RotateLeft extends Movement
final case class Props[T: CanMove](
app: GameApp,
movable: T,
playerMovementEventBus: ActorRef[
EventBus.Command[PlayerMovementEvent]
]
) {
def create: Behavior[Command] =
Behaviors.setup(ctx => {
ctx.log.info("Hello from MovementActor")
// val playerMovementEventHandler = ctx.spawn(
// PlayerMovementEventHandler(ctx.self),
// "playerMovementEventHandler"
// )
// playerMovementEventBus ! EventBus.Subscribe(playerMovementEventHandler)
new ImMovementActor(ctx, this).receive(State())
})
}
/**
* Internal state of the actor
*
* @param cardinalDir The four directions the character can move
*/
final case class State(cardinalDir: CardinalDirection = CardinalDirection())
// def apply[T: CanMove](props: Props[T]): Behavior[Command] =
// Behaviors.setup(ctx => {
// ctx.log.info("Hello from MovementActor")
// val playerMovementEventHandler = ctx.spawn(
// PlayerMovementEventHandler(ctx.self),
// "playerMovementEventHandler"
// )
// props.playerMovementEventBus ! EventBus.Subscribe(
// playerMovementEventHandler
// )
// new ImMovementActor(ctx, props).receive(State())
// })
}
class ImMovementActor[T](
ctx: ActorContext[ImMovementActor.Command],
props: ImMovementActor.Props[T]
) {
import ImMovementActor._
import Methods._
def receive(
state: ImMovementActor.State
)(implicit cm: CanMove[T]): Behavior[Command] =
Behaviors.receiveMessage {
case m: Movement =>
m match {
case MovedLeft(pressed) =>
props.app.enqueueF(stopIfNotPressed(pressed, props.movable))
receive(state = state.modify(_.cardinalDir.left).setTo(pressed))
case MovedUp(pressed) =>
props.app.enqueueF(stopIfNotPressed(pressed, props.movable))
receive(state = state.modify(_.cardinalDir.up).setTo(pressed))
case MovedRight(pressed) =>
props.app.enqueueF(stopIfNotPressed(pressed, props.movable))
receive(state = state.modify(_.cardinalDir.right).setTo(pressed))
case MovedDown(pressed) =>
props.app.enqueueF(stopIfNotPressed(pressed, props.movable))
receive(state = state.modify(_.cardinalDir.down).setTo(pressed))
case Jump =>
props.app.enqueueF(cm.jump(props.movable))
Behaviors.same
case RotateLeft =>
props.app.enqueueF(cm.rotate(props.movable, RotateDir.Left))
Behaviors.same
case RotateRight =>
props.app.enqueueF(cm.rotate(props.movable, RotateDir.Right))
Behaviors.same
}
case Tick(tpf) =>
val walkDir =
getDirection(state.cardinalDir, ctx.log.trace)
if (walkDir != ImVector3f.ZERO) {
val tmp = walkDir * 25f * tpf
props.app.enqueueF {
cm.move(props.movable, tmp)
}
// props.app
// .enqueueScala { () =>
// 1
// }
// .map(println)(scala.concurrent.ExecutionContext.global)
// monix.eval.Task
// .fromFuture(
// props.app
// .enqueueScala { () =>
// 1
// }
// )
// .flatMap(i => monix.eval.Task(println(i)))
// .runToFuture(monix.execution.Scheduler.Implicits.global)
}
Behaviors.same
}
}
object Methods {
def getDirection(cardinalDir: CardinalDirection, trace: String => Unit) = {
val zero = ImVector3f.ZERO
val dir = cardinalDir
val walkDir = {
val mutWalkDir = new Vector3f()
if (dir.left) {
trace("left")
mutWalkDir += zero +=: new Vector3f(-1, 0, 0)
}
if (dir.right) {
trace("right")
mutWalkDir += zero +=: new Vector3f(1, 0, 0)
}
if (dir.up) {
trace("up")
mutWalkDir += zero +=: new Vector3f(0, 0, -1)
}
if (dir.down) {
trace("down")
mutWalkDir += zero +=: new Vector3f(0, 0, 1)
}
mutWalkDir.immutable
}
walkDir
}
def stopIfNotPressed[T](pressed: Boolean, movable: T)(implicit
cm: CanMove[T]
) =
if (!pressed) cm.stop(movable)
}

392
src/main/scala/wow/doge/mygame/implicits/package.scala

@ -20,14 +20,11 @@ import com.jme3.math.Vector3f
import wow.doge.mygame.math.ImVector3f
import com.jme3.scene.Geometry
import wow.doge.mygame.state.CardinalDirection
import wow.doge.mygame.state.CanMove
import wow.doge.mygame.subsystems.movement.CanMove
import com.jme3.renderer.Camera
import scala.jdk.CollectionConverters._
import wow.doge.mygame.utils.JFXConsoleStreamable
import com.jme3.app.Application
import java.util.concurrent.Callable
import scala.concurrent.Future
import scala.concurrent.ExecutionContext
import com.jme3.scene.SceneGraphVisitor
import monix.reactive.Observable
import com.jme3.asset.AssetManager
@ -45,8 +42,24 @@ import com.jme3.bullet.PhysicsTickListener
import monix.reactive.observers.Subscriber
import monix.execution.Ack.Continue
import monix.execution.Ack.Stop
import com.jme3.bullet.BulletAppState
import wow.doge.mygame.state.MyBaseState
import monix.bio.UIO
import com.jme3.bullet.control.BetterCharacterControl
import com.jme3.scene.control.AbstractControl
import com.jme3.scene.CameraNode
import com.jme3.scene.control.CameraControl.ControlDirection
import com.jme3.bullet.control.AbstractPhysicsControl
import com.jme3.scene.control.Control
import com.typesafe.scalalogging.Logger
import com.typesafe.scalalogging.LazyLogging
import com.jme3.input.controls.AnalogListener
import com.jme3.math.Quaternion
import com.jme3.math.FastMath
import wow.doge.mygame.subsystems.movement.RotateDir
case class ActionEvent(binding: Action, value: Boolean, tpf: Float)
case class AnalogEvent(binding: Action, value: Float, tpf: Float)
case class PhysicsTickEvent(space: PhysicsSpace, tpf: Float)
package object implicits {
@ -54,35 +67,35 @@ package object implicits {
type PhysicsTickObservable =
Observable[Either[PrePhysicsTickEvent, PhysicsTickEvent]]
implicit class JMEAppExt(val app: Application) extends AnyVal {
/**
* Blocking task. Execute on a thread pool meant for blocking operations.
* Prefer [[wow.doge.mygame.implicits.JMEAppExt#enqueueT]] instead.
*
* @param cb
* @param ec
* @return
*/
def enqueueF[T](cb: () => T)(implicit ec: ExecutionContext): Future[T] =
Future {
app
.enqueue(new Callable[T]() {
override def call(): T = cb()
})
.get()
}
/**
* Blocking task. Execute on a thread pool meant for blocking operations.
* Same as enqueue, but returns a Monix Task instead of Future
* @param cb
* @param ec
* @return
*/
def enqueueL[T](cb: () => T): Task[T] =
Task
.deferFutureAction(implicit s => enqueueF(cb))
implicit class JMEAppExt(private val app: Application) extends AnyVal {
// /**
// * Blocking task. Execute on a thread pool meant for blocking operations.
// * Prefer [[wow.doge.mygame.implicits.JMEAppExt#enqueueT]] instead.
// *
// * @param cb
// * @param ec
// * @return
// */
// def enqueueF[T](cb: () => T)(implicit ec: ExecutionContext): Future[T] =
// Future {
// app
// .enqueue(new Callable[T]() {
// override def call(): T = cb()
// })
// .get()
// }
// /**
// * Blocking task. Execute on a thread pool meant for blocking operations.
// * Same as enqueue, but returns a Monix Task instead of Future
// * @param cb
// * @param ec
// * @return
// */
// def enqueueL[T](cb: () => T): Task[T] =
// Task
// .deferFutureAction(implicit s => enqueueF(cb))
def enqueueF[T](cb: => T) =
app.enqueue(new Runnable {
@ -92,13 +105,14 @@ package object implicits {
def enqueueT(cb: => Unit) =
Task(enqueueF(cb))
}
implicit class StateManagerExt(val sm: AppStateManager) extends AnyVal {
implicit class StateManagerExt(private val sm: AppStateManager)
extends AnyVal {
def state[S <: AppState]()(implicit c: ClassTag[S]): S =
sm.getState(c.runtimeClass.asInstanceOf[Class[S]])
}
implicit class SimpleApplicationExt(val sa: SimpleApplication)
implicit class SimpleApplicationExt[T <: SimpleApplication](private val sa: T)
extends AnyVal {
def stateManager: AppStateManager = sa.getStateManager()
def inputManager: InputManager = sa.getInputManager()
@ -107,9 +121,32 @@ package object implicits {
def flyCam = Option(sa.getFlyByCamera())
def camera = sa.getCamera()
def viewPort = sa.getViewPort()
def rootNode = sa.getRootNode()
def observableTick: Observable[Float] =
Observable.create(OverflowStrategy.Unbounded) { sub =>
val c = SingleAssignCancelable()
val as = new MyBaseState {
override def init(): Unit = {}
override def update(tpf: Float) = {
if (sub.onNext(tpf) == Ack.Stop)
c.cancel()
}
override def stop(): Unit = {}
override def onEnable() = {}
override def onDisable() = {}
}
sa.stateManager.attach(as)
c := Cancelable(() => sa.stateManager.detach(as))
c
}
}
implicit class NodeExt(val n: Node) extends AnyVal {
implicit class NodeExt[T <: Node](private val n: T) extends AnyVal {
/**
* Attaches the given child
@ -117,28 +154,34 @@ package object implicits {
* @param s
* @return
*/
def child(s: Spatial): Node = {
def withChild(s: Spatial): Node = {
n.attachChild(s)
n
}
/**
* Gets the list of children as a scala collection
* Gets the list of children as a monix observable
*
* @return
*/
// def children = n.getChildren().asScala.toSeq
def children = Observable.fromIterable(n.getChildren().asScala)
def observableChildren =
Observable.fromIterable(n.getChildren().asScala)
def children = LazyList.from(n.getChildren().asScala)
/**
* Attach given children
*
* @param lst
*/
def children(lst: Iterable[Spatial]): Unit = {
for (c <- lst) n.child(c)
def withChildren(lst: Spatial*): Node = {
for (c <- lst) n.withChild(c)
n
}
def +=(spatial: Spatial) = n.attachChild(spatial)
def depthFirst(cb: Spatial => Unit) =
n.depthFirstTraversal(new SceneGraphVisitor() {
override def visit(s: Spatial) = cb(s)
@ -156,7 +199,7 @@ package object implicits {
Task.deferFuture(subscriber.onNext(node)).flatMap {
case Ack.Continue => {
//modifying a node's children list is forbidden
val children = node.getChildren().asScala.to(LazyList)
val children = node.children
if (!children.isEmpty) {
Task.sequence(
children.map(c => loop(subscriber, c))
@ -191,9 +234,9 @@ package object implicits {
def loop(
subscriber: Subscriber[Spatial],
spatials: LazyList[Spatial]
): Task[Unit] = {
// spatial can be either a node or a geometry, but it's not a sealed trait
): Task[Unit] =
spatials match {
// spatial can be either a node or a geometry, but it's not a sealed trait
case head #:: tail =>
head match {
case g: Geometry =>
@ -203,7 +246,7 @@ package object implicits {
case Stop => Task.unit
}
case node: Node =>
val children = node.getChildren().asScala.to(LazyList)
val children = node.children
Task.deferFuture(subscriber.onNext(node)).flatMap {
case Continue =>
loop(subscriber, tail #::: children)
@ -213,23 +256,54 @@ package object implicits {
}
case LazyList() => Task.unit
}
}
Observable.create(OverflowStrategy.Unbounded) { sub =>
implicit val sched = sub.scheduler
loop(sub, LazyList(n)).runToFuture
}
}
def withControl[C <: Control](ctrl: C) = {
n.addControl(ctrl)
n
}
def withLocalTranslation(dir: ImVector3f) = {
n.setLocalTranslation(dir.mutable)
n
}
def withRotate(xAngle: Float, yAngle: Float, zAngle: Float) = {
n.rotate(xAngle, yAngle, zAngle)
n
}
def localRotation = n.getLocalRotation()
def localTranslation = n.getLocalTranslation()
}
implicit class CameraNodeExt(private val cn: CameraNode) {
def withControlDir(controlDir: ControlDirection) = {
cn.setControlDir(controlDir)
cn
}
def withLookAt(position: ImVector3f, upVector: ImVector3f) = {
cn.lookAt(position.mutable, upVector.mutable)
cn
}
}
implicit class EntityDataExt(val ed: EntityData) extends AnyVal {
implicit class EntityDataExt(private val ed: EntityData) extends AnyVal {
def query = new EntityQuery(ed)
// def entities[T <: EntityComponent](entities: Seq[T])
}
implicit class EntityExt(val e: EntityId) extends AnyVal {
implicit class EntityExt(private val e: EntityId) extends AnyVal {
def withComponents(classes: EntityComponent*)(implicit
ed: EntityData
): EntityId = {
@ -238,19 +312,40 @@ package object implicits {
}
}
implicit class ActorRefExt[Req](val a: ActorRef[Req]) extends AnyVal {
implicit class ActorRefExt[Req](private val a: ActorRef[Req]) extends AnyVal {
import akka.actor.typed.scaladsl.AskPattern._
def askT[Res](
/**
* @param replyTo
* @param timeout
* @param scheduler
* @return
*/
def askL[Res](
replyTo: ActorRef[Res] => Req
)(implicit timeout: Timeout, scheduler: Scheduler): Task[Res] = {
Task.deferFuture(a.ask(replyTo)(timeout, scheduler))
}
def ??[Res](
replyTo: ActorRef[Res] => Req
)(implicit timeout: Timeout, scheduler: Scheduler): Task[Res] =
askL(replyTo)
/**
* Same as [[tell]], but wrapped in a Task
*
* @param msg
* @return
*/
def tellL(msg: Req) = UIO(a.tell(msg))
def !!(msg: Req) = tellL(msg)
}
// def ?[Res](replyTo: ActorRef[Res] => Req)(implicit timeout: Timeout, scheduler: Scheduler): Future[Res] = {
// ask(replyTo)(timeout, scheduler)
// }
implicit class InputManagerExt(val inputManager: InputManager)
implicit class InputManagerExt(private val inputManager: InputManager)
extends AnyVal {
def withMapping(mapping: String, triggers: Trigger*): InputManager = {
inputManager.addMapping(mapping, triggers: _*)
@ -264,7 +359,7 @@ package object implicits {
def observableAction(mappingNames: String*): Observable[ActionEvent] = {
Observable.create(OverflowStrategy.Unbounded) { sub =>
Observable.create(OverflowStrategy.DropOld(10)) { sub =>
val c = SingleAssignCancelable()
val al = new ActionListener {
override def onAction(
@ -281,13 +376,37 @@ package object implicits {
inputManager.addListener(al, mappingNames: _*)
c := Cancelable(() => inputManager.removeListener(al))
c
}
}
def analogObservable(mappingNames: String*): Observable[AnalogEvent] = {
Observable.create(OverflowStrategy.DropOld(100)) { sub =>
val c = SingleAssignCancelable()
val al = new AnalogListener {
override def onAnalog(
binding: String,
value: Float,
tpf: Float
): Unit = {
if (
sub.onNext(AnalogEvent(Action(binding), value, tpf)) == Ack.Stop
)
c.cancel()
}
}
inputManager.addListener(al, mappingNames: _*)
c := Cancelable(() => inputManager.removeListener(al))
c
}
}
}
implicit class PhysicsSpaceExt(val space: PhysicsSpace) extends AnyVal {
implicit class PhysicsSpaceExt(private val space: PhysicsSpace)
extends AnyVal {
def collisionObservable(): Observable[PhysicsCollisionEvent] = {
@ -335,9 +454,15 @@ package object implicits {
c
}
}
//TODO Create a typeclass for this
def +=(anyObject: Any) = space.add(anyObject)
def +=(spatial: Spatial) = space.addAll(spatial)
}
implicit class AssetManagerExt(val am: AssetManager) extends AnyVal {
implicit class AssetManagerExt(private val am: AssetManager) extends AnyVal {
def registerLocator(
assetPath: os.RelPath,
locator: Class[_ <: AssetLocator]
@ -350,8 +475,26 @@ package object implicits {
}
}
implicit class Vector3fExt(val v: Vector3f) extends AnyVal {
implicit class BulletAppStateExt(private val bas: BulletAppState)
extends AnyVal {
def physicsSpace = bas.getPhysicsSpace()
def speed = bas.getSpeed()
}
implicit class BetterCharacterControlExt(
private val bcc: BetterCharacterControl
) {
def withJumpForce(force: ImVector3f) = {
bcc.setJumpForce(force.mutable)
bcc
}
}
implicit class Vector3fExt(private val v: Vector3f) extends AnyVal {
//TODO add more operations
def +=(that: Vector3f) = v.addLocal(that)
def +=(that: ImVector3f) = v.addLocal(that.x, that.y, that.z)
def +=:(that: ImVector3f) = v += that
def *=(that: Vector3f) = v.multLocal(that)
def -=(that: Vector3f) = v.subtractLocal(that)
def /=(that: Vector3f) = v.divideLocal(that)
@ -359,12 +502,12 @@ package object implicits {
def immutable = ImVector3f(v.x, v.y, v.z)
}
implicit class ImVector3fExt(val v: ImVector3f) extends AnyVal {
implicit class ImVector3fExt(private val v: ImVector3f) extends AnyVal {
def +(that: ImVector3f) = v.copy(v.x + that.x, v.y + that.y, v.z + that.z)
def *(that: ImVector3f) = v.copy(v.x * that.x, v.y * that.y, v.z * that.z)
def *(f: Float): ImVector3f =
// v.copy(v.x * f, v.y * f, v.x * f)
v * ImVector3f(f, f, f)
v.copy(v.x * f, v.y * f, v.z * f)
// v * ImVector3f(f, f, f)
def -(that: ImVector3f) = v.copy(v.x - that.x, v.y - that.y, v.z - that.z)
def /(that: ImVector3f) = v.copy(v.x / that.x, v.y / that.y, v.z / that.z)
def unary_- = v.copy(-v.x, -v.y, -v.z)
@ -372,106 +515,53 @@ package object implicits {
def mutable = new Vector3f(v.x, v.y, v.z)
}
// implicit val implVector3fForVector3 = new Vector3[Vector3f] {
// override def +(implicit v: Vector3f, that: com.jme3.math.Vector3f): Unit =
// v += that
// override def *(implicit v: Vector3f, that: Vector3f): Unit = v *= that
// override def -(implicit v: Vector3f, that: Vector3f): Unit = v -= that
// override def /(implicit v: Vector3f, that: Vector3f): Unit = v /= that
// }
// implicit val implImVector3fForVector3 = new Vector3[ImVector3f] {
// override def +(implicit v: ImVector3f, that: ImVector3f): Unit =
// v + that
// override def *(implicit v: ImVector3f, that: ImVector3f): Unit = v * that
// override def -(implicit v: ImVector3f, that: ImVector3f): Unit = v - that
// override def /(implicit v: ImVector3f, that: ImVector3f): Unit = v / that
// }
// def test[T](v: T)(implicit ev: Vector3[T]) = {
// import ev._
// ev.+
// }
implicit val implCanMoveForBetterCharacterControl =
new CanMove[BetterCharacterControl] {
override def move(
inst: BetterCharacterControl,
direction: ImVector3f
): Unit = {
// val dir = direction.mutable
// inst.setViewDirection(dir)
// inst.setViewDirection(direction.mutable)
inst.setWalkDirection(direction.mutable.multLocal(50f))
}
override def jump(inst: BetterCharacterControl): Unit = inst.jump()
override def rotate(
inst: BetterCharacterControl,
rotateDir: RotateDir
): Unit = {
val q =
rotateDir match {
case RotateDir.Left =>
new Quaternion()
.fromAngleAxis(-10f * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y)
case RotateDir.Right =>
new Quaternion()
.fromAngleAxis(10 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y)
}
implicit val implCanMoveForGeom = new CanMove[Spatial] {
val tmp = new Vector3f()
inst.getViewDirection(tmp)
inst.setViewDirection(q.mult(tmp))
}
override def stop(inst: BetterCharacterControl) =
inst.setWalkDirection(Vector3f.ZERO)
}
implicit val implCanMoveForGeom = new CanMove[Spatial] with LazyLogging {
override def move(inst: Spatial, direction: ImVector3f): Unit = {
// val v = inst.getLocalTranslation()
// inst match {
// case n: Node => println(n.getChildren())
// case _ =>
// }
inst.move(direction.mutable)
}
override def getDirection(
cam: Camera,
cardinalDir: CardinalDirection
): ImVector3f = {
// val camDir =
// cam.getDirection().immutable * 0.6f
// val camLeft = cam.getLeft().immutable * 0.4f
// val zero = ImVector3f.ZERO
// val dir = cardinalDir
// val walkDir = {
// val mutWalkDir = new Vector3f()
// if (dir.left) {
// // ctx.log.trace("left")
// mutWalkDir += (zero + camLeft).mutable
// }
// if (dir.right) {
// // ctx.log.trace("right")
// mutWalkDir += (zero + -camLeft).mutable
// }
// if (dir.up) {
// // ctx.log.trace("up")
// mutWalkDir += (zero + camDir).mutable
// }
// if (dir.down) {
// // ctx.log.trace("down")
// mutWalkDir += (zero + -camDir).mutable
// }
// mutWalkDir.immutable
// }
// walkDir
// val camDir =
// cam.getDirection().immutable * 0.6f
// val camLeft = cam.getLeft().immutable * 0.4f
val zero = ImVector3f.ZERO
val dir = cardinalDir
val walkDir = {
val mutWalkDir = new Vector3f()
if (dir.left) {
// ctx.log.trace("left")
mutWalkDir += (zero + ImVector3f(-1, 0, 0)).mutable
}
if (dir.right) {
// ctx.log.trace("right")
mutWalkDir += (zero + ImVector3f(1, 0, 0)).mutable
}
if (dir.up) {
// ctx.log.trace("up")
mutWalkDir += (zero + ImVector3f(0, 0, -1)).mutable
}
if (dir.down) {
// ctx.log.trace("down")
mutWalkDir += (zero + ImVector3f(0, 0, 1)).mutable
}
mutWalkDir.immutable
override def jump(inst: Spatial): Unit =
logger.warn("`Jump` is not implemented for type `Spatial`")
override def rotate(inst: Spatial, rotateDir: RotateDir): Unit = {
rotateDir match {
case RotateDir.Left => inst.rotate(0, -0.01f, 0)
case RotateDir.Right => inst.rotate(0, 0.01f, 0)
}
walkDir
}
override def stop(inst: Spatial) = {}
}
implicit val implJFXConsoleStreamForTextArea =

104
src/main/scala/wow/doge/mygame/subsystems/events/EventBus.scala

@ -1,102 +1,22 @@
package wow.doge.mygame.events
// import akka.event.ActorEventBus
// import akka.event.ManagedActorClassification
// import akka.event.ActorClassifier
import akka.actor.typed.ActorRef
// import akka.actor.ActorSystem
// import akka.event.EventBus
// import akka.util.Subclassification
// import java.util.concurrent.atomic.AtomicReference
import akka.actor.typed.Behavior
import akka.actor.typed.scaladsl.Behaviors
import scala.reflect.ClassTag
import akka.event.EventStream
// private[events] final case class ClassificationMessage(ref: ActorRef, id: Int)
// class ActorBusImpl(val system: ActorSystem, val busSize: Int)
// extends ActorEventBus
// with ActorClassifier
// with ManagedActorClassification {
// type Event = ClassificationMessage
// // is used for extracting the classifier from the incoming events
// override protected def classify(event: Event): ActorRef = event.ref
// // determines the initial size of the index data structure
// // used internally (i.e. the expected number of different classifiers)
// override protected def mapSize: Int = busSize
// }
// class StartsWithSubclassification extends Subclassification[String] {
// override def isEqual(x: String, y: String): Boolean =
// x == y
// override def isSubclass(x: String, y: String): Boolean =
// x.startsWith(y)
// }
// import akka.event.SubchannelClassification
// final case class MsgEnvelope(topic: String, payload: Any)
// import akka.actor.typed.scaladsl.adapter._
// /**
// * Publishes the payload of the MsgEnvelope when the topic of the
// * MsgEnvelope starts with the String specified when subscribing.
// */
// class SubchannelBusImpl extends EventBus with SubchannelClassification {
// type Event = Any
// type Classifier = Class[_]
// type Subscriber = ActorRef
// // Subclassification is an object providing `isEqual` and `isSubclass`
// // to be consumed by the other methods of this classifier
// // override protected val subclassification: Subclassification[Classifier] =
// // new StartsWithSubclassification
// private val initiallySubscribedOrUnsubscriber =
// new AtomicReference[Either[Set[ActorRef], ActorRef]](Left(Set.empty))
// override protected implicit val subclassification
// : Subclassification[Classifier] = new Subclassification[Class[_]] {
// def isEqual(x: Class[_], y: Class[_]) = x == y
// def isSubclass(x: Class[_], y: Class[_]) = y.isAssignableFrom(x)
// }
// // is used for extracting the classifier from the incoming events
// override protected def classify(event: Event): Classifier = event.getClass()
// // will be invoked for each event for all subscribers which registered
// // themselves for the events classifier
// override protected def publish(event: Event, subscriber: Subscriber): Unit = {
// // subscriber ! event.payload
// subscriber ! event
// }
// }
/**
* A (typed) event bus
* Copied (and repurposed) from Akka's EventStream
*/
object EventBus {
sealed trait Command[A]
final case class Publish[A, E <: A](event: E, publisher: ActorRef[_])
extends Command[A]
sealed trait Command[-A]
final case class Publish[A, E <: A](
event: E,
publisherName: String
) extends Command[A]
/**
* Subscribe a typed actor to listen for types or subtypes of E
* by sending this command to the [[akka.actor.typed.ActorSystem.eventStream]].
*
* ==Simple example==
* {{{
* sealed trait A
* case object A1 extends A
* //listen for all As
* def subscribe(actorSystem: ActorSystem[_], actorRef: ActorRef[A]) =
* actorSystem.eventStream ! EventStream.Subscribe(actorRef)
* //listen for A1s only
* def subscribe(actorSystem: ActorSystem[_], actorRef: ActorRef[A]) =
* actorSystem.eventStream ! EventStream.Subscribe[A1](actorRef)
* }}}
*/
final case class Subscribe[A, E <: A](subscriber: ActorRef[E])(implicit
classTag: ClassTag[E]
) extends Command[A] {
@ -104,10 +24,6 @@ object EventBus {
def topic: Class[_] = classTag.runtimeClass
}
/**
* Unsubscribe an actor ref from the event stream
* by sending this command to the [[akka.actor.typed.ActorSystem.eventStream]].
*/
final case class Unsubscribe[A, E <: A](subscriber: ActorRef[E])
extends Command[A]
@ -126,7 +42,7 @@ class EventBus[B] {
eventStream: akka.event.EventStream
): Behavior[EventBus.Command[B]] =
Behaviors.receiveMessage {
case EventBus.Publish(event, publisher) =>
case EventBus.Publish(event, name) =>
eventStream.publish(event)
Behaviors.same
case s @ EventBus.Subscribe(subscriber) =>

15
src/main/scala/wow/doge/mygame/subsystems/events/Events.scala

@ -1,17 +1,14 @@
package wow.doge.mygame.events
// object Test {
// Events.BulletFired
// }
object Events {
sealed trait Event
case object BulletFired extends Event
final case object BulletFired extends Event
// type BulletFired = BulletFired.type
case class EventWithData(data: Int) extends Event
final case class EventWithData(data: Int) extends Event
sealed trait Tick extends Event
case object RenderTick extends Tick
case object PhysicsTick extends Tick
object Tick {
final case object RenderTick extends Tick
final case object PhysicsTick extends Tick
}
}

81
src/main/scala/wow/doge/mygame/subsystems/events/EventsModule.scala

@ -1,3 +1,82 @@
package wow.doge.mygame.events
trait EventsModule {}
import akka.actor.typed.ActorRef
import akka.actor.typed.SpawnProtocol
import wow.doge.mygame.implicits._
import akka.actor.typed.scaladsl.AskPattern._
import akka.actor.typed.Props
import akka.util.Timeout
import akka.actor.typed.Scheduler
import akka.actor.typed.LogOptions
import com.typesafe.scalalogging.{Logger => SLLogger}
import wow.doge.mygame.events.EventBus
import akka.actor.typed.scaladsl.Behaviors
import wow.doge.mygame.subsystems.events.MovementEvent.PlayerMovementEvent
import akka.actor.typed.SupervisorStrategy
trait EventsModule {
def spawnProtocol: ActorRef[SpawnProtocol.Command]
implicit def akkaScheduler: Scheduler
implicit def timeout: Timeout
def eventBusLogger = SLLogger[EventBus[_]]
// val subscribingActor =
// spawnProtocol.askT(
// SpawnProtocol.Spawn[Events.PhysicsTick.type](
// SubscribingActor(),
// "subscriber-1",
// Props.empty,
// _
// )
// )
lazy val tickEventBusTask = createEventBus[Events.Tick]("tickEventBus")
// spawnProtocol.askL(
// SpawnProtocol.Spawn[EventBus.Command[Events.Tick]](
// Behaviors.logMessages(
// logOptions = LogOptions().withLogger(eventBusLogger.underlying),
// EventBus[Events.Tick]()
// ),
// "tickEventBus",
// Props.empty,
// _
// )
// )
lazy val playerMovementEventBusTask =
createEventBus[PlayerMovementEvent]("movementEventBus")
// spawnProtocol.askL(
// SpawnProtocol.Spawn[EventBus.Command[Events.Movement.PlayerMovement]](
// Behaviors.logMessages(
// logOptions = LogOptions().withLogger(eventBusLogger.underlying),
// EventBus[Events.Movement.PlayerMovement]()
// ),
// "movementEventBus",
// Props.empty,
// _
// )
// )
// tickEventBus ! EventBus.Subscribe(subscribingActor)
// tickEventBus ! EventBus.Publish(Events.PhysicsTick, ctx.self)
def createEventBus[T](busName: String) =
spawnProtocol.askL(
SpawnProtocol.Spawn[EventBus.Command[T]](
Behaviors.logMessages(
logOptions = LogOptions().withLogger(eventBusLogger.underlying),
Behaviors
.supervise(EventBus[T]())
.onFailure[Exception](SupervisorStrategy.restart)
),
busName,
Props.empty,
_
)
)
}
object EventTypes {
type EventBus[T] = ActorRef[EventBus.Command[T]]
}

59
src/main/scala/wow/doge/mygame/subsystems/events/EventsModule2.scala

@ -0,0 +1,59 @@
// package wow.doge.mygame.subsystems.events
// import akka.actor.typed.ActorRef
// import akka.actor.typed.SpawnProtocol
// import wow.doge.mygame.implicits._
// import akka.actor.typed.Props
// import akka.actor.typed.LogOptions
// import com.typesafe.scalalogging.{Logger => SLLogger}
// import wow.doge.mygame.events.EventBus
// import akka.actor.typed.scaladsl.Behaviors
// import wow.doge.mygame.events.Events
// import cats.effect.Resource
// import monix.bio.Task
// import akka.actor.typed.ActorSystem
// import scala.concurrent.duration._
// trait EventsModule2 {
// def eventBusesResource(
// spawnProtocol: ActorSystem[SpawnProtocol.Command],
// eventBusLogger: com.typesafe.scalalogging.Logger = SLLogger[EventBus[_]]
// ): Resource[
// Task,
// (
// ActorRef[EventBus.Command[Events.Tick]],
// ActorRef[EventBus.Command[Events.Movement.PlayerMovement]]
// )
// ] = {
// def createEventBus[T](busName: String) =
// spawnProtocol.askL(
// SpawnProtocol.Spawn[EventBus.Command[T]](
// Behaviors.logMessages(
// logOptions = LogOptions().withLogger(eventBusLogger.underlying),
// EventBus[T]()
// ),
// busName,
// Props.empty,
// _
// )
// )(1.second, spawnProtocol.scheduler)
// Resource.liftF {
// {
// lazy val tickEventBusTask = createEventBus[Events.Tick]("tickEventBus")
// lazy val playerMovementEventBusTask =
// createEventBus[Events.Movement.PlayerMovement]("movementEventBus")
// // val r = (tickEventBusTask, playerMovementEventBusTask)
// // Task(r)
// for {
// tickEventBus <- tickEventBusTask
// playerMovementEventBus <- playerMovementEventBusTask
// } yield (tickEventBus, playerMovementEventBus)
// }
// }
// }
// }

33
src/main/scala/wow/doge/mygame/subsystems/events/MovementEvents.scala

@ -0,0 +1,33 @@
package wow.doge.mygame.subsystems.events
import wow.doge.mygame.subsystems.movement.CanMove
sealed trait MovementEvent
object MovementEvent {
final case class MovedLeft[T: CanMove](pressed: Boolean, movable: T)
extends MovementEvent
final case class MovedUp[T: CanMove](pressed: Boolean, movable: T)
extends MovementEvent
final case class MovedRight[T: CanMove](pressed: Boolean, movable: T)
extends MovementEvent
final case class MovedDown[T: CanMove](pressed: Boolean, movable: T)
extends MovementEvent
sealed trait PlayerMovementEvent extends MovementEvent
object PlayerMovementEvent {
final case class PlayerMovedLeft(pressed: Boolean)
extends PlayerMovementEvent
final case class PlayerMovedRight(pressed: Boolean)
extends PlayerMovementEvent
final case class PlayerMovedForward(pressed: Boolean)
extends PlayerMovementEvent
final case class PlayerMovedBackward(pressed: Boolean)
extends PlayerMovementEvent
final case object PlayerJumped extends PlayerMovementEvent
final case object PlayerRotatedRight extends PlayerMovementEvent
final case object PlayerRotatedLeft extends PlayerMovementEvent
final case object PlayerCameraUp extends PlayerMovementEvent
final case object PlayerCameraDown extends PlayerMovementEvent
}
}

14
src/main/scala/wow/doge/mygame/subsystems/moddingsystem/ModdingSystem.scala

@ -15,19 +15,19 @@ import java.nio.file.NoSuchFileException
import io.circe.generic.JsonCodec
@JsonCodec
case class Test1(hello1: String, hello2: String)
final case class Test1(hello1: String, hello2: String)
@JsonCodec
case class Test2(hello1: String)
case class Plugin(name: String, priority: Int)
final case class Test2(hello1: String)
final case class Plugin(name: String, priority: Int)
object Plugin {
implicit val pluginFormat: Decoder[Plugin] = deriveDecoder
}
object ModdingSystem {
sealed trait Error extends Serializable with Product
case class CouldNotDecode(cause: String) extends Error
case class ParseFailure(cause: String) extends Error
case class FileNotFound(name: String) extends 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
case object GenericError extends Error
def readPluginsList(dir: os.Path): Try[Either[Error, ArraySeq[Plugin]]] =
@ -119,7 +119,7 @@ object ModdingSystem {
(readFailures, readSuccesses) <- UIO(findAndReadPluginFiles(wd, plugins))
(parseFailures, parseSuccesses) <- UIO(parsePluginFiles(readSuccesses))
res <- UIO(mergePluginData(parseSuccesses))
f <- UIO {
_ <- UIO {
println(s"Read Successes = ${readSuccesses.to(Seq)}")
println(s"Read Failures = ${readFailures.to(Seq)}")
println(s"Parse Successes = ${parseSuccesses.to(Seq)}")

48
src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptActor.scala

@ -12,21 +12,23 @@ import javax.script.ScriptEngine
import javax.script.ScriptEngineManager
import groovy.util.GroovyScriptEngine
import cats.implicits._
import akka.actor.typed.LogOptions
import org.slf4j.event.Level
import com.typesafe.scalalogging.Logger
import com.softwaremill.tagging._
object ScriptActor {
type Kotlin
type MyScriptEngine[T] = ScriptEngine
type KotlinScriptEngine = MyScriptEngine[Kotlin]
trait Kotlin
type KotlinScriptEngine = ScriptEngine @@ Kotlin
final case class Error(reason: String)
sealed trait Command
final case class CompileAny(
path: os.Path,
result: ActorRef[Either[Error, Any]]
) extends Command
def defaultScalaRunner() =
lazy val defaultScalaRunner =
ammonite
.Main(
storageBackend = new Folder(
@ -36,28 +38,34 @@ object ScriptActor {
)
)
def defaultKotlinRunner(): KotlinScriptEngine = {
lazy val defaultKotlinRunner: KotlinScriptEngine = {
val manager = new ScriptEngineManager()
val engine = manager.getEngineByExtension("main.kts")
engine
engine.taggedWith[Kotlin]
}
def defaultGroovyRunner(): GroovyScriptEngine =
new GroovyScriptEngine(os.pwd.toString())
lazy val defaultGroovyRunner: GroovyScriptEngine =
new GroovyScriptEngine(os.pwd.toString)
def apply(
scalaRunner: Main = defaultScalaRunner(),
kotlinRunner: KotlinScriptEngine = defaultKotlinRunner(),
groovyRunner: GroovyScriptEngine = defaultGroovyRunner()
// parent: ActorRef[ScriptStoringActor.Command]
scalaRunner: Main = defaultScalaRunner,
kotlinRunner: KotlinScriptEngine = defaultKotlinRunner,
groovyRunner: GroovyScriptEngine = defaultGroovyRunner
): Behavior[ScriptActor.Command] =
Behaviors.setup(ctx =>
new ScriptActor(
scalaRunner,
kotlinRunner,
groovyRunner,
ctx
).receiveMessage
Behaviors.logMessages(
LogOptions()
.withLevel(Level.TRACE)
.withLogger(
Logger[ScriptActor].underlying
),
Behaviors.setup(ctx =>
new ScriptActor(
scalaRunner,
kotlinRunner,
groovyRunner,
ctx
).receiveMessage
)
)
sealed trait ScriptType

71
src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptCachingActor.scala

@ -11,6 +11,9 @@ import akka.util.Timeout
import scala.util.Success
import scala.util.Failure
import akka.actor.typed.SupervisorStrategy
import akka.actor.typed.LogOptions
import org.slf4j.event.Level
import com.typesafe.scalalogging.Logger
object ScriptCachingActor {
@ -22,9 +25,16 @@ object ScriptCachingActor {
type ScriptResult = Either[ScriptActor.Error, ScriptObject]
sealed trait Command
/**
* @param scriptPath path of the script to compile
* @param requester return address of the asking actor
* @param force if true, forces script compilation even if a previous cached version exists
*/
final case class Get(
scriptPath: os.Path,
requester: ActorRef[ScriptResult]
requester: ActorRef[ScriptResult],
force: Boolean = false
) extends Command
final case class GetMap(requester: ActorRef[ScriptsMap]) extends Command
final case class Put(scriptPath: os.Path, script: ScriptObject)
@ -37,37 +47,58 @@ object ScriptCachingActor {
requester: ActorRef[ScriptResult]
) extends Command
final case class Props(
ctx: ActorContext[Command],
scriptActor: ActorRef[ScriptActor.Command]
)
// final case class Props(
// ctx: ActorContext[Command],
// scriptActor: ActorRef[ScriptActor.Command]
// ) {
// def create(state: State = State(Map.empty)): Behavior[Command] =
// Behaviors.logMessages {
// Behaviors.setup { ctx =>
// val pool = ScriptActorPool(4)
// val scriptsRouter = ctx.spawn(pool, "script-actors-pool")
// new ScriptCachingActor(this)
// .receiveMessage(state)
// }
// }
// }
final case class State(scriptsMap: ScriptsMap)
def apply(state: State = State(Map.empty)): Behavior[Command] =
Behaviors.logMessages {
Behaviors.logMessages(
LogOptions()
.withLevel(Level.TRACE)
.withLogger(
Logger[ScriptCachingActor].underlying
),
Behaviors.setup { ctx =>
val pool = ScriptActorPool(4)
val scriptsRouter = ctx.spawn(pool, "script-actors-pool")
new ScriptCachingActor(Props(ctx, scriptsRouter)).receiveMessage(state)
new ScriptCachingActor(ctx, scriptsRouter).receiveMessage(state)
}
}
)
}
class ScriptCachingActor(props: ScriptCachingActor.Props) {
class ScriptCachingActor(
ctx: ActorContext[ScriptCachingActor.Command],
scriptActor: ActorRef[ScriptActor.Command]
) {
import com.softwaremill.quicklens._
import ScriptCachingActor._
import Methods._
def receiveMessage(state: State): Behavior[Command] =
Behaviors.receiveMessage { msg =>
msg match {
case Get(scriptPath, requester) =>
getOrCompileScript(
props.ctx,
scriptPath,
state.scriptsMap,
props.scriptActor,
requester
)
case Get(scriptPath, requester, force) =>
if (force)
ctx.self ! DelegateToChild(scriptActor, scriptPath, requester)
else
getOrCompileScript(
ctx,
scriptPath,
state.scriptsMap,
scriptActor,
requester
)
Behaviors.same
case DelegateToChild(scriptActor, scriptPath, requester) =>
@ -75,7 +106,7 @@ class ScriptCachingActor(props: ScriptCachingActor.Props) {
implicit val timeout = Timeout(15.seconds)
// child ! ScriptActor.CompileAny(scriptPath, requester)
askChildForScriptCompilation(
props.ctx,
ctx,
scriptActor,
scriptPath,
requester
@ -87,10 +118,10 @@ class ScriptCachingActor(props: ScriptCachingActor.Props) {
Behaviors.same
case Put(scriptPath, script) =>
props.ctx.log.debug(s"Putting $script at path $scriptPath")
ctx.log.debug(s"Putting $script at path $scriptPath")
val newState =
state.modify(_.scriptsMap).using(_ + (scriptPath -> script))
props.ctx.log.trace(newState.toString())
ctx.log.trace(newState.toString())
receiveMessage(state = newState)
case NoOp => Behaviors.same

25
src/main/scala/wow/doge/mygame/utils/AkkaUtils.scala

@ -0,0 +1,25 @@
package wow.doge.mygame.utils
import akka.actor.typed.Props
import akka.util.Timeout
import akka.actor.typed.Scheduler
import akka.actor.typed.ActorRef
import akka.actor.typed.SpawnProtocol
import akka.actor.typed.Behavior
import wow.doge.mygame.implicits._
object AkkaUtils {
def spawnActorL[T](
spawnProtocol: ActorRef[SpawnProtocol.Command],
actorName: String,
behavior: Behavior[T]
)(implicit timeout: Timeout, scheduler: Scheduler) =
spawnProtocol.askL[ActorRef[T]](
SpawnProtocol.Spawn(
behavior,
actorName,
Props.empty,
_
)
)
}

12
src/main/scala/wow/doge/mygame/utils/IOUtils.scala

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

14
src/main/scala/wow/doge/mygame/utils/Settings.scala

@ -0,0 +1,14 @@
package wow.doge.mygame.utils
case class Display(
width: Int = 640,
height: Int = 480,
title: String = "JME-Game",
fullScren: Boolean = false,
vsync: Boolean = false,
frameRate: Int = -1
)
object Display {
val default = Display()
}
case class GlobalSettings(display: Display = Display.default)
Loading…
Cancel
Save