forked from nova/jmonkey-test
Added camera movement, lighting, physics
This commit is contained in:
parent
b0994bcdbe
commit
85a28b3c39
@ -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
|
||||
|
||||
|
@ -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,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,
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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 =
|
||||
|
@ -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 {
|
||||
|
@ -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](
|
||||
"log2.log"
|
||||
).withAsync(timeWindow = 1.seconds).allocated.unsafeRunSync()
|
||||
// Runtime
|
||||
// .getRuntime()
|
||||
// .addShutdownHook(new Thread {
|
||||
// release.unsafeRunSync()
|
||||
// })
|
||||
scala.sys.addShutdownHook(release.unsafeRunSync())
|
||||
"application-log-2.log",
|
||||
Formatter.json,
|
||||
minLevel = Level.Debug
|
||||
).withAsync(timeWindow = 1.milliseconds)
|
||||
.allocated
|
||||
.unsafeRunSync()
|
||||
|
||||
private lazy val (eventBusFileLogger, release3) =
|
||||
fileLogger[IO](
|
||||
"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
Normal file
27
src/main/scala/wow/doge/mygame/ActorSystemModule.scala
Normal file
@ -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()
|
||||
)
|
||||
)
|
||||
}
|
@ -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))
|
||||
actorSystem <- wireWith(actorSystemResource _)
|
||||
// _ <- Resource.liftF(
|
||||
// Task(gameApp.start()).asyncBoundary
|
||||
// .executeOn(Scheduler(JMEExecutorService))
|
||||
// )
|
||||
|
||||
_ <- Resource.liftF(gameApp.enqueueT(actorSystem ! RootActor.Start))
|
||||
_ <- Resource.liftF {
|
||||
IO(gameApp.start())
|
||||
.executeOn(jmeScheduler)
|
||||
}
|
||||
// _ <- 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(
|
||||
// gameApp.enqueueT(rootActor ! RootActor.Start(actorSystem.scheduler))
|
||||
// )
|
||||
// _ <- 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)
|
||||
}
|
||||
|
@ -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")
|
||||
)
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
// private lazy val taskQueueS = new ConcurrentLinkedQueue[MyTask[_]]()
|
||||
private lazy val taskQueue2 = Atomic(Queue.empty[MyTask[_]])
|
||||
|
||||
// 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 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)
|
||||
}
|
||||
|
@ -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")
|
||||
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")
|
||||
|
||||
val tickEventBus =
|
||||
ctx.spawn(Behaviors.logMessages(EventBus[Events.Tick]()), "eventBus1")
|
||||
{
|
||||
implicit val s = schedulers.async
|
||||
|
||||
tickEventBus ! EventBus.Subscribe(subscribingActor)
|
||||
val initializer: GameSystemsInitializer = wire[GameSystemsInitializer]
|
||||
|
||||
tickEventBus ! EventBus.Publish(Events.PhysicsTick, ctx.self)
|
||||
schedulers.async.execute(() => initializer.init.runAsyncAndForget)
|
||||
|
||||
// {
|
||||
// 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())
|
||||
// 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"
|
||||
// )
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
object SubscribingActor {
|
||||
def apply() =
|
||||
Behaviors.receive[Events.PhysicsTick.type] { (ctx, msg) =>
|
||||
ctx.log.debug(s"received event $msg")
|
||||
Behaviors.same
|
||||
}
|
||||
}
|
||||
// new PlayerMovementState(
|
||||
// // movementActor,
|
||||
// // movementActorTimer,
|
||||
|
@ -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
|
||||
|
||||
trait GameModule {
|
||||
|
||||
def gameAppResource(appStates: AppState*): Resource[Task, GameApp] =
|
||||
Resource.liftF {
|
||||
class GameAppResource(logger: Logger[Task], jmeScheduler: Scheduler) {
|
||||
def make: Resource[Task, (GameApp, Fiber[Throwable, Unit])] =
|
||||
Resource.make(
|
||||
for {
|
||||
app <- Task(new GameApp(appStates: _*))
|
||||
_ <- logger.info("Creating game app")
|
||||
app <- Task(new GameApp(new StatsAppState()))
|
||||
_ <- Task {
|
||||
val settings = new AppSettings(true)
|
||||
// settings.setVSync(true)
|
||||
settings.setFrameRate(144)
|
||||
settings.setVSync(true)
|
||||
settings.setUseInput(true)
|
||||
// new FlyCamAppState
|
||||
// settings.setFrameRate(250)
|
||||
app.setSettings(settings)
|
||||
// JMERunner.runner = app
|
||||
app
|
||||
}
|
||||
} yield (app)
|
||||
}
|
||||
fib <- Task(app.start()).executeOn(jmeScheduler).start
|
||||
} yield (app -> fib)
|
||||
)(logger.info("Closing game app") >> _._2.cancel)
|
||||
}
|
||||
|
||||
trait GameModule {
|
||||
|
||||
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(250)
|
||||
app.setSettings(settings)
|
||||
// JMERunner.runner = app
|
||||
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)))
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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))
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -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 = {}
|
||||
|
||||
|
@ -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)
|
||||
.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)
|
||||
|
||||
// lazy val camNode = new CameraNode("CameraNode", simpleApp.getCamera())
|
||||
// camNode.setCamera(simpleApp.getCamera())
|
||||
|
||||
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))
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
@ -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])
|
@ -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 "
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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 {
|
||||
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.
|
||||
// * 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))
|
||||
// /**
|
||||
// * 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 EntityDataExt(val ed: EntityData) extends AnyVal {
|
||||
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(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
|
||||
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)
|
||||
}
|
||||
|
||||
// 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 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 =
|
||||
|
@ -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 event’s 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) =>
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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]]
|
||||
}
|
||||
|
@ -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)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// }
|
@ -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
|
||||
}
|
||||
}
|
@ -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)}")
|
||||
|
@ -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
|
||||
|
@ -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
Normal file
25
src/main/scala/wow/doge/mygame/utils/AkkaUtils.scala
Normal file
@ -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
Normal file
12
src/main/scala/wow/doge/mygame/utils/IOUtils.scala
Normal file
@ -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
Normal file
14
src/main/scala/wow/doge/mygame/utils/Settings.scala
Normal file
@ -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…
Reference in New Issue
Block a user