diff --git a/.jvmopts b/.jvmopts new file mode 100644 index 0000000..35cfe67 --- /dev/null +++ b/.jvmopts @@ -0,0 +1,4 @@ +-Xms2G +-Xmx2G +-XX:+UseG1GC +-XX:MaxGCPauseMillis=50 \ No newline at end of file diff --git a/build.sbt b/build.sbt index 4facde8..cbe57ae 100644 --- a/build.sbt +++ b/build.sbt @@ -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") diff --git a/launch.sh b/launch.sh new file mode 100644 index 0000000..cbe1a59 --- /dev/null +++ b/launch.sh @@ -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 \ No newline at end of file diff --git a/lib/jme3-testdata.jar b/lib/jme3-testdata.jar new file mode 100644 index 0000000..df1ad44 Binary files /dev/null and b/lib/jme3-testdata.jar differ diff --git a/plugins.json b/plugins.json new file mode 100644 index 0000000..f270bdb --- /dev/null +++ b/plugins.json @@ -0,0 +1,4 @@ +[ + { "name": "test", "priority": 1 }, + { "name": "test2", "priority": 2 } +] \ No newline at end of file diff --git a/project/plugins.sbt b/project/plugins.sbt index eb7d2e4..fac4060 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -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") diff --git a/src/main/resources/hello.groovy b/src/main/resources/hello.groovy new file mode 100644 index 0000000..546f358 --- /dev/null +++ b/src/main/resources/hello.groovy @@ -0,0 +1,3 @@ +println "hello" + +this \ No newline at end of file diff --git a/src/main/resources/hello2.sc b/src/main/resources/hello2.sc index 34bd691..740501c 100644 --- a/src/main/resources/hello2.sc +++ b/src/main/resources/hello2.sc @@ -1,4 +1,5 @@ #!/usr/bin/env amm +// scala 2.13.3 // import coursierapi.MavenRepository diff --git a/src/main/scala/org/slf4j/impl/StaticLoggerBuilder.scala b/src/main/scala/org/slf4j/impl/StaticLoggerBuilder.scala new file mode 100644 index 0000000..1cfcdb7 --- /dev/null +++ b/src/main/scala/org/slf4j/impl/StaticLoggerBuilder.scala @@ -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 + +} diff --git a/src/main/scala/wow/doge/mygame/Main.scala b/src/main/scala/wow/doge/mygame/Main.scala index a395830..4c7fd95 100644 --- a/src/main/scala/wow/doge/mygame/Main.scala +++ b/src/main/scala/wow/doge/mygame/Main.scala @@ -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) +import monix.bio.Task +import cats.effect.Resource +import io.odin.syntax._ - // 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") +// import io.odin.monix._ +import cats.effect.ExitCode +import cats.implicits._ +import com.softwaremill.macwire._ +import scala.concurrent.duration._ +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._ - testActor ! TestActor.Test - SpawnProtocol() - } -} +// import wow.doge.mygame.implicits._ -object TestActor { - sealed trait Command - case object Test extends Command - private case object Done extends Command - // sealed trait Result - // case object Done extends Result +// object Main extends App { +// import java.util.logging.{Logger, Level} +// Logger.getLogger("").setLevel(Level.SEVERE) - import scala.concurrent.duration._ - implicit val timeout = Timeout(15.seconds) - // implicit val scheduler = +// // 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) +// } - 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", - // _ - // ) - // ) { - // 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) +object Main extends BIOApp with MainModule { + import java.util.logging.{Logger => JLogger, Level} + JLogger.getLogger("").setLevel(Level.SEVERE) - // 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 - // } + def run(args: List[String]): UIO[ExitCode] = { - Behaviors.same - case Done => Behaviors.same - } + 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)) + // ) - // SpawnProtocol() - // Behaviors.same + _ <- 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) + } } diff --git a/src/main/scala/wow/doge/mygame/MainModule.scala b/src/main/scala/wow/doge/mygame/MainModule.scala new file mode 100644 index 0000000..fab14a0 --- /dev/null +++ b/src/main/scala/wow/doge/mygame/MainModule.scala @@ -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 + + } + ) +} diff --git a/src/main/scala/wow/doge/mygame/executors/ExecutorsModule.scala b/src/main/scala/wow/doge/mygame/executors/ExecutorsModule.scala index f1b2e36..d607079 100644 --- a/src/main/scala/wow/doge/mygame/executors/ExecutorsModule.scala +++ b/src/main/scala/wow/doge/mygame/executors/ExecutorsModule.scala @@ -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())) } diff --git a/src/main/scala/wow/doge/mygame/executors/GUIExecutor.scala b/src/main/scala/wow/doge/mygame/executors/GUIExecutor.scala index 07faa02..f1f78d0 100644 --- a/src/main/scala/wow/doge/mygame/executors/GUIExecutor.scala +++ b/src/main/scala/wow/doge/mygame/executors/GUIExecutor.scala @@ -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( diff --git a/src/main/scala/wow/doge/mygame/executors/Schedulers.scala b/src/main/scala/wow/doge/mygame/executors/Schedulers.scala index a8e817d..6eb2fb8 100644 --- a/src/main/scala/wow/doge/mygame/executors/Schedulers.scala +++ b/src/main/scala/wow/doge/mygame/executors/Schedulers.scala @@ -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 ) diff --git a/src/main/scala/wow/doge/mygame/game/GameApp.scala b/src/main/scala/wow/doge/mygame/game/GameApp.scala index 676ed48..fb4e7c9 100644 --- a/src/main/scala/wow/doge/mygame/game/GameApp.scala +++ b/src/main/scala/wow/doge/mygame/game/GameApp.scala @@ -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() -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 = { + // println("stopping") + // } + def stop[T](cb: () => T): Unit = { + println("destroy") + cb() + super.stop() + } + + // override def stop(): Unit = {} } diff --git a/src/main/scala/wow/doge/mygame/game/GameAppActor.scala b/src/main/scala/wow/doge/mygame/game/GameAppActor.scala index 18a388a..6fd282c 100644 --- a/src/main/scala/wow/doge/mygame/game/GameAppActor.scala +++ b/src/main/scala/wow/doge/mygame/game/GameAppActor.scala @@ -1,63 +1,177 @@ 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() - Behaviors.stopped + + 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" + // ) + } +} + object SubscribingActor { def apply() = Behaviors.receive[Events.PhysicsTick.type] { (ctx, msg) => @@ -65,3 +179,12 @@ object SubscribingActor { Behaviors.same } } +// new PlayerMovementState( +// // movementActor, +// // movementActorTimer, +// imMovementActor, +// // geom, +// // camNode, +// playerNode +// // ctx.self +// ) diff --git a/src/main/scala/wow/doge/mygame/game/GameModule.scala b/src/main/scala/wow/doge/mygame/game/GameModule.scala new file mode 100644 index 0000000..632bbcc --- /dev/null +++ b/src/main/scala/wow/doge/mygame/game/GameModule.scala @@ -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) + } +} diff --git a/src/main/scala/wow/doge/mygame/game/GameSystemsInitializer.scala b/src/main/scala/wow/doge/mygame/game/GameSystemsInitializer.scala new file mode 100644 index 0000000..b17a583 --- /dev/null +++ b/src/main/scala/wow/doge/mygame/game/GameSystemsInitializer.scala @@ -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 = {} +} diff --git a/src/main/scala/wow/doge/mygame/game/TestActor.scala b/src/main/scala/wow/doge/mygame/game/TestActor.scala new file mode 100644 index 0000000..118c438 --- /dev/null +++ b/src/main/scala/wow/doge/mygame/game/TestActor.scala @@ -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 + // } + } +} diff --git a/src/main/scala/wow/doge/mygame/state/EntityDataState.java b/src/main/scala/wow/doge/mygame/game/appstates/EntityDataState.java similarity index 95% rename from src/main/scala/wow/doge/mygame/state/EntityDataState.java rename to src/main/scala/wow/doge/mygame/game/appstates/EntityDataState.java index d748e70..952c8cb 100644 --- a/src/main/scala/wow/doge/mygame/state/EntityDataState.java +++ b/src/main/scala/wow/doge/mygame/game/appstates/EntityDataState.java @@ -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; diff --git a/src/main/scala/wow/doge/mygame/state/MovementActor.scala b/src/main/scala/wow/doge/mygame/game/appstates/MovementActor.scala similarity index 81% rename from src/main/scala/wow/doge/mygame/state/MovementActor.scala rename to src/main/scala/wow/doge/mygame/game/appstates/MovementActor.scala index 7b4786d..50cb0b9 100644 --- a/src/main/scala/wow/doge/mygame/state/MovementActor.scala +++ b/src/main/scala/wow/doge/mygame/game/appstates/MovementActor.scala @@ -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 = { - cm.move(props.movable, tmp) - } - }) + 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)) diff --git a/src/main/scala/wow/doge/mygame/state/MyBaseState.scala b/src/main/scala/wow/doge/mygame/game/appstates/MyBaseState.scala similarity index 83% rename from src/main/scala/wow/doge/mygame/state/MyBaseState.scala rename to src/main/scala/wow/doge/mygame/game/appstates/MyBaseState.scala index 743e33e..fbf147a 100644 --- a/src/main/scala/wow/doge/mygame/state/MyBaseState.scala +++ b/src/main/scala/wow/doge/mygame/game/appstates/MyBaseState.scala @@ -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: _*) diff --git a/src/main/scala/wow/doge/mygame/state/PlayerMovementState2.scala b/src/main/scala/wow/doge/mygame/game/appstates/PlayerMovementState.scala similarity index 78% rename from src/main/scala/wow/doge/mygame/state/PlayerMovementState2.scala rename to src/main/scala/wow/doge/mygame/game/appstates/PlayerMovementState.scala index 663d6da..b188362 100644 --- a/src/main/scala/wow/doge/mygame/state/PlayerMovementState2.scala +++ b/src/main/scala/wow/doge/mygame/game/appstates/PlayerMovementState.scala @@ -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( - "Up", - // new KeyTrigger(KeyInput.KEY_W), - new KeyTrigger(KeyInput.KEY_UP) - ) - inputManager.addMapping( - "Down", - // new KeyTrigger(KeyInput.KEY_S), - new KeyTrigger(KeyInput.KEY_DOWN) - ) - inputManager.addMapping( - "Space", - new KeyTrigger(KeyInput.KEY_SPACE), - new KeyTrigger(KeyInput.KEY_H) - ) - inputManager.addMapping( - "Reset", - new KeyTrigger(KeyInput.KEY_R), - new KeyTrigger(KeyInput.KEY_RETURN) - ) - inputManager + .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( + "Space", + new KeyTrigger(KeyInput.KEY_SPACE), + new KeyTrigger(KeyInput.KEY_H) + ) + .withMapping( + "Reset", + new KeyTrigger(KeyInput.KEY_R), + new KeyTrigger(KeyInput.KEY_RETURN) + ) .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( diff --git a/src/main/scala/wow/doge/mygame/state/ScriptingEngineState.scala b/src/main/scala/wow/doge/mygame/game/appstates/ScriptingEngineState.scala similarity index 97% rename from src/main/scala/wow/doge/mygame/state/ScriptingEngineState.scala rename to src/main/scala/wow/doge/mygame/game/appstates/ScriptingEngineState.scala index 0083ec4..f4c2ad1 100644 --- a/src/main/scala/wow/doge/mygame/state/ScriptingEngineState.scala +++ b/src/main/scala/wow/doge/mygame/game/appstates/ScriptingEngineState.scala @@ -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) diff --git a/src/main/scala/wow/doge/mygame/state/TestAppState.scala b/src/main/scala/wow/doge/mygame/game/appstates/TestAppState.scala similarity index 83% rename from src/main/scala/wow/doge/mygame/state/TestAppState.scala rename to src/main/scala/wow/doge/mygame/game/appstates/TestAppState.scala index b17292d..b89678a 100644 --- a/src/main/scala/wow/doge/mygame/state/TestAppState.scala +++ b/src/main/scala/wow/doge/mygame/game/appstates/TestAppState.scala @@ -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 } diff --git a/src/main/scala/wow/doge/mygame/components/Position.scala b/src/main/scala/wow/doge/mygame/game/components/Position.scala similarity index 100% rename from src/main/scala/wow/doge/mygame/components/Position.scala rename to src/main/scala/wow/doge/mygame/game/components/Position.scala diff --git a/src/main/scala/wow/doge/mygame/components/Tag.scala b/src/main/scala/wow/doge/mygame/game/components/Tag.scala similarity index 100% rename from src/main/scala/wow/doge/mygame/components/Tag.scala rename to src/main/scala/wow/doge/mygame/game/components/Tag.scala diff --git a/src/main/scala/wow/doge/mygame/components/Test.java b/src/main/scala/wow/doge/mygame/game/components/Test.java similarity index 100% rename from src/main/scala/wow/doge/mygame/components/Test.java rename to src/main/scala/wow/doge/mygame/game/components/Test.java diff --git a/src/main/scala/wow/doge/mygame/components/TestComponent.scala b/src/main/scala/wow/doge/mygame/game/components/TestComponent.scala similarity index 100% rename from src/main/scala/wow/doge/mygame/components/TestComponent.scala rename to src/main/scala/wow/doge/mygame/game/components/TestComponent.scala diff --git a/src/main/scala/wow/doge/mygame/game/nodes/PlayerNode.scala b/src/main/scala/wow/doge/mygame/game/nodes/PlayerNode.scala new file mode 100644 index 0000000..19b4864 --- /dev/null +++ b/src/main/scala/wow/doge/mygame/game/nodes/PlayerNode.scala @@ -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 + } +} diff --git a/src/main/scala/wow/doge/mygame/implicits/package.scala b/src/main/scala/wow/doge/mygame/implicits/package.scala index aa900fd..c53476f 100644 --- a/src/main/scala/wow/doge/mygame/implicits/package.scala +++ b/src/main/scala/wow/doge/mygame/implicits/package.scala @@ -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 diff --git a/src/main/scala/wow/doge/mygame/math/ImVector3f.scala b/src/main/scala/wow/doge/mygame/math/ImVector3f.scala index e221f4f..e630a46 100644 --- a/src/main/scala/wow/doge/mygame/math/ImVector3f.scala +++ b/src/main/scala/wow/doge/mygame/math/ImVector3f.scala @@ -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) diff --git a/src/main/scala/wow/doge/mygame/events/EventBus.scala b/src/main/scala/wow/doge/mygame/subsystems/events/EventBus.scala similarity index 100% rename from src/main/scala/wow/doge/mygame/events/EventBus.scala rename to src/main/scala/wow/doge/mygame/subsystems/events/EventBus.scala diff --git a/src/main/scala/wow/doge/mygame/events/Events.scala b/src/main/scala/wow/doge/mygame/subsystems/events/Events.scala similarity index 100% rename from src/main/scala/wow/doge/mygame/events/Events.scala rename to src/main/scala/wow/doge/mygame/subsystems/events/Events.scala diff --git a/src/main/scala/wow/doge/mygame/events/EventsModule.scala b/src/main/scala/wow/doge/mygame/subsystems/events/EventsModule.scala similarity index 100% rename from src/main/scala/wow/doge/mygame/events/EventsModule.scala rename to src/main/scala/wow/doge/mygame/subsystems/events/EventsModule.scala diff --git a/src/main/scala/wow/doge/mygame/subsystems/moddingsystem/ModdingSystem.scala b/src/main/scala/wow/doge/mygame/subsystems/moddingsystem/ModdingSystem.scala new file mode 100644 index 0000000..1b26c24 --- /dev/null +++ b/src/main/scala/wow/doge/mygame/subsystems/moddingsystem/ModdingSystem.scala @@ -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) + // } + ; +} diff --git a/src/main/scala/wow/doge/mygame/scriptsystem/ScriptActor.scala b/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptActor.scala similarity index 51% rename from src/main/scala/wow/doge/mygame/scriptsystem/ScriptActor.scala rename to src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptActor.scala index cd73b75..30f9f17 100644 --- a/src/main/scala/wow/doge/mygame/scriptsystem/ScriptActor.scala +++ b/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptActor.scala @@ -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] - sealed trait Command - final case class Compile(path: os.Path, sender: ActorRef[Result]) - extends Command + final case class Error(reason: String) + sealed trait 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) - case l @ Left(err) => l + 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 - } - } diff --git a/src/main/scala/wow/doge/mygame/scriptsystem/ScriptCachingActor.scala b/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptCachingActor.scala similarity index 83% rename from src/main/scala/wow/doge/mygame/scriptsystem/ScriptCachingActor.scala rename to src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptCachingActor.scala index 7c4c85a..f884c0e 100644 --- a/src/main/scala/wow/doge/mygame/scriptsystem/ScriptCachingActor.scala +++ b/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptCachingActor.scala @@ -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) ) +} + +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 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) - // ) + 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 + } + } + } } diff --git a/src/main/scala/wow/doge/mygame/utils/JFXConsoleStream.scala b/src/main/scala/wow/doge/mygame/utils/JFXConsoleStream.scala new file mode 100644 index 0000000..8980757 --- /dev/null +++ b/src/main/scala/wow/doge/mygame/utils/JFXConsoleStream.scala @@ -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())) +} diff --git a/test.plugin.json b/test.plugin.json new file mode 100644 index 0000000..d916b5b --- /dev/null +++ b/test.plugin.json @@ -0,0 +1 @@ +{ "hello1": "world1", "hello2": "world2" } \ No newline at end of file diff --git a/test2.plugin.json b/test2.plugin.json new file mode 100644 index 0000000..1265a69 --- /dev/null +++ b/test2.plugin.json @@ -0,0 +1 @@ +{ "hello2": "world3" } \ No newline at end of file