many changes

This commit is contained in:
Rohan Sircar 2020-11-13 19:44:57 +05:30
parent 1f55e08fa5
commit b0994bcdbe
41 changed files with 1494 additions and 413 deletions

4
.jvmopts Normal file
View File

@ -0,0 +1,4 @@
-Xms2G
-Xmx2G
-XX:+UseG1GC
-XX:MaxGCPauseMillis=50

View File

@ -66,10 +66,11 @@ lazy val root = (project in file(".")).settings(
"com.lihaoyi" % "ammonite" % "2.2.0" cross CrossVersion.full,
"org.jetbrains.kotlin" % "kotlin-main-kts" % "1.4.10",
"org.jetbrains.kotlin" % "kotlin-scripting-jsr223" % "1.4.10",
"org.codehaus.groovy" % "groovy-all" % "3.0.6" pomOnly (),
// "wow.doge" % "game" % "1.0-SNAPSHOT",
"org.scalafx" %% "scalafx" % "14-R19",
"com.typesafe.akka" %% "akka-actor-typed" % "2.6.10",
"ch.qos.logback" % "logback-classic" % "1.2.3",
// "ch.qos.logback" % "logback-classic" % "1.2.3",
"org.typelevel" %% "cats-core" % "2.1.1",
"org.typelevel" %% "cats-effect" % "2.1.4",
"io.monix" %% "monix" % "3.2.2",
@ -85,7 +86,8 @@ lazy val root = (project in file(".")).settings(
"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"
"com.softwaremill.quicklens" %% "quicklens" % "1.6.1",
"org.scala-lang.modules" %% "scala-parallel-collections" % "1.0.0-RC1"
),
// Determine OS version of JavaFX binaries
@ -102,6 +104,7 @@ lazy val root = (project in file(".")).settings(
"-Xlint",
"-Ywarn-numeric-widen",
"-Ymacro-annotations",
"-Xlint:byname-implicit",
// "utf-8", // Specify character encoding used by source files.
"-explaintypes" // Explain type errors in more detail.
),
@ -141,6 +144,7 @@ lazy val root = (project in file(".")).settings(
// semanticdbVersion := scalafixSemanticdb.revision // use Scalafix compatible version
// semanticdbVersion := "4.3.24",
)
initialCommands in (console) := """ammonite.Main.main(Array.empty)"""
// Here, `libraryDependencies` is a set of dependencies, and by using `+=`,
// we're adding the scala-parser-combinators dependency to the set of dependencies
@ -190,3 +194,4 @@ lazy val root = (project in file(".")).settings(
// To learn more about multi-project builds, head over to the official sbt
// documentation at http://www.scala-sbt.org/documentation.html
addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1")

3
launch.sh Normal file
View File

@ -0,0 +1,3 @@
# sdk use java 20.2.0.r11-grl
# java -J-Xms2G -J-Xmx2G -J-XX:+UseG1GC -J-XX:MaxGCPauseMillis=50 -jar target/scala-2.13/mygame-assembly-1.0-SNAPSHOT.jar
java -Xms2G -Xmx2G -XX:+UseG1GC -XX:MaxGCPauseMillis=50 -jar target/scala-2.13/mygame-assembly-1.0-SNAPSHOT.jar

BIN
lib/jme3-testdata.jar Normal file

Binary file not shown.

4
plugins.json Normal file
View File

@ -0,0 +1,4 @@
[
{ "name": "test", "priority": 1 },
{ "name": "test2", "priority": 2 }
]

View File

@ -1,2 +1,3 @@
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.0")
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.23")
addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1")

View File

@ -0,0 +1,3 @@
println "hello"
this

View File

@ -1,4 +1,5 @@
#!/usr/bin/env amm
// scala 2.13.3
// import coursierapi.MavenRepository

View File

@ -0,0 +1,59 @@
package org.slf4j.impl
import cats.effect.{ContextShift, Clock, Effect, IO, Timer}
import io.odin._
import io.odin.slf4j.OdinLoggerBinder
import cats.implicits._
import scala.concurrent.ExecutionContext
import _root_.monix.execution.Scheduler
import cats.arrow.FunctionK
import _root_.monix.execution.Scheduler.Implicits.global
import io.odin.syntax._
import scala.concurrent.duration._
//effect type should be specified inbefore
//log line will be recorded right after the call with no suspension
class StaticLoggerBinder extends OdinLoggerBinder[IO] {
val ec: ExecutionContext = Scheduler.global
implicit val timer: Timer[IO] = IO.timer(ec)
implicit val clock: Clock[IO] = timer.clock
implicit val cs: ContextShift[IO] = IO.contextShift(ec)
implicit val F: Effect[IO] = IO.ioEffect
val monixToCats = new FunctionK[_root_.monix.bio.Task, IO] {
def apply[A](fa: _root_.monix.bio.Task[A]): IO[A] = fa.to[IO]
}
val (fLogger, release) =
// MainModule.DefaultFileLogger.mapK(monixToCats).allocated.unsafeRunSync()
fileLogger[IO](
"log2.log"
).withAsync(timeWindow = 1.seconds).allocated.unsafeRunSync()
// Runtime
// .getRuntime()
// .addShutdownHook(new Thread {
// release.unsafeRunSync()
// })
scala.sys.addShutdownHook(release.unsafeRunSync())
val loggers: PartialFunction[String, Logger[IO]] = {
case "some.external.package.SpecificClass" =>
consoleLogger[IO](minLevel = Level.Warn) //disable noisy external logs
case asyncHttpClient
if asyncHttpClient.startsWith("org.asynchttpclient.netty") =>
consoleLogger[IO](minLevel = Level.Warn)
case s if s.startsWith("akka.actor") || s.startsWith("wow.doge.mygame") =>
consoleLogger[IO]() |+| fLogger
case _ => //if wildcard case isn't provided, default logger is no-op
consoleLogger[IO]()
}
}
object StaticLoggerBinder extends StaticLoggerBinder {
var REQUESTED_API_VERSION: String = "1.7"
def getSingleton: StaticLoggerBinder = this
}

View File

@ -1,127 +1,105 @@
package wow.doge.mygame
import game.GameApp
import com.jme3.app.StatsAppState
import akka.actor.typed.ActorSystem
import akka.actor.typed.SpawnProtocol
import akka.actor.typed.scaladsl.Behaviors
import akka.actor.typed.Behavior
import akka.util.Timeout
import com.jme3.system.AppSettings
import wow.doge.mygame.game.GameAppActor
import wow.doge.mygame.scriptsystem.ScriptCachingActor
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)
}
object RootActor {
def apply(app: GameApp): Behavior[SpawnProtocol.Command] =
Behaviors.setup { ctx =>
ctx.log.debug("Starting root actor")
val testActor = ctx.spawn(TestActor(), "testActor")
val _ = ctx.spawn(GameAppActor(app), "gameAppActor")
testActor ! TestActor.Test
SpawnProtocol()
}
}
object TestActor {
sealed trait Command
case object Test extends Command
private case object Done extends Command
// sealed trait Result
// case object Done extends Result
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._
import scala.concurrent.duration._
implicit val timeout = Timeout(15.seconds)
// implicit val scheduler =
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._
def apply(): Behavior[Command] =
Behaviors.setup { ctx =>
ctx.spawn(ScriptCachingActor(), "scriptCacher")
Behaviors.receiveMessage { msg =>
msg match {
case Test =>
// ctx.ask(
// router,
// ScriptActor.Compile(
// // os.pwd / "some.sc",
// os.pwd / "src" / "main" / "resources" / "hello2.main.kts",
// _
// 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()
// )
// ) {
// case Success(value) =>
// ctx.log.debug("Received Value")
// ctx.log.debug(value.toString())
// Done
// case Failure(exception) =>
// ctx.log.debug(s"Received Error ${exception.getMessage()}")
// Done
// }
// val x = scriptStorer
// .askT(
// ScriptStoringActor
// .Get(os.pwd / "src" / "main" / "resources" / "hello2.sc", _)
// )(timeout, ctx.system.scheduler)
// ctx.ask(
// scriptStorer,
// ScriptStoringActor
// .Get(os.pwd / "src" / "main" / "resources" / "hello2.sc", _)
// ) {
// case Success(value) => {
// ctx.log.debug(value.toString())
// ctx.ask(
// scriptStorer,
// ScriptStoringActor
// .Get(os.pwd / "src" / "main" / "resources" / "hello2.sc", _)
// ) {
// case Success(value) => {
// ctx.log.debug(value.toString())
// Done
// }
// case Failure(exception) =>
// ctx.log.debug(exception.getMessage())
// Done
// }
// Done
// }
// case Failure(exception) =>
// ctx.log.debug(exception.getMessage())
// Done
// 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)
// }
Behaviors.same
case Done => Behaviors.same
}
object Main extends BIOApp with MainModule {
import java.util.logging.{Logger => JLogger, Level}
JLogger.getLogger("").setLevel(Level.SEVERE)
// SpawnProtocol()
// Behaviors.same
}
def run(args: List[String]): UIO[ExitCode] = {
lazy val appResource = for {
logger <-
consoleLogger().withAsync(timeWindow = 1.seconds) |+| fileLogger(
"log.log"
).withAsync(timeWindow = 1.seconds)
jmeScheduler <- jMESchedulerResource
// consoleTextArea <- Resource.liftF(Task(new TextArea()))
// consoleStream <- wireWith(JFXConsoleStream.textAreaStream _)
gameApp <- {
new BulletAppState()
// bas.setThreadingType(Thr)
gameAppResource(new StatsAppState())
}
_ <- 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)
}
// (_ => IO(gameApp.stop(() => actorSystem ! RootActor.Stop)))
} yield ()
// Console.withOut(
// new JFXConsoleStream(
// new scalafx.scene.control.TextArea(),
// new ByteArrayOutputStream(35)
// )
// )(())
appResource
.use(_ =>
// Task(gameApp.start())
// .executeOn(Scheduler(JMEExecutorService))
// .asyncBoundary
// Task.never
Task.unit
// >>
// .executeOn(Scheduler(JMEExecutorService))
)
.onErrorHandle(_.printStackTrace())
.as(ExitCode.Success)
}
}

View File

@ -0,0 +1,87 @@
package wow.doge.mygame
import akka.actor.typed.scaladsl.Behaviors
import wow.doge.mygame.game.GameApp
import akka.actor.typed.Behavior
import wow.doge.mygame.game.GameAppActor
import cats.effect.Resource
import akka.actor.typed.ActorSystem
import monix.bio.Task
import wow.doge.mygame.game.GameModule
import io.odin._
import io.odin.syntax._
import wow.doge.mygame.executors.ExecutorsModule
import akka.actor.typed.scaladsl.ActorContext
import wow.doge.mygame.executors.Schedulers
import com.softwaremill.macwire._
trait MainModule extends GameModule with ExecutorsModule {
def actorSystemResource(
logger: Logger[Task],
app: GameApp,
schedulers: Schedulers
): Resource[Task, ActorSystem[RootActor.Command]] =
Resource.make(logger.info("Creating Actor System") >> Task {
ActorSystem(RootActor(app, schedulers), name = "GameActorSystem")
})(sys =>
logger.info("Shutting down actor system") >> Task(
sys.terminate()
)
)
}
object MainModule {
// import cats.implicits._
import scala.concurrent.duration._
val DefaultFileLogger: Resource[Task, Logger[Task]] =
fileLogger[Task](
"log.log"
).withAsync(timeWindow = 1.seconds)
}
object RootActor {
sealed trait Command
final case object Start extends Command
final case object Stop extends Command
final case class State(initialized: Boolean = false)
def apply(
app: GameApp,
schedulers: Schedulers,
state: State = State()
): Behavior[Command] =
Behaviors.setup { ctx =>
ctx.log.info("Hello from root actor")
wire[RootActor].receive(state)
}
}
class RootActor(
ctx: ActorContext[RootActor.Command],
app: GameApp,
schedulers: Schedulers
) {
import RootActor._
def receive(state: State): Behavior[Command] =
Behaviors.receiveMessage(msg =>
msg match {
case Start =>
if (!state.initialized) {
ctx.log.info("Starting GameAppActor")
val _ = ctx.spawn(
wireWith(GameAppActor.apply _),
"gameAppActor"
// DispatcherSelector.fromConfig("jme-dispatcher")
)
receive(state.copy(initialized = true))
} else {
ctx.log.warn("Already Initialized")
Behaviors.same
}
case Stop =>
ctx.log.info("Stopping")
Behaviors.stopped
}
)
}

View File

@ -1,5 +1,23 @@
package wow.doge.mygame.executors
import monix.bio.Task
import cats.effect.Resource
import monix.execution.Scheduler
trait ExecutorsModule {
lazy val schedulers = new Schedulers()
lazy val schedulers = Schedulers()
// Resource.make(
// Task(
// new Schedulers(
// jme = Scheduler
// .singleThread(name = "JME-Application-Thread", daemonic = false)
// )
// )
// )(s => Task(s.jme.shutdown()))
lazy val jMESchedulerResource = Resource.make(
Task(
Scheduler
.singleThread(name = "JME-Application-Thread", daemonic = false)
)
)(e => Task(e.shutdown()))
}

View File

@ -18,7 +18,6 @@ import javafx.application.Platform
import monix.execution.Scheduler
import scala.concurrent.ExecutionContext
import java.util.concurrent.Executor
import wow.doge.mygame.Main
// First we wrap invokeLater/runLater as an ExecutorService
trait GUIExecutorService extends AbstractExecutorService {
@ -44,7 +43,15 @@ object SwingExecutorService extends GUIExecutorService {
}
object JMEExecutorService extends GUIExecutorService {
override def execute(command: Runnable) = Main.gameApp.enqueue(command)
override def execute(command: Runnable) =
JMERunner.runner.enqueue(command)
// new SingleThreadEventExecutor()
sys.addShutdownHook(JMEExecutorService.shutdown())
}
object JMERunner {
var runner: com.jme3.app.Application = null
}
class JavaFXEventThreadExecutorServiceConfigurator(

View File

@ -4,7 +4,7 @@ import monix.execution.Scheduler
final case class Schedulers(
blockingIO: Scheduler = Scheduler.io(),
cpu: Scheduler = Scheduler.global,
fx: Scheduler = JFXExecutionContexts.fxScheduler,
jme: Option[Scheduler] = None
async: Scheduler = Scheduler.global,
fx: Scheduler = JFXExecutionContexts.fxScheduler
// jme: SchedulerService
)

View File

@ -3,23 +3,16 @@ package wow.doge.mygame.game
import com.jme3.app.SimpleApplication
import com.jme3.app.state.AppState
import akka.actor.typed.ActorRef
import akka.actor.typed.Behavior
import akka.actor.typed.scaladsl.Behaviors
object Greeter {
final case class Greet(whom: String, replyTo: ActorRef[Greeted])
final case class Greeted(whom: String, from: ActorRef[Greet])
def apply(): Behavior[Greet] =
Behaviors.receive { (context, message) =>
// context.log.info("Hello {}!", message.whom)
//#greeter-send-messages
message.replyTo ! Greeted(message.whom, context.self)
//#greeter-send-messages
Behaviors.same
}
}
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._
class GameApp(
// actorSystem: ActorSystem[SpawnProtocol.Command],
@ -27,8 +20,47 @@ class GameApp(
) extends SimpleApplication(appStates: _*) {
// implicit val timeout = Timeout(10.seconds)
// implicit val scheduler = actorSystem.scheduler
private lazy val sceneModel: Spatial = assetManager.loadModel("main.scene")
private lazy val bulletAppState: BulletAppState = new BulletAppState()
// bulletAppState.setThreadingType(ThreadingType.SEQUENTIAL)
// We set up collision detection for the scene by creating a
// compound collision shape and a static RigidBodyControl with mass zero.
private lazy val sceneShape = CollisionShapeFactory.createMeshShape(
sceneModel.toNode match {
case util.Right(node) => node
case util.Left(ex) =>
throw new NotImplementedError("No fallback sceneshape")
}
)
private lazy val landscape: RigidBodyControl =
new RigidBodyControl(sceneShape, 0)
// We set up collision detection for the player by creating
// a capsule collision shape and a CharacterControl.
// The CharacterControl offers extra settings for
// size, stepheight, jumping, falling, and gravity.
// We also put the player in its starting position.
protected lazy val capsuleShape = new CapsuleCollisionShape(1.5f, 6f, 1)
private lazy val player: CharacterControl =
new CharacterControl(capsuleShape, 0.05f)
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())
// val ship = ed.createEntity()
// val mbState = stateManager().state[EntityDataState]().map(_.getEntityData())
@ -88,22 +120,23 @@ class GameApp(
// 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()
super.stop()
}
object GameApp {
// def myExec(app: SimpleApplication, command: Runnable) = {
// app.enqueue(command)
// }
// val javaFxExecutionContext: ExecutionContext =
// ExecutionContext.fromExecutor(new Executor {
// def execute(command: Runnable): Unit = {
// Platform.runLater(command)
// }
// })
// def jmeEC(app: SimpleApplication): ExecutionContext =
// ExecutionContext.fromExecutor(new Executor {
// override def execute(command: Runnable): Unit = app.enqueue(command)
// })
// def jmeScheduler(app: SimpleApplication) = Sch
// override def stop(): Unit = {}
}

View File

@ -1,60 +1,174 @@
package wow.doge.mygame.game
import akka.actor.typed.scaladsl.Behaviors
import wow.doge.mygame.state.MovementActor
import wow.doge.mygame.state.PlayerMovementState2
import wow.doge.mygame.state.MovementActorTimer
import com.jme3.scene.shape.Box
import wow.doge.mygame.state.PlayerMovementState
import com.jme3.scene.Geometry
import wow.doge.mygame.implicits._
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._
object GameAppActor {
import Methods._
sealed trait Command
def apply(app: GameApp) =
case object XD extends Command
case object Stop extends Command
def apply(app: GameApp, schedulers: Schedulers) =
Behaviors.setup[Command] { ctx =>
lazy val b = new Box(1, 1, 1)
lazy val geom = new Geometry("Box", b)
val movementActor =
ctx.spawn(
MovementActor(MovementActor.Props(app, geom)),
"movementActor"
// DispatcherSelector.fromConfig("jme-dispatcher")
)
val movementActorTimer = ctx.spawn(
MovementActorTimer(movementActor),
"movementActorTimer"
)
val imMovementActor = ctx.spawn(
ImMovementActor(ImMovementActor.Props(app, geom)),
"imMovementActor"
)
ctx.log.info("Hello from GameAppActor")
// lazy val b = new Box(1, 1, 1)
// lazy val geom = new Geometry("Box", b)
// lazy val playerNode = new Node("PlayerNode")
// lazy val camNode = new CameraNode("CameraNode", app.getCamera())
// lazy val players = createPlayer(geom, app.getCamera())
// ctx.pipeToSelf(
// app.enqueueF(() => ())(monix.execution.Scheduler.io("aege"))
// ) {
// case x =>
// println("SENDEDEEEEEd")
// XD
// }
// ctx.pipeToSelf(
// createPlayer(
// geom,
// app.getCamera(),
// schedulers.jme
// )
// ) {
// case x =>
// println(x)
// XD
// }
val subscribingActor = ctx.spawn(SubscribingActor(), "subscriber-1")
val eventBus =
val tickEventBus =
ctx.spawn(Behaviors.logMessages(EventBus[Events.Tick]()), "eventBus1")
eventBus ! EventBus.Subscribe(subscribingActor)
tickEventBus ! EventBus.Subscribe(subscribingActor)
eventBus ! EventBus.Publish(Events.PhysicsTick, ctx.self)
tickEventBus ! EventBus.Publish(Events.PhysicsTick, ctx.self)
// {
// app
// .getInputManager()
// .observableAction("Left")
// .map { action =>
// action.binding.name match {
// case "Left" => Task(println("Pressed left"))
// }
// }
// }
// binding match {
// case "Left" =>
def playerNodeFactory =
PlayerNode(
modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o",
cam = app.camera
) _
// (assetManager = app.assetManager)
{
lazy val playerNode = playerNodeFactory(app.assetManager)
lazy val actor = ctx.spawn(
ImMovementActor(ImMovementActor.Props(app, playerNode)),
"imMovementActor"
)
lazy val state = wire[PlayerMovementState]
app.stateManager.attach(state)
}
Thread.sleep(2000)
app
.getStateManager()
.attach(
new PlayerMovementState2(
movementActor,
movementActorTimer,
imMovementActor,
geom
.getRootNode()
.depthFirst(s =>
// s match {
// case node: Node =>
// println("node" + s.getName() + " children " + node.getChildren())
// case g: Geometry => println(s.getName())
// }
println(s.getName())
)
)
app.start()
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
}
}
}
}
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 =
// ctx.spawn(
// MovementActor(MovementActor.Props(app, geom)),
// "movementActor"
// // DispatcherSelector.fromConfig("jme-dispatcher")
// )
// val movementActorTimer = ctx.spawn(
// MovementActorTimer(movementActor),
// "movementActorTimer"
// )
}
}
@ -65,3 +179,12 @@ object SubscribingActor {
Behaviors.same
}
}
// new PlayerMovementState(
// // movementActor,
// // movementActorTimer,
// imMovementActor,
// // geom,
// // camNode,
// playerNode
// // ctx.self
// )

View File

@ -0,0 +1,25 @@
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 wow.doge.mygame.executors.JMERunner
trait GameModule {
def gameAppResource(appStates: AppState*): Resource[Task, GameApp] =
Resource.liftF {
for {
app <- Task(new GameApp(appStates: _*))
_ <- Task {
val settings = new AppSettings(true)
// settings.setVSync(true)
settings.setFrameRate(144)
app.setSettings(settings)
// JMERunner.runner = app
app
}
} yield (app)
}
}

View File

@ -0,0 +1,13 @@
package wow.doge.mygame.game
import wow.doge.mygame.state.MyBaseState
class GameSystemsInitializer extends MyBaseState {
override protected def onEnable(): Unit = {}
override protected def onDisable(): Unit = {}
override protected def init(): Unit = {}
override def stop(): Unit = {}
}

View File

@ -0,0 +1,84 @@
package wow.doge.mygame.game
import akka.actor.typed.Behavior
import akka.actor.typed.scaladsl.Behaviors
import akka.util.Timeout
import wow.doge.mygame.scriptsystem.ScriptCachingActor
object TestActor {
sealed trait Command
case object Test extends Command
private case object Done extends Command
import scala.concurrent.duration._
implicit val timeout = Timeout(15.seconds)
def apply(
// scriptCacheBehavior: Behavior[ScriptCachingActor.Command]
): Behavior[Command] =
Behaviors.setup { ctx =>
ctx.spawn(ScriptCachingActor(), "scriptCacher")
Behaviors.receiveMessage { msg =>
msg match {
case Test =>
Behaviors.same
case Done => Behaviors.same
}
}
}
def testKotlinScriptCompilation() = {
// ctx.ask(
// router,
// ScriptActor.Compile(
// // os.pwd / "some.sc",
// os.pwd / "src" / "main" / "resources" / "hello2.main.kts",
// _
// )
// ) {
// case Success(value) =>
// ctx.log.debug("Received Value")
// ctx.log.debug(value.toString())
// Done
// case Failure(exception) =>
// ctx.log.debug(s"Received Error ${exception.getMessage()}")
// Done
// }
}
def testTaskWrapper() = {
// val x = scriptStorer
// .askT(
// ScriptStoringActor
// .Get(os.pwd / "src" / "main" / "resources" / "hello2.sc", _)
// )(timeout, ctx.system.scheduler)
}
def testScriptCaching() = {
// ctx.ask(
// scriptStorer,
// ScriptStoringActor
// .Get(os.pwd / "src" / "main" / "resources" / "hello2.sc", _)
// ) {
// case Success(value) => {
// ctx.log.debug(value.toString())
// ctx.ask(
// scriptStorer,
// ScriptStoringActor
// .Get(os.pwd / "src" / "main" / "resources" / "hello2.sc", _)
// ) {
// case Success(value) => {
// ctx.log.debug(value.toString())
// Done
// }
// case Failure(exception) =>
// ctx.log.debug(exception.getMessage())
// Done
// }
// Done
// }
// case Failure(exception) =>
// ctx.log.debug(exception.getMessage())
// Done
// }
}
}

View File

@ -1,4 +1,4 @@
package wow.doge.mygame.state;
package wow.doge.mygame.game.appstates;
import com.jme3.app.state.AbstractAppState;
import com.simsilica.es.EntityData;

View File

@ -8,9 +8,9 @@ import wow.doge.mygame.implicits._
import com.jme3.renderer.Camera
import wow.doge.mygame.math.ImVector3f
trait CanMove[T] {
trait CanMove[-A] {
def getDirection(cam: Camera, cardinalDir: CardinalDirection): ImVector3f
def move(inst: T, direction: ImVector3f): Unit
def move(inst: A, direction: ImVector3f): Unit
}
object ImMovementActor {
@ -40,7 +40,10 @@ object ImMovementActor {
)
def apply[T: CanMove](props: Props[T]): Behavior[Command] =
Behaviors.setup(ctx => new ImMovementActor(ctx, props).receive(State()))
Behaviors.setup(ctx => {
ctx.log.info("Hello from MovementActor")
new ImMovementActor(ctx, props).receive(State())
})
}
@ -71,12 +74,15 @@ class ImMovementActor[T](
val walkDir =
cm.getDirection(props.app.getCamera(), state.cardinalDir)
if (walkDir != ImVector3f.ZERO) {
val tmp = walkDir * 2f
props.app.enqueue(new Runnable {
override def run(): Unit = {
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))

View File

@ -7,6 +7,7 @@ import com.jme3.scene.Node
import com.jme3.app.state.BaseAppState
import com.simsilica.es.EntityData
import com.simsilica.es.base.DefaultEntityData
import com.jme3.scene.Spatial
trait MyBaseState extends BaseAppState {
@ -26,17 +27,22 @@ trait MyBaseState extends BaseAppState {
}
protected def init(): Unit
protected def stop(): Unit
override protected def cleanup(app: Application): Unit = {
entityData.close()
// stop()
}
protected def getOrCreateNode(parent: Node, id: String) =
Option(parent.getChild(id)).fold {
val node = new Node(id)
protected def getChildOption(parent: Node, id: String) =
Option(parent.getChild(id))
protected def getOrCreateSpatial(parent: Node, id: String): Spatial =
Option(parent.getChild(id)).getOrElse {
val node: Spatial = new Node(id)
parent.attachChild(node)
node
}(node => node.asInstanceOf[Node])
}
protected def enableStates(classes: Class[_ <: AppState]*) =
setEnabledToStates(true, classes: _*)

View File

@ -1,6 +1,5 @@
package wow.doge.mygame.state
import scala.concurrent.duration.DurationInt
import com.jme3.input.InputManager
@ -17,55 +16,64 @@ 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._
class PlayerMovementState2(
movementActor: ActorRef[MovementActor.Command],
movementActorTimer: ActorRef[MovementActorTimer.Command],
class PlayerMovementState(
// movementActor: ActorRef[MovementActor.Command],
// movementActorTimer: ActorRef[MovementActorTimer.Command],
imMovementActor: ActorRef[ImMovementActor.Command],
geom: Geometry
// geom: Geometry,
// camNode: CameraNode,
playerNode: Node
// gameAppActor: ActorRef[GameAppActor.Command]
) extends MyBaseState
with ActionListener {
protected lazy val mat = MyMaterial(
assetManager = assetManager,
path = "Common/MatDefs/Misc/Unshaded.j3md"
path = os.rel / "Common" / "MatDefs" / "Misc" / "Unshaded.j3md"
)
override protected[state] def onEnable(): Unit = {}
override protected[state] def onDisable(): Unit = {}
override protected def init(): Unit = {
setupKeys(inputManager)
geom.setMaterial(mat)
rootNode.attachChild(geom)
println("playermovementstate " + Thread.currentThread().getName())
// geom.setMaterial(mat)
// camNode.setControlDir(ControlDirection.SpatialToCamera)
// // lazy val camNode = new CameraNode("CameraNode", simpleApp.getCamera())
// // camNode.setCamera(simpleApp.getCamera())
// discard {
// playerNode
// .child(camNode)
// .child(geom)
// // playerNode.children(Seq(camNode, geom))
// }
discard { rootNode.child(playerNode) }
// camNode.setLocalTranslation(
// new Vector3f(0, 1.5f, 10)
// )
// camNode.lookAt(playerNode.getLocalTranslation(), Vector3f.UNIT_Y)
// movementActorTimer ! MovementActorTimer.Start(geom, cam)
// movementActorTimer ! MovementActorTimer.Start
}
// def system =
// simpleApp.getStateManager.getState(classOf[ActorSystemState]).system
// def player = system.actorSelection("/user/player") //.resolveOne(1.second)
var lastDir: Vector3f = Vector3f.UNIT_X
override def update(tpf: Float) = {
// val direction = new Vector3f()
// direction.multLocal(10 * tpf)
// if (direction.length() > 0f) {
// // player ! PlayerMove(direction)
// lastDir = direction.normalize
// }
// if (shoot) {
// shoot = false
// // player ! Shoot(lastDir)
// }
// movementActor ! MovementActor.Tick(tpf, geom, cam)
imMovementActor ! ImMovementActor.Tick(tpf)
// movementActorTimer ! MovementActorTimer.Update(tpf)
}
override def stop(): Unit = {}
// override protected def cleanup(app: Application): Unit = {
// // gameAppActor ! GameAppActor.Stop
// super.cleanup(app)
// }
def setupKeys(inputManager: InputManager) = {
inputManager
@ -79,33 +87,32 @@ class PlayerMovementState2(
// new KeyTrigger(KeyInput.KEY_D),
new KeyTrigger(KeyInput.KEY_RIGHT)
)
inputManager.addMapping(
.withMapping(
"Up",
// new KeyTrigger(KeyInput.KEY_W),
new KeyTrigger(KeyInput.KEY_UP)
)
inputManager.addMapping(
.withMapping(
"Down",
// new KeyTrigger(KeyInput.KEY_S),
new KeyTrigger(KeyInput.KEY_DOWN)
)
inputManager.addMapping(
.withMapping(
"Space",
new KeyTrigger(KeyInput.KEY_SPACE),
new KeyTrigger(KeyInput.KEY_H)
)
inputManager.addMapping(
.withMapping(
"Reset",
new KeyTrigger(KeyInput.KEY_R),
new KeyTrigger(KeyInput.KEY_RETURN)
)
inputManager
.withListener(this, "Left")
.withListener(this, "Right")
inputManager.addListener(this, "Up")
inputManager.addListener(this, "Down")
inputManager.addListener(this, "Space")
inputManager.addListener(this, "Reset")
.withListener(this, "Up")
.withListener(this, "Down")
.withListener(this, "Space")
.withListener(this, "Reset")
}
def onAction(binding: String, value: Boolean, tpf: Float) =
@ -118,6 +125,10 @@ class PlayerMovementState2(
case _ =>
}
override protected def onEnable(): Unit = {}
override protected def onDisable(): Unit = {}
}
final case class CardinalDirection(

View File

@ -4,7 +4,6 @@ import ammonite.runtime.Storage.Folder
import ammonite.main.Defaults
import ammonite.Main
import javax.script.ScriptEngine
import com.jme3.app.Application
import com.jme3.app.state.AppState
import akka.actor.typed.scaladsl.AbstractBehavior
import akka.actor.typed.scaladsl.ActorContext
@ -31,10 +30,11 @@ class ScriptingEngineState(
// _
// )
// )
override def stop(): Unit = {}
override protected def cleanup(app: Application): Unit = {
// actorSystem.terminate()
}
// override protected def cleanup(app: Application): Unit = {
// // actorSystem.terminate()
// }
// override protected def initialize(app: Application): Unit = {
// super.initialize(app)

View File

@ -1,6 +1,5 @@
package wow.doge.mygame.state
import com.jme3.app.Application
import wow.doge.mygame.implicits._
import wow.doge.mygame.components.TestComponent
import com.jme3.scene.shape.Box
@ -32,7 +31,7 @@ class TestAppState(
val mat = MyMaterial(
assetManager = assetManager,
path = "Common/MatDefs/Misc/Unshaded.j3md"
path = os.rel / "Common" / "MatDefs" / "Misc" / "Unshaded.j3md"
)
geom.foreach(e => {
e.setMaterial(mat)
@ -45,14 +44,15 @@ class TestAppState(
geom.foreach(_.move(new Vector3f(0, 1 * tpf, 0)))
}
override def cleanup(app: Application): Unit = {
// _entity.map(_.close())
// _entity = None
}
// override def cleanup(app: Application): Unit = {
// // _entity.map(_.close())
// // _entity = None
// }
override def onEnable(): Unit = {}
override def onDisable(): Unit = {}
override def stop(): Unit = {}
}
@ -61,10 +61,10 @@ object MyMaterial {
color: String = "Color",
colorType: com.jme3.math.ColorRGBA = ColorRGBA.Blue,
assetManager: AssetManager,
path: String
path: os.RelPath
): Material = {
val mat =
new Material(assetManager, path)
new Material(assetManager, path.toString())
mat.setColor(color, colorType)
mat
}

View File

@ -0,0 +1,94 @@
package wow.doge.mygame.game.nodes
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
// class PlayerNode(val name: String) extends Node(name) {}
object PlayerNode {
def defaultMesh() = {
lazy val b = new Box(1, 1, 1)
lazy val geom = new Geometry("playerMesh", b)
geom
}
def defaultTexture(assetManager: AssetManager) =
MyMaterial(
assetManager = assetManager,
path = os.rel / "Common" / "MatDefs" / "Misc" / "Unshaded.j3md"
)
def apply(
modelPath: os.RelPath,
cam: Camera
)(assetManager: AssetManager) = {
lazy val playerNode = new Node("PlayerNode")
lazy val camNode = new CameraNode("CameraNode", cam)
// lazy val camNode = new CameraNode("CameraNode", simpleApp.getCamera())
// camNode.setCamera(simpleApp.getCamera())
val playerModel: Node =
assetManager.loadModel(modelPath).asInstanceOf[Node]
discard {
playerNode
.child(camNode)
.child(playerModel)
// playerNode.children(Seq(camNode, geom))
}
{
camNode.setControlDir(ControlDirection.SpatialToCamera)
camNode.setLocalTranslation(
new Vector3f(0, 1.5f, 10)
)
camNode.lookAt(playerNode.getLocalTranslation(), Vector3f.UNIT_Y)
}
playerNode
}
def apply(
mesh: Geometry = defaultMesh(),
texturePath: os.RelPath =
os.rel / "Common" / "MatDefs" / "Misc" / "Unshaded.j3md",
cam: Camera
)(assetManager: AssetManager): Node = {
lazy val playerNode = new Node("PlayerNode")
lazy val camNode = new CameraNode("CameraNode", cam)
{
val mat = MyMaterial(
assetManager = assetManager,
path = texturePath
)
mesh.setMaterial(mat)
}
// lazy val camNode = new CameraNode("CameraNode", simpleApp.getCamera())
// camNode.setCamera(simpleApp.getCamera())
discard {
playerNode
.child(camNode)
.child(mesh)
// playerNode.children(Seq(camNode, geom))
}
{
camNode.setControlDir(ControlDirection.SpatialToCamera)
camNode.setLocalTranslation(
new Vector3f(0, 1.5f, 10)
)
camNode.lookAt(playerNode.getLocalTranslation(), Vector3f.UNIT_Y)
}
playerNode
}
}

View File

@ -12,7 +12,7 @@ import com.simsilica.es.EntityId
import akka.actor.typed.ActorRef
import akka.util.Timeout
import akka.actor.typed.Scheduler
import monix.eval.Task
import monix.bio.Task
import com.jme3.input.InputManager
import com.jme3.input.controls.Trigger
import com.jme3.input.controls.InputListener
@ -22,8 +22,76 @@ import com.jme3.scene.Geometry
import wow.doge.mygame.state.CardinalDirection
import wow.doge.mygame.state.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
import com.jme3.asset.AssetLocator
import com.jme3.input.controls.ActionListener
import monix.reactive.OverflowStrategy
import monix.execution.Ack
import monix.execution.Cancelable
import monix.execution.cancelables.SingleAssignCancelable
import com.jme3.input.Action
import com.jme3.bullet.PhysicsSpace
import com.jme3.bullet.collision.PhysicsCollisionListener
import com.jme3.bullet.collision.PhysicsCollisionEvent
import com.jme3.bullet.PhysicsTickListener
import monix.reactive.observers.Subscriber
import monix.execution.Ack.Continue
import monix.execution.Ack.Stop
case class ActionEvent(binding: Action, value: Boolean, tpf: Float)
case class PhysicsTickEvent(space: PhysicsSpace, tpf: Float)
package object implicits {
type PrePhysicsTickEvent = PhysicsTickEvent
type PhysicsTickObservable =
Observable[Either[PrePhysicsTickEvent, PhysicsTickEvent]]
implicit class JMEAppExt(val app: Application) extends AnyVal {
/**
* Blocking task. Execute on a thread pool meant for blocking operations.
* Prefer [[wow.doge.mygame.implicits.JMEAppExt#enqueueT]] instead.
*
* @param cb
* @param ec
* @return
*/
def enqueueF[T](cb: () => T)(implicit ec: ExecutionContext): Future[T] =
Future {
app
.enqueue(new Callable[T]() {
override def call(): T = cb()
})
.get()
}
/**
* Blocking task. Execute on a thread pool meant for blocking operations.
* Same as enqueue, but returns a Monix Task instead of Future
* @param cb
* @param ec
* @return
*/
def enqueueL[T](cb: () => T): Task[T] =
Task
.deferFutureAction(implicit s => enqueueF(cb))
def enqueueF[T](cb: => T) =
app.enqueue(new Runnable {
override def run() = cb
})
def enqueueT(cb: => Unit) =
Task(enqueueF(cb))
}
implicit class StateManagerExt(val sm: AppStateManager) extends AnyVal {
def state[S <: AppState]()(implicit c: ClassTag[S]): S =
sm.getState(c.runtimeClass.asInstanceOf[Class[S]])
@ -32,14 +100,126 @@ package object implicits {
implicit class SimpleApplicationExt(val sa: SimpleApplication)
extends AnyVal {
def stateManager() = sa.getStateManager()
def stateManager: AppStateManager = sa.getStateManager()
def inputManager: InputManager = sa.getInputManager()
def assetManager: AssetManager = sa.getAssetManager()
def guiNode = sa.getGuiNode()
def flyCam = Option(sa.getFlyByCamera())
def camera = sa.getCamera()
def viewPort = sa.getViewPort()
}
implicit class NodeExt(val n: Node) extends AnyVal {
/**
* Attaches the given child
*
* @param s
* @return
*/
def child(s: Spatial): Node = {
n.attachChild(s)
n
}
/**
* Gets the list of children as a scala collection
*
* @return
*/
// def children = n.getChildren().asScala.toSeq
def children = Observable.fromIterable(n.getChildren().asScala)
/**
* Attach given children
*
* @param lst
*/
def children(lst: Iterable[Spatial]): Unit = {
for (c <- lst) n.child(c)
}
def depthFirst(cb: Spatial => Unit) =
n.depthFirstTraversal(new SceneGraphVisitor() {
override def visit(s: Spatial) = cb(s)
})
def observableDepthFirst(): Observable[Spatial] = {
def loop(
subscriber: Subscriber[Spatial],
spatial: Spatial
): Task[Unit] = {
//spatial can be either a node or a geometry, but it's not a sealed trait
spatial match {
case node: Node =>
Task.deferFuture(subscriber.onNext(node)).flatMap {
case Ack.Continue => {
//modifying a node's children list is forbidden
val children = node.getChildren().asScala.to(LazyList)
if (!children.isEmpty) {
Task.sequence(
children.map(c => loop(subscriber, c))
) >> Task.unit
} else {
Task.unit
}
}
case Ack.Stop => Task.unit
}
//geomtries do not/cannot have children
case g: Geometry =>
Task.deferFuture(subscriber.onNext(g)) >> Task.unit
case _ => Task.unit
}
}
Observable.create(OverflowStrategy.Unbounded) { sub =>
implicit val sched = sub.scheduler
loop(sub, n).runToFuture
}
}
def breadthFirst(cb: Spatial => Unit) =
n.breadthFirstTraversal(new SceneGraphVisitor() {
override def visit(s: Spatial) = cb(s)
})
def observableBreadthFirst(): Observable[Spatial] = {
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
spatials match {
case head #:: tail =>
head match {
case g: Geometry =>
Task.deferFuture(subscriber.onNext(g)).flatMap {
case Continue =>
loop(subscriber, tail)
case Stop => Task.unit
}
case node: Node =>
val children = node.getChildren().asScala.to(LazyList)
Task.deferFuture(subscriber.onNext(node)).flatMap {
case Continue =>
loop(subscriber, tail #::: children)
case Stop => Task.unit
}
// case _ => loop(subscriber, tail)
}
case LazyList() => Task.unit
}
}
Observable.create(OverflowStrategy.Unbounded) { sub =>
implicit val sched = sub.scheduler
loop(sub, LazyList(n)).runToFuture
}
}
}
implicit class EntityDataExt(val ed: EntityData) extends AnyVal {
@ -81,6 +261,93 @@ package object implicits {
inputManager.addListener(listener, mappings: _*)
inputManager
}
def observableAction(mappingNames: String*): Observable[ActionEvent] = {
Observable.create(OverflowStrategy.Unbounded) { sub =>
val c = SingleAssignCancelable()
val al = new ActionListener {
override def onAction(
binding: String,
value: Boolean,
tpf: Float
): Unit = {
if (
sub.onNext(ActionEvent(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 {
def collisionObservable(): Observable[PhysicsCollisionEvent] = {
Observable.create(OverflowStrategy.Unbounded) { sub =>
val c = SingleAssignCancelable()
val cl = new PhysicsCollisionListener {
override def collision(event: PhysicsCollisionEvent): Unit = {
if (sub.onNext(event) == Ack.Stop)
c.cancel()
}
}
space.addCollisionListener(cl)
c := Cancelable(() => space.removeCollisionListener(cl))
c
}
}
def physicsTickObservable(): PhysicsTickObservable = {
Observable.create(OverflowStrategy.Unbounded) { sub =>
val c = SingleAssignCancelable()
val cl = new PhysicsTickListener {
override def prePhysicsTick(space: PhysicsSpace, tpf: Float): Unit = {
val event = PhysicsTickEvent(space, tpf)
if (sub.onNext(Left(event)) == Ack.Stop)
c.cancel()
}
override def physicsTick(space: PhysicsSpace, tpf: Float): Unit = {
val event = PhysicsTickEvent(space, tpf)
if (sub.onNext(Right(event)) == Ack.Stop)
c.cancel()
}
}
space.addTickListener(cl)
c := Cancelable(() => space.removeTickListener(cl))
c
}
}
}
implicit class AssetManagerExt(val am: AssetManager) extends AnyVal {
def registerLocator(
assetPath: os.RelPath,
locator: Class[_ <: AssetLocator]
): Unit = {
am.registerLocator(assetPath.toString(), locator)
}
def loadModel(assetPath: os.RelPath): Spatial = {
am.loadModel(assetPath.toString())
}
}
implicit class Vector3fExt(val v: Vector3f) extends AnyVal {
@ -134,14 +401,52 @@ package object implicits {
// ev.+
// }
implicit val implCanMoveForGeom = new CanMove[Geometry] {
implicit val implCanMoveForGeom = new CanMove[Spatial] {
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 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
@ -149,27 +454,61 @@ package object implicits {
val mutWalkDir = new Vector3f()
if (dir.left) {
// ctx.log.trace("left")
mutWalkDir += (zero + camLeft).mutable
mutWalkDir += (zero + ImVector3f(-1, 0, 0)).mutable
}
if (dir.right) {
// ctx.log.trace("right")
mutWalkDir += (zero + -camLeft).mutable
mutWalkDir += (zero + ImVector3f(1, 0, 0)).mutable
}
if (dir.up) {
// ctx.log.trace("up")
mutWalkDir += (zero + camDir).mutable
mutWalkDir += (zero + ImVector3f(0, 0, -1)).mutable
}
if (dir.down) {
// ctx.log.trace("down")
mutWalkDir += (zero + -camDir).mutable
mutWalkDir += (zero + ImVector3f(0, 0, 1)).mutable
}
mutWalkDir.immutable
}
walkDir
}
override def move(geom: Geometry, direction: ImVector3f): Unit = {
val v = geom.getLocalTranslation()
geom.setLocalTranslation(v += direction.mutable)
}
implicit val implJFXConsoleStreamForTextArea =
new JFXConsoleStreamable[scalafx.scene.control.TextArea] {
override def println(
ta: scalafx.scene.control.TextArea,
text: String
): Unit =
ta.appendText(text + "\n")
override def print(
ta: scalafx.scene.control.TextArea,
text: String
): Unit =
ta.appendText(text)
}
// val TasktoUIO = new FunctionK[Task, UIO] {
// def apply[T](f: Task[T]): UIO[T] =
// f.hideErrors
// }
}
// Observable.create(OverflowStrategy.Unbounded) { sub =>
// // val c = SingleAssignCancelable()
// val visitor = new SceneGraphVisitor {
// override def visit(s: Spatial): Unit = {
// sub.onNext(s)
// // if (sub.onNext(s) == Ack.Stop)
// // c.cancel()
// }
// }
// n.depthFirstTraversal(visitor)
// // c := Cancelable(() => ???)
// // c
// Cancelable.empty

View File

@ -1,4 +1,4 @@
package wow.doge.mygame.math
package wow.doge.mygame.math;
case class ImVector3f(x: Float = 0f, y: Float = 0f, z: Float = 0f)

View File

@ -0,0 +1,142 @@
package wow.doge.mygame.subsystems.moddingsystem
import java.io.FileNotFoundException
import scala.collection.View
import scala.collection.immutable.ArraySeq
import scala.util.Try
import cats.implicits._
import io.circe._
import io.circe.generic.semiauto._
import io.circe.parser._
import monix.bio.IO
import monix.bio.UIO
import java.nio.file.NoSuchFileException
import io.circe.generic.JsonCodec
@JsonCodec
case class Test1(hello1: String, hello2: String)
@JsonCodec
case class Test2(hello1: String)
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
case object GenericError extends Error
def readPluginsList(dir: os.Path): Try[Either[Error, ArraySeq[Plugin]]] =
Try(
parse(os.read(dir / "plugins.json"))
.map(
_.as[ArraySeq[Plugin]]
.leftMap(e => CouldNotDecode(e.getMessage()))
)
.leftMap((e: ParsingFailure) => ParseFailure(e.message))
.flatten
)
// .toValidated
def findPluginFiles(dir: os.Path): View[os.Path] =
os.list(dir)
.view
.filter(f => f.ext == "json" && f.baseName.endsWith("plugin"))
def findAndReadPluginFiles(
dir: os.Path,
plugins: ArraySeq[Plugin]
) =
plugins
.sortBy(_.priority)
.view
.map(p =>
p ->
Either
.catchNonFatal {
val path = dir / os.RelPath(p.name + ".plugin.json")
os.read(path)
}
.leftMap {
case _: FileNotFoundException =>
FileNotFound(p.name)
case _: NoSuchFileException => FileNotFound(p.name)
case e => GenericError
}
)
.partitionMap {
case (p, either) =>
either match {
case Left(value) => Left(p -> value)
case Right(value) => Right(p -> value)
}
}
def readPluginFiles(filePaths: View[os.Path]) = {
filePaths.map(path => os.read(path))
}
def parsePluginFiles(files: View[(Plugin, String)]) =
files
.map {
case (p, s) => p -> parse(s)
}
.partitionMap {
case (p, Left(value)) => Left(p -> value)
case (p, Right(value)) => Right(p -> value)
}
def mergePluginData(plugins: View[(Plugin, Json)]) = {
plugins.foldLeft(Json.fromString("empty")) {
case (json, that) =>
that match {
case (p, io.circe.Json.Null) => json //ignore null values
case (p, value) => json.deepMerge(value)
}
}
}
// def test =
// for {
// filePaths <- Task(findPluginFiles(os.pwd))
// files <- Task(readPluginFiles(filePaths))
// (failures, successes) <- Task(parsePluginFiles(files))
// merged <- Task(mergePluginData(successes))
// _ <- Task {
// println(s"Successes = ${successes.to(Seq)}")
// println(s"Failure = ${failures.to(Seq)}")
// println(s"Merged = $merged")
// }
// } yield ()
def test(wd: os.Path = os.pwd) =
for {
plugins <- IO.fromTryEither(readPluginsList(wd))
(readFailures, readSuccesses) <- UIO(findAndReadPluginFiles(wd, plugins))
(parseFailures, parseSuccesses) <- UIO(parsePluginFiles(readSuccesses))
res <- UIO(mergePluginData(parseSuccesses))
f <- UIO {
println(s"Read Successes = ${readSuccesses.to(Seq)}")
println(s"Read Failures = ${readFailures.to(Seq)}")
println(s"Parse Successes = ${parseSuccesses.to(Seq)}")
println(s"Parse Failures = ${parseFailures.to(Seq)}")
println(s"Merged = $res")
}
} yield ()
// monix.eval.Task.deferAction(implicit s =>
// ModdingSystem
// .test()
// .leftMap(e => new Throwable(e.toString()))
// .to[monix.eval.Task]
// )
// def test3(wd: os.Path = os.pwd) = {
// (readPluginsList(os.pwd).toValidatedNec)
// }
;
}

View File

@ -3,7 +3,6 @@ package wow.doge.mygame.state
import akka.actor.typed.scaladsl.Behaviors
import ammonite.Main
import akka.actor.typed.ActorRef
import com.jme3.app.state.AppState
import ammonite.runtime.Storage.Folder
import ammonite.main.Defaults
import akka.actor.typed.Behavior
@ -11,18 +10,17 @@ import akka.actor.typed.scaladsl.ActorContext
import ammonite.util.Res.Success
import javax.script.ScriptEngine
import javax.script.ScriptEngineManager
import scala.util.Try
import groovy.util.GroovyScriptEngine
import cats.implicits._
object ScriptActor {
sealed trait Result
final case class AppStateResult(state: AppState) extends Result
final case class Error(reason: String) extends Result
type Kotlin
type MyScriptEngine[T] = ScriptEngine
type KotlinScriptEngine = MyScriptEngine[Kotlin]
final case class Error(reason: String)
sealed trait Command
final case class Compile(path: os.Path, sender: ActorRef[Result])
extends Command
final case class CompileAny(
path: os.Path,
result: ActorRef[Either[Error, Any]]
@ -38,19 +36,28 @@ object ScriptActor {
)
)
def defaultKotlinRunner(): ScriptEngine = {
def defaultKotlinRunner(): KotlinScriptEngine = {
val manager = new ScriptEngineManager()
val engine = manager.getEngineByExtension("main.kts")
engine
}
def defaultGroovyRunner(): GroovyScriptEngine =
new GroovyScriptEngine(os.pwd.toString())
def apply(
scalaRunner: Main = defaultScalaRunner(),
kotlinRunner: ScriptEngine = defaultKotlinRunner()
kotlinRunner: KotlinScriptEngine = defaultKotlinRunner(),
groovyRunner: GroovyScriptEngine = defaultGroovyRunner()
// parent: ActorRef[ScriptStoringActor.Command]
): Behavior[ScriptActor.Command] =
Behaviors.setup(ctx =>
new ScriptActor(scalaRunner, kotlinRunner, ctx).receiveMessage
new ScriptActor(
scalaRunner,
kotlinRunner,
groovyRunner,
ctx
).receiveMessage
)
sealed trait ScriptType
@ -80,20 +87,31 @@ object ScriptActor {
case _ => Left(Error("Failed to run script"))
}
def runKotlin(path: os.Path, kotlinRunner: ScriptEngine): Either[Error, Any] =
Try(kotlinRunner.eval(os.read(path))).toEither.leftMap(t =>
Error(t.getMessage())
)
def runKotlin(
path: os.Path,
kotlinRunner: KotlinScriptEngine
): Either[Error, Any] =
Either
.catchNonFatal(kotlinRunner.eval(os.read(path)))
.leftMap(t => Error(t.getMessage()))
def runGroovy(path: os.Path): Either[Error, Any] =
Left(Error("Not implemented yet"))
def runGroovy(
path: os.Path,
groovyRunner: GroovyScriptEngine
): Either[Error, Any] =
Either
.catchNonFatal(groovyRunner.run(path.relativeTo(os.pwd).toString(), ""))
.leftMap(t => Error(t.getMessage()))
def ensureReturnedObjectNotNull(scriptObject: Any): Either[Error, Any] =
Either.fromOption(Option(scriptObject), Error("unknown object"))
}
class ScriptActor(
val scalaRunner: Main,
val kotlinRunner: ScriptEngine,
// parent: ActorRef[ScriptStoringActor.Command],
val scalaRunner: ammonite.Main,
val kotlinRunner: ScriptActor.KotlinScriptEngine,
val groovyRunner: GroovyScriptEngine,
context: ActorContext[ScriptActor.Command]
) {
import ScriptActor._
@ -101,57 +119,24 @@ class ScriptActor(
def receiveMessage: Behavior[Command] =
Behaviors.receiveMessage { msg =>
msg match {
case Compile(path, sender) =>
context.log.debug(s"Received $path")
val res = getScript(path)
sender ! res
Behaviors.same
// case CompileScripts(sender, paths) =>
case CompileAny(path, requester) =>
context.log.debug(s"Received $path")
val res = getScript2(path)
val res = getScript(path)
context.log.debug(s"result = $res")
requester ! res
// parent ! ScriptStoringActor.Put(path, res)
Behaviors.same
}
}
def getScript(path: os.Path) = {
val res = determineScriptType(path) match {
case Right(ScalaType) => runScala(path, scalaRunner)
case Right(KotlinType) => runKotlin(path, kotlinRunner)
case Right(GroovyType) => runGroovy(path)
def getScript(path: os.Path): Either[Error, Any] =
determineScriptType(path) match {
case Right(ScalaType) =>
runScala(path, scalaRunner).flatMap(ensureReturnedObjectNotNull)
case Right(KotlinType) =>
runKotlin(path, kotlinRunner).flatMap(ensureReturnedObjectNotNull)
case Right(GroovyType) =>
runGroovy(path, groovyRunner).flatMap(ensureReturnedObjectNotNull)
case l @ Left(err) => l
}
res match {
case Left(err) => err
case Right(obj) =>
obj match {
case s: MyBaseState => AppStateResult(s)
case _ => Error("Class in script does not match known types")
}
}
}
def getScript2(path: os.Path): Either[Error, Any] = {
val res = determineScriptType(path) match {
case Right(ScalaType) => runScala(path, scalaRunner)
case Right(KotlinType) => runKotlin(path, kotlinRunner)
case Right(GroovyType) => runGroovy(path)
case l @ Left(err) => l
}
// res match {
// case Left(err) => err
// case Right(obj) =>
// obj match {
// case s: MyBaseState => AppStateResult(s)
// case _ => Error("Class in script does not match known types")
// }
// }
res
}
}

View File

@ -17,9 +17,9 @@ object ScriptCachingActor {
/**
* aka script representation
*/
type ScriptRepr = Any
type ScriptsMap = Map[os.Path, ScriptRepr]
type ScriptResult = Either[ScriptActor.Error, ScriptRepr]
type ScriptObject = Any
type ScriptsMap = Map[os.Path, ScriptObject]
type ScriptResult = Either[ScriptActor.Error, ScriptObject]
sealed trait Command
final case class Get(
@ -27,10 +27,11 @@ object ScriptCachingActor {
requester: ActorRef[ScriptResult]
) extends Command
final case class GetMap(requester: ActorRef[ScriptsMap]) extends Command
final case class Put(scriptPath: os.Path, script: ScriptRepr) extends Command
private final case object NoOp extends Command
final case class Put(scriptPath: os.Path, script: ScriptObject)
extends Command
private[scriptsystem] final case object NoOp extends Command
private final case class DelegateToChild(
private[scriptsystem] final case class DelegateToChild(
scriptActor: ActorRef[ScriptActor.Command],
scriptPath: os.Path,
requester: ActorRef[ScriptResult]
@ -41,6 +42,7 @@ object ScriptCachingActor {
scriptActor: ActorRef[ScriptActor.Command]
)
final case class State(scriptsMap: ScriptsMap)
def apply(state: State = State(Map.empty)): Behavior[Command] =
Behaviors.logMessages {
Behaviors.setup { ctx =>
@ -49,58 +51,12 @@ object ScriptCachingActor {
new ScriptCachingActor(Props(ctx, scriptsRouter)).receiveMessage(state)
}
}
private def getOrCompileScript(
ctx: ActorContext[Command],
scriptPath: os.Path,
scriptsMap: ScriptsMap,
scriptActor: ActorRef[ScriptActor.Command],
requester: ActorRef[ScriptResult]
) = {
scriptsMap
.get(scriptPath)
.fold {
ctx.log.debug("Delegating to child")
ctx.self ! DelegateToChild(
scriptActor,
scriptPath,
requester
)
} { s =>
ctx.log.debug("Getting script from cache")
requester ! Right(s)
}
}
private def askChildForScriptCompilation(
ctx: ActorContext[Command],
scriptActor: ActorRef[ScriptActor.Command],
scriptPath: os.Path,
requester: ActorRef[ScriptResult]
)(implicit timeout: Timeout) = {
ctx.ask(scriptActor, ScriptActor.CompileAny(scriptPath, _)) {
case Success(value) =>
requester ! value
value.fold(
err => {
ctx.log.error(err.reason)
NoOp
},
res => {
Put(scriptPath, res)
}
)
case Failure(exception) => {
ctx.log.error(exception.getMessage())
NoOp
}
}
}
}
class ScriptCachingActor(props: ScriptCachingActor.Props) {
import com.softwaremill.quicklens._
import ScriptCachingActor._
import Methods._
def receiveMessage(state: State): Behavior[Command] =
Behaviors.receiveMessage { msg =>
msg match {
@ -134,7 +90,7 @@ class ScriptCachingActor(props: ScriptCachingActor.Props) {
props.ctx.log.debug(s"Putting $script at path $scriptPath")
val newState =
state.modify(_.scriptsMap).using(_ + (scriptPath -> script))
props.ctx.log.debug(newState.toString())
props.ctx.log.trace(newState.toString())
receiveMessage(state = newState)
case NoOp => Behaviors.same
@ -152,15 +108,54 @@ object ScriptActorPool {
.supervise(ScriptActor())
.onFailure[Exception](SupervisorStrategy.restart)
)
// def apply(
// poolSize: Int,
// parent: ActorRef[ScriptStoringActor.Command]
// ): PoolRouter[ScriptActor.Command] =
// Routers.pool(poolSize = poolSize)(
// // make sure the workers are restarted if they fail
// Behaviors
// .supervise(ScriptActor(parent = parent))
// .onFailure[Exception](SupervisorStrategy.restart)
// )
}
private[scriptsystem] object Methods {
import ScriptCachingActor._
def getOrCompileScript(
ctx: ActorContext[Command],
scriptPath: os.Path,
scriptsMap: ScriptsMap,
scriptActor: ActorRef[ScriptActor.Command],
requester: ActorRef[ScriptResult]
) = {
scriptsMap
.get(scriptPath)
.fold {
ctx.log.debug("Delegating to child")
ctx.self ! DelegateToChild(
scriptActor,
scriptPath,
requester
)
} { s =>
ctx.log.debug("Getting script from cache")
requester ! Right(s)
}
}
def askChildForScriptCompilation(
ctx: ActorContext[Command],
scriptActor: ActorRef[ScriptActor.Command],
scriptPath: os.Path,
requester: ActorRef[ScriptResult]
)(implicit timeout: Timeout) = {
ctx.ask(scriptActor, ScriptActor.CompileAny(scriptPath, _)) {
case Success(value) =>
requester ! value
value.fold(
err => {
ctx.log.error(err.reason)
NoOp
},
res => {
Put(scriptPath, res)
}
)
case Failure(exception) => {
ctx.log.error(exception.getMessage())
NoOp
}
}
}
}

View File

@ -0,0 +1,53 @@
package wow.doge.mygame.utils
import java.io.PrintStream
import java.io.OutputStream
import java.io.ByteArrayOutputStream
import scalafx.scene.control.TextArea
import wow.doge.mygame.implicits._
import cats.effect.Resource
import monix.bio.Task
trait JFXConsoleStreamable[T] {
def println(inst: T, text: String): Unit
def print(inst: T, text: String): Unit
}
class JFXConsoleStream[T](
outputStream: OutputStream,
val config: JFXConsoleStream.Config = JFXConsoleStream.Config.default,
control: T
)(implicit
jcs: JFXConsoleStreamable[T]
) extends PrintStream(outputStream, true) {
private lazy val defaultOut = System.out
override def println(text: String): Unit =
if (config.exclusive) {
jcs.println(control, text + "\n")
} else {
defaultOut.println(text)
jcs.println(control, text + "\n")
}
override def print(text: String): Unit = jcs.println(control, text)
}
object JFXConsoleStream {
/**
* for future use
*/
case class Config(exclusive: Boolean = false)
object Config {
lazy val default = Config()
}
def textAreaStream(ta: TextArea) =
Resource.make(
Task(
new JFXConsoleStream(
outputStream = new ByteArrayOutputStream(),
control = ta
)
)
)(s => Task(s.close()))
}

1
test.plugin.json Normal file
View File

@ -0,0 +1 @@
{ "hello1": "world1", "hello2": "world2" }

1
test2.plugin.json Normal file
View File

@ -0,0 +1 @@
{ "hello2": "world3" }