Compare commits

..

No commits in common. "development" and "master" have entirely different histories.

107 changed files with 2618 additions and 6014 deletions

3
.gitignore vendored
View File

@ -15,7 +15,6 @@ metals.sbt
.metals .metals
.bloop .bloop
.ammonite .ammonite
.bsp
# Scala-IDE specific # Scala-IDE specific
.scala_dependencies .scala_dependencies
@ -25,5 +24,3 @@ metals.sbt
.vscode .vscode
assets/ assets/
*.j3o *.j3o
.attach_pid*
hs_err_pid*

View File

@ -1,8 +0,0 @@
rules = [
# ScalalintClasses,
# ScalalintImports,
# ScalalintPackages,
# ScalalintInference,
OrganizeImports
]
# ScalalintClasses.removeEmptyConstructor = false

View File

@ -1,4 +1 @@
version = "2.6.4" version = "2.6.4"
rewrite {
rules = [SortImports, RedundantBraces]
}

166
build.sbt Executable file → Normal file
View File

@ -1,4 +1,23 @@
// The simplest possible sbt build file is just one line:
scalaVersion := "2.13.3" scalaVersion := "2.13.3"
// That is, to create a valid sbt build, all you've got to do is define the
// version of Scala you'd like your project to use.
// ============================================================================
// Lines like the above defining `scalaVersion` are called "settings". Settings
// are key/value pairs. In the case of `scalaVersion`, the key is "scalaVersion"
// and the value is "2.13.1"
// It's possible to define many kinds of settings, such as:
// Note, it's not required for you to define these three settings. These are
// mostly only necessary if you intend to publish your library's binaries on a
// place like Sonatype or Bintray.
// Want to use a published library in your project?
// You can define other libraries as dependencies in your build like this:
resolvers += "Jcenter" at "https://jcenter.bintray.com/" resolvers += "Jcenter" at "https://jcenter.bintray.com/"
resolvers += "JME Bintray" at "https://bintray.com/jmonkeyengine/com.jme3" resolvers += "JME Bintray" at "https://bintray.com/jmonkeyengine/com.jme3"
@ -7,7 +26,7 @@ resolvers += "Jitpack" at "https://jitpack.io"
resolvers += Resolver.mavenLocal resolvers += Resolver.mavenLocal
resolvers += Resolver.sonatypeRepo("snapshots") resolvers += Resolver.sonatypeRepo("snapshots")
val jmeVersion = "3.3.2-stable" lazy val jmeVersion = "3.3.2-stable"
lazy val osName = System.getProperty("os.name") match { lazy val osName = System.getProperty("os.name") match {
case n if n.startsWith("Linux") => "linux" case n if n.startsWith("Linux") => "linux"
@ -18,44 +37,52 @@ lazy val osName = System.getProperty("os.name") match {
lazy val javaFXModules = lazy val javaFXModules =
Seq("base", "controls", "fxml", "graphics", "media", "swing", "web") Seq("base", "controls", "fxml", "graphics", "media", "swing", "web")
testFrameworks += new TestFramework("munit.Framework")
lazy val root = (project in file(".")).settings( lazy val root = (project in file(".")).settings(
inThisBuild(
List(
scalaVersion := scalaVersion.value, // 2.11.12, or 2.13.3
semanticdbEnabled := true, // enable SemanticDB
semanticdbVersion := "4.3.24" // use Scalafix compatible version
)
),
name := "mygame", name := "mygame",
organization := "wow.doge", organization := "wow.doge",
version := "1.0-SNAPSHOT", version := "1.0-SNAPSHOT",
libraryDependencies ++= Seq( libraryDependencies ++= Seq(
// "org.scala-lang.modules" %% "scala-parser-combinators" % "1.1.2",
// https://mvnrepository.com/artifact/org.jmonkeyengine/jme3-core
"org.jmonkeyengine" % "jme3-core" % jmeVersion, "org.jmonkeyengine" % "jme3-core" % jmeVersion,
// https://mvnrepository.com/artifact/org.jmonkeyengine/jme3-desktop
"org.jmonkeyengine" % "jme3-desktop" % jmeVersion, "org.jmonkeyengine" % "jme3-desktop" % jmeVersion,
// https://mvnrepository.com/artifact/org.jmonkeyengine/jme3-lwjgl3
"org.jmonkeyengine" % "jme3-lwjgl3" % jmeVersion, "org.jmonkeyengine" % "jme3-lwjgl3" % jmeVersion,
"org.jmonkeyengine" % "jme3-effects" % jmeVersion, "org.jmonkeyengine" % "jme3-effects" % jmeVersion,
"org.jmonkeyengine" % "jme3-plugins" % jmeVersion, "org.jmonkeyengine" % "jme3-plugins" % jmeVersion,
"org.jmonkeyengine" % "jme3-blender" % jmeVersion, "org.jmonkeyengine" % "jme3-blender" % jmeVersion,
"com.github.stephengold" % "Minie" % "3.0.0", "com.github.stephengold" % "Minie" % "3.0.0",
"com.simsilica" % "zay-es" % "1.2.1", "com.simsilica" % "zay-es" % "1.2.1",
"org.typelevel" %% "cats-core" % "2.3.0", "org.typelevel" %% "cats-core" % "2.1.1",
"com.lihaoyi" % "ammonite" % "2.2.0" cross CrossVersion.full, "com.lihaoyi" % "ammonite" % "2.2.0" cross CrossVersion.full,
// "com.lihaoyi" % "ammonite" % "2.3.8" % "test" cross CrossVersion.full,
"org.jetbrains.kotlin" % "kotlin-main-kts" % "1.4.10", "org.jetbrains.kotlin" % "kotlin-main-kts" % "1.4.10",
"org.jetbrains.kotlin" % "kotlin-scripting-jsr223" % "1.4.10", "org.jetbrains.kotlin" % "kotlin-scripting-jsr223" % "1.4.10",
"org.codehaus.groovy" % "groovy-all" % "3.0.6" pomOnly (), "org.codehaus.groovy" % "groovy-all" % "3.0.6" pomOnly (),
"org.scalafx" %% "scalafx" % "14-R19", "org.scalafx" %% "scalafx" % "14-R19",
"com.typesafe.akka" %% "akka-actor-typed" % "2.6.10", "com.typesafe.akka" %% "akka-actor-typed" % "2.6.10",
"org.typelevel" %% "cats-core" % "2.3.0", "org.typelevel" %% "cats-core" % "2.1.1",
"org.typelevel" %% "cats-effect" % "2.3.0", "org.typelevel" %% "cats-effect" % "2.1.4",
"io.monix" %% "monix" % "3.2.2", "io.monix" %% "monix" % "3.2.2",
"io.monix" %% "monix-bio" % "1.1.0", "io.monix" %% "monix-bio" % "1.1.0",
"io.monix" %% "monix-nio" % "0.0.9",
"io.circe" %% "circe-core" % "0.13.0", "io.circe" %% "circe-core" % "0.13.0",
"io.circe" %% "circe-generic" % "0.13.0", "io.circe" %% "circe-generic" % "0.13.0",
"com.softwaremill.sttp.client3" %% "core" % "3.0.0", "com.softwaremill.sttp.client" %% "core" % "2.2.5",
"com.softwaremill.sttp.client3" %% "monix" % "3.0.0", "com.softwaremill.sttp.client" %% "monix" % "2.2.5",
"com.softwaremill.sttp.client3" %% "circe" % "3.0.0", "com.softwaremill.sttp.client" %% "circe" % "2.2.5",
"com.softwaremill.sttp.client3" %% "async-http-client-backend-monix" % "3.0.0", "com.softwaremill.sttp.client" %% "async-http-client-backend-monix" % "2.2.5",
"com.softwaremill.sttp.client3" %% "httpclient-backend-monix" % "3.0.0",
"com.github.valskalla" %% "odin-monix" % "0.8.1", "com.github.valskalla" %% "odin-monix" % "0.8.1",
"com.github.valskalla" %% "odin-json" % "0.9.1", "com.github.valskalla" %% "odin-json" % "0.9.1",
"com.softwaremill.macwire" %% "util" % "2.3.7", "com.softwaremill.macwire" %% "util" % "2.3.7",
"com.softwaremill.macwire" %% "macros" % "2.3.7" % "provided", "com.softwaremill.macwire" %% "macros" % "2.3.7" % "provided",
// "com.softwaremill.macwire" %% "macrosakka" % "2.3.6" % "provided",
"com.github.valskalla" %% "odin-slf4j" % "0.8.1", "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", "org.scala-lang.modules" %% "scala-parallel-collections" % "1.0.0-RC1",
@ -63,31 +90,18 @@ lazy val root = (project in file(".")).settings(
"io.circe" %% "circe-config" % "0.8.0", "io.circe" %% "circe-config" % "0.8.0",
"com.beachape" %% "enumeratum-circe" % "1.6.1", "com.beachape" %% "enumeratum-circe" % "1.6.1",
"com.lihaoyi" %% "os-lib" % "0.7.1", "com.lihaoyi" %% "os-lib" % "0.7.1",
// "com.jayfella" % "jme-jfx-11" % "1.1.5",
// "com.github.goxr3plus" % "FX-BorderlessScene" % "4.4.0",
// "com.github.Oshan96" % "CustomStage" % "v1.3.1",
"com.badlogicgames.gdx" % "gdx-ai" % "1.8.2", "com.badlogicgames.gdx" % "gdx-ai" % "1.8.2",
"org.recast4j" % "recast" % "1.2.5", "org.recast4j" % "recast" % "1.2.5",
"org.recast4j" % "detour" % "1.2.5", "org.recast4j" % "detour" % "1.2.5"
"com.lihaoyi" %% "pprint" % "0.6.0",
"org.scalatest" %% "scalatest" % "3.2.2" % "test",
"org.typelevel" %% "cats-mtl" % "1.1.1",
"io.estatico" %% "newtype" % "0.4.4",
"io.methvin" %% "directory-watcher-better-files" % "0.14.0",
"com.github.rohan-sircar" % "scalafx-utils" % "0.15.0-SNAPSHOT",
"com.jfoenix" % "jfoenix" % "9.0.10",
"org.kordamp.ikonli" % "ikonli-core" % "12.0.0",
"org.kordamp.ikonli" % "ikonli-javafx" % "12.0.0",
"org.kordamp.ikonli" % "ikonli-fontawesome5-pack" % "12.0.0",
"org.kordamp.ikonli" % "ikonli-material-pack" % "12.0.0",
"org.kordamp.bootstrapfx" % "bootstrapfx-core" % "0.4.0",
"org.scalameta" %% "munit" % "0.7.23" % Test,
"de.lolhens" %% "munit-tagless-final" % "0.0.1" % Test,
"org.scalameta" %% "munit-scalacheck" % "0.7.23" % Test,
"org.scalacheck" %% "scalacheck" % "1.15.3" % Test
), ),
// Determine OS version of JavaFX binaries // Determine OS version of JavaFX binaries
// Add JavaFX dependencies // Add JavaFX dependencies
libraryDependencies ++= javaFXModules.map(m => libraryDependencies ++= javaFXModules.map(m =>
"org.openjfx" % s"javafx-$m" % "11.0.1" classifier osName "org.openjfx" % s"javafx-$m" % "14.0.1" classifier osName
), ),
scalacOptions ++= Seq( scalacOptions ++= Seq(
"-encoding", "-encoding",
@ -102,6 +116,8 @@ lazy val root = (project in file(".")).settings(
"-Xlint", "-Xlint",
"-Ywarn-numeric-widen", "-Ywarn-numeric-widen",
"-Ymacro-annotations", "-Ymacro-annotations",
// "-Xlint:byname-implicit",
// "utf-8", // Specify character encoding used by source files.
//silence warnings for by-name implicits //silence warnings for by-name implicits
"-Wconf:cat=lint-byname-implicit:s", "-Wconf:cat=lint-byname-implicit:s",
//give errors on non exhaustive matches //give errors on non exhaustive matches
@ -139,40 +155,60 @@ lazy val root = (project in file(".")).settings(
// val oldStrategy = (assemblyMergeStrategy in assembly).value // val oldStrategy = (assemblyMergeStrategy in assembly).value
// oldStrategy(x) // oldStrategy(x)
} }
// scalaVersion := "2.13.2", // 2.11.12, or 2.13.3
// semanticdbEnabled := true, // enable SemanticDB
// semanticdbVersion := scalafixSemanticdb.revision // use Scalafix compatible version
// semanticdbVersion := "4.3.24",
) )
// initialCommands in (console) := """ammonite.Main.main(Array.empty)""" initialCommands in (console) := """ammonite.Main.main(Array.empty)"""
ammoniteVersion := "2.2.0"
addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1")
addCompilerPlugin(
"org.typelevel" %% "kind-projector" % "0.11.1" cross CrossVersion.full
)
inThisBuild(
List(
scalaVersion := scalaVersion.value, // 2.11.12, or 2.13.3
semanticdbEnabled := true, // enable SemanticDB
semanticdbVersion := "4.4.10" // use Scalafix compatible version
)
)
ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.4.3"
scalafixDependencies in ThisBuild += "org.scalalint" %% "rules" % "0.1.4"
wartremoverErrors in (Compile, compile) ++= // Here, `libraryDependencies` is a set of dependencies, and by using `+=`,
Warts.allBut( // we're adding the scala-parser-combinators dependency to the set of dependencies
Wart.Any, // that sbt will go and fetch when it starts up.
Wart.NonUnitStatements, // Now, in any Scala file, you can import classes, objects, etc., from
// Wart.StringPlusAny, // scala-parser-combinators with a regular import.
Wart.Overloading,
Wart.PublicInference, // TIP: To find the "dependency" that you need to add to the
Wart.Nothing, // `libraryDependencies` set, which in the above example looks like this:
Wart.Var,
Wart.DefaultArguments, // "org.scala-lang.modules" %% "scala-parser-combinators" % "1.1.2"
// Wart.MutableDataStructures,
Wart.ImplicitConversion, // You can use Scaladex, an index of all known published Scala libraries. There,
Wart.ImplicitParameter, // after you find the library you want, you can just copy/paste the dependency
Wart.ToString, // information that you need into your build file. For example, on the
Wart.Recursion, // scala/scala-parser-combinators Scaladex page,
Wart.While, // https://index.scala-lang.org/scala/scala-parser-combinators, you can copy/paste
Wart.ExplicitImplicitTypes, // the sbt dependency from the sbt box on the right-hand side of the screen.
Wart.ListUnapply
) // IMPORTANT NOTE: while build files look _kind of_ like regular Scala, it's
// Seq(Wart.FinalCaseClass) // important to note that syntax in *.sbt files doesn't always behave like
// regular Scala. For example, notice in this build file that it's not required
// to put our settings into an enclosing object or class. Always remember that
// sbt is a bit different, semantically, than vanilla Scala.
// ============================================================================
// Most moderately interesting Scala projects don't make use of the very simple
// build file style (called "bare style") used in this build.sbt file. Most
// intermediate Scala projects make use of so-called "multi-project" builds. A
// multi-project build makes it possible to have different folders which sbt can
// be configured differently for. That is, you may wish to have different
// dependencies or different testing frameworks defined for different parts of
// your codebase. Multi-project builds make this possible.
// Here's a quick glimpse of what a multi-project build looks like for this
// build, with only one "subproject" defined, called `root`:
// lazy val root = (project in file(".")).
// settings(
// inThisBuild(List(
// organization := "ch.epfl.scala",
// scalaVersion := "2.13.1"
// )),
// name := "hello-world"
// )
// 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")
ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.4.3"

View File

@ -1 +1 @@
sbt.version=1.4.7 sbt.version=1.3.13

View File

@ -1,7 +1,3 @@
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.0") addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.0")
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.23") addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.23")
addSbtPlugin("io.spray" % "sbt-revolver" % "0.9.1") addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1")
addSbtPlugin(
"com.thoughtworks.deeplearning" % "sbt-ammonite-classpath" % "2.0.0"
)
addSbtPlugin("org.wartremover" % "sbt-wartremover" % "2.4.13")

View File

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

View File

@ -1,11 +0,0 @@
.red-bar > .bar {
-fx-background-color: red;
}
.green-bar > .bar {
-fx-background-color: green;
}
.yellow-bar > .bar {
-fx-background-color: yellow;
}

View File

@ -0,0 +1,5 @@
Hey guys, few days ago I discovered I could load objects created in scripts at runtime, and cast them to an interface to call their methods provided
1. both host program and script have access to the same interface via a common library
2. script object implements that interface
I was thinking, maybe I could implement appstates in scripts to implement game mechanics and attach them to the state manager at runtime, and similarly for components of an ECS. What do you guys think?

View File

@ -32,13 +32,13 @@ class StaticLoggerBinder extends OdinLoggerBinder[IO] {
def apply[A](fa: _root_.monix.bio.Task[A]): IO[A] = fa.to[IO] def apply[A](fa: _root_.monix.bio.Task[A]): IO[A] = fa.to[IO]
} }
val (defaultConsoleLogger, release1) = private lazy val (defaultConsoleLogger, release1) =
consoleLogger[IO](minLevel = Level.Debug) consoleLogger[IO](minLevel = Level.Debug)
.withAsync(timeWindow = 1.milliseconds, maxBufferSize = Some(2000)) .withAsync(timeWindow = 1.milliseconds, maxBufferSize = Some(2000))
.allocated .allocated
.unsafeRunSync() .unsafeRunSync()
val (mainFileLogger, release2) = private lazy val (mainFileLogger, release2) =
fileLogger[IO]( fileLogger[IO](
"application-log-2.log", "application-log-2.log",
Formatter.json, Formatter.json,
@ -47,11 +47,7 @@ class StaticLoggerBinder extends OdinLoggerBinder[IO] {
.allocated .allocated
.unsafeRunSync() .unsafeRunSync()
val mainFileLogger2 = mainFileLogger.contramap(lm => private lazy val (eventBusFileLogger, release3) =
lm.copy(message = lm.message.map(s => fansi.Str(s).plainText))
)
val (eventBusFileLogger, release3) =
fileLogger[IO]( fileLogger[IO](
"eventbus.log", "eventbus.log",
Formatter.json, Formatter.json,
@ -60,9 +56,11 @@ class StaticLoggerBinder extends OdinLoggerBinder[IO] {
.allocated .allocated
.unsafeRunSync() .unsafeRunSync()
ArraySeq(release1, release2, release3).foreach(r => {
sys.addShutdownHook(r.unsafeRunSync()) ArraySeq(release1, release2, release3).foreach(r =>
) sys.addShutdownHook(r.unsafeRunSync())
)
}
val loggers: PartialFunction[String, Logger[IO]] = { val loggers: PartialFunction[String, Logger[IO]] = {
case "some.external.package.SpecificClass" => case "some.external.package.SpecificClass" =>
@ -74,22 +72,15 @@ class StaticLoggerBinder extends OdinLoggerBinder[IO] {
case s if s.startsWith("com.jayfella.jme.jfx.util.JfxPlatform") => case s if s.startsWith("com.jayfella.jme.jfx.util.JfxPlatform") =>
defaultConsoleLogger.withMinimalLevel(Level.Info) defaultConsoleLogger.withMinimalLevel(Level.Info)
case s // case s
if s.startsWith( // if s.startsWith(
"wow.doge.mygame.subsystems.movement.PlayerMovementEventHandler" // "wow.doge.mygame.subsystems.movement.PlayerMovementEventHandler"
) => // ) =>
defaultConsoleLogger.withMinimalLevel(Level.Info)
case s
if s.startsWith(
"wow.doge.mygame.game.entities.NpcMovementActor"
) =>
defaultConsoleLogger.withMinimalLevel(Level.Trace) |+| mainFileLogger2
.withMinimalLevel(Level.Trace)
// defaultConsoleLogger.withMinimalLevel( Level.Trace) //selectively turn on trace logging for specific classes // defaultConsoleLogger.withMinimalLevel( Level.Trace) //selectively turn on trace logging for specific classes
case s if s.startsWith("wow.doge.mygame.subsystems.events.EventBus") => case s if s.startsWith("wow.doge.mygame.subsystems.events.EventBus") =>
defaultConsoleLogger.withMinimalLevel(Level.Debug) |+| eventBusFileLogger defaultConsoleLogger.withMinimalLevel(Level.Debug) |+| eventBusFileLogger
case s if s.startsWith("akka.actor") || s.startsWith("wow.doge.mygame") => case s if s.startsWith("akka.actor") || s.startsWith("wow.doge.mygame") =>
defaultConsoleLogger.withMinimalLevel(Level.Debug) |+| mainFileLogger2 defaultConsoleLogger.withMinimalLevel(Level.Debug) |+| mainFileLogger
case _ => //if wildcard case isn't provided, default logger is no-op case _ => //if wildcard case isn't provided, default logger is no-op
defaultConsoleLogger.withMinimalLevel(Level.Debug) defaultConsoleLogger.withMinimalLevel(Level.Debug)
} }

View File

@ -1,33 +0,0 @@
package wow.doge.mygame
import java.util.concurrent.TimeoutException
import cats.Show
import cats.kernel.Eq
import monix.bio.IO
import wow.doge.mygame.utils.wrappers.jme.AssetManager
import wow.doge.mygame.utils.wrappers.jme.CollisionShapeFactory
import wow.doge.mygame.utils.wrappers.jme.NodeWrapper2
sealed trait AppError extends Product with Serializable
object AppError {
final case class TimeoutError(reason: String) extends AppError
object TimeoutError {
def from: PartialFunction[Throwable, IO[TimeoutError, Nothing]] = {
case ex: TimeoutException => IO.raiseError(TimeoutError(ex.getMessage))
}
}
final case class AssetManagerError(error: AssetManager.Error) extends AppError
final case class AppNodeError(error: NodeWrapper2.Error) extends AppError
final case class CollisionShapeCreationFailed(
err: CollisionShapeFactory.Error
) extends AppError
def fromThrowable: PartialFunction[Throwable, IO[AppError, Nothing]] = {
case ex: TimeoutException => IO.raiseError(TimeoutError(ex.getMessage))
}
implicit val show = Show.fromToString[AppError]
implicit val eq = Eq.fromUniversalEquals[AppError]
}

View File

@ -1,7 +0,0 @@
package wow.doge.mygame
import akka.actor.typed.DispatcherSelector
object Dispatchers {
val jmeDispatcher = DispatcherSelector.fromConfig("jme-dispatcher")
}

View File

@ -2,65 +2,68 @@ package wow.doge.mygame
import scala.concurrent.duration._ import scala.concurrent.duration._
import _root_.monix.bio.BIOApp
import _root_.monix.bio.Task
import _root_.monix.bio.UIO
import akka.util.Timeout import akka.util.Timeout
import cats.effect.ExitCode import cats.effect.ExitCode
import cats.effect.Resource import cats.effect.Resource
import cats.implicits._ import cats.implicits._
import io.odin.consoleLogger import com.softwaremill.macwire._
import io.odin.fileLogger import io.odin._
import io.odin.json.Formatter import io.odin.json.Formatter
import io.odin.syntax._ import io.odin.syntax._
import monix.bio.BIOApp
import monix.bio.Task
import monix.bio.UIO
import monix.execution.Scheduler
import scalafx.scene.control.TextArea import scalafx.scene.control.TextArea
import wow.doge.mygame.ActorSystemResource import wow.doge.mygame.game.GameAppResource
import wow.doge.mygame.executors.ExecutorsModule
import wow.doge.mygame.types.AkkaScheduler
import wow.doge.mygame.utils.GenericConsoleStream import wow.doge.mygame.utils.GenericConsoleStream
import io.odin
object Main extends BIOApp with ExecutorsModule { object Main extends BIOApp with MainModule {
import java.util.logging.{Logger => JLogger, Level} import java.util.logging.{Logger => JLogger, Level}
JLogger.getLogger("").setLevel(Level.SEVERE) JLogger.getLogger("").setLevel(Level.SEVERE)
implicit val timeout = Timeout(1.second) implicit val timeout = Timeout(1.second)
override def scheduler: Scheduler = schedulers.async.value
def appResource(consoleStream: GenericConsoleStream[TextArea]) = def appResource(consoleStream: GenericConsoleStream[TextArea]) =
for { for {
logger <- logger <-
consoleLogger(minLevel = odin.Level.Debug).withAsync( consoleLogger().withAsync(
timeWindow = 1.milliseconds, timeWindow = 1.milliseconds,
maxBufferSize = Some(100) maxBufferSize = Some(2000)
) |+| ) |+|
fileLogger( fileLogger(
"application-log-1.log", "application-log-1.log",
Formatter.json Formatter.json
).withAsync(timeWindow = 1.milliseconds, maxBufferSize = Some(100)) ).withAsync(timeWindow = 1.milliseconds, maxBufferSize = Some(2000))
jmeScheduler <- jmeSchedulerResource jmeScheduler <- jMESchedulerResource
// backend <- Resource.make(toIO(HttpClientMonixBackend()))(backend => actorSystem <- actorSystemResource(logger)
// toIO(backend.close()) gameApp <- {
// ) // new BulletAppState()
actorSystem <- new ActorSystemResource(logger, schedulers.async).get // bas.setThreadingType(Thr)
// gameAppResource(new StatsAppState())
wire[GameAppResource].get
}
_ <- Resource.liftF( _ <- Resource.liftF(
new MainApp( new MainApp(
logger, logger,
gameApp,
actorSystem,
jmeScheduler, jmeScheduler,
schedulers, schedulers,
consoleStream consoleStream
)(actorSystem, timeout, AkkaScheduler(actorSystem.scheduler)).program )(
timeout,
actorSystem.scheduler
).program
) )
} yield () } yield ()
def run(args: List[String]): UIO[ExitCode] = { def run(args: List[String]): UIO[ExitCode] = {
val consoleStream = GenericConsoleStream.textAreaStream()
lazy val consoleStream = GenericConsoleStream.textAreaStream()
Console.withOut(consoleStream)( Console.withOut(consoleStream)(
appResource(consoleStream) appResource(consoleStream)
.use(_ => Task.unit) .use(_ => Task.unit >> Task(consoleStream.close()))
.flatMap(_ => Task(consoleStream.close())) .onErrorHandle(_.printStackTrace())
.onErrorHandleWith(ex => UIO(ex.printStackTrace()))
.as(ExitCode.Success) .as(ExitCode.Success)
) )
} }

View File

@ -1,228 +1,143 @@
package wow.doge.mygame package wow.doge.mygame
import java.util.concurrent.TimeoutException import akka.actor.typed.ActorSystem
import akka.actor.typed.Scheduler
import scala.annotation.switch
import scala.concurrent.duration._
import akka.actor.typed.ActorRef
import akka.actor.typed.SpawnProtocol import akka.actor.typed.SpawnProtocol
import akka.util.Timeout import akka.util.Timeout
import cats.effect.Resource
import cats.effect.concurrent.Deferred import cats.effect.concurrent.Deferred
import cats.syntax.eq._ import com.jme3.app.state.AppStateManager
import cats.syntax.show._ import com.jme3.asset.AssetManager
import com.jayfella.jme.jfx.JavaFxUI
import com.jme3.asset.plugins.ZipLocator import com.jme3.asset.plugins.ZipLocator
import com.jme3.bullet.BulletAppState
import com.jme3.bullet.control.BetterCharacterControl import com.jme3.bullet.control.BetterCharacterControl
import com.jme3.input.InputManager import com.jme3.input.InputManager
import com.jme3.material.Material
import com.jme3.material.MaterialDef
import com.jme3.math.ColorRGBA
import com.jme3.math.FastMath
import com.jme3.math.Quaternion
import com.jme3.math.Vector3f
import com.jme3.renderer.Camera import com.jme3.renderer.Camera
import com.jme3.renderer.RenderManager
import com.jme3.renderer.ViewPort import com.jme3.renderer.ViewPort
import com.jme3.scene.Node import com.jme3.scene.Node
import com.jme3.scene.control.AbstractControl
import com.softwaremill.macwire._ import com.softwaremill.macwire._
import com.softwaremill.tagging._ import com.softwaremill.tagging._
import io.odin.Logger import io.odin.Logger
import monix.bio.Fiber import monix.bio.Fiber
import monix.bio.IO import monix.bio.IO
import monix.bio.Task import monix.bio.Task
import monix.bio.UIO import monix.execution.exceptions.DummyException
import monix.eval.Coeval
import monix.execution.cancelables.CompositeCancelable
import monix.reactive.Observable
import monix.{eval => me}
import scalafx.scene.control.Label
import scalafx.scene.control.TextArea import scalafx.scene.control.TextArea
import scalafx.scene.layout.HBox
import scalafx.scene.layout.Priority
import scalafx.scene.layout.VBox
import scalafx.scene.paint.Color
import wow.doge.mygame.AppError.TimeoutError
import wow.doge.mygame.executors.Schedulers import wow.doge.mygame.executors.Schedulers
import wow.doge.mygame.game.GameApp import wow.doge.mygame.game.GameApp
import wow.doge.mygame.game.GameAppResource import wow.doge.mygame.game.GameAppActor
import wow.doge.mygame.game.controls.FollowControl import wow.doge.mygame.game.GameAppTags
import wow.doge.mygame.game.entities.CharacterStats
import wow.doge.mygame.game.entities.EntityIds import wow.doge.mygame.game.entities.EntityIds
import wow.doge.mygame.game.entities.NpcActorSupervisor import wow.doge.mygame.game.entities.NpcActorSupervisor
import wow.doge.mygame.game.entities.NpcMovementActor import wow.doge.mygame.game.entities.NpcMovementActor
import wow.doge.mygame.game.entities.player.PlayerActor import wow.doge.mygame.game.entities.PlayerController
import wow.doge.mygame.game.entities.player.PlayerController import wow.doge.mygame.game.entities.PlayerControllerTags
import wow.doge.mygame.game.entities.player.PlayerMovementReducer import wow.doge.mygame.game.subsystems.input.GameInputHandler
import wow.doge.mygame.game.subsystems.input.InputMappings
import wow.doge.mygame.game.subsystems.input.PlayerCameraInput
import wow.doge.mygame.game.subsystems.input.PlayerMovementInput
import wow.doge.mygame.game.subsystems.level.DefaultGameLevel import wow.doge.mygame.game.subsystems.level.DefaultGameLevel
import wow.doge.mygame.implicits._ import wow.doge.mygame.implicits._
import wow.doge.mygame.launcher.Launcher import wow.doge.mygame.launcher.Launcher
import wow.doge.mygame.launcher.Launcher.LauncherResult import wow.doge.mygame.launcher.Launcher.LauncherResult
import wow.doge.mygame.math.ImVector3f import wow.doge.mygame.math.ImVector3f
import wow.doge.mygame.subsystems.events.Event
import wow.doge.mygame.subsystems.events.EventBus
import wow.doge.mygame.subsystems.events.EventBus.ObservableSubscription
import wow.doge.mygame.subsystems.events.EventsModule import wow.doge.mygame.subsystems.events.EventsModule
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
import wow.doge.mygame.subsystems.events.PlayerCameraEvent
import wow.doge.mygame.subsystems.events.PlayerEvent import wow.doge.mygame.subsystems.events.PlayerEvent
import wow.doge.mygame.subsystems.events.PlayerMovementEvent
import wow.doge.mygame.subsystems.events.StatsEvent.DamageEvent
import wow.doge.mygame.subsystems.events.TickEvent import wow.doge.mygame.subsystems.events.TickEvent
import wow.doge.mygame.subsystems.scriptsystem.ScriptCompiler
import wow.doge.mygame.subsystems.scriptsystem.ScriptInitMode import wow.doge.mygame.subsystems.scriptsystem.ScriptInitMode
import wow.doge.mygame.subsystems.scriptsystem.ScriptSystemResource import wow.doge.mygame.subsystems.scriptsystem.ScriptSystemResource
import wow.doge.mygame.types._
import wow.doge.mygame.utils.AkkaUtils import wow.doge.mygame.utils.AkkaUtils
import wow.doge.mygame.utils.GenericConsoleStream import wow.doge.mygame.utils.GenericConsoleStream
import wow.doge.mygame.utils.IOUtils import wow.doge.mygame.utils.IOUtils
import wow.doge.mygame.utils.MonixDirectoryWatcher
import wow.doge.mygame.utils.MonixDirectoryWatcher.ModifyEvent import EventsModule.GameEventBus
import wow.doge.mygame.utils.controls.JFXProgressBar
import wow.doge.mygame.utils.wrappers.jme
import wow.doge.mygame.utils.wrappers.jme.AssetManager
import wow.doge.mygame.utils.wrappers.jme.PhysicsSpace
class MainApp( class MainApp(
logger: Logger[Task], logger: Logger[Task],
jmeThread: JmeScheduler, gameApp: GameApp,
implicit val spawnProtocol: ActorSystem[SpawnProtocol.Command],
jmeThread: monix.execution.Scheduler,
schedulers: Schedulers, schedulers: Schedulers,
consoleStream: GenericConsoleStream[TextArea] consoleStream: GenericConsoleStream[TextArea]
)(implicit )(implicit
spawnProtocol: ActorRef[SpawnProtocol.Command], @annotation.unused timeout: Timeout,
timeout: Timeout, @annotation.unused scheduler: Scheduler
scheduler: AkkaScheduler
) { ) {
implicit val as = scheduler.value
val scriptSystemResource: Resource[UIO, ScriptCompiler] = lazy val scriptSystemInit =
new ScriptSystemResource( new ScriptSystemResource(os.pwd, spawnProtocol, ScriptInitMode.Eager).init
os.pwd,
logger,
ScriptInitMode.Eager,
schedulers.io
).init2
val eventsModule = new EventsModule(scheduler, spawnProtocol) def gameInit: Task[Fiber[Throwable, Unit]] =
def eval(
tickEventBus: GameEventBus[TickEvent],
gameApp: GameApp,
fib: Fiber[Nothing, Unit]
) =
for { for {
// g <- UIO.pure(gameApp) eventsModule <- Task(new EventsModule(spawnProtocol))
playerEventBus <- eventsModule.playerEventBus playerEventBus <- eventsModule.playerEventBusTask
mainEventBus <- eventsModule.mainEventBus mainEventBus <- eventsModule.mainEventBusTask
obs <- tickEventBus <- eventsModule.tickEventBusTask
playerEventBus gameAppActor <- AkkaUtils.spawnActorL2(
.askL[Observable[PlayerMovementEvent]](ObservableSubscription(_)) GameAppActor.Props(tickEventBus).behavior,
.onErrorHandleWith(TimeoutError.from) "gameAppActor"
_ <- )
IOUtils _ <- gameAppActor !! GameAppActor.Start
.toIO( gameAppFib <- gameApp.start.executeOn(jmeThread).start
obs /**
.doOnNextF(pme => * schedule a task to run on the JME thread and wait for it's completion
logger.trace(show"Received event $pme").toTask.void * before proceeding forward, as a signal that the JME thread has been
) * initialized, otherwise we'll get NPEs trying to access the fields
.completedL * of the game app
.startAndForget */
) res <- gameApp.enqueueL(() => "done")
.hideErrors _ <- logger.info(s"Result = $res")
/**
* JME Thread has been initialized at this point. We can now access the
* field of the game application
*/
inputManager <- gameApp.inputManager inputManager <- gameApp.inputManager
assetManager <- UIO.pure(gameApp.assetManager) assetManager <- gameApp.assetManager
stateManager <- gameApp.stateManager
camera <- gameApp.camera camera <- gameApp.camera
rootNode <- UIO.pure(gameApp.rootNode) rootNode <- gameApp.rootNode
enqueueR <- UIO(gameApp.enqueue _) enqueueR <- Task(gameApp.enqueue _)
viewPort <- gameApp.viewPort viewPort <- gameApp.viewPort
physicsSpace <- UIO.pure(gameApp.physicsSpace) _ <- logger.info("before")
_ <- logger.infoU("before") // jfxUI <- gameApp.jfxUI
jfxUI <- gameApp.jfxUI.hideErrors consoleTextArea <- Task(new TextArea {
consoleTextArea <- UIO(new TextArea {
text = "hello \n" text = "hello \n"
editable = false editable = false
wrapText = true wrapText = true
// maxHeight = 150 // maxHeight = 150
// maxWidth = 300 // maxWidth = 300
}) })
// _ <- Task(consoleStream := consoleTextArea) // _ <- Task(consoleStream := consoleTextArea)
// _ <- Task(jfxUI += consoleTextArea) // _ <- Task(jfxUI += consoleTextArea)
_ <- logger.infoU("after") _ <- logger.info("after")
_ <- logger.infoU("Initializing console stream") bulletAppState <- Task(new BulletAppState())
_ <- _ <- Task(stateManager.attach(bulletAppState))
wire[MainAppDelegate] _ <- logger.info("Initializing console stream")
.init() _ <- wire[MainAppDelegate].init(gameApp.scheduler)
.executeOn(gameApp.scheduler.value) } yield (gameAppFib)
} yield fib
def gameInit( lazy val program = for {
tickEventBus: GameEventBus[TickEvent] scriptSystem <- scriptSystemInit
): Resource[UIO, Either[AppError, Fiber[Nothing, Unit]]] = /**
for { * Signal for synchronization between the JavaFX launcher and the in-game JavaFX GUI
r1 <- wire[GameAppResource].resource.evalMap(e => * Without this, we get a "Toolkit already initialized" exception. The launch button
IO.fromEither(e) * in the launcher completes the signal. The game init process which listens for this
.flatMap { * signal can then continue
case (gameApp -> gameAppFib) => */
eval(tickEventBus, gameApp, gameAppFib) launchSignal <- Deferred[Task, Launcher.LauncherResult]
} launcher <- new Launcher.Props(schedulers, launchSignal).create
.attempt cancelToken <- launcher.init()
) launchResult <- launchSignal.get
dirWatcher <- Resource.liftF( _ <- cancelToken.cancel
MonixDirectoryWatcher(
os.pwd / "assets" / "scripts"
).hideErrors
)
sc <- scriptSystemResource
obs = dirWatcher.doOnNext {
case ModifyEvent(file, count) =>
sc.request(ScriptCompiler.GetScript(os.Path(file.path), _, true))(
15.seconds
).toTask
.void
case _ => me.Task.unit
}
_ <- Resource.make(obs.completedL.toIO.hideErrors.start)(_.cancel)
// _ <-
// dirWatcher
// .doOnNextF(event =>
// Coeval(pprint.log(show"Received file event $event")).void
// )
// .completedL
// .executeOn(schedulers.io.value)
// .startAndForget
// .toIO
// .hideErrors
} yield r1
val program = for {
// scriptSystem <- scriptSystemInit
launchSignal <- Deferred[Task, Launcher.LauncherResult].hideErrors
launcher <- new Launcher.Props(schedulers.fx, launchSignal).create
launchResult <-
launcher.init
.use(_ => launchSignal.get)
.hideErrors
tickEventBus <-
eventsModule.tickEventBus.hideErrorsWith(e => new Exception(e.toString))
_ <- _ <-
/** /**
* User chose to quit * User chose to quit
*/ */
if (launchResult === LauncherResult.Exit) if (launchResult == LauncherResult.Exit)
logger.infoU("Exiting") logger.info("Exiting")
/** /**
* User chose launch. Wait for game window to close * User chose launch. Wait for game window to close
*/ */
else else
gameInit(tickEventBus).use { gameInit.flatMap(_.join)
case Right(fib) => fib.join >> Task.unit
case Left(error) => IO.terminate(new Exception(error.toString))
}.hideErrors
} yield () } yield ()
} }
@ -231,388 +146,173 @@ class MainApp(
*/ */
class MainAppDelegate( class MainAppDelegate(
gameApp: GameApp, gameApp: GameApp,
implicit val spawnProtocol: ActorSystem[SpawnProtocol.Command],
loggerL: Logger[Task], loggerL: Logger[Task],
mainEventBus: GameEventBus[Event],
playerEventBus: GameEventBus[PlayerEvent], playerEventBus: GameEventBus[PlayerEvent],
tickEventBus: GameEventBus[TickEvent], tickEventBus: GameEventBus[TickEvent],
inputManager: InputManager, inputManager: InputManager,
assetManager: AssetManager, assetManager: AssetManager,
physicsSpace: PhysicsSpace, stateManager: AppStateManager,
camera: Camera, camera: Camera,
viewPort: ViewPort, viewPort: ViewPort,
enqueueR: Function1[() => Unit, Unit], enqueueR: Function1[() => Unit, Unit],
rootNode: RootNode, rootNode: Node @@ GameAppTags.RootNode,
schedulers: Schedulers, bulletAppState: BulletAppState
jfxUI: JavaFxUI
)(implicit )(implicit
spawnProtocol: ActorRef[SpawnProtocol.Command], @annotation.unused timeout: Timeout,
timeout: Timeout, @annotation.unused scheduler: Scheduler
scheduler: AkkaScheduler
) { ) {
implicit val as = scheduler.value lazy val physicsSpace = bulletAppState.physicsSpace
def init( def init(
// appScheduler: monix.execution.Scheduler appScheduler: monix.execution.Scheduler
// consoleStream: GenericConsoleStream[TextArea] // consoleStream: GenericConsoleStream[TextArea]
): IO[AppError, Unit] = ) =
for { for {
_ <- loggerL.infoU("Initializing Systems") _ <- loggerL.info("Initializing Systems")
_ <- assetManager.registerLocator( _ <- Task(
os.rel / "assets" / "town.zip", assetManager.registerLocator(
classOf[ZipLocator] os.rel / "assets" / "town.zip",
classOf[ZipLocator]
)
) )
_ <- loggerL.info("test")
// _ <- Task(consoleStream.println("text")) // _ <- Task(consoleStream.println("text"))
level <- DefaultGameLevel(assetManager, viewPort) _ <- DefaultGameLevel(assetManager, viewPort)
_ <- level.addToGame(rootNode, physicsSpace) .addToGame(
playerActor <- createPlayerController() rootNode,
// .onErrorRestart(3) bulletAppState.physicsSpace
// _ <- wire[GameInputHandler.Props].begin )
_ <- new InputMappings(new jme.InputManager(inputManager)).setup .executeOn(appScheduler)
// .onErrorRestart(3) _ <- createPlayerController(appScheduler)
johnActor <- createTestNpc("John") .absorbWith(e => DummyException("boom"))
.onErrorRestart(3)
_ <- wire[GameInputHandler.Props].begin.onErrorRestart(3)
// johnActor <- createTestNpc(appScheduler, "John").executeOn(appScheduler)
// _ <- johnActor !! NpcActorSupervisor.Move(ImVector3f(0, 0, 20)) // _ <- johnActor !! NpcActorSupervisor.Move(ImVector3f(0, 0, 20))
// _ <- // _ <- (johnActor !! NpcActorSupervisor.Move(
// johnActor // ImVector3f(-80, 0, 100)
// .tellL(NpcActorSupervisor.Move(ImVector3f(-30, 0, 10))) // )).executeAsync.delayExecution(2.seconds)
// .delayExecution(2.seconds)
_ <- _ <-
rootNode.depthFirstTraversal IOUtils
.doOnNextF(spat => loggerL.debug(spat.getName).toTask) .toIO(
.completedL rootNode
.toIO .observableBreadthFirst()
.hideErrors .doOnNext(spat => IOUtils.toTask(loggerL.debug(spat.getName())))
damageObs <- .completedL
mainEventBus
.askL[Observable[DamageEvent]](ObservableSubscription(_))
.onErrorHandleWith(TimeoutError.from)
_ <-
damageObs
.doOnNextF(event =>
(loggerL.debug(show"Received Damage Event $event") >>
(if (event.victimName === "PlayerNode")
playerActor
.askL(PlayerActor.TakeDamage(event.amount, _))
.void
.onErrorHandle { case ex: TimeoutException => () }
else IO.unit)).toTask
) )
.completedL .executeOn(appScheduler)
.toIO
.hideErrors
.startAndForget .startAndForget
// _ <-
// Observable
// .interval(1.second)
// .doOnNextF(_ =>
// playerActor
// .askL(PlayerActorSupervisor.GetStatus)
// .flatMap(s => loggerL.debug(show"Player actor status: $s"))
// // .flatMap(s =>
// // if (s == Status.Alive)
// // playerActor
// // .askL(PlayerActorSupervisor.CurrentStats )
// // .flatMap(s => loggerL.debug(show"Got state $s"))
// // else IO.unit
// // )
// .toTask
// )
// // .doOnNextF(_ =>
// // playerActor
// // .askL(PlayerActorSupervisor.GetStatus )
// // .flatMap(s => loggerL.debug(show"Player actor status: $s"))
// // .toTask
// // )
// .completedL
// .toIO
// .hideErrors
// .startAndForget
_ <-
physicsSpace.collisionObservable
// .filter(event =>
// (for {
// nodeA <- event.nodeA
// nodeB <- event.nodeB
// } yield nodeA.getName === "PlayerNode" && nodeB.getName === "John" ||
// nodeB.getName === "PlayerNode" && nodeA.getName === "John")
// .getOrElse(false)
// )
// .doOnNextF(event =>
// loggerL
// .debug(show"$event ${event.appliedImpulse()}")
// .toTask
// )
.filter(_.nodeA.map(_.getName =!= "main-scene_node").getOrElse(false))
.filter(_.nodeB.map(_.getName =!= "main-scene_node").getOrElse(false))
.doOnNextF(event =>
(for {
victim <- Coeval(for {
nodeA <- event.nodeA
nodeB <- event.nodeB
} yield if (nodeB.getName === "John") nodeA else nodeB)
_ <- Coeval(
victim.foreach { v =>
pprint.log(show"emitted event ${v.getName}")
mainEventBus ! EventBus.Publish(
DamageEvent(
"John",
v.getName,
CharacterStats.DamageHealth(10)
),
"damageHandler"
)
}
)
} yield ()).void
)
.completedL
.toIO
.hideErrors
.startAndForget
// _ <-
// IOUtils
// .toIO(
// rootNode
// .observableBreadthFirst()
// .doOnNext(spat => IOUtils.toTask(loggerL.debug(spat.getName())))
// .completedL
// )
// .executeOn(appScheduler)
// .startAndForget
statsObs <-
playerActor
.askL(PlayerActor.GetStatsObservable(_))
.onErrorHandleWith(TimeoutError.from)
.flatten
playerHud <-
UIO
.deferAction(implicit s =>
UIO(new VBox {
implicit val c = CompositeCancelable()
hgrow = Priority.Always
spacing = 10
style = """-fx-background-color: rgba(0,0,0,0.7);"""
stylesheets = Seq((os.rel / "main.css").toString)
children = List(
new HBox {
spacing = 5
children = List(
new Label("Health") { textFill = Color.White },
new Label("100") {
text <-- statsObs
.doOnNextF(i => loggerL.trace(show"Received stats: $i"))
.map(_.hp.toInt.toString)
textFill = Color.White
},
new JFXProgressBar {
progress = 100
minHeight = 10
progress <-- statsObs
.map(_.hp.toInt.toDouble / 100)
c += statsObs
.scanEval(me.Task.pure("green-bar")) {
case (a, b) =>
me.Task(styleClass.removeAll(a)) >>
me.Task.pure(
(b.hp.toInt: @switch) match {
case v if v > 80 => "green-bar"
case v if v > 20 && v <= 80 => "yellow-bar"
case _ => "red-bar"
}
)
}
.doOnNext(cls => me.Task(styleClass += cls))
.subscribe()
}
)
},
new HBox {
spacing = 5
children = List(
new Label("Stamina") {
textFill = Color.White
},
new Label("100") {
textFill = Color.White
text <-- statsObs
.doOnNextF(i => loggerL.trace(show"Received stats: $i"))
.map(_.stamina.toInt.toString)
},
new JFXProgressBar {
progress = 100
minHeight = 10
progress <-- statsObs.map(_.stamina.toInt.toDouble / 100)
styleClass ++= Seq("green-bar")
c += statsObs
.scanEval(me.Task.pure("green-bar")) {
case (a, b) =>
me.Task(styleClass.removeAll(a)) >>
me.Task.pure(
(b.stamina.toInt: @switch) match {
case v if v > 80 => "green-bar"
case v if v > 20 && v <= 40 => "yellow-bar"
case _ => "red-bar"
}
)
}
.doOnNext(cls => me.Task(styleClass += cls))
.subscribe()
}
)
}
)
})
)
.executeOn(schedulers.fx.value)
_ <- UIO(jfxUI += playerHud)
} yield () } yield ()
def createPlayerController( def createPlayerController(
// appScheduler: monix.execution.Scheduler appScheduler: monix.execution.Scheduler
): IO[AppError, PlayerActor.Ref] = { ): IO[PlayerController.Error, Unit] = {
val playerPos = ImVector3f.Zero val playerPos = ImVector3f.ZERO
val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o" val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o"
// val modelPath = os.rel / "Models" / "Oto" / "Oto.mesh.xml" lazy val playerPhysicsControl =
val playerPhysicsControl =
PlayerController.Defaults.defaultPlayerPhysicsControl PlayerController.Defaults.defaultPlayerPhysicsControl
.taggedWith[PlayerControllerTags.PlayerTag]
// lazy val camNode =
// PlayerController.Defaults
// .defaultCamerNode(camera, playerPos)
// .taggedWith[PlayerControllerTags.PlayerCameraNode]
lazy val mbPlayerNode = PlayerController.Defaults
.defaultPlayerNode(
assetManager,
modelPath,
playerPos,
// camNode
playerPhysicsControl
)
lazy val cameraPivotNode = new Node(EntityIds.CameraPivot.value)
.taggedWith[PlayerControllerTags.PlayerCameraPivotNode]
for { for {
playerModel <- playerNode <- IO.fromEither(mbPlayerNode)
assetManager _ <- IO(cameraPivotNode.addControl(new FollowControl(playerNode)))
.loadModelAs[Node](modelPath) .onErrorHandleWith(e =>
.map(_.withRotate(0, FastMath.PI, 0)) IO.raiseError(PlayerController.GenericError(e.getMessage()))
.tapEval(m => UIO(m.center())) )
.mapError(AppError.AssetManagerError) camNode <- IO(
playerNode <- UIO(
PlayerController.Defaults PlayerController.Defaults
.defaultPlayerNode( .defaultCamerNode(camera, cameraPivotNode, playerNode, playerPos)
playerPos, ).onErrorHandleWith(e =>
playerModel, IO.raiseError(PlayerController.GenericError(e.getMessage()))
playerPhysicsControl ).map(_.taggedWith[PlayerControllerTags.PlayerCameraNode])
) // _ <- Task {
) // val chaseCam = new ChaseCamera(camera, playerNode, inputManager)
cameraPivotNode <- UIO( // chaseCam.setSmoothMotion(false)
new Node(EntityIds.CameraPivot.value) // chaseCam.setLookAtOffset(new Vector3f(0, 1.5f, 10))
.withControl(new FollowControl(playerNode)) // chaseCam
.taggedWith[PlayerController.Tags.PlayerCameraPivotNode] // }
) // .onErrorHandleWith(e =>
camNode <- UIO( // IO.raiseError(PlayerController.GenericError(e.getMessage()))
PlayerController.Defaults // )
.defaultCamerNode(camera, playerPos) _ <- wire[PlayerController.Props].create
.taggedWith[PlayerController.Tags.PlayerCameraNode] } yield ()
)
playerCameraEvents <-
playerEventBus
.askL[Observable[PlayerCameraEvent]](ObservableSubscription(_))
.onErrorHandleWith(TimeoutError.from)
_ <-
inputManager
.enumAnalogObservable(PlayerCameraInput)
.sample(1.millis)
.scan(new Quaternion) {
case (rotationBuf, action) =>
action.binding match {
case PlayerCameraInput.CameraRotateLeft =>
val rot = rotationBuf
.fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y)
cameraPivotNode.rotate(rot)
rotationBuf
case PlayerCameraInput.CameraRotateRight =>
val rot = rotationBuf
.fromAngleAxis(-1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y)
cameraPivotNode.rotate(rot)
rotationBuf
case PlayerCameraInput.CameraRotateUp =>
val rot = rotationBuf
.fromAngleAxis(-1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X)
cameraPivotNode.rotate(rot)
rotationBuf
case PlayerCameraInput.CameraRotateDown =>
val rot = rotationBuf
.fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X)
cameraPivotNode.rotate(rot)
rotationBuf
}
}
.completedL
.toIO
.hideErrors
.startAndForget
sched <- UIO.pure(schedulers.async)
fxSched <- UIO.pure(schedulers.fx)
playerActor <- wire[PlayerController.Props].create
playerMovementReducer = new PlayerMovementReducer(playerActor, loggerL)
_ <-
inputManager
.enumObservableAction(PlayerMovementInput)
.sample(1.millis)
.scanEval(me.Task.pure(PlayerMovementReducer.State.empty))(
playerMovementReducer.value
)
.completedL
.toIO
.hideErrors
.startAndForget
} yield playerActor
} }
def createTestNpc( def createTestNpc(
// appScheduler: monix.execution.Scheduler, appScheduler: monix.execution.Scheduler,
npcName: String npcName: String
): IO[AppError, NpcActorSupervisor.Ref] = { ) =
val initialPos = ImVector3f(50, 5, 0) // : IO[PlayerController.Error, Unit] =
val npcPhysicsControl = new BetterCharacterControl(1.5f, 6f, 1f) {
// (1f, 2.1f, 10f) val initialPos = ImVector3f(100, 0, 0)
.withJumpForce(ImVector3f(0, 5f, 0)) // val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o"
lazy val npcPhysicsControl =
val npcActorTask = AkkaUtils.spawnActorL( new BetterCharacterControl(1f, 2.1f, 10f)
new NpcActorSupervisor.Props( // .withJumpForce(ImVector3f(0, 5f, 0))
new NpcMovementActor.Props( // val npcMovementActor = AkkaUtils.spawnActorL2(
enqueueR, // new NpcMovementActor2.Props(
initialPos, // initialPos,
npcName, // tickEventBus,
npcPhysicsControl // npcPhysicsControl
).behavior, // ).behavior,
npcName, // s"${npcName}-npcMovementActor"
initialPos // )
).behavior, lazy val mbNpcNode = PlayerController.Defaults.defaultNpcNode(
actorName = Some(show"${npcName}-npcActorSupervisor") assetManager,
)
(for {
materialDef <-
assetManager
.loadAssetAs[MaterialDef](
os.rel / "Common" / "MatDefs" / "Misc" / "Unshaded.j3md"
)
.mapError(AppError.AssetManagerError)
material = new Material(materialDef)
_ = material.setColor("Color", ColorRGBA.Blue)
mesh = PlayerController.Defaults.defaultMesh.withMaterial(material)
npcNode = PlayerController.Defaults.defaultNpcNode(
mesh,
initialPos, initialPos,
npcPhysicsControl, npcPhysicsControl,
npcName npcName
) )
_ <- (for { val npcActorTask = AkkaUtils.spawnActorL2(
_ <- physicsSpace += npcPhysicsControl NpcActorSupervisor
_ <- physicsSpace += npcNode .Props(
_ <- rootNode += npcNode new NpcMovementActor.Props(
} yield ()).mapError(AppError.AppNodeError) enqueueR,
npcActor <- npcActorTask initialPos,
} yield npcActor) // tickEventBus,
npcPhysicsControl
).behavior,
npcName,
initialPos
)
.behavior,
s"${npcName}-npcActorSupervisor"
)
// .taggedWith[PlayerControllerTags.PlayerTag]
} for {
npcNode <- IO.fromEither(mbNpcNode)
npcActor <- npcActorTask
_ <- IO {
physicsSpace += npcPhysicsControl
physicsSpace += npcNode
rootNode += npcNode
}
} yield (npcActor)
}
} }
class FollowControl(playerNode: Node) extends AbstractControl {
override def controlUpdate(tpf: Float): Unit = {
this.spatial.setLocalTranslation(playerNode.getLocalTranslation())
}
override def controlRender(
rm: RenderManager,
vp: ViewPort
): Unit = {}
}

View File

@ -1,26 +1,25 @@
package wow.doge.mygame package wow.doge.mygame
import akka.actor.BootstrapSetup
import akka.actor.typed.ActorSystem import akka.actor.typed.ActorSystem
import akka.actor.typed.SpawnProtocol import akka.actor.typed.SpawnProtocol
import cats.effect.Resource import cats.effect.Resource
import io.odin.Logger import io.odin.Logger
import monix.bio.Task import monix.bio.Task
import wow.doge.mygame.executors.Schedulers.AsyncScheduler import wow.doge.mygame.executors.ExecutorsModule
class ActorSystemResource(logger: Logger[Task], scheduler: AsyncScheduler) { trait MainModule extends ExecutorsModule {
def get: Resource[Task, ActorSystem[SpawnProtocol.Command]] =
Resource.make( def actorSystemResource(
logger.info("Creating Actor System") >> Task( logger: Logger[Task]
ActorSystem( ): Resource[Task, ActorSystem[SpawnProtocol.Command]] =
SpawnProtocol(), Resource.make(logger.info("Creating Actor System") >> Task {
name = "GameActorSystem", ActorSystem(
BootstrapSetup().withDefaultExecutionContext(scheduler.value) SpawnProtocol(),
) name = "GameActorSystem"
) )
)(sys => })(sys =>
for { for {
_ <- Task(sys.terminate()) _ <- Task(sys.terminate())
_ <- Task.deferFuture(sys.whenTerminated) _ <- Task.fromFuture(sys.whenTerminated)
_ <- logger.info("Actor System Terminated") _ <- logger.info("Actor System Terminated")
} yield () } yield ()
) )

View File

@ -1,81 +0,0 @@
package wow.doge.mygame.actor
import akka.actor.typed.ActorRef
import akka.actor.typed.SpawnProtocol
import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.scaladsl.Behaviors
import wow.doge.mygame.implicits._
// import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
// import wow.doge.mygame.subsystems.events.Event
// import scala.reflect.ClassTag
// import akka.actor.typed.LogOptions
// import wow.doge.mygame.subsystems.events.EventBus
// import scala.concurrent.duration._
// import akka.util.Timeout
// import akka.actor.typed.SupervisorStrategy
object GameActorSystem {
sealed trait Command
final case class GetSpawnProtocol(
replyTo: ActorRef[ActorRef[SpawnProtocol.Command]]
) extends Command
class Props {
def create =
Behaviors.setup[Command] { ctx =>
val systemSpawnProtocol = ctx.spawnN(SpawnProtocol())
new GameActorSystem(ctx, this, systemSpawnProtocol).receive
}
}
}
class GameActorSystem(
ctx: ActorContext[GameActorSystem.Command],
props: GameActorSystem.Props,
sp: ActorRef[SpawnProtocol.Command]
) {
import GameActorSystem._
def receive =
Behaviors.receiveMessage[Command] {
case GetSpawnProtocol(replyTo) =>
replyTo ! sp
Behaviors.same
}
}
// object EventBusSupervisor {
// sealed trait Command
// final case class GetMainEventBus(replyTo: ActorRef[GameEventBus[Event]])
// extends Command
// final case class GetEventBus[T](replyTo: ActorRef[GameEventBus[T]])(implicit
// classTag: ClassTag[T]
// ) extends Command {
// def ct = classTag
// }
// class Props(val spawnProtocol: ActorRef[SpawnProtocol.Command]) {
// def create =
// Behaviors.setup[Command] { ctx =>
// new EventBusSupervisor(ctx, this).receive
// }
// }
// }
// class EventBusSupervisor(
// ctx: ActorContext[EventBusSupervisor.Command],
// props: EventBusSupervisor.Props
// ) {
// import EventBusSupervisor._
// implicit val timeout = Timeout(1.second)
// implicit val sp = props.spawnProtocol
// def receive =
// Behaviors.receiveMessage[Command] {
// case g @ GetEventBus(replyTo) =>
// implicit val ct = g.ct
// Behaviors
// .supervise(EventBus())
// .onFailure[Exception](
// SupervisorStrategy.restart.withLimit(2, 100.millis)
// )
// Behaviors.same
// case _ => Behaviors.same
// }
// }

View File

@ -3,21 +3,21 @@ package wow.doge.mygame.executors
import cats.effect.Resource import cats.effect.Resource
import monix.bio.Task import monix.bio.Task
import monix.execution.Scheduler import monix.execution.Scheduler
import wow.doge.mygame.types.JmeScheduler
trait ExecutorsModule { trait ExecutorsModule {
lazy val schedulers = Schedulers()
val schedulers = Schedulers.default // Resource.make(
// Task(
val jmeSchedulerResource = Resource.make( // new Schedulers(
// jme = Scheduler
// .singleThread(name = "JME-Application-Thread", daemonic = false)
// )
// )
// )(s => Task(s.jme.shutdown()))
lazy val jMESchedulerResource = Resource.make(
Task( Task(
JmeScheduler( Scheduler
Scheduler .singleThread(name = "JME-Application-Thread", daemonic = false)
.singleThread(name = "JME-Application-Thread", daemonic = false)
)
) )
)(s => Task(s.value.shutdown())) )(e => Task(e.shutdown()))
} }
sealed trait Error
case object Error extends Error

View File

@ -13,7 +13,6 @@ import scala.concurrent.ExecutionContext
import akka.dispatch.DispatcherPrerequisites import akka.dispatch.DispatcherPrerequisites
import akka.dispatch.ExecutorServiceConfigurator import akka.dispatch.ExecutorServiceConfigurator
import akka.dispatch.ExecutorServiceFactory import akka.dispatch.ExecutorServiceFactory
import com.jme3.app.Application
import com.typesafe.config.Config import com.typesafe.config.Config
import javafx.application.Platform import javafx.application.Platform
import monix.execution.Scheduler import monix.execution.Scheduler
@ -44,11 +43,12 @@ object SwingExecutorService extends GUIExecutorService {
object JMEExecutorService extends GUIExecutorService { object JMEExecutorService extends GUIExecutorService {
override def execute(command: Runnable) = override def execute(command: Runnable) =
JMERunner.runner.enqueue(command) JMERunner.runner.enqueue(command)
// new SingleThreadEventExecutor()
sys.addShutdownHook(JMEExecutorService.shutdown())
} }
object JMERunner { object JMERunner {
@SuppressWarnings(Array("org.wartremover.warts.Null")) var runner: com.jme3.app.Application = null
var runner: Application = null
} }
@ -98,8 +98,9 @@ class SwingEventThreadExecutorServiceConfigurator(
object JFXExecutionContexts { object JFXExecutionContexts {
val javaFxExecutionContext: ExecutionContext = val javaFxExecutionContext: ExecutionContext =
ExecutionContext.fromExecutor(new Executor { ExecutionContext.fromExecutor(new Executor {
def execute(command: Runnable): Unit = def execute(command: Runnable): Unit = {
Platform.runLater(command) Platform.runLater(command)
}
}) })
val fxScheduler = val fxScheduler =
Scheduler(javaFxExecutionContext) Scheduler(javaFxExecutionContext)

View File

@ -5,9 +5,13 @@ import monix.execution.Scheduler
import monix.execution.UncaughtExceptionReporter import monix.execution.UncaughtExceptionReporter
final case class Schedulers( final case class Schedulers(
io: Schedulers.IoScheduler, blockingIO: Scheduler = Scheduler
async: Schedulers.AsyncScheduler, .io()
fx: Schedulers.FxScheduler .withUncaughtExceptionReporter(Schedulers.reporter),
async: Scheduler = Scheduler.global
.withUncaughtExceptionReporter(Schedulers.reporter),
fx: Scheduler = JFXExecutionContexts.fxScheduler
.withUncaughtExceptionReporter(Schedulers.reporter)
) )
object Schedulers { object Schedulers {
@ -16,24 +20,4 @@ object Schedulers {
logger.error("Uncaught exception", ex) logger.error("Uncaught exception", ex)
} }
val default = Schedulers(
IoScheduler(
Scheduler
.io()
.withUncaughtExceptionReporter(Schedulers.reporter)
),
AsyncScheduler(
Scheduler.global
.withUncaughtExceptionReporter(Schedulers.reporter)
),
FxScheduler(
JFXExecutionContexts.fxScheduler
.withUncaughtExceptionReporter(Schedulers.reporter)
)
)
final case class AsyncScheduler(value: Scheduler)
final case class IoScheduler(value: Scheduler)
final case class FxScheduler(value: Scheduler)
} }

View File

@ -1,130 +1,193 @@
package wow.doge.mygame.game package wow.doge.mygame.game
import scala.concurrent.duration._ import cats.effect.concurrent.Deferred
import com.jme3.app.state.AppStateManager
import akka.actor.typed.ActorRef import com.jme3.asset.AssetManager
import akka.actor.typed.Behavior
import akka.actor.typed.Props
import akka.actor.typed.SpawnProtocol
import akka.actor.typed.scaladsl.AskPattern._
import akka.util.Timeout
import cats.effect.Resource
import com.jme3.bullet.BulletAppState
import com.jme3.input.InputManager import com.jme3.input.InputManager
import com.jme3.system.AppSettings import com.jme3.scene.Node
import com.jme3.scene.Spatial
import com.softwaremill.tagging._ import com.softwaremill.tagging._
import com.typesafe.scalalogging.{Logger => SLogger}
import io.odin.Logger import io.odin.Logger
import monix.bio.Fiber
import monix.bio.IO import monix.bio.IO
import monix.bio.Task import monix.bio.Task
import monix.bio.UIO import monix.catnap.ConcurrentChannel
import wow.doge.mygame.AppError import monix.catnap.ConsumerF
import wow.doge.mygame.AppError.TimeoutError import monix.catnap.Semaphore
import wow.doge.mygame.Dispatchers import monix.eval.Coeval
import wow.doge.mygame.executors.JMERunner
import wow.doge.mygame.executors.Schedulers
import wow.doge.mygame.game.subsystems.ui.JFxUI import wow.doge.mygame.game.subsystems.ui.JFxUI
import wow.doge.mygame.implicits._
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus sealed trait Error
import wow.doge.mygame.subsystems.events.TickEvent case object FlyCamNotExists extends Error
import wow.doge.mygame.types._
import wow.doge.mygame.utils.AkkaUtils
import wow.doge.mygame.utils.wrappers.jme._
object GameAppTags { object GameAppTags {
sealed trait RootNode sealed trait RootNode
sealed trait GuiNode sealed trait GuiNode
} }
class GameApp private[game] ( class GameApp(logger: Logger[Task], val app: SimpleAppExt) {
logger: Logger[Task], import Ops._
app: SimpleAppExt,
gameActor: ActorRef[GameAppActor.Command], def stateManager: Task[AppStateManager] = Task(app.getStateManager())
gameSpawnProtocol: ActorRef[SpawnProtocol.Command], def inputManager: Task[InputManager] = Task(app.getInputManager())
akkaScheduler: AkkaScheduler def assetManager: Task[AssetManager] = Task(app.getAssetManager())
) { def guiNode = Task(app.getGuiNode().taggedWith[GameAppTags.GuiNode])
def inputManager: UIO[InputManager] = UIO(app.getInputManager()) def addToGuiNode = guiNode.flatMap(rn => Task(new AddToNode(rn)))
val assetManager = new AssetManager(app.getAssetManager()) def flyCam =
val guiNode: GuiNode = IO(app.getFlyByCamera()).onErrorHandleWith(_ =>
AppNode2(app.getGuiNode()).taggedWith[GameAppTags.GuiNode] IO.raiseError(FlyCamNotExists)
// def flyCam = Option(app.getFlyByCamera()) )
def camera = UIO(app.getCamera()) def camera = Task(app.getCamera())
def viewPort = UIO(app.getViewPort()) def viewPort = Task(app.getViewPort())
val rootNode: RootNode = def rootNode = Task(app.getRootNode().taggedWith[GameAppTags.RootNode])
AppNode2(app.getRootNode()).taggedWith[GameAppTags.RootNode] // def rootNode2 = SynchedObject(app.getRootNode())
val physicsSpace = def addToRootNode = rootNode.flatMap(rn => Task(new AddToNode(rn)))
new PhysicsSpace(app.bulletAppState.physicsSpace) def enqueue(cb: () => Unit) =
def enqueue(cb: () => Unit) = app.enqueueR(() => cb()) app.enqueue(new Runnable {
override def run() = cb()
})
def enqueueL[T](cb: () => T): Task[T] = app.enqueueL(cb) def enqueueL[T](cb: () => T): Task[T] = app.enqueueL(cb)
def whenTerminated: IO[AppError, Unit] = def start = Task(app.start())
IO.deferFuture(app.whenTerminated).onErrorHandleWith(TimeoutError.from) def stop = Task(app.stop())
def scheduler = app.scheduler
def spawnGameActor[T](
behavior: Behavior[T],
actorName: Option[String] = None,
props: Props = Dispatchers.jmeDispatcher
)(implicit name: sourcecode.Name) =
AkkaUtils.spawnActorL(behavior, actorName, props)(
2.seconds,
akkaScheduler.value,
gameSpawnProtocol,
name
)
def scheduler = JmeScheduler(app.scheduler)
def jfxUI = JFxUI(app) def jfxUI = JFxUI(app)
} }
class GameAppResource( object GameApp {
logger: Logger[Task],
jmeThread: JmeScheduler,
schedulers: Schedulers,
tickEventBus: GameEventBus[TickEvent]
)(implicit
timeout: Timeout,
scheduler: AkkaScheduler,
spawnProtocol: ActorRef[SpawnProtocol.Command]
) {
implicit val as = scheduler.value
def resource
: Resource[UIO, Either[AppError, (GameApp, Fiber[Nothing, Unit])]] =
Resource.make(
(for {
app <- UIO(new SimpleAppExt(schedulers, new BulletAppState))
_ <- UIO(JMERunner.runner = app)
_ <- UIO {
val settings = new AppSettings(true)
settings.setVSync(true)
/** class WrappedNode(node: Node, lock: Semaphore[Task]) {
* disables the launcher
* We'll be making our own launcher anyway def +=(spat: Spatial) = lock.withPermit(Task(node.attachChild(spat)))
*/ }
app.setShowSettings(false)
app.setSettings(settings) /**
} * Synchronization wrapper for a mutable object
*
* @param obj the mutable object
* @param lock lock for synchronization
*/
class SynchedObject[A](obj: A, lock: Semaphore[Task]) {
def modify(f: A => Unit): Task[Unit] =
lock.withPermit(Task(f(obj)))
def flatModify(f: A => Task[Unit]): Task[Unit] =
lock.withPermit(f(obj))
def get: Task[A] = lock.withPermit(Task(obj))
}
object SynchedObject {
def apply[A](obj: A) =
Semaphore[Task](1).flatMap(lock => Task(new SynchedObject(obj, lock)))
}
fib <- UIO(app.start).executeOn(jmeThread.value).start }
_ <- Task.deferFuture(app.started).onErrorHandleWith(TimeoutError.from)
gameAppActor <- AkkaUtils.spawnActorL( object Ops {
new GameAppActor.Props(tickEventBus).behavior, final class AddToNode[T <: Node](private val node: T) extends AnyVal {
Some("testGameActor"),
props = Dispatchers.jmeDispatcher /**
) * Pure version
_ <- gameAppActor !! GameAppActor.Start */
sp <- def apply(spatial: Spatial)(implicit logger: Logger[Task]) =
gameAppActor logger.debug(
.askL(GameAppActor.GetSpawnProtocol) s"Request to add spatial with name ${spatial.getName()} to node ${node.getName()}"
.onErrorHandleWith(TimeoutError.from) ) >> Task(node.attachChild(spatial))
gameApp <- UIO(new GameApp(logger, app, gameAppActor, sp, scheduler))
_ <- UIO { /**
val fut = () => gameAppActor.ask(GameAppActor.Stop).flatten * Impure version
app.cancelToken = Some(fut) */
} def apply(spatial: Spatial)(implicit logger: SLogger) =
} yield (gameApp, fib)).attempt Coeval {
) { logger.debug(
case Right(gameApp -> fib) => fib.cancel s"Request to add spatial with name ${spatial.getName()} to node ${node.getName()}"
case Left(error) => IO.terminate(new Exception(error.toString)) )
} node.attachChild(spatial)
}
}
}
object SpawnSystem {
sealed trait Result
final case object Ok extends Result
sealed trait Complete
final case object Complete extends Complete
sealed trait SpawnRequest
final case class SpawnSpatial(nodeTask: Task[Node]) extends SpawnRequest
final case class SpawnRequestWrapper(
spawnRequest: SpawnRequest,
result: Deferred[Task, Result]
)
def apply(logger: Logger[Task]) =
for {
spawnChannel <- ConcurrentChannel[Task].of[Complete, SpawnRequestWrapper]
spawnSystem <- Task(new SpawnSystem(logger, spawnChannel))
consumer <-
spawnChannel.consume
.use(consumer => spawnSystem.receive(consumer))
.startAndForget
} yield (spawnSystem)
}
class SpawnSystem(
logger: Logger[Task],
spawnChannel: ConcurrentChannel[
Task,
SpawnSystem.Complete,
SpawnSystem.SpawnRequestWrapper
]
) {
import SpawnSystem._
for {
spawnSystem <- SpawnSystem(logger)
res <- spawnSystem.request(SpawnSpatial(Task(new Node("Test"))))
} yield ()
// val spawnChannel = ConcurrentChannel[Task].of[Result, SpawnRequest]
private def receive(
consumer: ConsumerF[Task, Complete, SpawnRequestWrapper]
): Task[Unit] =
consumer.pull.flatMap {
case Right(message) =>
for {
_ <-
logger
.debug(s"Received spawn request $message")
_ <- handleSpawn(message)
} yield receive(consumer)
case Left(r) =>
logger.info("Closing Spawn System")
}
private def handleSpawn(spawnRequestWrapper: SpawnRequestWrapper) =
spawnRequestWrapper match {
case SpawnRequestWrapper(spawnRequest, result) =>
spawnRequest match {
case SpawnSpatial(spatialTask) =>
spatialTask.flatMap(spatial =>
logger.debug(
s"Spawning spatial with name ${spatial.getName()}"
) >> result
.complete(Ok)
)
}
}
def request(spawnRequest: SpawnRequest) =
for {
d <- Deferred[Task, Result]
_ <- spawnChannel.push(SpawnRequestWrapper(spawnRequest, d))
res <- d.get
} yield (res)
def stop = spawnChannel.halt(Complete)
} }

View File

@ -2,88 +2,61 @@ package wow.doge.mygame.game
import scala.concurrent.duration._ import scala.concurrent.duration._
import akka.actor.typed.ActorRef
import akka.actor.typed.PostStop
import akka.actor.typed.SpawnProtocol
import akka.actor.typed.SupervisorStrategy import akka.actor.typed.SupervisorStrategy
import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.scaladsl.Behaviors
import monix.execution.CancelableFuture
import monix.execution.CancelablePromise
import wow.doge.mygame.game.TickGenerator.Send import wow.doge.mygame.game.TickGenerator.Send
import wow.doge.mygame.implicits._ import wow.doge.mygame.game.entities.GenericTimerActor
import wow.doge.mygame.subsystems.events.EventBus import wow.doge.mygame.subsystems.events.EventBus
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
import wow.doge.mygame.subsystems.events.TickEvent import wow.doge.mygame.subsystems.events.TickEvent
import wow.doge.mygame.utils.GenericTimerActor import wow.doge.mygame.subsystems.events.TickEvent.PhysicsTick
object GameAppActor { object GameAppActor {
sealed trait Command sealed trait Command
case object Start extends Command case object Start extends Command
case object Pause extends Command case object Pause extends Command
case object Ping extends Command case object Stop extends Command
final case class Stop(stopSignal: ActorRef[CancelableFuture[Unit]])
extends Command
final case class GetSpawnProtocol(
replyTo: ActorRef[ActorRef[SpawnProtocol.Command]]
) extends Command
final case class Props(tickEventBus: GameEventBus[TickEvent]) { case class Props(
// app: SimpleAppExt,
// akkaScheduler: Scheduler,
// schedulers: Schedulers,
// spawnProtocol: ActorRef[SpawnProtocol.Command],
// loggerL: Logger[Task]
tickEventBus: GameEventBus[TickEvent]
) {
def behavior = def behavior =
Behaviors.setup[Command] { ctx => Behaviors.setup[Command] { ctx =>
ctx.log.infoP("Hello from GameAppActor") ctx.log.info("Hello from GameAppActor")
val renderTickGenerator = val renderTickGenerator =
ctx.spawnN( ctx.spawn(
Behaviors Behaviors
.supervise(renderTickGeneratorBehavior) .supervise(renderTickGeneratorBehavior)
.onFailure[Exception]( .onFailure[Exception](SupervisorStrategy.restart),
SupervisorStrategy.restart.withLimit(2, 100.millis) "tickGeneratorActor"
)
) )
val tickGeneratorTimer = ctx.spawnN( val tickGeneratorTimer = ctx.spawn(
GenericTimerActor GenericTimerActor
.Props(renderTickGenerator, TickGenerator.Send, 10.millis) .Props(renderTickGenerator, TickGenerator.Send, 10.millis)
.behavior .behavior,
"tickGeneratorTimer"
) )
val sp = ctx.spawn(SpawnProtocol(), "gameSpawnProtocol") Behaviors.receiveMessage {
case Start =>
tickGeneratorTimer ! GenericTimerActor.Start
Behaviors.same
case Pause =>
tickGeneratorTimer ! GenericTimerActor.Stop
Behaviors.same
case Stop =>
ctx.log.info("Received stop")
tickGeneratorTimer ! GenericTimerActor.Stop
Behaviors.stopped
// ctx.spawn( }
// GenericTimerActor
// .Props(ctx.self, Ping, 1000.millis)
// .behavior,
// "pingTimer"
// ) ! GenericTimerActor.Start
val stopPromise = CancelablePromise[Unit]()
Behaviors
.receiveMessage[Command] {
case Start =>
tickGeneratorTimer ! GenericTimerActor.Start
Behaviors.same
case Pause =>
tickGeneratorTimer ! GenericTimerActor.Stop
Behaviors.same
case Stop(replyTo) =>
ctx.log.infoP("Received stop")
tickGeneratorTimer ! GenericTimerActor.Stop
replyTo ! stopPromise.future
Behaviors.stopped
case Ping =>
ctx.log.debugP("ping")
Behaviors.same
case GetSpawnProtocol(replyTo) =>
replyTo ! sp
Behaviors.same
}
.receiveSignal {
case (_, PostStop) =>
stopPromise.success(())
Behaviors.same
}
} }
val renderTickGeneratorBehavior = val renderTickGeneratorBehavior =
@ -102,3 +75,92 @@ object TickGenerator {
sealed trait Command sealed trait Command
case object Send extends Command case object Send extends Command
} }
object SubscribingActor {
def apply() =
Behaviors.receive[PhysicsTick.type] { (ctx, msg) =>
ctx.log.debug(s"received event $msg")
Behaviors.same
}
}
object Methods {
def old() = {
// val movementActor =
// ctx.spawn(
// MovementActor(MovementActor.Props(app, geom)),
// "movementActor"
// // DispatcherSelector.fromConfig("jme-dispatcher")
// )
// val movementActorTimer = ctx.spawn(
// MovementActorTimer(movementActor),
// "movementActorTimer"
// )
}
def old2() = {
// ctx.log.info("here")
// {
// implicit val s = schedulers.async
// Task
// .parZip2(
// loggerL.info("Test").executeOn(app.scheduler),
// app
// .enqueueL(() => loggerL.info("here 2").executeOn(app.scheduler))
// .flatten
// )
// .runToFuture
// }
// app
// .getRootNode()
// .depthFirst(s =>
// // s match {
// // case node: Node =>
// // println("node" + s.getName() + " children " + node.getChildren())
// // case g: Geometry => println(s.getName())
// // }
// println(s.getName())
// )
// println("----------------")
// {
// app
// .getRootNode()
// .observableDepthFirst()
// .map(s => s.getName())
// // .takeWhileInclusive(_.getName() != "level")
// .onErrorHandle(e => e.getMessage())
// .foreach(println)(schedulers.async)
// }
// println("----------------")
// {
// app
// .getRootNode()
// .observableBreadthFirst()
// .map(s => s.getName())
// // .takeWhileInclusive(_.getName() != "level")
// .onErrorHandle(e => e.getMessage())
// .foreach(println)(schedulers.async)
// }
// app.start()
// Behaviors.same
}
}
// new PlayerMovementState(
// // movementActor,
// // movementActorTimer,
// imMovementActor,
// // geom,
// // camNode,
// playerNode
// // ctx.self
// )

View File

@ -0,0 +1,36 @@
package wow.doge.mygame.game
import cats.effect.Resource
import com.jme3.app.StatsAppState
import com.jme3.system.AppSettings
import io.odin.Logger
import monix.bio.Task
import monix.execution.Scheduler
import wow.doge.mygame.executors.Schedulers
class GameAppResource(
logger: Logger[Task],
jmeScheduler: Scheduler,
schedulers: Schedulers
) {
def get: Resource[Task, GameApp] =
Resource.make(
for {
_ <- logger.info("Creating game app")
appExt <- Task(new SimpleAppExt(schedulers, new StatsAppState()))
app <- Task {
val settings = new AppSettings(true)
settings.setVSync(true)
/**
* disables the launcher
* We'll be making our own launcher anyway
*/
appExt.setShowSettings(false)
appExt.setSettings(settings)
// JMERunner.runner = app
new GameApp(logger, appExt)
}
} yield (app)
)(_ => logger.info("Closing game app"))
}

View File

@ -1,21 +1,22 @@
package wow.doge.mygame.game package wow.doge.mygame.game
import scala.collection.immutable.Queue import scala.collection.immutable.Queue
import scala.concurrent.Future
import com.jme3.app.SimpleApplication import com.jme3.app.SimpleApplication
import com.jme3.app.state.AppState import com.jme3.app.state.AppState
import com.jme3.bullet.BulletAppState
import monix.bio.Task import monix.bio.Task
import monix.execution.CancelableFuture import monix.execution.CancelableFuture
import monix.execution.CancelablePromise
import monix.execution.Scheduler import monix.execution.Scheduler
import monix.execution.atomic.Atomic import monix.execution.atomic.Atomic
import monix.execution.{CancelablePromise => Promise}
import monix.reactive.MulticastStrategy
import monix.reactive.Observable
import monix.reactive.subjects.ConcurrentSubject
import wow.doge.mygame.executors.GUIExecutorService import wow.doge.mygame.executors.GUIExecutorService
import wow.doge.mygame.executors.Schedulers import wow.doge.mygame.executors.Schedulers
class SimpleAppExt( class SimpleAppExt(
schedulers: Schedulers, schedulers: Schedulers,
val bulletAppState: BulletAppState,
appStates: AppState* appStates: AppState*
) extends SimpleApplication(appStates: _*) { ) extends SimpleApplication(appStates: _*) {
import SimpleAppExt._ import SimpleAppExt._
@ -23,49 +24,34 @@ class SimpleAppExt(
/** /**
* A non blocking synchronized queue using an immutable scala queue and monix's atomic class * A non blocking synchronized queue using an immutable scala queue and monix's atomic class
*/ */
private val taskQueue2 = Atomic(Queue.empty[MyTask[_]]) private lazy val taskQueue2 = Atomic(Queue.empty[MyTask[_]])
private val startSignal: CancelablePromise[Unit] = CancelablePromise() private val tickSubject =
private val terminationSignal: CancelablePromise[Unit] = CancelablePromise() ConcurrentSubject[Float](multicast = MulticastStrategy.publish)(
private implicit val ec = schedulers.async.value schedulers.async
)
var cancelToken: Option[() => Future[Unit]] = None def tickObservable: Observable[Float] = tickSubject
def started: CancelableFuture[Unit] = startSignal.future override def simpleInitApp(): Unit = {}
def whenTerminated: CancelableFuture[Unit] = terminationSignal.future override def simpleUpdate(tpf: Float): Unit = {
tickSubject.onNext(tpf)
// override def physicsSpace: PhysicsSpace = ???
override def simpleInitApp(): Unit = {
stateManager.attach(bulletAppState)
startSignal.success(())
} }
override def simpleUpdate(tpf: Float): Unit = {} override def stop(): Unit = {
tickSubject.onComplete()
super.stop()
}
override def stop(waitFor: Boolean): Unit = def enqueueScala[T](cb: () => T): CancelableFuture[T] = {
cancelToken match { val p = Promise[T]()
case Some(value) =>
value().foreach { _ =>
pprint.log("Called cancel in simpleapp")
super.stop(true)
terminationSignal.success(())
}
case None =>
pprint.log("Called cancel in simpleapp")
super.stop(true)
terminationSignal.success(())
}
def enqueueFuture[T](cb: () => T): CancelableFuture[T] = {
val p = CancelablePromise[T]()
taskQueue2.transform(_ :+ MyTask(p, cb)) taskQueue2.transform(_ :+ MyTask(p, cb))
p.future p.future
} }
def enqueueL[T](cb: () => T): Task[T] = def enqueueL[T](cb: () => T): Task[T] =
Task.deferFuture(enqueueFuture(cb)) Task.deferFuture(enqueueScala(cb))
override protected def runQueuedTasks(): Unit = { override protected def runQueuedTasks(): Unit = {
taskQueue2.transform { current => taskQueue2.transform { current =>
@ -84,10 +70,10 @@ class SimpleAppExt(
enqueue(command) enqueue(command)
} }
val scheduler = Scheduler(JMEExecutorService) lazy val scheduler = Scheduler(JMEExecutorService)
} }
object SimpleAppExt { object SimpleAppExt {
private[game] final case class MyTask[T](p: CancelablePromise[T], cb: () => T) private[game] case class MyTask[T](p: Promise[T], cb: () => T)
} }
// val ship = ed.createEntity() // val ship = ed.createEntity()

View File

@ -36,11 +36,11 @@ object TestActor {
// ) // )
// ) { // ) {
// case Success(value) => // case Success(value) =>
// ctx.log.debugP("Received Value") // ctx.log.debug("Received Value")
// ctx.log.debugP(value.toString()) // ctx.log.debug(value.toString())
// Done // Done
// case Failure(exception) => // case Failure(exception) =>
// ctx.log.debugP(show"Received Error ${exception.getMessage()}") // ctx.log.debug(s"Received Error ${exception.getMessage()}")
// Done // Done
// } // }
} }
@ -60,24 +60,24 @@ object TestActor {
// .Get(os.pwd / "src" / "main" / "resources" / "hello2.sc", _) // .Get(os.pwd / "src" / "main" / "resources" / "hello2.sc", _)
// ) { // ) {
// case Success(value) => { // case Success(value) => {
// ctx.log.debugP(value.toString()) // ctx.log.debug(value.toString())
// ctx.ask( // ctx.ask(
// scriptStorer, // scriptStorer,
// ScriptStoringActor // ScriptStoringActor
// .Get(os.pwd / "src" / "main" / "resources" / "hello2.sc", _) // .Get(os.pwd / "src" / "main" / "resources" / "hello2.sc", _)
// ) { // ) {
// case Success(value) => { // case Success(value) => {
// ctx.log.debugP(value.toString()) // ctx.log.debug(value.toString())
// Done // Done
// } // }
// case Failure(exception) => // case Failure(exception) =>
// ctx.log.debugP(exception.getMessage()) // ctx.log.debug(exception.getMessage())
// Done // Done
// } // }
// Done // Done
// } // }
// case Failure(exception) => // case Failure(exception) =>
// ctx.log.debugP(exception.getMessage()) // ctx.log.debug(exception.getMessage())
// Done // Done
// } // }
} }

View File

@ -0,0 +1,69 @@
package wow.doge.mygame.state
import com.jme3.app.Application
import com.jme3.app.SimpleApplication
import com.jme3.app.state.AppState
import com.jme3.app.state.BaseAppState
import com.jme3.scene.Node
import com.jme3.scene.Spatial
import com.simsilica.es.EntityData
import com.simsilica.es.base.DefaultEntityData
trait MyBaseState extends BaseAppState {
var simpleApp: SimpleApplication = null
implicit val entityData: EntityData = new DefaultEntityData()
def stateManager = simpleApp.getStateManager
def guiNode = simpleApp.getGuiNode
def rootNode = simpleApp.getRootNode
def assetManager = simpleApp.getAssetManager
def inputManager = simpleApp.getInputManager
def cam = simpleApp.getCamera
override protected final def initialize(app: Application): Unit = {
simpleApp = app.asInstanceOf[SimpleApplication]
init()
// stateManager.getState(classOf[FlyCamAppState]).getCamera().setMoveSpeed(100)
}
protected def init(): Unit
protected def stop(): Unit
override protected def cleanup(app: Application): Unit = {
entityData.close()
// stop()
}
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
}
protected def enableStates(classes: Class[_ <: AppState]*) =
setEnabledToStates(true, classes: _*)
protected def disableStates(classes: Class[_ <: AppState]*) =
setEnabledToStates(false, classes: _*)
protected def setEnabledToStates(
enabled: Boolean,
classes: Class[_ <: AppState]*
) = {
for (clazz <- classes) {
val st = stateManager.getState(clazz)
if (st != null) st.setEnabled(enabled)
}
}
protected def removeStates(classes: Class[_ <: AppState]*) = {
for (clazz <- classes) {
val st = stateManager.getState(clazz)
if (st != null) stateManager.detach(st)
}
}
}

View File

@ -0,0 +1,294 @@
package wow.doge.mygame.state
import scala.concurrent.duration.DurationInt
import akka.actor.typed.ActorRef
import akka.actor.typed.Behavior
import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.scaladsl.Behaviors
import akka.actor.typed.scaladsl.TimerScheduler
import com.jme3.input.InputManager
import com.jme3.input.KeyInput
import com.jme3.input.controls.KeyTrigger
import com.jme3.math.Vector3f
import com.jme3.scene.Geometry
import wow.doge.mygame.implicits._
import wow.doge.mygame.subsystems.movement.ImMovementActor
class PlayerMovementState(
// movementActor: ActorRef[MovementActor.Command],
// movementActorTimer: ActorRef[MovementActorTimer.Command],
imMovementActor: ActorRef[ImMovementActor.Command]
// geom: Geometry,
// camNode: CameraNode,
// playerNode: Node
// gameAppActor: ActorRef[GameAppActor.Command]
) extends MyBaseState
// with ActionListener
{
protected lazy val mat = MyMaterial(
assetManager = assetManager,
path = os.rel / "Common" / "MatDefs" / "Misc" / "Unshaded.j3md"
)
override protected def init(): Unit = {
// setupKeys(inputManager)
// 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.withChild(playerNode) }
// camNode.setLocalTranslation(
// new Vector3f(0, 1.5f, 10)
// )
// camNode.lookAt(playerNode.getLocalTranslation(), Vector3f.UNIT_Y)
// movementActorTimer ! MovementActorTimer.Start(geom, cam)
// movementActorTimer ! MovementActorTimer.Start
}
override def update(tpf: Float) = {
// 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
.withMapping(
"Left",
// new KeyTrigger(KeyInput.KEY_A),
new KeyTrigger(KeyInput.KEY_LEFT)
)
.withMapping(
"Right",
// new KeyTrigger(KeyInput.KEY_D),
new KeyTrigger(KeyInput.KEY_RIGHT)
)
.withMapping(
"Up",
// new KeyTrigger(KeyInput.KEY_W),
new KeyTrigger(KeyInput.KEY_UP)
)
.withMapping(
"Down",
// new KeyTrigger(KeyInput.KEY_S),
new KeyTrigger(KeyInput.KEY_DOWN)
)
.withMapping(
"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")
// .withListener(this, "Up")
// .withListener(this, "Down")
// .withListener(this, "Space")
// .withListener(this, "Reset")
}
// def onAction(binding: String, value: Boolean, tpf: Float) =
// binding match {
// case "Left" => imMovementActor ! ImMovementActor.MovedLeft(value)
// case "Right" => imMovementActor ! ImMovementActor.MovedRight(value)
// case "Up" => imMovementActor ! ImMovementActor.MovedUp(value)
// case "Down" => imMovementActor ! ImMovementActor.MovedDown(value)
// case "Space" =>
// case _ =>
// }
override protected def onEnable(): Unit = {}
override protected def onDisable(): Unit = {}
}
final case class CardinalDirection(
left: Boolean = false,
right: Boolean = false,
up: Boolean = false,
down: Boolean = false
)
object MovementActor {
sealed trait Command
// final case class Tick(tpf: Float, geom: Geometry, cam: Camera) extends Command
// final case class Tick(tpf: Float) extends Command
final case object Tick extends Command
sealed trait Movement extends Command
final case class MovedLeft(pressed: Boolean) extends Movement
final case class MovedUp(pressed: Boolean) extends Movement
final case class MovedRight(pressed: Boolean) extends Movement
final case class MovedDown(pressed: Boolean) extends Movement
final case class Props(app: com.jme3.app.Application, geom: Geometry)
/**
* Internal state of the actor
*
* @param cardinalDir Immutable, can be shared as is
* @param walkDirection scratch space to avoid allocations on every tick. Do not share outside the actor
*/
final case class State(
cardinalDir: CardinalDirection = CardinalDirection(),
walkDirection: Vector3f = Vector3f.UNIT_X
)
def apply(props: Props): Behavior[Command] =
Behaviors.setup(ctx => new MovementActor(ctx, props).receive(State()))
}
class MovementActor(
ctx: ActorContext[MovementActor.Command],
props: MovementActor.Props
) {
import MovementActor._
import com.softwaremill.quicklens._
def receive(state: MovementActor.State): Behavior[Command] =
Behaviors.receiveMessage { msg =>
msg match {
case m: Movement =>
m match {
case MovedLeft(pressed) =>
receive(state = state.modify(_.cardinalDir.left).setTo(pressed))
case MovedUp(pressed) =>
receive(state = state.modify(_.cardinalDir.up).setTo(pressed))
case MovedRight(pressed) =>
receive(state = state.modify(_.cardinalDir.right).setTo(pressed))
case MovedDown(pressed) =>
receive(state = state.modify(_.cardinalDir.down).setTo(pressed))
}
case Tick =>
val camDir =
props.app.getCamera.getDirection().clone().multLocal(0.6f)
val camLeft = props.app.getCamera.getLeft().clone().multLocal(0.4f)
val walkDir = state.walkDirection.set(0, 0, 0)
// val walkDir = new Vector3f
val dir = state.cardinalDir
if (dir.up) {
ctx.log.debug("up")
// ctx.log.debug(Thread.currentThread().getName())
// walkDir.addLocal(0, 0, -1)
walkDir += camDir
}
if (dir.left) {
ctx.log.debug("left")
// walkDir.addLocal(-1, 0, 0)
walkDir.addLocal(camLeft)
}
if (dir.right) {
ctx.log.debug("right")
// walkDir.addLocal(1, 0, 0)
walkDir.addLocal(camLeft.negateLocal())
}
if (dir.down) {
ctx.log.debug("down")
walkDir.addLocal(camDir.negateLocal())
// walkDir.addLocal(0, 0, 1)
}
// (dir.up, dir.down, dir.left, dir.right) match {
// case (true, false, true, false) =>
// case _ =>
// }
walkDir.multLocal(2f)
// walkDir.multLocal(100f)
// .multLocal(tpf)
// val v = props.geom.getLocalTranslation()
// props.geom.setLocalTranslation(
// (v += walkDir)
// )
props.app.enqueue(new Runnable {
override def run(): Unit = {
// geom.setLocalTranslation(walkDir)
val v = props.geom.getLocalTranslation()
props.geom.setLocalTranslation(
(v += walkDir)
)
}
})
Behaviors.same
// receive(state = state.modify(_.walkDirection).setTo(walkDir))
}
}
}
object MovementActorTimer {
sealed trait Command
final case object Start extends Command
final case object Update extends Command
private case object Send extends Command
case object TimerKey
final case class Props(
timers: TimerScheduler[MovementActorTimer.Command],
target: ActorRef[MovementActor.Command]
)
final case class State()
def apply(target: ActorRef[MovementActor.Command]) =
Behaviors.withTimers[Command] { timers =>
new MovementActorTimer(Props(timers, target)).idle()
}
}
class MovementActorTimer(
props: MovementActorTimer.Props
) {
import MovementActorTimer._
// import com.softwaremill.quicklens._
def idle(): Behavior[Command] =
Behaviors.receiveMessage { msg =>
msg match {
case Start =>
props.timers.startTimerWithFixedDelay(
Send,
10.millis
)
active()
case _ => Behaviors.unhandled
}
}
def active(): Behavior[Command] =
Behaviors.receiveMessage { msg =>
msg match {
case Update => active()
case Send =>
props.target ! MovementActor.Tick
Behaviors.same
case _ => Behaviors.unhandled
}
}
}

View File

@ -0,0 +1,207 @@
package wow.doge.mygame.state
import javax.script.ScriptEngine
import akka.actor.typed.ActorRef
import akka.actor.typed.Behavior
import akka.actor.typed.SpawnProtocol
import akka.actor.typed.scaladsl.AbstractBehavior
import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.scaladsl.Behaviors
import ammonite.Main
import ammonite.main.Defaults
import ammonite.runtime.Storage.Folder
import ammonite.util.Res.Success
import com.jme3.app.state.AppState
class ScriptingEngineState(
sse: ScalaScriptingEngine,
kse: KotlinScriptingEngine
) extends MyBaseState {
// implicit val actorSystem =
// ActorSystem.create(MyActorSystem(), "rootActor")
// implicit val timeout: Timeout = Timeout(3.seconds)
// val scalaScriptActor: Future[ActorRef[ScalaScriptBehavior.Command]] =
// actorSystem.ask(
// SpawnProtocol.Spawn(
// ScalaScriptBehavior(sse.runner),
// name = "ScalaScriptCompilerActor",
// Props.empty,
// _
// )
// )
override def stop(): Unit = {}
// override protected def cleanup(app: Application): Unit = {
// // actorSystem.terminate()
// }
// override protected def initialize(app: Application): Unit = {
// super.initialize(app)
// }
override def init() = {
// Future {
// while (true) {
// // super.update(tpf)
// val (res, k) = sse.runner.runScript(
// // os.Path(getClass().getResource("/hello.sc").getPath),
// os.pwd / "src" / "main" / "resources" / "hello.sc",
// Seq.empty
// // Seq(("start", None))
// // Scripts.groupArgs(List(""))
// )
// val ms = res.map(_.asInstanceOf[GameScript])
// ms.map(_.start())
// val res2 = kse.engine.eval(
// os.read(os.pwd / "src" / "main" / "resources" / "hello.main.kts")
// )
// // val res2 = engine.eval(getClass().getResource("/hello.main.kts").getPath)
// // val invoker = engine.asInstanceOf[Invocable]
// // val scr = invoker.getInterface(res2, classOf[GameScript])
// val scr = res2.asInstanceOf[GameScript]
// scr.start()
// Thread.sleep(2000)
// }
// }
// Future {
// sse.runner
// .runScript(
// os.pwd / "src" / "main" / "resources" / "hello2.sc",
// Seq.empty
// )
// ._1
// .map(_.asInstanceOf[MyBaseState])
// .map(s => stateManager.attach(s))
// ()
// }
// val res = scalaScriptActor
// .map(
// _.ask(ref =>
// ScalaScriptBehavior.Compile(
// ref,
// os.pwd / "src" / "main" / "resources" / "hello2.sc"
// // os.Path(getClass().getResource("/hello2.sc").getPath)
// )
// )(Timeout(10.seconds), actorSystem.scheduler)
// )
// .flatten
// res.foreach(_ match {
// case AppStateResult(state) => {
// stateManager.attach(state)
// }
// case wow.doge.mygame.state.ScalaScriptBehavior.Error(reason) =>
// println("error")
// })
}
override def update(tpf: Float): Unit = {}
override protected def onEnable(): Unit = {}
override protected def onDisable(): Unit = {}
}
object MyActorSystem {
def apply(): Behavior[SpawnProtocol.Command] =
Behaviors.setup { context =>
// Start initial tasks
// context.spawn(...)
SpawnProtocol()
}
}
class ScalaScriptingEngine(
val runner: Main = ammonite
.Main(
// predefCode = """
// import coursierapi.MavenRepository
// interp.repositories.update(
// interp.repositories() ::: List(
// MavenRepository.of("file://home/rohan/.m2/repository")
// )
// )
// @
// """,
defaultPredef = false,
storageBackend = new Folder(Defaults.ammoniteHome, isRepl = false)
)
) {}
class KotlinScriptingEngine(val engine: ScriptEngine) {
// val manager = new ScriptEngineManager()
// val engine = manager.getEngineByExtension("main.kts")
}
object ScalaScriptBehavior {
sealed trait Result
final case class AppStateResult(state: AppState) extends Result
final case class Error(reason: String) extends Result
sealed trait Command
final case class Compile(sender: ActorRef[Result], path: os.Path)
extends Command
// final case class CompileScripts(sender: ActorRef[Result], paths: os.Path*)
// extends Command
def apply(
runner: Main = ammonite
.Main(
storageBackend = new Folder(
// os.pwd / "target"
Defaults.ammoniteHome,
isRepl = false
)
)
) =
Behaviors.setup(ctx => new ScalaScriptActor(runner, ctx))
private class ScalaScriptActor(
val runner: Main,
context: ActorContext[Command]
) extends AbstractBehavior[Command](context) {
override def onMessage(msg: Command): Behavior[Command] = {
msg match {
case Compile(sender, path) =>
context.log.debug(s"Received $path")
val res = getScript(path)
println(res)
sender ! res
Behaviors.same
// case CompileScripts(sender, paths) =>
}
}
def getScript(path: os.Path): Result = {
runner
.runScript(
path,
Seq.empty
)
._1 match {
case ammonite.util.Res.Exception(t, msg) => Error(msg)
case Success(obj) =>
obj match {
case s: MyBaseState => AppStateResult(s)
case _ => Error("Unknown script type")
// AppStateResult(s.asInstanceOf[AppState])
}
case _ => Error("Failed to run script")
}
}
}
}

View File

@ -0,0 +1,71 @@
package wow.doge.mygame.state
import com.jme3.asset.AssetManager
import com.jme3.material.Material
import com.jme3.math.ColorRGBA
import com.jme3.math.Vector3f
import com.jme3.scene.Geometry
import com.jme3.scene.shape.Box
import wow.doge.mygame.components.TestComponent
import wow.doge.mygame.implicits._
class TestAppState(
// private var _entity: Option[EntityData] = Some(new DefaultEntityData())
) extends MyBaseState {
var geom: Option[Geometry] = None
// def entity = _entity
// override def initialize(app: Application): Unit = {
// super.initialize(app)
// }
override def init() = {
entityData
.createEntity()
.withComponents(TestComponent())
// entityData.setComponents(x, TestComponent())
val es = entityData.getEntities(classOf[TestComponent])
println(es)
val b = new Box(1, 1, 1)
geom = Some(new Geometry("Box", b))
val mat = MyMaterial(
assetManager = assetManager,
path = os.rel / "Common" / "MatDefs" / "Misc" / "Unshaded.j3md"
)
geom.foreach(e => {
e.setMaterial(mat)
rootNode.attachChild(e)
})
}
override def update(tpf: Float) = {
geom.foreach(_.rotate(0, 0.5f * tpf, 0))
geom.foreach(_.move(new Vector3f(0, 1 * tpf, 0)))
}
// override def cleanup(app: Application): Unit = {
// // _entity.map(_.close())
// // _entity = None
// }
override def onEnable(): Unit = {}
override def onDisable(): Unit = {}
override def stop(): Unit = {}
}
object MyMaterial {
def apply(
color: String = "Color",
colorType: com.jme3.math.ColorRGBA = ColorRGBA.Blue,
assetManager: AssetManager,
path: os.RelPath
): Material = {
val mat =
new Material(assetManager, path.toString())
mat.setColor(color, colorType)
mat
}
}

View File

@ -1,101 +0,0 @@
package wow.doge.mygame.game.controls
import scala.concurrent.Future
import cats.syntax.eq._
import com.jme3.math.FastMath
import com.jme3.math.Quaternion
import com.jme3.math.Vector3f
import com.jme3.renderer.RenderManager
import com.jme3.renderer.ViewPort
import com.jme3.scene.Node
import com.jme3.scene.Spatial
import com.jme3.scene.control.AbstractControl
import monix.execution.Ack
import monix.execution.Cancelable
import monix.execution.Scheduler
import monix.reactive.Observable
import monix.reactive.Observer
import wow.doge.mygame.game.subsystems.input.PlayerCameraInput
/**
* A very low level (and error prone) camera movement control implementation.
* Not used currently
*
* @param rotationBuf
* @param obs
* @param rotateFn
* @param s
*/
@SuppressWarnings(Array("org.wartremover.warts.Null"))
class CameraMovementControl(
rotationBuf: Quaternion,
obs: Observable[PlayerCameraInput],
rotateFn: Quaternion => Unit
)(implicit s: Scheduler)
extends AbstractControl {
private var _event: PlayerCameraInput = null
private var _subscriptionToken: Cancelable = null
private val sink = new Observer[PlayerCameraInput] {
override def onNext(event: PlayerCameraInput): Future[Ack] = {
_event = event
Ack.Continue
}
override def onError(ex: Throwable): Unit = {}
override def onComplete(): Unit = {}
}
override def controlUpdate(tpf: Float): Unit =
if (_event =!= null) {
_event match {
case PlayerCameraInput.CameraRotateLeft =>
val rot = rotationBuf
.fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y)
rotateFn(rot)
case PlayerCameraInput.CameraRotateRight =>
val rot = rotationBuf
.fromAngleAxis(-1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y)
rotateFn(rot)
case PlayerCameraInput.CameraRotateUp =>
val rot = rotationBuf
.fromAngleAxis(-1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X)
rotateFn(rot)
case PlayerCameraInput.CameraRotateDown =>
val rot = rotationBuf
.fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X)
rotateFn(rot)
}
_event = null
}
override def controlRender(
x$1: RenderManager,
x$2: ViewPort
): Unit = {}
@SuppressWarnings(Array("org.wartremover.warts.Equals"))
override def setSpatial(spatial: Spatial): Unit = {
super.setSpatial(spatial)
if (this.spatial != null)
_subscriptionToken = obs.subscribe(sink)
else {
_subscriptionToken.cancel()
_subscriptionToken = null
}
}
}
class FollowControl(playerNode: Node) extends AbstractControl {
override def controlUpdate(tpf: Float): Unit =
this.spatial.setLocalTranslation(playerNode.getLocalTranslation())
override def controlRender(
rm: RenderManager,
vp: ViewPort
): Unit = {}
}

View File

@ -1,138 +0,0 @@
package wow.doge.mygame.game.entities
import akka.actor.typed.ActorRef
import akka.actor.typed.Behavior
import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.scaladsl.Behaviors
import cats.Show
import cats.kernel.Eq
import io.estatico.newtype.macros.newtype
final case class CharacterStats(
hp: CharacterStats.Health,
stamina: CharacterStats.Stamina
)
object CharacterStats {
@newtype final case class HealHealth(toInt: Int)
@newtype final case class DamageHealth(toInt: Int)
@newtype final case class Health(toInt: Int)
object Health {
implicit class HealthOps(private val h: Health) extends AnyVal {
def :+(v: HealHealth): Health = Health(h.toInt + v.toInt)
def -(v: DamageHealth): Health = Health(h.toInt - v.toInt)
}
}
@newtype final case class HealStamina(toInt: Int)
@newtype final case class DamageStamina(toInt: Int)
@newtype final case class Stamina(toInt: Int)
object Stamina {
implicit class StaminaOps(private val h: Stamina) extends AnyVal {
def :+(v: HealStamina): Stamina = Stamina(h.toInt + v.toInt)
def -(v: DamageStamina): Stamina = Stamina(h.toInt - v.toInt)
}
}
implicit val show = Show.fromToString[CharacterStats]
implicit val eq = Eq.fromUniversalEquals[CharacterStats]
}
object StatsActor {
import CharacterStats._
sealed trait Status
object Status {
case object Alive extends Status
case object Dead extends Status
}
sealed trait Command
final case class TakeDamageResult(
value: CharacterStats.DamageHealth,
replyTo: ActorRef[(Status, CharacterStats)]
) extends Command
final case class ConsumeStaminaResult(
value: CharacterStats.DamageStamina,
replyTo: ActorRef[(Status, CharacterStats)]
) extends Command
final case class HealHealthResult(
value: HealHealth,
replyTo: ActorRef[CharacterStats]
) extends Command
final case class HealStaminaResult(
value: HealStamina,
replyTo: ActorRef[CharacterStats]
) extends Command
final case class CurrentStats(replyTo: ActorRef[CharacterStats])
extends Command
class Props(
startingHealth: CharacterStats.Health,
startingStamina: CharacterStats.Stamina
) {
def behavior =
Behaviors.setup[Command] { ctx =>
new StatsActor(ctx, this)
.receive(
State(CharacterStats(startingHealth, startingStamina))
)
}
}
final case class State(stats: CharacterStats)
}
class StatsActor(
ctx: ActorContext[StatsActor.Command],
props: StatsActor.Props
) {
import StatsActor._
import CharacterStats._
import com.softwaremill.quicklens._
def receive(state: State): Behavior[Command] =
Behaviors.receiveMessage[Command] {
// Todo add min max values
// case TakeDamage(value) =>
// val nextState =
// if (state.stats.hp - value <= 0)
// state.modify(_.stats.hp).setTo(0)
// else
// state.modify(_.stats.hp).using(_ - value)
// receive(nextState)
case TakeDamageResult(value, replyTo) =>
val nextState = if ((state.stats.hp - value).toInt <= 0) {
val s = state.modify(_.stats.hp).setTo(Health(0))
replyTo ! Status.Dead -> s.stats
s
} else {
val s = state.modify(_.stats.hp).using(_ - value)
replyTo ! Status.Alive -> s.stats
s
}
receive(nextState)
case ConsumeStaminaResult(value, replyTo) =>
val nextState = if ((state.stats.stamina - value).toInt <= 0) {
val s = state.modify(_.stats.stamina).setTo(Stamina(0))
replyTo ! Status.Alive -> s.stats
s
} else {
val s = state.modify(_.stats.stamina).using(_ - value)
replyTo ! Status.Alive -> s.stats
s
}
receive(nextState)
case HealHealthResult(value, replyTo) =>
val nextState = receive(state.modify(_.stats.hp).using(_ :+ value))
replyTo ! state.stats
nextState
case HealStaminaResult(value, replyTo) =>
val nextState = receive(state.modify(_.stats.stamina).using(_ :+ value))
replyTo ! state.stats
nextState
case CurrentStats(replyTo) =>
replyTo ! state.stats
Behaviors.same
}
}

View File

@ -6,20 +6,28 @@ import scala.util.Success
import akka.actor.typed.ActorRef import akka.actor.typed.ActorRef
import akka.actor.typed.Behavior import akka.actor.typed.Behavior
import akka.actor.typed.SupervisorStrategy
import akka.actor.typed.scaladsl.ActorContext import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.scaladsl.Behaviors
import akka.util.Timeout import akka.util.Timeout
import cats.syntax.show._
import monix.execution.CancelableFuture import monix.execution.CancelableFuture
import monix.execution.CancelablePromise import monix.execution.CancelablePromise
import wow.doge.mygame.game.subsystems.movement.CanMove import wow.doge.mygame.game.subsystems.movement.CanMove
import wow.doge.mygame.implicits._ import wow.doge.mygame.implicits._
import wow.doge.mygame.math.ImVector3f import wow.doge.mygame.math.ImVector3f
import wow.doge.mygame.utils.GenericTimerActor import wow.doge.mygame.subsystems.events.EntityMovementEvent
import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedDown
import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedLeft
import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedRight
import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedUp
import wow.doge.mygame.subsystems.events.Event
import wow.doge.mygame.subsystems.events.EventBus
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
import wow.doge.mygame.subsystems.events.TickEvent
import wow.doge.mygame.subsystems.events.TickEvent.RenderTick
import wow.doge.mygame.subsystems.movement.ImMovementActor
object NpcActorSupervisor { object NpcActorSupervisor {
type Ref = ActorRef[Command]
sealed trait Command sealed trait Command
final case class Move(pos: ImVector3f) extends Command final case class Move(pos: ImVector3f) extends Command
private final case class InternalMove( private final case class InternalMove(
@ -27,28 +35,28 @@ object NpcActorSupervisor {
signal: CancelableFuture[NpcMovementActor.DoneMoving.type] signal: CancelableFuture[NpcMovementActor.DoneMoving.type]
) extends Command ) extends Command
private case object DoneMoving extends Command private case object DoneMoving extends Command
private final case class LogError(err: Throwable) extends Command // private case class MovementResponse(response: CancelableFuture[_]) extends Command
private final case class MovementFailed(err: Throwable) extends Command private case class LogError(err: Throwable) extends Command
private case object NoOp extends Command
class Props( final case class Props(
val npcMovementActorBehavior: Behavior[NpcMovementActor.Command], npcMovementActorBehavior: Behavior[NpcMovementActor.Command],
val npcName: String, npcName: String,
val initialPos: ImVector3f initialPos: ImVector3f
) { ) {
def behavior = def behavior =
Behaviors.withMdc[Command](Map("actorName" -> npcName))( Behaviors.setup[Command] { ctx =>
Behaviors.setup[Command] { ctx => val npcMovementActor = ctx.spawn(
val npcMovementActor = ctx.spawn( (npcMovementActorBehavior),
(npcMovementActorBehavior), s"npc-${npcName}-NpcMovementActor"
s"npc-${npcName}-NpcMovementActor" )
)
new NpcActorSupervisor(ctx, this, Children(npcMovementActor)) new NpcActorSupervisor(ctx, this, Children(npcMovementActor))
.idle(State()) .idle(State())
} }
)
} }
final case class State() final case class State(
)
final case class Children( final case class Children(
npcMovementActor: ActorRef[NpcMovementActor.Command] npcMovementActor: ActorRef[NpcMovementActor.Command]
) )
@ -61,7 +69,7 @@ class NpcActorSupervisor(
import NpcActorSupervisor._ import NpcActorSupervisor._
implicit val timeout = Timeout(1.second) implicit val timeout = Timeout(1.second)
val movementTimer = ctx.spawn( private val movementTimer = ctx.spawn(
GenericTimerActor GenericTimerActor
.Props( .Props(
children.npcMovementActor, children.npcMovementActor,
@ -69,12 +77,12 @@ class NpcActorSupervisor(
100.millis 100.millis
) )
.behavior, .behavior,
s"npc-${props.npcName}-NpcActorTimer" s"npc-John-NpcActorTimer"
) )
def idle(state: State): Behavior[Command] = def idle(state: State): Behavior[NpcActorSupervisor.Command] =
Behaviors.setup { _ => Behaviors.setup { _ =>
ctx.log.debugP(show"npcActor-${props.npcName}: Entered Idle State") ctx.log.debug("Inside Idle State")
Behaviors.receiveMessage[Command] { Behaviors.receiveMessage[Command] {
case m @ Move(pos) => case m @ Move(pos) =>
ctx.ask( ctx.ask(
@ -89,7 +97,7 @@ class NpcActorSupervisor(
moving(state, move.pos, signal) moving(state, move.pos, signal)
case LogError(err) => case LogError(err) =>
logError(err) ctx.log.warn(err.getMessage())
Behaviors.same Behaviors.same
case _ => Behaviors.unhandled case _ => Behaviors.unhandled
} }
@ -99,46 +107,45 @@ class NpcActorSupervisor(
state: State, state: State,
targetPos: ImVector3f, targetPos: ImVector3f,
signal: CancelableFuture[NpcMovementActor.DoneMoving.type] signal: CancelableFuture[NpcMovementActor.DoneMoving.type]
): Behavior[Command] = ): Behavior[NpcActorSupervisor.Command] =
Behaviors.setup { _ => Behaviors.setup { _ =>
ctx.log.debugP(show"npcActor-${props.npcName}: Entered Moving State")
movementTimer ! GenericTimerActor.Start movementTimer ! GenericTimerActor.Start
// ctx
// .ask(state.npcMovementActor, NpcMovementActor2.MoveTo(targetPos, _))(
// _.fold(LogError(_), MovementResponse(_))
// )
ctx.pipeToSelf(signal) { ctx.pipeToSelf(signal) {
case Success(value) => DoneMoving case Success(value) => DoneMoving
case Failure(exception) => MovementFailed(exception) case Failure(exception) => LogError(exception)
} }
Behaviors.receiveMessagePartial[Command] { Behaviors.receiveMessagePartial[Command] {
case LogError(err) => case LogError(err) =>
logError(err) ctx.log.error(err.getMessage())
Behaviors.same Behaviors.same
case MovementFailed(err) =>
ctx.self ! LogError(err)
movementTimer ! GenericTimerActor.Stop
idle(state)
case m @ Move(pos) => case m @ Move(pos) =>
movementTimer ! GenericTimerActor.Stop movementTimer ! GenericTimerActor.Stop
children.npcMovementActor ! NpcMovementActor.StopMoving children.npcMovementActor ! NpcMovementActor.StopMoving
// new movement request received, cancel previous request
signal.cancel() signal.cancel()
ctx.ask( ctx.ask(
children.npcMovementActor, children.npcMovementActor,
NpcMovementActor.MoveTo(pos, _) NpcMovementActor.MoveTo(pos, _)
) { ) {
case Success(signal) => InternalMove(m, signal) case Success(signal) => InternalMove(m, signal)
case Failure(exception) => MovementFailed(exception) case Failure(exception) => LogError(exception)
} }
Behaviors.same Behaviors.same
case InternalMove(move, signal) => case InternalMove(move, signal) =>
moving(state, targetPos, signal) moving(state, targetPos, signal)
case NoOp => Behaviors.same
// case MovementResponse(x: CancelableFuture[_]) =>
// // ctx.pipeToSelf(x)(_.)
case DoneMoving => case DoneMoving =>
movementTimer ! GenericTimerActor.Stop movementTimer ! GenericTimerActor.Stop
idle(state) idle(state)
} }
} }
def logError(err: Throwable) =
ctx.log.errorP(show"npcActor-${props.npcName}: " + err.getMessage)
} }
object NpcMovementActor { object NpcMovementActor {
@ -146,19 +153,18 @@ object NpcMovementActor {
case object DoneMoving case object DoneMoving
sealed trait Command sealed trait Command
final case class AskPosition(replyTo: ActorRef[ImVector3f]) extends Command case class AskPosition(replyTo: ActorRef[ImVector3f]) extends Command
case object MovementTick extends Command case object MovementTick extends Command
case object StopMoving extends Command case object StopMoving extends Command
final case class MoveTo( case class MoveTo(
target: ImVector3f, target: ImVector3f,
doneSignal: ActorRef[CancelableFuture[DoneMoving.type]] doneSignal: ActorRef[CancelableFuture[DoneMoving.type]]
) extends Command ) extends Command
class Props[T: CanMove]( final class Props[T: CanMove](
val enqueueR: Function1[() => Unit, Unit], val enqueueR: Function1[() => Unit, Unit],
val initialPos: ImVector3f, val initialPos: ImVector3f,
// val tickEventBus: GameEventBus[TickEvent], // val tickEventBus: GameEventBus[TickEvent],
val npcName: String,
val movable: T val movable: T
) { ) {
def behavior = def behavior =
@ -177,19 +183,28 @@ class NpcMovementActor[T](
def location = cm.location(props.movable) def location = cm.location(props.movable)
def receive(state: State): Behavior[NpcMovementActor.Command] = def receive(
state: State
): Behavior[NpcMovementActor.Command] =
Behaviors.receiveMessagePartial { Behaviors.receiveMessagePartial {
case AskPosition(replyTo) => case AskPosition(replyTo) =>
replyTo ! location replyTo ! location
Behaviors.same Behaviors.same
case StopMoving =>
ctx.log.debug(
"Position at Stop = " + location.toString
)
props.enqueueR(() => cm.stop(props.movable))
receive(state)
case MoveTo( case MoveTo(
target: ImVector3f, target: ImVector3f,
replyTo: ActorRef[CancelableFuture[DoneMoving.type]] replyTo: ActorRef[CancelableFuture[DoneMoving.type]]
) => ) =>
props.enqueueR(() => cm.move(props.movable, target - location, 20f)) props.enqueueR(() => cm.move(props.movable, target - location))
val p = CancelablePromise[DoneMoving.type]() val p = CancelablePromise[DoneMoving.type]()
replyTo ! p.future replyTo ! p.future
ticking(p, target, state) ticking(p, target, state)
} }
def ticking( def ticking(
@ -199,27 +214,91 @@ class NpcMovementActor[T](
): Behavior[NpcMovementActor.Command] = ): Behavior[NpcMovementActor.Command] =
Behaviors.receiveMessagePartial { Behaviors.receiveMessagePartial {
case StopMoving => case StopMoving =>
ctx.log.debugP( ctx.log.debug(
show"npcActor-${props.npcName}: Position at Stop = $location" "Position at Stop = " + location.toString
) )
props.enqueueR(() => cm.stop(props.movable)) props.enqueueR(() => cm.stop(props.movable))
receive(state) receive(state)
case MovementTick => case MovementTick =>
val dst = ImVector3f.manhattanDst(targetPos, location) val dst = ImVector3f.dst(targetPos, location)
if (dst <= 10f) { if (dst <= 10f) {
ctx.self ! StopMoving ctx.self ! StopMoving
reachDestination.success(DoneMoving) reachDestination.success(DoneMoving)
receive(state)
} else { } else {
// format:off ctx.log.trace("Difference = " + dst.toString())
ctx.log.traceP( ctx.log.trace("Current pos = " + location.toString())
show"npcActor-${props.npcName}: Difference = ${dst.formatted("%.2f")}" Behaviors.same
)
ctx.log.traceP(
show"npcActor-${props.npcName}: Current pos = $location"
)
} }
Behaviors.same
} }
} }
object NpcMovementActorNotUsed {
sealed trait Command
final case class Props(
imMovementActorBehavior: Behavior[ImMovementActor.Command],
npcName: String,
// movementActor: ActorRef[ImMovementActor.Command],
mainEventBus: ActorRef[
EventBus.Command[Event]
],
tickEventBus: GameEventBus[TickEvent]
) {
def behavior: Behavior[Command] =
Behaviors.setup { ctx =>
val movementActor = ctx.spawn(
Behaviors
.supervise(imMovementActorBehavior)
.onFailure[Exception](SupervisorStrategy.restart),
s"npc-${npcName}-MovementActorChild"
)
val npcMovementElBehavior = Behaviors.receiveMessagePartial[Event] {
case event: EntityMovementEvent =>
event match {
case MovedLeft(name, pressed) =>
if (name == npcName)
movementActor ! ImMovementActor.MovedLeft(pressed)
Behaviors.same
case MovedUp(name, pressed) =>
if (name == npcName)
movementActor ! ImMovementActor.MovedUp(pressed)
Behaviors.same
case MovedRight(name, pressed) =>
if (name == npcName)
movementActor ! ImMovementActor.MovedRight(pressed)
Behaviors.same
case MovedDown(name, pressed) =>
if (name == npcName)
movementActor ! ImMovementActor.MovedDown(pressed)
Behaviors.same
}
}
val npcMovementEl = ctx.spawn(
Behaviors
.supervise(npcMovementElBehavior)
.onFailure[Exception](SupervisorStrategy.restart),
s"npc-${npcName}-MovementEventHandler"
)
val renderTickElBehavior =
Behaviors.receiveMessage[RenderTick.type] {
case RenderTick =>
movementActor ! ImMovementActor.Tick
Behaviors.same
}
val renderTickEl =
ctx.spawn(
renderTickElBehavior,
s"npc-${npcName}-MovementTickListener"
)
mainEventBus ! EventBus.Subscribe(npcMovementEl)
tickEventBus ! EventBus.Subscribe(renderTickEl)
Behaviors.receiveMessage { msg => Behaviors.same }
}
}
}

View File

@ -1,31 +0,0 @@
package wow.doge.mygame.game.entities.character
import cats.Show
import cats.kernel.Eq
object CharacterStates {
sealed trait AliveSubstate
object AliveSubstate {
final case class InCombat(substate: CombatSubstate) extends AliveSubstate
final case class Moving(substate: MovementSubstate) extends AliveSubstate
case object Idle extends AliveSubstate
implicit val eq = Eq.fromUniversalEquals[AliveSubstate]
implicit val show = Show.fromToString[AliveSubstate]
}
sealed trait CombatSubstate
object CombatSubstate {
final case class Moving(substate: MovementSubstate) extends CombatSubstate
final case class Attacking(victimName: String) extends CombatSubstate
}
sealed trait MovementSubstate
case object Walking extends MovementSubstate
case object Running extends MovementSubstate
sealed trait DeadSubstate
object DeadSubstate {
implicit val eq = Eq.fromUniversalEquals[DeadSubstate]
implicit val show = Show.fromToString[DeadSubstate]
}
}

View File

@ -1,250 +0,0 @@
package wow.doge.mygame.game.entities.player
import scala.concurrent.duration._
import akka.actor.typed.ActorRef
import akka.actor.typed.Behavior
import akka.actor.typed.LogOptions
import akka.actor.typed.PostStop
import akka.actor.typed.SupervisorStrategy
import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.scaladsl.Behaviors
import akka.util.Timeout
import com.typesafe.scalalogging.Logger
import monix.bio.UIO
import monix.execution.AsyncQueue
import monix.reactive.Observable
import org.slf4j.event.Level
import wow.doge.mygame.Dispatchers
import wow.doge.mygame.executors.Schedulers
import wow.doge.mygame.executors.Schedulers.AsyncScheduler
import wow.doge.mygame.game.entities.CharacterStats
import wow.doge.mygame.game.entities.StatsActor
import wow.doge.mygame.game.entities.character.CharacterStates._
import wow.doge.mygame.game.entities.player.behaviors.IdleBehaviorFactory
import wow.doge.mygame.implicits._
import wow.doge.mygame.subsystems.events.EventBus
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
import wow.doge.mygame.subsystems.events.PlayerEvent
import wow.doge.mygame.subsystems.events.TickEvent
import wow.doge.mygame.subsystems.events.TickEvent.RenderTick
import wow.doge.mygame.subsystems.movement.ImMovementActor
import wow.doge.mygame.utils.MovementDirection
object PlayerActor {
type Ref = ActorRef[PlayerActor.Command]
sealed trait Status
object Status {
case object Alive extends Status
case object Dead extends Status
}
sealed trait Command extends Product with Serializable
final case class TakeDamage(
value: CharacterStats.DamageHealth,
replyTo: ActorRef[CharacterStats]
) extends Command
final case class ConsumeStamina(
value: CharacterStats.DamageStamina,
replyTo: ActorRef[CharacterStats]
) extends Command
final case class HealHealth(
value: CharacterStats.HealHealth,
replyTo: ActorRef[CharacterStats]
) extends Command
final case class HealStamina(
value: CharacterStats.HealStamina,
replyTo: ActorRef[CharacterStats]
) extends Command
final case class CurrentStats(replyTo: ActorRef[CharacterStats])
extends Command
final case class GetStatus(replyTo: ActorRef[Status]) extends Command
final case class GetStatsObservable(
replyTo: ActorRef[UIO[Observable[CharacterStats]]]
) extends Command
final case class Walk(pressed: Boolean, dir: MovementDirection)
extends Command
case object StopMoving extends Command
case object Jump extends Command
private[player] final case class HandleWalk(
b: CharacterStats,
pressed: Boolean,
direction: MovementDirection
) extends Command
private[player] case object Die extends Command
private[player] final case class StatsResponse(
response: (StatsActor.Status, CharacterStats),
replyTo: ActorRef[CharacterStats]
) extends Command
private[player] final case class LogError(ex: Throwable) extends Command
class Props(
val playerEventBus: GameEventBus[PlayerEvent],
val tickEventBus: GameEventBus[TickEvent],
val imMovementActorBehavior: Behavior[ImMovementActor.Command],
val statsActorBehavior: Behavior[StatsActor.Command],
val scheduler: AsyncScheduler,
val fxScheduler: Schedulers.FxScheduler,
val statsQueue: AsyncQueue[CharacterStats]
) {
def behavior =
Behaviors.logMessages(
LogOptions()
.withLevel(Level.DEBUG)
.withLogger(Logger[PlayerActor].underlying),
Behaviors
.setup[Command] { ctx =>
ctx.log.infoP("Starting PlayerActor")
// spawn children actors
val playerStatsActor = ctx.spawnN(statsActorBehavior)
val playerMovementActor =
ctx.spawnN(
Behaviors
.supervise(imMovementActorBehavior)
.onFailure[Exception](
SupervisorStrategy.restart.withLimit(2, 100.millis)
),
Dispatchers.jmeDispatcher
)
// val playerMovementEl = ctx.spawnN(
// Behaviors
// .supervise(
// new PlayerMovementEventListener.Props(
// ctx.self,
// scheduler
// ).behavior
// )
// .onFailure[Exception](
// SupervisorStrategy.restart.withLimit(2, 100.millis)
// )
// )
val renderTickEl = {
val behavior: Behavior[RenderTick.type] =
Behaviors.setup(ctx =>
Behaviors
.receiveMessage[RenderTick.type] {
case RenderTick =>
playerMovementActor ! ImMovementActor.Tick
// playerCameraActor ! PlayerCameraActor.Tick
Behaviors.same
}
.receiveSignal {
case (_, PostStop) =>
ctx.log.infoP("stopped")
Behaviors.same
}
)
ctx.spawn(behavior, "playerMovementTickListener")
}
//init listeners
// playerEventBus ! EventBus.Subscribe(playerMovementEl)
tickEventBus ! EventBus.Subscribe(renderTickEl)
new PlayerActor(
ctx,
this,
Children(playerMovementActor, playerStatsActor)
).aliveState(AliveSubstate.Idle)
}
)
}
final case class Children(
movementActor: ActorRef[ImMovementActor.Command],
statsActor: ActorRef[StatsActor.Command]
)
final case class Env(
ctx: ActorContext[PlayerActor.Command],
props: PlayerActor.Props,
children: PlayerActor.Children
)
}
class PlayerActor(
ctx: ActorContext[PlayerActor.Command],
props: PlayerActor.Props,
children: PlayerActor.Children
) {
import PlayerActor._
implicit val timeout = Timeout(1.second)
val env = Env(ctx, props, children)
val deadState = Behaviors
.receiveMessage[Command] {
// case TakeDamage(value) =>
// children.statsActor ! StatsActor.TakeDamage(value)
// // children.movementActor ! ImMovementActor.MovedDown(true)
// Behaviors.same
// case CurrentStats(replyTo) =>
// // ctx.ask(children.statsActor, StatsActor.CurrentStats())
// children.statsActor ! StatsActor.CurrentStats(replyTo)
// Behaviors.same
// case Heal(_) =>
// Behaviors.same
case CurrentStats(replyTo) =>
// ctx.ask(children.statsActor, StatsActor.CurrentStats())
children.statsActor ! StatsActor.CurrentStats(replyTo)
Behaviors.same
case GetStatus(replyTo) =>
replyTo ! Status.Dead
Behaviors.same
case _ => Behaviors.unhandled
}
.receiveSignal {
case (_, PostStop) =>
ctx.log.infoP("stopped")
Behaviors.same
}
def idleBehavior(
nextStateFn: AliveSubstate => Behavior[Command],
consumptionMultiplier: Int => Int
) =
new IdleBehaviorFactory(
env,
nextStateFn,
deadState,
consumptionMultiplier
).behavior
def aliveState(substate: AliveSubstate): Behavior[Command] =
substate match {
case AliveSubstate.InCombat(substate) =>
substate match {
case CombatSubstate.Moving(substate) =>
substate match {
case Walking =>
Behaviors.receiveMessage[Command](_ => Behaviors.same)
case Running =>
Behaviors.receiveMessage[Command](_ => Behaviors.same)
}
case CombatSubstate.Attacking(victimName) =>
Behaviors.receiveMessage[Command](_ => Behaviors.same)
}
case AliveSubstate.Moving(Walking) =>
ctx.log.debugP("In Walking State")
idleBehavior(aliveState, _ * 2).value
case AliveSubstate.Moving(Running) =>
idleBehavior(aliveState, _ * 3).value
case AliveSubstate.Idle =>
ctx.log.debugP("In Idle State")
idleBehavior(aliveState, identity).value
}
}

View File

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

View File

@ -37,7 +37,7 @@ object PlayerCameraActor {
) )
} }
final case class State() case class State()
object State { object State {
val empty = State() val empty = State()
} }
@ -55,27 +55,28 @@ class PlayerCameraActor(
case RotateLeft => case RotateLeft =>
val rot = rotationBuf val rot = rotationBuf
.fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y) .fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y)
props.cameraPivotNode.rotate(rot) props.enqueueR(() => props.cameraPivotNode.rotate(rot))
Behaviors.same Behaviors.same
case RotateRight => case RotateRight =>
val rot = rotationBuf val rot = rotationBuf
.fromAngleAxis(-1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y) .fromAngleAxis(-1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y)
props.cameraPivotNode.rotate(rot) props.enqueueR(() => props.cameraPivotNode.rotate(rot))
Behaviors.same Behaviors.same
case RotateUp => case RotateUp =>
val rot = rotationBuf val rot = rotationBuf
.fromAngleAxis(-1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X) .fromAngleAxis(-1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X)
props.cameraPivotNode.rotate(rot) props.enqueueR(() => props.cameraPivotNode.rotate(rot))
Behaviors.same Behaviors.same
case RotateDown => case RotateDown =>
val rot = rotationBuf val rot = rotationBuf
.fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X) .fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X)
props.cameraPivotNode.rotate(rot) props.enqueueR(() => props.cameraPivotNode.rotate(rot))
Behaviors.same Behaviors.same
case Tick => case Tick =>
val location = props.getPlayerLocation() props.enqueueR(() => {
props.cameraPivotNode.setLocalTranslation(location) val location = props.getPlayerLocation()
props.cameraPivotNode.setLocalTranslation(location)
})
Behaviors.same Behaviors.same
} }
} }

View File

@ -1,110 +1,129 @@
package wow.doge.mygame.game.entities.player package wow.doge.mygame.game.entities
import akka.actor.typed.ActorRef import akka.actor.typed.ActorRef
import akka.actor.typed.DispatcherSelector import akka.actor.typed.Scheduler
import akka.actor.typed.SpawnProtocol
import akka.util.Timeout
import cats.implicits._
import com.jme3.asset.AssetManager
import com.jme3.bullet.BulletAppState
import com.jme3.bullet.PhysicsSpace
import com.jme3.bullet.control.BetterCharacterControl import com.jme3.bullet.control.BetterCharacterControl
import com.jme3.math.FastMath
import com.jme3.renderer.Camera import com.jme3.renderer.Camera
import com.jme3.scene.CameraNode import com.jme3.scene.CameraNode
import com.jme3.scene.Geometry import com.jme3.scene.Geometry
import com.jme3.scene.Node import com.jme3.scene.Node
import com.jme3.scene.Spatial
import com.jme3.scene.shape.Box import com.jme3.scene.shape.Box
import com.softwaremill.tagging._ import com.softwaremill.tagging._
import io.odin.Logger import io.odin.Logger
import monix.bio.IO import monix.bio.IO
import monix.bio.Task import monix.bio.Task
import monix.execution.AsyncQueue import wow.doge.mygame.game.GameAppTags
import wow.doge.mygame.AppError import wow.doge.mygame.game.SimpleAppExt
import wow.doge.mygame.executors.Schedulers
import wow.doge.mygame.executors.Schedulers.AsyncScheduler
import wow.doge.mygame.game.GameApp
import wow.doge.mygame.game.entities.CharacterStats
import wow.doge.mygame.game.entities.StatsActor
import wow.doge.mygame.implicits._ import wow.doge.mygame.implicits._
import wow.doge.mygame.math.ImVector3f import wow.doge.mygame.math.ImVector3f
import wow.doge.mygame.state.MyMaterial
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
import wow.doge.mygame.subsystems.events.PlayerEvent import wow.doge.mygame.subsystems.events.PlayerEvent
import wow.doge.mygame.subsystems.events.TickEvent import wow.doge.mygame.subsystems.events.TickEvent
import wow.doge.mygame.subsystems.movement.ImMovementActor import wow.doge.mygame.subsystems.movement.ImMovementActor
import wow.doge.mygame.types._ import wow.doge.mygame.utils.AkkaUtils
import wow.doge.mygame.utils.wrappers.jme._
object PlayerControllerTags {
sealed trait PlayerTag
sealed trait PlayerCameraNode
sealed trait PlayerCameraPivotNode
}
object PlayerController { object PlayerController {
sealed trait Error sealed trait Error
final case class CouldNotCreatePlayerModel(reason: AssetManager.Error) case class CouldNotCreatePlayerNode(reason: String) extends Error
extends Error case class GenericError(reason: String) extends Error
object Tags {
sealed trait PlayerNode
sealed trait PlayerCameraNode
sealed trait PlayerCameraPivotNode
}
type PlayerNode = Node @@ Tags.PlayerNode
type PlayerCameraNode = CameraNode @@ Tags.PlayerCameraNode
type PlayerCameraPivotNode =
Node @@ Tags.PlayerCameraPivotNode
class Props( class Props(
gameApp: GameApp,
enqueueR: Function1[() => Unit, Unit], enqueueR: Function1[() => Unit, Unit],
rootNode: RootNode, rootNode: Node @@ GameAppTags.RootNode,
loggerL: Logger[Task], loggerL: Logger[Task],
physicsSpace: PhysicsSpace, physicsSpace: PhysicsSpace,
initialPlayerPos: ImVector3f, initialPlayerPos: ImVector3f = ImVector3f.ZERO,
spawnProtocol: ActorRef[SpawnProtocol.Command],
playerEventBus: GameEventBus[PlayerEvent], playerEventBus: GameEventBus[PlayerEvent],
playerPhysicsControl: BetterCharacterControl, playerPhysicsControl: BetterCharacterControl,
scheduler: AsyncScheduler, appScheduler: monix.execution.Scheduler,
fxScheduler: Schedulers.FxScheduler, playerNode: Node @@ PlayerControllerTags.PlayerTag,
playerNode: PlayerNode, cameraNode: CameraNode @@ PlayerControllerTags.PlayerCameraNode,
cameraNode: PlayerCameraNode, cameraPivotNode: Node @@ PlayerControllerTags.PlayerCameraPivotNode,
cameraPivotNode: PlayerCameraPivotNode,
tickEventBus: GameEventBus[TickEvent], tickEventBus: GameEventBus[TickEvent],
camera: Camera camera: Camera
) { )(implicit timeout: Timeout, scheduler: Scheduler) {
val playerActorBehavior = { val create: IO[Error, Unit] =
val movementActorBeh = new ImMovementActor.Props(
enqueueR,
camera
).behavior(playerPhysicsControl)
val statsActorBeh = new StatsActor.Props(
CharacterStats.Health(100),
CharacterStats.Stamina(100)
).behavior
new PlayerActor.Props(
playerEventBus,
tickEventBus,
movementActorBeh,
statsActorBeh,
scheduler,
fxScheduler,
AsyncQueue.bounded(50)(scheduler.value)
).behavior
}
val playerActor =
gameApp.spawnGameActor(
playerActorBehavior,
props = DispatcherSelector.default()
)
val create: IO[AppError, ActorRef[PlayerActor.Command]] =
(for { (for {
// playerActor <- playerActor <- AkkaUtils.spawnActorL(
// // AkkaUtils.spawnActorL(playerActorBehavior, "playerActorSupervisor") spawnProtocol,
// gameApp.spawnGameActor( "playerActorSupervisor",
// playerActorBehavior, new PlayerActorSupervisor.Props(
// Some("playerActorSupervisor"), playerEventBus,
// props = DispatcherSelector.default() // playerCameraEventBus,
// ) tickEventBus,
pa <- playerActor new ImMovementActor.Props(
_ <- (for { enqueueR,
_ <- rootNode += playerNode playerPhysicsControl,
_ <- physicsSpace += playerNode camera
_ <- physicsSpace += playerPhysicsControl ).behavior,
_ = cameraPivotNode += cameraNode // wireWith(PlayerCameraEventListener.apply _)
_ <- rootNode += cameraPivotNode // PlayerCameraEventListener()
} yield ()).mapError(AppError.AppNodeError) new PlayerCameraActor.Props(
} yield pa) cameraPivotNode,
enqueueR,
playerNode.getWorldTranslation _
).behavior
).behavior(playerPhysicsControl)
)
_ <- Task(rootNode += playerNode)
_ <- IO {
physicsSpace += playerNode
physicsSpace += playerPhysicsControl
// rootNode += cameraNode
cameraPivotNode += cameraNode
// playerNode += cameraPivotNode
rootNode += cameraPivotNode
}
} yield ())
.onErrorHandleWith(e => IO.raiseError(GenericError(e.getMessage())))
.executeOn(appScheduler)
}
def apply(
app: SimpleAppExt,
modelPath: os.RelPath,
cam: Camera
)(assetManager: AssetManager, bulletAppState: BulletAppState) = {
lazy val playerPos = ImVector3f.ZERO
lazy val playerPhysicsControl = new BetterCharacterControl(1.5f, 6f, 1f)
.withJumpForce(ImVector3f(0, 5f, 0))
lazy val playerNode = new Node("PlayerNode")
.withChildren(
assetManager
.loadModel(modelPath)
.asInstanceOf[Node]
.withRotate(0, FastMath.PI, 0)
)
.withLocalTranslation(playerPos)
.withControl(playerPhysicsControl)
{
bulletAppState.physicsSpace += playerNode
bulletAppState.physicsSpace += playerPhysicsControl
}
{
app.rootNode += playerNode
}
playerPhysicsControl
} }
object Defaults { object Defaults {
@ -113,50 +132,106 @@ object PlayerController {
val geom = Geometry("playerGeom", b) val geom = Geometry("playerGeom", b)
geom geom
} }
def defaultTexture(assetManager: AssetManager) =
MyMaterial(
assetManager = assetManager,
path = os.rel / "Common" / "MatDefs" / "Misc" / "Unshaded.j3md"
)
// new CameraControl(cam) {
// override def controlUpdate(tpf: Float) = {
// this.getCamera().setRotation(spatial.getWorldRotation())
// cameraPivotNode.setLocalTranslation(
// playerNode.getWorldTranslation()
// )
// this.getCamera().setLocation(spatial.getWorldTranslation())
// }
// }
def defaultCamerNode( def defaultCamerNode(
cam: Camera, cam: Camera,
// playerNode: Node, playerNode: Node,
// cameraPivotNode: Node, cameraPivotNode: Node,
playerPos: ImVector3f playerPos: ImVector3f
) = ) =
new CameraNode("CameraNode", cam) new CameraNode(
"CameraNode",
cam
)
// .withControlDir(ControlDirection.SpatialToCamera) // .withControlDir(ControlDirection.SpatialToCamera)
.withLocalTranslation(ImVector3f(0, 1.5f, 10)) .withLocalTranslation(ImVector3f(0, 1.5f, 10))
.withLookAt(playerPos, ImVector3f.UnitY) .withLookAt(playerPos, ImVector3f.UNIT_Y)
def defaultPlayerNode( def defaultPlayerNode(
assetManager: AssetManager,
modelPath: os.RelPath,
playerPos: ImVector3f, playerPos: ImVector3f,
playerModel: Node,
// camNode: CameraNode, // camNode: CameraNode,
playerPhysicsControl: BetterCharacterControl playerPhysicsControl: BetterCharacterControl
) = ) =
Node("PlayerNode") Either
.withChildren(playerModel) .catchNonFatal(
.withLocalTranslation(playerPos) Node("PlayerNode")
.withControl(playerPhysicsControl) .withChildren(
.taggedWith[Tags.PlayerNode] // camNode,
assetManager
.loadModel(modelPath)
.asInstanceOf[Node]
.withRotate(0, FastMath.PI, 0)
)
.withLocalTranslation(playerPos)
.withControl(playerPhysicsControl)
)
.map(_.taggedWith[PlayerControllerTags.PlayerTag])
.leftMap(e => PlayerController.CouldNotCreatePlayerNode(e.getMessage()))
def defaultNpcNode( def defaultNpcNode(
// assetManager: AssetManager, assetManager: AssetManager,
npcModel: Spatial, // modelPath: os.RelPath,
initialPos: ImVector3f, initialPos: ImVector3f,
npcPhysicsControl: BetterCharacterControl, npcPhysicsControl: BetterCharacterControl,
npcName: String npcName: String
) = ) =
// Either Either
// .catchNonFatal( .catchNonFatal(
Node(npcName) Node(npcName)
.withChildren( .withChildren(
// defaultMesh.withMaterial(defaultTexture(assetManager)) // assetManager
npcModel // .loadModel(modelPath)
// .asInstanceOf[Node]
// .withRotate(0, FastMath.PI, 0)
defaultMesh.withMaterial(defaultTexture(assetManager))
)
.withLocalTranslation(initialPos)
.withControl(npcPhysicsControl)
) )
.withLocalTranslation(initialPos) // .map(_.taggedWith[PlayerControllerTags.PlayerTag])
.withControl(npcPhysicsControl) // .leftMap(e => PlayerController.CouldNotCreatePlayerNode(e.getMessage()))
// )
def defaultPlayerPhysicsControl = def defaultPlayerPhysicsControl =
new BetterCharacterControl(1.5f, 6f, 1f) new BetterCharacterControl(1.5f, 6f, 1f)
.withJumpForce(ImVector3f(0, 5f, 0)) .withJumpForce(ImVector3f(0, 5f, 0))
} }
} }
object Methods {}
// camNode <- IO(
// _cameraNode.getOrElse(defaultCamerNode(camera, initialPlayerPos))
// )
// playerPhysicsControl <- IO(
// _playerPhysicsControl
// .getOrElse(defaultPlayerPhysicsControl)
// .taggedWith[PlayerTag]
// )
// playerNode <- IO.fromEither(
// _playerNode.fold(
// defaultPlayerNode(
// assetManager,
// modelPath,
// initialPlayerPos,
// camNode,
// playerPhysicsControl
// )
// )(_.asRight)
// )

View File

@ -1,167 +1,41 @@
package wow.doge.mygame.game.entities.player package wow.doge.mygame.game.entities
import scala.concurrent.duration._ import akka.actor.typed.ActorRef
import akka.actor.typed.Behavior
import akka.actor.typed.LogOptions import akka.actor.typed.LogOptions
import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.scaladsl.AskPattern._
import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.scaladsl.Behaviors
import akka.util.Timeout
import cats.syntax.eq._
import com.typesafe.scalalogging.Logger import com.typesafe.scalalogging.Logger
import monix.eval.Task
import monix.execution.CancelableFuture
import monix.reactive.Observable
import org.slf4j.event.Level import org.slf4j.event.Level
import wow.doge.mygame.executors.Schedulers import wow.doge.mygame.subsystems.events.PlayerCameraEvent
import wow.doge.mygame.game.entities.CharacterStats
import wow.doge.mygame.implicits._
import wow.doge.mygame.subsystems.events.PlayerMovementEvent import wow.doge.mygame.subsystems.events.PlayerMovementEvent
import wow.doge.mygame.utils.MovementDirection import wow.doge.mygame.subsystems.movement.ImMovementActor
object PlayerMovementEventListener { object PlayerMovementEventListener {
final case class State(
keysPressed: Int,
staminaTimer: CancelableFuture[Unit],
staminaRegenTimer: CancelableFuture[Unit]
)
class Props(
val playerActor: PlayerActor.Ref,
val asyncScheduler: Schedulers.AsyncScheduler
) {
def behavior =
Behaviors.setup[PlayerMovementEvent] { ctx =>
new PlayerMovementEventListener(ctx, this)
.receive(State(0, CancelableFuture.unit, CancelableFuture.unit))
}
}
}
class PlayerMovementEventListener(
ctx: ActorContext[PlayerMovementEvent],
props: PlayerMovementEventListener.Props
) {
import PlayerMovementEventListener._
import PlayerMovementEvent._ import PlayerMovementEvent._
import com.softwaremill.quicklens._ def apply(
def receive(state: State): Behavior[PlayerMovementEvent] = movementActor: ActorRef[ImMovementActor.Command]
) =
Behaviors.logMessages( Behaviors.logMessages(
LogOptions() LogOptions()
.withLevel(Level.TRACE) .withLevel(Level.TRACE)
.withLogger( .withLogger(
Logger[PlayerMovementEventListener.type].underlying Logger[PlayerMovementEventListener.type].underlying
), ),
Behaviors.setup[PlayerMovementEvent] { ctx => Behaviors.setup[PlayerMovementEvent](ctx =>
implicit val timeout = Timeout(1.second)
implicit val sched = ctx.system.scheduler
val staminaTimer =
Task.deferAction(implicit s =>
Observable
.interval(250.millis)
.doOnNext(_ => Task(pprint.log("Sending Stamina Consume Item")))
.mapEvalF(_ =>
props.playerActor
.askL(
PlayerActor
.ConsumeStamina(CharacterStats.DamageStamina(25), _)
)
)
.doOnNext(stats =>
if (stats.stamina.toInt === 0)
Task(props.playerActor ! PlayerActor.StopMoving)
else Task.unit
)
.takeWhile(_.stamina.toInt >= 0)
.completedL
)
val staminaRegenTimer =
Task.deferAction(implicit s =>
Observable
.interval(500.millis)
.doOnNext(_ => Task(pprint.log("Sending Stamina Regen Item")))
.mapEvalF(_ =>
props.playerActor.askL(
PlayerActor
.HealStamina(CharacterStats.HealStamina(1), _)
)
)
.takeWhile(_.stamina.toInt =!= 100)
.delayExecution(1.second)
.completedL
)
def handleStamina(pressed: Boolean) = {
state.staminaRegenTimer.cancel()
if (pressed) {
val nextState1 =
if (state.keysPressed === 0)
state
.modify(_.staminaTimer)
.setTo(
staminaTimer.runToFuture(props.asyncScheduler.value)
)
else state
val nextState2 = nextState1
.modify(_.keysPressed)
.using(_ + 1)
nextState2
} else {
val nextState1 = state
.modify(_.keysPressed)
.using(_ - 1)
if (nextState1.keysPressed === 0) {
nextState1.staminaTimer.cancel()
nextState1
.modify(_.staminaRegenTimer)
.setTo(
staminaRegenTimer.runToFuture(props.asyncScheduler.value)
)
} else
nextState1
}
}
Behaviors.receiveMessage { Behaviors.receiveMessage {
case PlayerMovedLeft(pressed) => case PlayerMovedLeft(pressed) =>
// props.movementActor ! ImMovementActor.MoveLeft(pressed) movementActor ! ImMovementActor.MovedLeft(pressed)
// props.playerActor ! PlayerActor.MoveLeft(pressed) Behaviors.same
props.playerActor ! PlayerActor.Walk(
pressed,
MovementDirection.Left
)
receive(handleStamina(pressed))
case PlayerMovedRight(pressed) => case PlayerMovedRight(pressed) =>
// props.playerActor ! PlayerActor.MoveRight(pressed) movementActor ! ImMovementActor.MovedRight(pressed)
props.playerActor ! PlayerActor.Walk( Behaviors.same
pressed,
MovementDirection.Right
)
receive(handleStamina(pressed))
case PlayerMovedForward(pressed) => case PlayerMovedForward(pressed) =>
// props.playerActor ! PlayerActor.MoveUp(pressed) movementActor ! ImMovementActor.MovedUp(pressed)
props.playerActor ! PlayerActor.Walk( Behaviors.same
pressed,
MovementDirection.Forward
)
receive(handleStamina(pressed))
case PlayerMovedBackward(pressed) => case PlayerMovedBackward(pressed) =>
// props.playerActor ! PlayerActor.MoveDown(pressed) movementActor ! ImMovementActor.MovedDown(pressed)
props.playerActor ! PlayerActor.Walk( Behaviors.same
pressed,
MovementDirection.Backward
)
receive(handleStamina(pressed))
case PlayerJumped => case PlayerJumped =>
props.playerActor ! PlayerActor.Jump movementActor ! ImMovementActor.Jump
// props.playerActor
// ctx.ask(
// props.playerActor,
// PlayerActor
// .ConsumeStamina(CharacterStats.DamageStamina(1), _)
// )(_ => ())
Behaviors.same Behaviors.same
// case PlayerTurnedRight => // case PlayerTurnedRight =>
// movementActor ! ImMovementActor.RotateRight // movementActor ! ImMovementActor.RotateRight
@ -170,6 +44,37 @@ class PlayerMovementEventListener(
// movementActor ! ImMovementActor.RotateLeft // movementActor ! ImMovementActor.RotateLeft
// Behaviors.same // Behaviors.same
} }
} )
)
}
object PlayerCameraEventListener {
import PlayerCameraEvent._
def apply(
playerCameraActor: ActorRef[PlayerCameraActor.Command]
) =
Behaviors.logMessages(
LogOptions()
.withLevel(Level.TRACE)
.withLogger(
Logger[PlayerCameraEventListener.type].underlying
),
Behaviors.setup[PlayerCameraEvent](ctx =>
Behaviors.receiveMessagePartial {
case CameraMovedUp =>
playerCameraActor ! PlayerCameraActor.RotateUp
Behaviors.same
case CameraMovedDown =>
playerCameraActor ! PlayerCameraActor.RotateDown
Behaviors.same
case CameraLeft =>
playerCameraActor ! PlayerCameraActor.RotateLeft
Behaviors.same
case CameraRight =>
playerCameraActor ! PlayerCameraActor.RotateRight
Behaviors.same
}
)
) )
} }

View File

@ -1,139 +0,0 @@
package wow.doge.mygame.game.entities.player
import scala.concurrent.duration._
import akka.util.Timeout
import cats.syntax.eq._
import io.odin.Logger
import monix.eval.Fiber
import monix.eval.Task
import monix.reactive.Observable
import monix.{eval => me}
import wow.doge.mygame.EnumActionEvent
import wow.doge.mygame.game.entities.CharacterStats
import wow.doge.mygame.game.subsystems.input.PlayerMovementInput
import wow.doge.mygame.game.subsystems.input.PlayerMovementInput.Jump
import wow.doge.mygame.game.subsystems.input.PlayerMovementInput.WalkBackward
import wow.doge.mygame.game.subsystems.input.PlayerMovementInput.WalkForward
import wow.doge.mygame.game.subsystems.input.PlayerMovementInput.WalkLeft
import wow.doge.mygame.game.subsystems.input.PlayerMovementInput.WalkRight
import wow.doge.mygame.implicits._
import wow.doge.mygame.types.AkkaScheduler
import wow.doge.mygame.utils.MovementDirection
class PlayerMovementReducer(
val playerActor: PlayerActor.Ref,
logger: Logger[monix.bio.Task]
)(implicit
akkaSched: AkkaScheduler
) {
import PlayerMovementReducer._
import com.softwaremill.quicklens._
implicit val timeout = Timeout(1.second)
implicit val sched = akkaSched.value
def staminaTimer(multiplier: Int) =
Task.deferAction(implicit s =>
Observable
.interval(250.millis)
.doOnNextF(_ => logger.trace("Sending Stamina Consume Item"))
.mapEvalF(_ =>
playerActor
.askL(
PlayerActor
.ConsumeStamina(CharacterStats.DamageStamina(1 * multiplier), _)
)
)
.doOnNext(stats =>
if (stats.stamina.toInt === 0)
Task(playerActor ! PlayerActor.StopMoving)
else Task.unit
)
.takeWhile(_.stamina.toInt >= 0)
.completedL
)
def staminaRegenTimer(multiplier: Int) =
Task.deferAction(implicit s =>
Observable
.interval(500.millis)
.doOnNextF(_ => logger.trace("Sending Stamina Regen Item"))
.mapEvalF(_ =>
playerActor.askL(
PlayerActor
.HealStamina(CharacterStats.HealStamina(1 * multiplier), _)
)
)
.takeWhile(_.stamina.toInt =!= 100)
.delayExecution(1.second)
.completedL
)
def handleStamina(
state: State,
pressed: Boolean,
consumptionMultiplier: Int,
regenMultiplier: Int
): Task[State] =
state.staminaRegenTimer.cancel >>
(if (pressed) {
val nextState1 =
if (state.keysPressed === 0)
staminaTimer(consumptionMultiplier).start.map(
state.modify(_.staminaTimer).setTo
)
else Task.pure(state)
val nextState2 =
nextState1.map(_.modify(_.keysPressed).using(_ + 1))
nextState2
} else {
val nextState1 = state
.modify(_.keysPressed)
.using(_ - 1)
if (nextState1.keysPressed === 0)
nextState1.staminaTimer.cancel >>
staminaRegenTimer(regenMultiplier).start.map(
nextState1.modify(_.staminaRegenTimer).setTo
)
else
Task.pure(nextState1)
})
def value(
state: State,
action: EnumActionEvent[PlayerMovementInput]
): Task[State] =
action match {
case EnumActionEvent(WalkForward, pressed, tpf) =>
playerActor ! PlayerActor.Walk(pressed, MovementDirection.Forward)
handleStamina(state, pressed, 1, 1)
case EnumActionEvent(WalkRight, pressed, tpf) =>
playerActor ! PlayerActor.Walk(pressed, MovementDirection.Right)
handleStamina(state, pressed, 1, 1)
case EnumActionEvent(WalkLeft, pressed, tpf) =>
playerActor ! PlayerActor.Walk(pressed, MovementDirection.Left)
handleStamina(state, pressed, 1, 1)
case EnumActionEvent(WalkBackward, pressed, tpf) =>
playerActor ! PlayerActor.Walk(pressed, MovementDirection.Backward)
handleStamina(state, pressed, 1, 1)
case EnumActionEvent(Jump, pressed, tpf) =>
if (pressed) playerActor ! PlayerActor.Jump else ()
handleStamina(state, pressed, 10, 1)
}
}
object PlayerMovementReducer {
final case class State(
keysPressed: Int,
staminaTimer: Fiber[Unit],
staminaRegenTimer: Fiber[Unit]
)
object State {
val empty = State(
0,
me.Fiber(me.Task.unit, me.Task.unit),
me.Fiber(me.Task.unit, me.Task.unit)
)
}
}

View File

@ -1,182 +0,0 @@
package wow.doge.mygame.game.entities.player.behaviors
import scala.concurrent.Future
import akka.actor.typed.Behavior
import akka.actor.typed.PostStop
import akka.actor.typed.scaladsl.AskPattern._
import akka.actor.typed.scaladsl.Behaviors
import akka.util.Timeout
import cats.syntax.show._
import monix.bio.UIO
import monix.reactive.Observable
import wow.doge.mygame.game.entities.CharacterStats
import wow.doge.mygame.game.entities.StatsActor
import wow.doge.mygame.game.entities.character.CharacterStates._
import wow.doge.mygame.game.entities.player.PlayerActor
import wow.doge.mygame.implicits._
import wow.doge.mygame.subsystems.movement.ImMovementActor
import wow.doge.mygame.utils.MovementDirection
class IdleBehaviorFactory(
env: PlayerActor.Env,
nextStateFn: AliveSubstate => Behavior[PlayerActor.Command],
deadState: Behavior[PlayerActor.Command],
consumptionMultiplier: Int => Int
)(implicit timeout: Timeout) {
import env._
implicit val sched = ctx.system.scheduler
def behavior =
IdleBehavior(
Behaviors
.receiveMessage[PlayerActor.Command] {
case PlayerActor.HandleWalk(curStats, pressed, direction) =>
if (curStats.stamina.toInt > 0) {
children.movementActor ! (direction match {
case MovementDirection.Forward =>
ImMovementActor.MoveUp(pressed)
case MovementDirection.Backward =>
ImMovementActor.MoveDown(pressed)
case MovementDirection.Left => ImMovementActor.MoveLeft(pressed)
case MovementDirection.Right =>
ImMovementActor.MoveRight(pressed)
})
if (pressed) nextStateFn(AliveSubstate.Moving(Walking))
else nextStateFn(AliveSubstate.Idle)
} else {
children.movementActor ! ImMovementActor.StopMoving
Behaviors.same
}
// case PlayerActor.Walk(pressed, Direction.Up) => Behaviors.same
// case PlayerActor.Walk(pressed, Direction.Down) => Behaviors.same
// case PlayerActor.Walk(pressed, Direction.Left) => Behaviors.same
// case PlayerActor.Walk(pressed, Direction.Right) => Behaviors.same
case PlayerActor.StopMoving =>
children.movementActor ! ImMovementActor.StopMoving
Behaviors.same
case PlayerActor.Walk(pressed, direction) =>
implicit val ec = props.scheduler.value
for {
curStats <- children.statsActor.ask(StatsActor.CurrentStats)
_ <- Future.successful(
ctx.self ! PlayerActor.HandleWalk(curStats, pressed, direction)
)
} yield ()
Behaviors.same
case PlayerActor.Jump =>
children.movementActor ! ImMovementActor.Jump
ctx.self.ask(
PlayerActor.ConsumeStamina(CharacterStats.DamageStamina(10), _)
)
Behaviors.same
case PlayerActor.TakeDamage(value, replyTo) =>
implicit val ec = props.scheduler.value
for {
res <-
children.statsActor.ask(StatsActor.TakeDamageResult(value, _))
_ = ctx.self ! PlayerActor.StatsResponse(res, replyTo)
} yield ()
Behaviors.same
case PlayerActor.ConsumeStamina(value, replyTo) =>
implicit val ec = props.scheduler.value
val newValue =
CharacterStats.DamageStamina(consumptionMultiplier(value.toInt))
for {
response <- children.statsActor.ask(
StatsActor.ConsumeStaminaResult(newValue, _)
)
_ =
ctx.self ! PlayerActor
.StatsResponse(response, replyTo)
} yield ()
Behaviors.same
case PlayerActor.CurrentStats(replyTo) =>
children.statsActor ! StatsActor.CurrentStats(replyTo)
Behaviors.same
case PlayerActor.HealHealth(value, replyTo) =>
implicit val ec = props.scheduler.value
for {
response <- children.statsActor.ask(
StatsActor.HealHealthResult(value, _)
)
_ =
ctx.self ! PlayerActor
.StatsResponse(StatsActor.Status.Alive -> response, replyTo)
} yield ()
Behaviors.same
case PlayerActor.HealStamina(value, replyTo) =>
implicit val ec = props.scheduler.value
for {
response <- children.statsActor.ask(
StatsActor.HealStaminaResult(value, _)
)
_ =
ctx.self ! PlayerActor
.StatsResponse(StatsActor.Status.Alive -> response, replyTo)
} yield ()
Behaviors.same
case PlayerActor.GetStatus(replyTo) =>
replyTo ! PlayerActor.Status.Alive
Behaviors.same
case PlayerActor.GetStatsObservable(replyTo) =>
import monix.{eval => me}
replyTo !
UIO(
Observable
.repeatEvalF(
me.Task.deferFuture(props.statsQueue.poll())
)
.publish(props.fxScheduler.value)
.refCount
)
Behaviors.same
case PlayerActor.StatsResponse(response, replyTo) =>
response match {
case (status, stats) =>
status match {
case StatsActor.Status.Dead => ctx.self ! PlayerActor.Die
case StatsActor.Status.Alive => ()
}
props.statsQueue
.offer(stats)
.foreach { _ =>
pprint.log(show"Published stats $stats")
replyTo ! stats
}(props.scheduler.value)
}
Behaviors.same
case PlayerActor.Die => deadState
case PlayerActor.LogError(ex) =>
ctx.log.error(ex.getMessage)
Behaviors.same
}
.receiveSignal {
case (_, PostStop) =>
ctx.log.infoP("stopped")
Behaviors.same
}
)
}
final case class IdleBehavior(value: Behavior[PlayerActor.Command])

View File

@ -11,8 +11,8 @@ import wow.doge.mygame.game.subsystems.ai.gdx.MyIndexedGraph
// import com.badlogic.gdx.ai.pfa.indexed.IndexedGraph // import com.badlogic.gdx.ai.pfa.indexed.IndexedGraph
// import scala.jdk.javaapi.CollectionConverters._ // import scala.jdk.javaapi.CollectionConverters._
final case class City(x: Float, y: Float, name: String, index: Int) case class City(x: Float, y: Float, name: String, index: Int)
final case class Street(fromNode: City, toNode: City, cost: Float) case class Street(fromNode: City, toNode: City, cost: Float)
extends Connection[City] { extends Connection[City] {
override def getCost(): Float = cost override def getCost(): Float = cost

View File

@ -18,11 +18,10 @@ import wow.doge.mygame.subsystems.events.PlayerCameraEvent
import wow.doge.mygame.subsystems.events.PlayerEvent import wow.doge.mygame.subsystems.events.PlayerEvent
import wow.doge.mygame.subsystems.events.PlayerMovementEvent import wow.doge.mygame.subsystems.events.PlayerMovementEvent
import wow.doge.mygame.utils.IOUtils._ import wow.doge.mygame.utils.IOUtils._
import wow.doge.mygame.utils.methodName
object GameInputHandler { object GameInputHandler {
class Props( final class Props(
inputManager: InputManager, inputManager: InputManager,
playerEventBus: GameEventBus[PlayerEvent] playerEventBus: GameEventBus[PlayerEvent]
// playerCameraEventBus: GameEventBus[PlayerCameraEvent] // playerCameraEventBus: GameEventBus[PlayerCameraEvent]
@ -30,44 +29,44 @@ object GameInputHandler {
) { ) {
def begin = def begin =
for { for {
_ <- UIO(setupMovementKeys) _ <- UIO(setupMovementKeys(inputManager))
// _ <- UIO(setupAnalogMovementKeys) // _ <- UIO(setupAnalogMovementKeys)
_ <- UIO(setupCameraKeys()) _ <- UIO(setupCameraKeys())
_ <- toIO( _ <- toIO(
me.Task.parSequence( generateMovementInputEvents(
Seq( inputManager,
// playerMovementInputEventsGenerator( playerEventBus
// inputManager, ).completedL.startAndForget
// playerEventBus )
// ).completedL, _ <- toIO(
// generateAnalogMovementEvents( generateAnalogMovementEvents(
// inputManager, inputManager,
// playerEventBus playerEventBus
// ).completedL, ).completedL.startAndForget
// cameraMovementEventsGenerator( )
// inputManager, _ <- toIO(
// playerEventBus generateCameraEvents(
// ).completedL, inputManager,
Ref playerEventBus
.of[me.Task, Boolean](false) ).completedL.startAndForget
.flatMap(value => cursorToggle(value)) )
) _ <- toIO(
) Ref.of[me.Task, Boolean](false).flatMap(value => cursorToggle(value))
).startAndForget )
} yield () } yield ()
def setupMovementKeys = def setupMovementKeys(inputManager: InputManager) =
inputManager.withEnumMappings(PlayerMovementInput) { inputManager.withEnumMappings(PlayerMovementInput) {
case PlayerMovementInput.WalkRight => case PlayerMovementInput.WalkRight =>
new KeyTrigger(KeyInput.KEY_D) :: Nil Seq(new KeyTrigger(KeyInput.KEY_D))
case PlayerMovementInput.WalkLeft => case PlayerMovementInput.WalkLeft =>
new KeyTrigger(KeyInput.KEY_A) :: Nil Seq(new KeyTrigger(KeyInput.KEY_A))
case PlayerMovementInput.WalkForward => case PlayerMovementInput.WalkForward =>
new KeyTrigger(KeyInput.KEY_W) :: Nil Seq(new KeyTrigger(KeyInput.KEY_W))
case PlayerMovementInput.WalkBackward => case PlayerMovementInput.WalkBackward =>
new KeyTrigger(KeyInput.KEY_S) :: Nil Seq(new KeyTrigger(KeyInput.KEY_S))
case PlayerMovementInput.Jump => case PlayerMovementInput.Jump =>
new KeyTrigger(KeyInput.KEY_SPACE) :: Nil Seq(new KeyTrigger(KeyInput.KEY_SPACE))
} }
def setupAnalogMovementKeys() = def setupAnalogMovementKeys() =
@ -125,15 +124,16 @@ object GameInputHandler {
} }
) )
.completedL .completedL
.startAndForget
} yield () } yield ()
} }
def playerMovementInputEventsGenerator( def generateMovementInputEvents(
inputManager: InputManager, inputManager: InputManager,
playerEventBus: GameEventBus[PlayerEvent] playerEventBus: GameEventBus[PlayerEvent]
) = { ) = {
val name = methodName val name = "playerMovementInputEventsGenerator"
inputManager inputManager
.enumObservableAction(PlayerMovementInput) .enumObservableAction(PlayerMovementInput)
// .dump("O") // .dump("O")
@ -142,40 +142,40 @@ object GameInputHandler {
case PlayerMovementInput.WalkLeft => case PlayerMovementInput.WalkLeft =>
me.Task( me.Task(
playerEventBus ! EventBus.Publish( playerEventBus ! EventBus.Publish(
PlayerMovementEvent.PlayerMovedLeft(action.value), PlayerMovementEvent.PlayerMovedLeft(pressed = action.value),
name name
) )
) )
case PlayerMovementInput.WalkRight => case PlayerMovementInput.WalkRight =>
me.Task( me.Task(
playerEventBus ! EventBus.Publish( playerEventBus ! EventBus.Publish(
PlayerMovementEvent.PlayerMovedRight(action.value), PlayerMovementEvent.PlayerMovedRight(pressed = action.value),
name name
) )
) )
case PlayerMovementInput.WalkForward => case PlayerMovementInput.WalkForward =>
me.Task( me.Task(
playerEventBus ! EventBus.Publish( playerEventBus ! EventBus.Publish(
PlayerMovementEvent.PlayerMovedForward(action.value), PlayerMovementEvent.PlayerMovedForward(pressed = action.value),
name name
) )
) )
case PlayerMovementInput.WalkBackward => case PlayerMovementInput.WalkBackward =>
me.Task( me.Task(
playerEventBus ! EventBus.Publish( playerEventBus ! EventBus.Publish(
PlayerMovementEvent.PlayerMovedBackward(action.value), PlayerMovementEvent.PlayerMovedBackward(pressed = action.value),
name name
) )
) )
case PlayerMovementInput.Jump => case PlayerMovementInput.Jump =>
if (action.value) if (action.value) {
me.Task( me.Task(
playerEventBus ! EventBus.Publish( playerEventBus ! EventBus.Publish(
PlayerMovementEvent.PlayerJumped, PlayerMovementEvent.PlayerJumped,
name name
) )
) )
else me.Task.unit } else me.Task.unit
} }
} }
} }
@ -183,35 +183,36 @@ object GameInputHandler {
def generateAnalogMovementEvents( def generateAnalogMovementEvents(
inputManager: InputManager, inputManager: InputManager,
playerEventBus: GameEventBus[PlayerEvent] playerEventBus: GameEventBus[PlayerEvent]
) = ) = {
// val name = "analogMovementEventsGenerator" // val name = "analogMovementEventsGenerator"
inputManager inputManager
.enumAnalogObservable(PlayerAnalogMovementInput) .enumAnalogObservable(PlayerAnalogMovementInput)
.sample(1.millis) .sample(1.millis)
// .doOnNext(analogEvent => // .doOnNext(analogEvent =>
// analogEvent.binding match { // analogEvent.binding match {
// case PlayerAnalogMovementInput.TurnRight => // case PlayerAnalogMovementInput.TurnRight =>
// me.Task( // me.Task(
// playerMovementEventBus ! EventBus.Publish( // playerMovementEventBus ! EventBus.Publish(
// PlayerMovementEvent.PlayerTurnedRight, // PlayerMovementEvent.PlayerTurnedRight,
// name // name
// ) // )
// ) // )
// case PlayerAnalogMovementInput.TurnLeft => // case PlayerAnalogMovementInput.TurnLeft =>
// me.Task( // me.Task(
// playerMovementEventBus ! EventBus.Publish( // playerMovementEventBus ! EventBus.Publish(
// PlayerMovementEvent.PlayerTurnedLeft, // PlayerMovementEvent.PlayerTurnedLeft,
// name // name
// ) // )
// ) // )
// } // }
// ) // )
}
def cameraMovementEventsGenerator( def generateCameraEvents(
inputManager: InputManager, inputManager: InputManager,
playerEventBus: ActorRef[EventBus.Command[PlayerEvent]] playerEventBus: ActorRef[EventBus.Command[PlayerEvent]]
) = { ) = {
val name = methodName val name = "cameraMovementEventsGenerator"
inputManager inputManager
.enumAnalogObservable(PlayerCameraInput) .enumAnalogObservable(PlayerCameraInput)
.sample(1.millis) .sample(1.millis)

View File

@ -1,48 +1,35 @@
package wow.doge.mygame.game.subsystems.input package wow.doge.mygame.game.subsystems.input
import cats.Show
import cats.kernel.Eq
import enumeratum.EnumEntry._ import enumeratum.EnumEntry._
import enumeratum._ import enumeratum._
import io.circe.generic.semiauto._
sealed trait PlayerMovementInput extends EnumEntry with UpperSnakecase sealed trait PlayerMovementInput extends EnumEntry with UpperSnakecase
object PlayerMovementInput extends Enum[PlayerMovementInput] { final object PlayerMovementInput extends Enum[PlayerMovementInput] {
val values = findValues val values = findValues
case object WalkForward extends PlayerMovementInput final case object WalkForward extends PlayerMovementInput
case object WalkRight extends PlayerMovementInput final case object WalkRight extends PlayerMovementInput
case object WalkLeft extends PlayerMovementInput final case object WalkLeft extends PlayerMovementInput
case object WalkBackward extends PlayerMovementInput final case object WalkBackward extends PlayerMovementInput
case object Jump extends PlayerMovementInput final case object Jump extends PlayerMovementInput
implicit val eq = Eq.fromUniversalEquals[PlayerMovementInput]
implicit val show = Show.fromToString[PlayerMovementInput]
} }
sealed trait PlayerAnalogMovementInput extends EnumEntry with UpperSnakecase sealed trait PlayerAnalogMovementInput extends EnumEntry with UpperSnakecase
object PlayerAnalogMovementInput extends Enum[PlayerAnalogMovementInput] { final object PlayerAnalogMovementInput extends Enum[PlayerAnalogMovementInput] {
val values = findValues val values = findValues
case object TurnRight extends PlayerAnalogMovementInput final case object TurnRight extends PlayerAnalogMovementInput
case object TurnLeft extends PlayerAnalogMovementInput final case object TurnLeft extends PlayerAnalogMovementInput
implicit val eq = Eq.fromUniversalEquals[PlayerAnalogMovementInput]
implicit val show = Show.fromToString[PlayerAnalogMovementInput]
} }
sealed trait PlayerCameraInput extends EnumEntry with UpperSnakecase sealed trait PlayerCameraInput extends EnumEntry with UpperSnakecase
object PlayerCameraInput extends Enum[PlayerCameraInput] { final object PlayerCameraInput extends Enum[PlayerCameraInput] {
val values = findValues val values = findValues
case object CameraRotateLeft extends PlayerCameraInput final case object CameraRotateLeft extends PlayerCameraInput
case object CameraRotateRight extends PlayerCameraInput final case object CameraRotateRight extends PlayerCameraInput
case object CameraRotateUp extends PlayerCameraInput final case object CameraRotateUp extends PlayerCameraInput
case object CameraRotateDown extends PlayerCameraInput final case object CameraRotateDown extends PlayerCameraInput
implicit val eq = Eq.fromUniversalEquals[PlayerCameraInput]
implicit val show = Show.fromToString[PlayerCameraInput]
implicit val codec = deriveCodec[PlayerCameraInput]
} }
sealed trait MiscInput extends EnumEntry with UpperSnakecase sealed trait MiscInput extends EnumEntry with UpperSnakecase
object MiscInput extends Enum[MiscInput] { final object MiscInput extends Enum[MiscInput] {
val values = findValues val values = findValues
case object ToggleCursor extends MiscInput final case object ToggleCursor extends MiscInput
} }

View File

@ -1,87 +0,0 @@
package wow.doge.mygame.game.subsystems.input
import com.jme3.input.KeyInput
import com.jme3.input.MouseInput
import com.jme3.input.controls.KeyTrigger
import com.jme3.input.controls.MouseAxisTrigger
import monix.{eval => me}
import wow.doge.mygame.implicits._
import wow.doge.mygame.utils.wrappers.jme.InputManager
class InputMappings(inputManager: InputManager) {
def setup =
for {
_ <- setupMovementKeys
// _ <- setupAnalogMovementKeys
_ <- setupCameraKeys
_ <- cursorToggle
} yield ()
def setupMovementKeys =
inputManager.withEnumMappings(PlayerMovementInput) {
case PlayerMovementInput.WalkRight =>
new KeyTrigger(KeyInput.KEY_D) :: Nil
case PlayerMovementInput.WalkLeft =>
new KeyTrigger(KeyInput.KEY_A) :: Nil
case PlayerMovementInput.WalkForward =>
new KeyTrigger(KeyInput.KEY_W) :: Nil
case PlayerMovementInput.WalkBackward =>
new KeyTrigger(KeyInput.KEY_S) :: Nil
case PlayerMovementInput.Jump =>
new KeyTrigger(KeyInput.KEY_SPACE) :: Nil
}
def setupAnalogMovementKeys =
inputManager.withEnumMappings(PlayerAnalogMovementInput) {
case PlayerAnalogMovementInput.TurnRight =>
Seq(new KeyTrigger(KeyInput.KEY_D))
case PlayerAnalogMovementInput.TurnLeft =>
Seq(new KeyTrigger(KeyInput.KEY_A))
}
def setupCameraKeys =
inputManager.withEnumMappings(PlayerCameraInput) {
case PlayerCameraInput.CameraRotateLeft =>
Seq(
new KeyTrigger(KeyInput.KEY_LEFT),
new MouseAxisTrigger(MouseInput.AXIS_X, false)
)
case PlayerCameraInput.CameraRotateRight =>
Seq(
new KeyTrigger(KeyInput.KEY_RIGHT),
new MouseAxisTrigger(MouseInput.AXIS_X, true)
)
case PlayerCameraInput.CameraRotateUp =>
Seq(
new KeyTrigger(KeyInput.KEY_UP),
new MouseAxisTrigger(MouseInput.AXIS_Y, false)
)
case PlayerCameraInput.CameraRotateDown =>
Seq(
new KeyTrigger(KeyInput.KEY_DOWN),
new MouseAxisTrigger(MouseInput.AXIS_Y, true)
)
}
def cursorToggle =
inputManager.withMapping(
MiscInput.ToggleCursor,
new KeyTrigger(KeyInput.KEY_Z)
) >>
inputManager
.enumEntryObservableAction(MiscInput.ToggleCursor)
.doOnNext(action =>
action.binding match {
case MiscInput.ToggleCursor =>
if (action.value)(inputManager.cursorVisible =
!inputManager.cursorVisible).toTask
else me.Task.unit
}
)
.completedL
.toIO
.hideErrors
.startAndForget
}

View File

@ -1,65 +1,50 @@
package wow.doge.mygame.game.subsystems.level package wow.doge.mygame.game.subsystems.level
import com.jme3.asset.AssetManager
import com.jme3.bullet.control.RigidBodyControl
import com.jme3.bullet.util.CollisionShapeFactory
import com.jme3.light.AmbientLight import com.jme3.light.AmbientLight
import com.jme3.light.DirectionalLight import com.jme3.light.DirectionalLight
import com.jme3.math.ColorRGBA import com.jme3.math.ColorRGBA
import com.jme3.math.Vector3f import com.jme3.math.Vector3f
import com.jme3.renderer.ViewPort import com.jme3.renderer.ViewPort
import monix.bio.IO import com.jme3.scene.Spatial
import monix.bio.UIO
import wow.doge.mygame.AppError
object DefaultGameLevel { object DefaultGameLevel {
def apply( def apply(
assetManager: wow.doge.mygame.utils.wrappers.jme.AssetManager, assetManager: AssetManager,
viewPort: ViewPort viewPort: ViewPort
): IO[AppError, GameLevel] = ) = {
// for { lazy val sceneModel: Spatial = assetManager.loadModel("main.scene")
// sceneModel <- assetManager.loadModelAs[Node](os.rel / "main.scene") lazy val sceneShape = CollisionShapeFactory.createMeshShape(
// sceneShape <- UIO(CollisionShapeFactory.createMeshShape(sceneModel)) sceneModel.toNode match {
// landscape <- UIO(new RigidBodyControl(sceneShape, 0)) case Right(node) => node
case Left(ex) =>
// _ <- UIO { throw new NotImplementedError("No fallback sceneshape")
// viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f))
// sceneModel.setLocalScale(2f)
// sceneModel.addControl(landscape)
// }
// al = {
// val al = new AmbientLight()
// al.setColor(ColorRGBA.White.mult(1.3f))
// al
// }
// dl = {
// val dl = new DirectionalLight()
// dl.setColor(ColorRGBA.White)
// dl.setDirection(new Vector3f(2.8f, -2.8f, -2.8f).normalizeLocal())
// dl
// }
// } yield new GameLevel(
// model = sceneModel,
// physicsControl = landscape,
// ambientLight = al,
// directionalLight = dl
// )
GameLevel(
os.rel / "main.scene", {
val al = new AmbientLight()
al.setColor(ColorRGBA.White.mult(1.3f))
al
}, {
val dl = new DirectionalLight()
dl.setColor(ColorRGBA.White)
dl.setDirection(new Vector3f(2.8f, -2.8f, -2.8f).normalizeLocal())
dl
}
)(assetManager).flatMap(gameLevel =>
UIO {
viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f))
gameLevel.model.setLocalScale(2f)
gameLevel.model.addControl(gameLevel.physicsControl)
gameLevel
} }
) )
lazy val landscape: RigidBodyControl =
new RigidBodyControl(sceneShape, 0)
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)
val al = new AmbientLight();
al.setColor(ColorRGBA.White.mult(1.3f));
// app.rootNode.addLight(al);
val dl = new DirectionalLight();
dl.setColor(ColorRGBA.White);
dl.setDirection(new Vector3f(2.8f, -2.8f, -2.8f).normalizeLocal());
// app.rootNode.addLight(dl);
new GameLevel(
model = sceneModel,
physicsControl = landscape,
ambientLight = al,
directionalLight = dl
)
}
} }

View File

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

View File

@ -12,7 +12,7 @@ import wow.doge.mygame.subsystems.movement.RotateDir
trait CanMove[-A] { trait CanMove[-A] {
// def getDirection(cam: Camera, cardinalDir: CardinalDirection): ImVector3f // def getDirection(cam: Camera, cardinalDir: CardinalDirection): ImVector3f
def move(inst: A, direction: ImVector3f, speedFactor: Float): Unit def move(inst: A, direction: ImVector3f, speedFactor: Float = 20f): Unit
def location(inst: A): ImVector3f def location(inst: A): ImVector3f
def jump(inst: A): Unit def jump(inst: A): Unit
def stop(inst: A): Unit def stop(inst: A): Unit
@ -25,7 +25,7 @@ object CanMove {
override def move( override def move(
inst: BetterCharacterControl, inst: BetterCharacterControl,
direction: ImVector3f, direction: ImVector3f,
speedFactor: Float speedFactor: Float = 20f
): Unit = { ): Unit = {
val dir = direction.mutable.normalizeLocal() val dir = direction.mutable.normalizeLocal()
inst.setViewDirection(dir.negate()) inst.setViewDirection(dir.negate())
@ -61,17 +61,19 @@ object CanMove {
inst: Spatial, inst: Spatial,
direction: ImVector3f, direction: ImVector3f,
speedFactor: Float = 1f speedFactor: Float = 1f
): Unit = ): Unit = {
inst.move(direction.mutable multLocal speedFactor) inst.move(direction.mutable multLocal speedFactor)
}
override def location(inst: Spatial) = override def location(inst: Spatial) =
inst.getLocalTranslation().immutable inst.getLocalTranslation().immutable
override def jump(inst: Spatial): Unit = override def jump(inst: Spatial): Unit =
logger.warn("`Jump` is not implemented for type `Spatial`") logger.warn("`Jump` is not implemented for type `Spatial`")
override def rotate(inst: Spatial, rotateDir: RotateDir): Unit = override def rotate(inst: Spatial, rotateDir: RotateDir): Unit = {
rotateDir match { rotateDir match {
case RotateDir.Left => inst.rotate(0, -0.01f, 0) case RotateDir.Left => inst.rotate(0, -0.01f, 0)
case RotateDir.Right => inst.rotate(0, 0.01f, 0) case RotateDir.Right => inst.rotate(0, 0.01f, 0)
} }
}
override def stop(inst: Spatial) = { /*not required*/ } override def stop(inst: Spatial) = { /*not required*/ }
} }
} }

View File

@ -1,6 +1,5 @@
package wow.doge.mygame.game.subsystems.movement package wow.doge.mygame.game.subsystems.movement
import cats.Id
import com.jme3.bullet.control.BetterCharacterControl import com.jme3.bullet.control.BetterCharacterControl
import com.jme3.math.FastMath import com.jme3.math.FastMath
import com.jme3.math.Quaternion import com.jme3.math.Quaternion
@ -9,6 +8,7 @@ import monix.eval.Coeval
import wow.doge.mygame.implicits._ import wow.doge.mygame.implicits._
import wow.doge.mygame.math.ImVector3f import wow.doge.mygame.math.ImVector3f
import wow.doge.mygame.subsystems.movement.RotateDir import wow.doge.mygame.subsystems.movement.RotateDir
// experiment to see if it would be useful to use an effect wrapper for a typeclass like this // experiment to see if it would be useful to use an effect wrapper for a typeclass like this
trait CanMove2[-A, F[_]] { trait CanMove2[-A, F[_]] {
// def getDirection(cam: Camera, cardinalDir: CardinalDirection): ImVector3f // def getDirection(cam: Camera, cardinalDir: CardinalDirection): ImVector3f
@ -19,34 +19,7 @@ trait CanMove2[-A, F[_]] {
def rotate(inst: A, rotateDir: RotateDir): F[Unit] def rotate(inst: A, rotateDir: RotateDir): F[Unit]
} }
object Test {
val x = new BetterCharacterControl(4, 10, 5)
def test[T](x: T)(implicit cm: CanMove2[T, Id]) =
cm.move(x, ImVector3f.Zero)
}
object CanMove2 { object CanMove2 {
implicit val testImpl = new CanMove2[BetterCharacterControl, Id] {
override def move(
inst: BetterCharacterControl,
direction: ImVector3f,
speedFactor: Float
): Id[Unit] = {}
override def location(inst: BetterCharacterControl): Id[ImVector3f] =
ImVector3f.Zero
override def jump(inst: BetterCharacterControl): Id[Unit] = ???
override def stop(inst: BetterCharacterControl): Id[Unit] = ???
override def rotate(
inst: BetterCharacterControl,
rotateDir: RotateDir
): Id[Unit] = ???
}
implicit val implCanMoveForBetterCharacterControl = implicit val implCanMoveForBetterCharacterControl =
new CanMove2[BetterCharacterControl, Coeval] { new CanMove2[BetterCharacterControl, Coeval] {
override def move( override def move(

View File

@ -1,25 +1,15 @@
package wow.doge.mygame.subsystems.movement package wow.doge.mygame.subsystems.movement
import akka.actor.typed.Behavior import akka.actor.typed.Behavior
import akka.actor.typed.PostStop
import akka.actor.typed.scaladsl.ActorContext import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.scaladsl.Behaviors
import com.jme3.math.Vector3f import com.jme3.math.Vector3f
import com.jme3.renderer.Camera import com.jme3.renderer.Camera
import com.jme3.scene.Geometry
import com.softwaremill.quicklens._ import com.softwaremill.quicklens._
import wow.doge.mygame.game.subsystems.movement.CanMove import wow.doge.mygame.game.subsystems.movement.CanMove
import wow.doge.mygame.implicits._ import wow.doge.mygame.implicits._
import wow.doge.mygame.math.ImVector3f
final case class CardinalDirection( import wow.doge.mygame.state.CardinalDirection
left: Boolean,
right: Boolean,
up: Boolean,
down: Boolean
)
object CardinalDirection {
val default = CardinalDirection(false, false, false, false)
}
sealed trait RotateDir sealed trait RotateDir
object RotateDir { object RotateDir {
@ -29,27 +19,28 @@ object RotateDir {
object ImMovementActor { object ImMovementActor {
sealed trait Command sealed trait Command
case object Tick extends Command // final case class Tick(tpf: Float) extends Command
final case object Tick extends Command
sealed trait Movement extends Command sealed trait Movement extends Command
final case class MoveLeft(pressed: Boolean) extends Movement final case class MovedLeft(pressed: Boolean) extends Movement
final case class MoveUp(pressed: Boolean) extends Movement final case class MovedUp(pressed: Boolean) extends Movement
final case class MoveRight(pressed: Boolean) extends Movement final case class MovedRight(pressed: Boolean) extends Movement
final case class MoveDown(pressed: Boolean) extends Movement final case class MovedDown(pressed: Boolean) extends Movement
case object StopMoving extends Movement final case object Jump extends Movement
case object Jump extends Movement // final case object RotateRight extends Movement
// final case object RotateLeft extends Movement
class Props( final class Props[T: CanMove](
val enqueueR: Function1[() => Unit, Unit], val enqueueR: Function1[() => Unit, Unit],
val movable: T,
// playerMovementEventBus: ActorRef[ // playerMovementEventBus: ActorRef[
// EventBus.Command[PlayerMovementEvent] // EventBus.Command[PlayerMovementEvent]
// ] // ]
val camera: Camera val camera: Camera
) { ) {
def behavior[T: CanMove](movable: T): Behavior[Command] = def behavior: Behavior[Command] =
Behaviors.setup(ctx => Behaviors.setup(ctx => new ImMovementActor(ctx, this).receive(State()))
new ImMovementActor(ctx, this, movable).receive(State(), new Vector3f)
)
} }
/** /**
@ -57,99 +48,53 @@ object ImMovementActor {
* *
* @param cardinalDir The four directions the character can move * @param cardinalDir The four directions the character can move
*/ */
final case class State( final case class State(cardinalDir: CardinalDirection = CardinalDirection())
cardinalDir: CardinalDirection = CardinalDirection.default
)
} }
class ImMovementActor[T]( class ImMovementActor[T](
ctx: ActorContext[ImMovementActor.Command], ctx: ActorContext[ImMovementActor.Command],
props: ImMovementActor.Props, props: ImMovementActor.Props[T]
val movable: T ) {
)(implicit cm: CanMove[T]) {
import ImMovementActor._ import ImMovementActor._
import Methods._
def receive( def receive(
state: ImMovementActor.State, state: ImMovementActor.State
walkDirBuf: Vector3f )(implicit cm: CanMove[T]): Behavior[Command] =
): Behavior[Command] = Behaviors.receiveMessage {
Behaviors case m: Movement =>
.receiveMessage[Command] { m match {
case m: Movement => case MovedLeft(pressed) =>
m match { props.enqueueR(() => stopIfNotPressed(pressed, props.movable))
case StopMoving => receive(state = state.modify(_.cardinalDir.left).setTo(pressed))
cm.stop(movable) case MovedUp(pressed) =>
receive(State(CardinalDirection.default), new Vector3f) props.enqueueR(() => stopIfNotPressed(pressed, props.movable))
receive(state = state.modify(_.cardinalDir.up).setTo(pressed))
case MovedRight(pressed) =>
props.enqueueR(() => stopIfNotPressed(pressed, props.movable))
receive(state = state.modify(_.cardinalDir.right).setTo(pressed))
case MovedDown(pressed) =>
props.enqueueR(() => stopIfNotPressed(pressed, props.movable))
receive(state = state.modify(_.cardinalDir.down).setTo(pressed))
case Jump =>
props.enqueueR(() => cm.jump(props.movable))
Behaviors.same
}
case MoveLeft(pressed) => case Tick =>
stopIfNotPressed(pressed) val walkDir =
receive( getDirection2(state.cardinalDir, ctx.log.trace)
state.modify(_.cardinalDir.left).setTo(pressed), // if (walkDir != ImVector3f.ZERO) {
walkDirBuf val tmp = walkDir * 25f * (1f / 144)
) props.enqueueR { () =>
case MoveUp(pressed) => cm.move(props.movable, tmp)
stopIfNotPressed(pressed) }
receive( // }
state.modify(_.cardinalDir.up).setTo(pressed), Behaviors.same
walkDirBuf }
)
case MoveRight(pressed) =>
stopIfNotPressed(pressed)
receive(
state.modify(_.cardinalDir.right).setTo(pressed),
walkDirBuf
)
case MoveDown(pressed) =>
stopIfNotPressed(pressed)
receive(
state.modify(_.cardinalDir.down).setTo(pressed),
walkDirBuf
)
case Jump =>
cm.jump(movable)
Behaviors.same
}
case Tick => def getDirection2(cardinalDir: CardinalDirection, debug: String => Unit) = {
val camDir =
props.camera.getDirection().clone().normalizeLocal.multLocal(0.6f)
val camLeft =
props.camera.getLeft().clone().normalizeLocal.multLocal(0.4f)
val dir = state.cardinalDir
walkDirBuf.set(0, 0, 0)
if (dir.up) {
ctx.log.traceP("up")
walkDirBuf += camDir
}
if (dir.left) {
ctx.log.traceP("left")
walkDirBuf += camLeft
}
if (dir.right) {
ctx.log.traceP("right")
walkDirBuf += -camLeft
}
if (dir.down) {
ctx.log.traceP("down")
walkDirBuf += -camDir
}
walkDirBuf *= 25f *= (1f / 144)
cm.move(movable, walkDirBuf.immutable, 20f)
Behaviors.same
}
.receiveSignal {
case (_, PostStop) =>
ctx.log.debugP("Stopped")
Behaviors.same
}
def stopIfNotPressed(pressed: Boolean) = if (!pressed) cm.stop(movable)
def getDirection2(
cardinalDir: CardinalDirection
// debug: sourcecode.Text[String] => sourcecode.Text[String]
) = {
val camDir = val camDir =
props.camera.getDirection().clone().normalizeLocal.multLocal(0.6f) props.camera.getDirection().clone().normalizeLocal.multLocal(0.6f)
val camLeft = props.camera.getLeft().clone().normalizeLocal.multLocal(0.4f) val camLeft = props.camera.getLeft().clone().normalizeLocal.multLocal(0.4f)
@ -157,19 +102,19 @@ class ImMovementActor[T](
val walkDir = { val walkDir = {
val mutWalkDir = new Vector3f() val mutWalkDir = new Vector3f()
if (dir.up) { if (dir.up) {
ctx.log.traceP("up") debug("up")
mutWalkDir += camDir mutWalkDir += camDir
} }
if (dir.left) { if (dir.left) {
ctx.log.traceP("left") debug("left")
mutWalkDir += camLeft mutWalkDir += camLeft
} }
if (dir.right) { if (dir.right) {
ctx.log.traceP("right") debug("right")
mutWalkDir += -camLeft mutWalkDir += -camLeft
} }
if (dir.down) { if (dir.down) {
ctx.log.traceP("down") debug("down")
mutWalkDir += -camDir mutWalkDir += -camDir
} }
mutWalkDir.immutable mutWalkDir.immutable
@ -177,111 +122,50 @@ class ImMovementActor[T](
walkDir walkDir
} }
} }
object Methods {
// old/unused def getDirection(cardinalDir: CardinalDirection, trace: String => Unit) = {
object MovementActor { val zero = ImVector3f.ZERO
sealed trait Command val dir = cardinalDir
case object Tick extends Command val walkDir = {
val mutWalkDir = new Vector3f()
sealed trait Movement extends Command if (dir.left) {
final case class MovedLeft(pressed: Boolean) extends Movement trace("left")
final case class MovedUp(pressed: Boolean) extends Movement mutWalkDir += zero +=: new Vector3f(-1, 0, 0)
final case class MovedRight(pressed: Boolean) extends Movement
final case class MovedDown(pressed: Boolean) extends Movement
class Props(val app: com.jme3.app.Application, val geom: Geometry)
/**
* Internal state of the actor
*
* @param cardinalDir Immutable, can be shared as is
* @param walkDirection scratch space to avoid allocations on every tick. Do not share outside the actor
*/
final case class State(
cardinalDir: CardinalDirection = CardinalDirection.default,
walkDirection: Vector3f = Vector3f.UNIT_X
)
def apply(props: Props): Behavior[Command] =
Behaviors.setup(ctx => new MovementActor(ctx, props).receive(State()))
}
class MovementActor(
ctx: ActorContext[MovementActor.Command],
props: MovementActor.Props
) {
import MovementActor._
import com.softwaremill.quicklens._
def receive(state: MovementActor.State): Behavior[Command] =
Behaviors.receiveMessage { msg =>
msg match {
case m: Movement =>
m match {
case MovedLeft(pressed) =>
receive(state = state.modify(_.cardinalDir.left).setTo(pressed))
case MovedUp(pressed) =>
receive(state = state.modify(_.cardinalDir.up).setTo(pressed))
case MovedRight(pressed) =>
receive(state = state.modify(_.cardinalDir.right).setTo(pressed))
case MovedDown(pressed) =>
receive(state = state.modify(_.cardinalDir.down).setTo(pressed))
}
case Tick =>
val camDir =
props.app.getCamera.getDirection().clone().multLocal(0.6f)
val camLeft = props.app.getCamera.getLeft().clone().multLocal(0.4f)
val walkDir = state.walkDirection.set(0, 0, 0)
// val walkDir = new Vector3f
val dir = state.cardinalDir
if (dir.up) {
ctx.log.debugP("up")
// ctx.log.debugP(Thread.currentThread().getName())
// walkDir.addLocal(0, 0, -1)
walkDir += camDir
}
if (dir.left) {
ctx.log.debugP("left")
// walkDir.addLocal(-1, 0, 0)
walkDir.addLocal(camLeft)
}
if (dir.right) {
ctx.log.debugP("right")
// walkDir.addLocal(1, 0, 0)
walkDir.addLocal(camLeft.negateLocal())
}
if (dir.down) {
ctx.log.debugP("down")
walkDir.addLocal(camDir.negateLocal())
// walkDir.addLocal(0, 0, 1)
}
// (dir.up, dir.down, dir.left, dir.right) match {
// case (true, false, true, false) =>
// case _ =>
// }
walkDir.multLocal(2f)
// walkDir.multLocal(100f)
// .multLocal(tpf)
// val v = props.geom.getLocalTranslation()
// props.geom.setLocalTranslation(
// (v += walkDir)
// )
props.app.enqueue(new Runnable {
override def run(): Unit = {
// geom.setLocalTranslation(walkDir)
val v = props.geom.getLocalTranslation()
props.geom.setLocalTranslation(
(v += walkDir)
)
}
})
Behaviors.same
// receive(state = state.modify(_.walkDirection).setTo(walkDir))
} }
if (dir.right) {
trace("right")
mutWalkDir += zero +=: new Vector3f(1, 0, 0)
}
if (dir.up) {
trace("up")
mutWalkDir += zero +=: new Vector3f(0, 0, -1)
}
if (dir.down) {
trace("down")
mutWalkDir += zero +=: new Vector3f(0, 0, 1)
}
mutWalkDir.immutable
} }
walkDir
}
def stopIfNotPressed[T](pressed: Boolean, movable: T)(implicit
cm: CanMove[T]
) =
if (!pressed) cm.stop(movable)
} }
// props.app
// .enqueueScala { () =>
// 1
// }
// .map(println)(scala.concurrent.ExecutionContext.global)
// monix.eval.Task
// .fromFuture(
// props.app
// .enqueueScala { () =>
// 1
// }
// )
// .flatMap(i => monix.eval.Task(println(i)))
// .runToFuture(monix.execution.Scheduler.Implicits.global)

View File

@ -1,61 +0,0 @@
package wow.doge.mygame.implicits
import cats.Functor
import cats.Show
import cats.data.Kleisli
import cats.effect.Resource
import cats.syntax.show._
import monix.bio.IO
import monix.bio.Task
import monix.bio.UIO
trait CatsExtensions {
implicit class KleisliCompanionExt(k: Kleisli.type) {
def io[S, E, A](f: S => IO[E, A]): Kleisli[UIO, S, Either[E, A]] =
k.apply(s => f(s).attempt)
}
implicit class KleisliExt[S, E, A](k: Kleisli[UIO, S, Either[E, A]]) {
def runIO(s: S) = k.run(s).rethrow
}
implicit class ResourceCompanionExt(r: Resource.type) {
// def ioMake[UIO, R](acquire: )(release: ) = r.make()()
def ioMake[E: Show, A](
acquire: IO[E, A]
)(
release: A => UIO[Unit]
)(implicit F: Functor[UIO]): Resource[UIO, Either[E, A]] =
r.make(acquire.attempt)(a =>
IO.fromEither(a)
.onErrorHandleWith(err => IO.terminate(new Exception(err.show)))
.flatMap(release)
)
val acq = IO(1).onErrorHandleWith(_ => IO.raiseError(""))
val res =
ioMake(acq)(_ => IO.unit)
val result = res
.use {
case Left(value) => Task(Left(value))
case Right(value) => Task(Right(value))
}
.hideErrors
.rethrow
// IO.unit.bracket()
}
implicit class ResourceExt[E, A](k: Resource[UIO, Either[E, A]]) {
// def runIO(s: S) = k.run(s).rethrow
// k.use
// : IO[E, B]
// def useIO[B](f: Either[E, A] => IO[E, B]) =
// k.use(f).rethrow
// type test[A] = Tuple2[*, Double]
type IoResource[X, D] = Resource[IO[X, *], D]
val x: Resource[IO[String, *], String] =
Resource.make(IO.raiseError(""))(_ => IO.unit)
// x.use(s => Task.unit)
val x2: IoResource[String, String] =
Resource.make(UIO(""))(_ => IO.unit)
}
}

View File

@ -11,18 +11,11 @@ import com.simsilica.es.Filters
class EntityQuery(ed: EntityData) { class EntityQuery(ed: EntityData) {
private var cfilter: Option[ComponentFilter[_ <: EntityComponent]] = None private var cfilter: Option[ComponentFilter[_ <: EntityComponent]] = None
@SuppressWarnings(Array("org.wartremover.warts.MutableDataStructures"))
private val buf = collection.mutable.ListBuffer.empty[Class[_]] private val buf = collection.mutable.ListBuffer.empty[Class[_]]
def filter[T <: EntityComponent](field: String, value: Object)(implicit def filter[T <: EntityComponent](field: String, value: Object)(implicit
ev: ClassTag[T] ev: ClassTag[T]
): EntityQuery = { ): EntityQuery = {
@SuppressWarnings(
Array(
"org.wartremover.warts.AsInstanceOf",
"org.wartremover.warts.Equals"
)
)
val c = ev.runtimeClass.asInstanceOf[Class[T]] val c = ev.runtimeClass.asInstanceOf[Class[T]]
cfilter = Try(Filters.fieldEquals(c, field, value)).toOption cfilter = Try(Filters.fieldEquals(c, field, value)).toOption
this this
@ -31,12 +24,6 @@ class EntityQuery(ed: EntityData) {
def filterOr[T <: EntityComponent](operands: ComponentFilter[_ <: T]*)( def filterOr[T <: EntityComponent](operands: ComponentFilter[_ <: T]*)(
implicit ev: ClassTag[T] implicit ev: ClassTag[T]
): EntityQuery = { ): EntityQuery = {
@SuppressWarnings(
Array(
"org.wartremover.warts.AsInstanceOf",
"org.wartremover.warts.Equals"
)
)
val c = ev.runtimeClass.asInstanceOf[Class[T]] val c = ev.runtimeClass.asInstanceOf[Class[T]]
cfilter = Try(Filters.or(c, operands: _*)).toOption cfilter = Try(Filters.or(c, operands: _*)).toOption
this this
@ -45,12 +32,6 @@ class EntityQuery(ed: EntityData) {
def filterAnd[T <: EntityComponent](operands: ComponentFilter[_ <: T]*)( def filterAnd[T <: EntityComponent](operands: ComponentFilter[_ <: T]*)(
implicit ev: ClassTag[T] implicit ev: ClassTag[T]
): EntityQuery = { ): EntityQuery = {
@SuppressWarnings(
Array(
"org.wartremover.warts.AsInstanceOf",
"org.wartremover.warts.Equals"
)
)
val c = ev.runtimeClass.asInstanceOf[Class[T]] val c = ev.runtimeClass.asInstanceOf[Class[T]]
cfilter = Try(Filters.and(c, operands: _*)).toOption cfilter = Try(Filters.and(c, operands: _*)).toOption
this this
@ -64,8 +45,9 @@ class EntityQuery(ed: EntityData) {
this this
} }
def components[T <: EntityComponent](lst: Class[T]*): EntitySet = def components[T <: EntityComponent](lst: Class[T]*): EntitySet = {
ed.getEntities(lst: _*) ed.getEntities(lst: _*)
}
def result: EntitySet = def result: EntitySet =
cfilter.fold(ed.getEntities(buf.toList: _*)) { filters => cfilter.fold(ed.getEntities(buf.toList: _*)) { filters =>

View File

@ -1,74 +1,26 @@
package wow.doge.mygame.implicits package wow.doge.mygame.implicits
import javafx.beans.property.ObjectProperty
import javafx.collections.ObservableList
import javafx.event.ActionEvent
import javafx.event.EventHandler
import javafx.scene.{input => jfxsi} import javafx.scene.{input => jfxsi}
import javafx.{event => jfxe} import javafx.{event => jfxe}
import monix.bio.Task
import monix.eval.Coeval
import monix.execution.Ack import monix.execution.Ack
import monix.execution.Cancelable import monix.execution.Cancelable
import monix.execution.Scheduler
import monix.execution.cancelables.CompositeCancelable
import monix.execution.cancelables.SingleAssignCancelable
import monix.reactive.Observable import monix.reactive.Observable
import monix.reactive.Observer
import monix.reactive.OverflowStrategy import monix.reactive.OverflowStrategy
import monix.tail.Iterant
import monix.{eval => me}
import org.gerweck.scalafx.util._
import scalafx.Includes._
import scalafx.beans.property.Property
import scalafx.beans.property.ReadOnlyProperty
import scalafx.collections.ObservableBuffer
import scalafx.event.subscriptions.Subscription
import scalafx.scene.Scene import scalafx.scene.Scene
import scalafx.scene.control.ButtonBase import scalafx.scene.control.ButtonBase
import scalafx.scene.control.MenuItem
trait JavaFXMonixObservables {
import JavaFXMonixObservables._
implicit def extendedScene(scene: Scene) = new SceneExt(scene)
implicit def extendedProperty[T, J](
propery: Property[T, J]
): PropertyExt[T, J] =
new PropertyExt(propery)
implicit def extendedObjectPropety[A](prop: ObjectProperty[A]) =
new ObjectPropertyExt[A](prop)
implicit def extendedReadOnlyObjectPropety[T, J](
prop: ReadOnlyProperty[T, J]
) =
new ReadOnlyPropertyExt[T, J](prop)
implicit def extendedObservableList[A](
list: ObservableBuffer[A]
) = new ObservableListExt(list)
implicit def extendedStringObservableList(
list: ObservableList[String]
) = new StringObservableListExt(list)
implicit def extendedObjectPropertyObservableList[A](
prop: ObjectProperty[ObservableList[A]]
) = new ObjectPropertyObservableListExt(prop)
implicit def extendedButton(button: ButtonBase) = new ButtonBaseExt(button)
implicit def extendedMenuItem(item: MenuItem) = new MenuItemExt(item)
// implicit val implShowForOsRelPath = Show.fromToString[os.Path]
implicit def osRelPath2String(path: os.RelPath): String = path.toString()
}
@SuppressWarnings(Array("org.wartremover.warts.Equals"))
object JavaFXMonixObservables { object JavaFXMonixObservables {
final class SceneExt(private val scene: Scene) extends AnyVal { implicit final class SceneObservables(private val scene: Scene)
extends AnyVal {
def observableMousePressed(): Observable[jfxsi.MouseEvent] = { def observableMousePressed(): Observable[jfxsi.MouseEvent] = {
import monix.execution.cancelables.SingleAssignCancelable import monix.execution.cancelables.SingleAssignCancelable
Observable.create(OverflowStrategy.Unbounded) { sub => Observable.create(OverflowStrategy.Unbounded) { sub =>
val c = SingleAssignCancelable() val c = SingleAssignCancelable()
val l = new jfxe.EventHandler[jfxsi.MouseEvent] { val l = new jfxe.EventHandler[jfxsi.MouseEvent] {
override def handle(event: jfxsi.MouseEvent): Unit = override def handle(event: jfxsi.MouseEvent): Unit = {
if (sub.onNext(event) == Ack.Stop) c.cancel() if (sub.onNext(event) == Ack.Stop) c.cancel()
}
} }
scene.onMousePressed = l scene.onMousePressed = l
@ -81,17 +33,17 @@ object JavaFXMonixObservables {
c c
} }
} }
def observableMouseDragged(): Observable[jfxsi.MouseEvent] = { def observableMouseDragged(): Observable[jfxsi.MouseEvent] = {
import monix.execution.cancelables.SingleAssignCancelable import monix.execution.cancelables.SingleAssignCancelable
Observable.create(OverflowStrategy.Unbounded) { sub => Observable.create(OverflowStrategy.Unbounded) { sub =>
val c = SingleAssignCancelable() val c = SingleAssignCancelable()
val l = new jfxe.EventHandler[jfxsi.MouseEvent] { val l = new jfxe.EventHandler[jfxsi.MouseEvent] {
override def handle(event: jfxsi.MouseEvent): Unit = override def handle(event: jfxsi.MouseEvent): Unit = {
if (sub.onNext(event) == Ack.Stop) c.cancel() if (sub.onNext(event) == Ack.Stop) c.cancel()
}
} }
scene.onMouseDragged = l; scene.onMouseDragged = l
c := Cancelable(() => c := Cancelable(() =>
scene.removeEventHandler( scene.removeEventHandler(
jfxsi.MouseEvent.MOUSE_DRAGGED, jfxsi.MouseEvent.MOUSE_DRAGGED,
@ -103,224 +55,17 @@ object JavaFXMonixObservables {
} }
} }
final class PropertyExt[T, J](private val prop: Property[T, J]) implicit final class OnActionObservable(
extends AnyVal { private val button: ButtonBase
def -->[J1 >: J](sub: Observer[J1]) = ) extends AnyVal {
prop.onChange((a, b, c) => if (c != null) sub.onNext(c)) def observableAction(): Observable[jfxe.ActionEvent] = {
def ==>(op: Property[T, J]) =
op <== prop
def <--(
obs: Observable[T]
)(implicit s: Scheduler, c: CompositeCancelable): Unit =
c += obs.doOnNextF(v => Coeval(prop.value = v)).subscribe()
def asOption = prop.map(Option(_))
def observableChange[J1 >: J]: Observable[J1] = {
import monix.execution.cancelables.SingleAssignCancelable import monix.execution.cancelables.SingleAssignCancelable
Observable.create(OverflowStrategy.Unbounded) { sub => Observable.create(OverflowStrategy.Unbounded) { sub =>
val c = SingleAssignCancelable() val c = SingleAssignCancelable()
val canc =
prop.onChange((a, b, c1) =>
if (c1 != null && sub.onNext(c1) == Ack.Stop) c.cancel()
)
c := Cancelable(() => canc.cancel())
c
}
}
}
final class ObjectPropertyExt[A](private val prop: ObjectProperty[A])
extends AnyVal {
@SuppressWarnings(Array("org.wartremover.warts.Throw"))
def -->(sub: Observer[A]) =
prop.onChange((a, b, c) =>
if (c != null)
if (sub.onNext(c) == Ack.Stop) throw new Exception("boom")
)
def ==>(op: Property[A, A]) =
prop.onChange((a, b, c) => if (c != null) op() = c)
def <--(obs: Observable[A])(implicit s: Scheduler) =
obs.doOnNextF(v => Coeval(prop() = v)).subscribe()
def observableChange[J1 >: A]: Observable[J1] = {
import monix.execution.cancelables.SingleAssignCancelable
Observable.create(OverflowStrategy.Unbounded) { sub =>
val c = SingleAssignCancelable()
val canc = prop.onChange((_, _, c1) =>
if (c1 != null && sub.onNext(c1) == Ack.Stop) c.cancel()
)
c := Cancelable(() => canc.cancel())
c
}
}
}
final class ObservableListExt[A](
private val buffer: ObservableBuffer[A]
) extends AnyVal {
// def -->(sub: Observer[A]) =
// buffer.onChange((a, b, c) => if (c != null) sub.onNext(c))
// def -->(op: Property[A, A]) = {
// buffer.onChange((a, b, c) => if (c != null) op() = c)
// }
def <--(
obs: Observable[A]
)(implicit s: Scheduler, c: CompositeCancelable): Unit =
c += obs
.doOnNextF(v =>
for {
_ <- Coeval(
println(s"Current Thread: ${Thread.currentThread().getName}")
)
_ <- Coeval(buffer.clear())
_ <- Coeval(buffer += v)
} yield ()
)
.subscribe()
def observableChange[J1 >: A]: Observable[J1] = {
import monix.execution.cancelables.SingleAssignCancelable
Observable.create(OverflowStrategy.Unbounded) { sub =>
val c = SingleAssignCancelable()
implicit val s = sub.scheduler
val canc =
buffer.onChange((buf, _) =>
loop(sub, buf.toIterable.iterator, c).runToFuture
)
c := Cancelable(() => canc.cancel())
c
}
}
private def loop(
sub: Observer[A],
it: Iterator[A],
c: Cancelable
): Task[Unit] =
if (it.hasNext) {
val next = it.next()
Task.deferFuture(sub.onNext(next)).flatMap {
case Ack.Continue => loop(sub, it, c)
case Ack.Stop => Task(c.cancel())
}
} else Task.unit
}
final class StringObservableListExt(
private val buffer: ObservableList[String]
) extends AnyVal {
// def ++=[T](that: Seq[T])(implicit S: Show[T]): Unit =
// buffer ++= that.map(S.show)
// def ++=[T](that: Seq[T])(implicit C: CssPath[T]): Unit =
// buffer ++= that.map(C.path)
}
final class ReadOnlyPropertyExt[T, J](
private val prop: ReadOnlyProperty[T, J]
) extends AnyVal {
def -->[J1 >: J](sub: Observer[J1]) =
prop.onChange((a, b, c) => if (c != null) sub.onNext(c))
def ==>(op: Property[T, J]) =
op <== prop
def observableChange[J1 >: J]: Observable[J1] = {
import monix.execution.cancelables.SingleAssignCancelable
Observable.create(OverflowStrategy.Unbounded) { sub =>
val c = SingleAssignCancelable()
val canc = prop.onChange((a, b, c1) =>
if (c1 != null && sub.onNext(c1) == Ack.Stop) c.cancel()
)
c := Cancelable(() => canc.cancel())
c
}
}
}
final class ObjectPropertyObservableListExt[A](
private val prop: ObjectProperty[ObservableList[A]]
) extends AnyVal {
def <--(obs: Observable[Seq[A]])(implicit s: Scheduler) =
obs.doOnNext(v => me.Task(prop() = ObservableBuffer.from(v))).subscribe()
def -->(sub: Observer[A])(implicit s: Scheduler) = {
val c = SingleAssignCancelable()
val subs: Subscription = prop.onChange((a, b, c1) =>
if (c1 != null)
Iterant[Task]
.fromIterable(c1.toIterable)
.consume
.use(consume(sub, c, _))
.runToFuture
)
c := Cancelable(() => subs.cancel())
}
private def loop(sub: Observer[A], it: Iterator[A]): Task[Unit] =
if (it.hasNext) {
val next = it.next()
Task.deferFuture(sub.onNext(next)).flatMap {
case Ack.Continue => loop(sub, it)
case Ack.Stop => Task.unit
}
} else Task.unit
private def consume(
sub: Observer[A],
c: Cancelable,
consumer: Iterant.Consumer[Task, A]
): Task[Unit] =
consumer.pull.flatMap {
case Left(value) => Task.unit
case Right(value) =>
Task.deferFuture(sub.onNext(value)).flatMap {
case Ack.Continue => consume(sub, c, consumer)
case Ack.Stop => Task(c.cancel())
}
}
}
final class ObjectPropertyActionEvent(
private val prop: ObjectProperty[EventHandler[ActionEvent]]
) extends AnyVal {
// def <--(obs: Observable[ActionEvent])(implicit s: Scheduler) = {
// obs.doOnNext(v => me.Task(prop() = ObservableBuffer.from(v))).subscribe()
// }
// def -->(sub: Observer[ActionEvent]) =
// prop().
}
// def emit(prop: ObjectProperty[EventHandler[ActionEvent]]) =
final class ButtonBaseExt(private val button: ButtonBase) extends AnyVal {
def observableAction: Observable[jfxe.ActionEvent] = {
import monix.execution.cancelables.SingleAssignCancelable
Observable.create(OverflowStrategy.DropNew(50)) { sub =>
val c = SingleAssignCancelable()
val l = new jfxe.EventHandler[jfxe.ActionEvent] { val l = new jfxe.EventHandler[jfxe.ActionEvent] {
override def handle(event: jfxe.ActionEvent): Unit = override def handle(event: jfxe.ActionEvent): Unit = {
if (sub.onNext(event) == Ack.Stop) c.cancel() if (sub.onNext(event) == Ack.Stop) c.cancel()
}
} }
button.onAction = l button.onAction = l
@ -334,27 +79,4 @@ object JavaFXMonixObservables {
} }
} }
} }
final class MenuItemExt(private val item: MenuItem) extends AnyVal {
def observableAction: Observable[jfxe.ActionEvent] = {
import monix.execution.cancelables.SingleAssignCancelable
Observable.create(OverflowStrategy.DropNew(50)) { sub =>
val c = SingleAssignCancelable()
val l = new jfxe.EventHandler[jfxe.ActionEvent] {
override def handle(event: jfxe.ActionEvent): Unit =
if (sub.onNext(event) == Ack.Stop) c.cancel()
}
item.onAction = l
c := Cancelable(() =>
item.removeEventHandler(
jfxe.ActionEvent.ACTION,
l
)
)
c
}
}
}
} }

View File

@ -4,12 +4,8 @@ import scala.jdk.CollectionConverters._
import scala.reflect.ClassTag import scala.reflect.ClassTag
import akka.actor.typed.ActorRef import akka.actor.typed.ActorRef
import akka.actor.typed.Behavior
import akka.actor.typed.Props
import akka.actor.typed.Scheduler import akka.actor.typed.Scheduler
import akka.actor.typed.scaladsl.ActorContext
import akka.util.Timeout import akka.util.Timeout
import cats.Show
import com.jayfella.jme.jfx.JavaFxUI import com.jayfella.jme.jfx.JavaFxUI
import com.jme3.app.Application import com.jme3.app.Application
import com.jme3.app.SimpleApplication import com.jme3.app.SimpleApplication
@ -18,12 +14,10 @@ import com.jme3.app.state.AppStateManager
import com.jme3.asset.AssetLocator import com.jme3.asset.AssetLocator
import com.jme3.asset.AssetManager import com.jme3.asset.AssetManager
import com.jme3.bullet.BulletAppState import com.jme3.bullet.BulletAppState
import com.jme3.bullet.PhysicsSpace
import com.jme3.bullet.PhysicsTickListener import com.jme3.bullet.PhysicsTickListener
import com.jme3.bullet.collision.PhysicsCollisionEvent
import com.jme3.bullet.collision.PhysicsCollisionListener import com.jme3.bullet.collision.PhysicsCollisionListener
import com.jme3.bullet.collision.PhysicsCollisionObject
import com.jme3.bullet.collision.{
PhysicsCollisionEvent => jmePhysicsCollisionEvent
}
import com.jme3.bullet.control.BetterCharacterControl import com.jme3.bullet.control.BetterCharacterControl
import com.jme3.input.Action import com.jme3.input.Action
import com.jme3.input.InputManager import com.jme3.input.InputManager
@ -41,17 +35,12 @@ import com.jme3.scene.SceneGraphVisitor
import com.jme3.scene.Spatial import com.jme3.scene.Spatial
import com.jme3.scene.control.CameraControl.ControlDirection import com.jme3.scene.control.CameraControl.ControlDirection
import com.jme3.scene.control.Control import com.jme3.scene.control.Control
import com.jme3.{bullet => jmeb}
import com.simsilica.es.EntityComponent import com.simsilica.es.EntityComponent
import com.simsilica.es.EntityData import com.simsilica.es.EntityData
import com.simsilica.es.EntityId import com.simsilica.es.EntityId
import enumeratum._ import enumeratum._
import io.odin.meta.Position
import io.odin.meta.Render
import monix.bio.IO
import monix.bio.Task import monix.bio.Task
import monix.bio.UIO import monix.bio.UIO
import monix.eval.Coeval
import monix.execution.Ack import monix.execution.Ack
import monix.execution.Ack.Continue import monix.execution.Ack.Continue
import monix.execution.Ack.Stop import monix.execution.Ack.Stop
@ -60,9 +49,8 @@ import monix.execution.cancelables.SingleAssignCancelable
import monix.reactive.Observable import monix.reactive.Observable
import monix.reactive.OverflowStrategy import monix.reactive.OverflowStrategy
import monix.reactive.observers.Subscriber import monix.reactive.observers.Subscriber
import org.slf4j.Logger
import wow.doge.mygame.math.ImVector3f import wow.doge.mygame.math.ImVector3f
import wow.doge.mygame.utils.wrappers.jme.PhysicsSpace import wow.doge.mygame.state.MyBaseState
final case class ActionEvent(binding: Action, value: Boolean, tpf: Float) final case class ActionEvent(binding: Action, value: Boolean, tpf: Float)
final case class EnumActionEvent[T <: EnumEntry]( final case class EnumActionEvent[T <: EnumEntry](
@ -77,17 +65,10 @@ final case class EnumAnalogEvent[T <: EnumEntry](
value: Float, value: Float,
tpf: Float tpf: Float
) )
final case class PrePhysicsTickEvent(space: PhysicsSpace) final class PrePhysicsTickEvent(val space: PhysicsSpace) extends AnyVal
final case class PhysicsTickEvent(space: PhysicsSpace) final class PhysicsTickEvent(val space: PhysicsSpace) extends AnyVal
final case class CollisionEvent(
nodeA: Option[Spatial],
nodeB: Option[Spatial],
objectA: PhysicsCollisionObject,
objectB: PhysicsCollisionObject,
appliedImpulse: Function0[Float]
)
package object implicits extends JavaFXMonixObservables { package object implicits {
type PrePhysicsTickEvent = PhysicsTickEvent type PrePhysicsTickEvent = PhysicsTickEvent
type PhysicsTickObservable = type PhysicsTickObservable =
Observable[Either[PrePhysicsTickEvent, PhysicsTickEvent]] Observable[Either[PrePhysicsTickEvent, PhysicsTickEvent]]
@ -102,12 +83,6 @@ package object implicits extends JavaFXMonixObservables {
} }
implicit final class StateManagerExt(private val asm: AppStateManager) implicit final class StateManagerExt(private val asm: AppStateManager)
extends AnyVal { extends AnyVal {
@SuppressWarnings(
Array(
"org.wartremover.warts.AsInstanceOf",
"org.wartremover.warts.Equals"
)
)
def state[S <: AppState]()(implicit c: ClassTag[S]): S = def state[S <: AppState]()(implicit c: ClassTag[S]): S =
asm.getState(c.runtimeClass.asInstanceOf[Class[S]]) asm.getState(c.runtimeClass.asInstanceOf[Class[S]])
@ -127,6 +102,27 @@ package object implicits extends JavaFXMonixObservables {
def viewPort = sa.getViewPort() def viewPort = sa.getViewPort()
def rootNode = sa.getRootNode() def rootNode = sa.getRootNode()
def observableTick: Observable[Float] =
Observable.create(OverflowStrategy.Unbounded) { sub =>
val c = SingleAssignCancelable()
val as = new MyBaseState {
override def init(): Unit = {}
override def update(tpf: Float) = {
if (sub.onNext(tpf) == Ack.Stop)
c.cancel()
}
override def stop(): Unit = {}
override def onEnable() = {}
override def onDisable() = {}
}
sa.stateManager.attach(as)
c := Cancelable(() => sa.stateManager.detach(as))
c
}
} }
implicit final class AssetManagerExt(private val am: AssetManager) implicit final class AssetManagerExt(private val am: AssetManager)
@ -134,11 +130,13 @@ package object implicits extends JavaFXMonixObservables {
def registerLocator( def registerLocator(
assetPath: os.RelPath, assetPath: os.RelPath,
locator: Class[_ <: AssetLocator] locator: Class[_ <: AssetLocator]
): Unit = ): Unit = {
am.registerLocator(assetPath.toString(), locator) am.registerLocator(assetPath.toString(), locator)
}
def loadModel(assetPath: os.RelPath): Spatial = def loadModel(assetPath: os.RelPath): Spatial = {
am.loadModel(assetPath.toString()) am.loadModel(assetPath.toString())
}
} }
implicit final class BulletAppStateExt(private val bas: BulletAppState) implicit final class BulletAppStateExt(private val bas: BulletAppState)
@ -250,7 +248,7 @@ package object implicits extends JavaFXMonixObservables {
} }
// case _ => loop(subscriber, tail) // case _ => loop(subscriber, tail)
} }
case LazyList() => Task(subscriber.onComplete()) case LazyList() => Task.unit
} }
Observable.create(OverflowStrategy.Unbounded) { sub => Observable.create(OverflowStrategy.Unbounded) { sub =>
@ -296,7 +294,7 @@ package object implicits extends JavaFXMonixObservables {
Task(subscriber.onComplete()) Task(subscriber.onComplete())
} }
} }
case LazyList() => Task(subscriber.onComplete()) case LazyList() => Task.unit
} }
Observable.create(OverflowStrategy.Unbounded) { sub => Observable.create(OverflowStrategy.Unbounded) { sub =>
@ -371,8 +369,9 @@ package object implicits extends JavaFXMonixObservables {
*/ */
def askL[Res]( def askL[Res](
replyTo: ActorRef[Res] => Req replyTo: ActorRef[Res] => Req
)(implicit timeout: Timeout, scheduler: Scheduler): Task[Res] = )(implicit timeout: Timeout, scheduler: Scheduler): Task[Res] = {
Task.deferFuture(a.ask(replyTo)(timeout, scheduler)) Task.deferFuture(a.ask(replyTo)(timeout, scheduler))
}
def ??[Res]( def ??[Res](
replyTo: ActorRef[Res] => Req replyTo: ActorRef[Res] => Req
)(implicit timeout: Timeout, scheduler: Scheduler): Task[Res] = )(implicit timeout: Timeout, scheduler: Scheduler): Task[Res] =
@ -479,8 +478,8 @@ package object implicits extends JavaFXMonixObservables {
* @see [[ActionEvent]] * @see [[ActionEvent]]
* @see [[enumObservableAction]] * @see [[enumObservableAction]]
*/ */
@SuppressWarnings(Array("org.wartremover.warts.Equals")) def observableAction(mappingNames: String*): Observable[ActionEvent] = {
def observableAction(mappingNames: String*): Observable[ActionEvent] =
Observable.create(OverflowStrategy.DropOld(10)) { sub => Observable.create(OverflowStrategy.DropOld(10)) { sub =>
val c = SingleAssignCancelable() val c = SingleAssignCancelable()
val al = new ActionListener { val al = new ActionListener {
@ -488,13 +487,14 @@ package object implicits extends JavaFXMonixObservables {
binding: String, binding: String,
value: Boolean, value: Boolean,
tpf: Float tpf: Float
): Unit = ): Unit = {
if ( if (
sub.onNext(ActionEvent(Action(binding), value, tpf)) == Ack.Stop sub.onNext(ActionEvent(Action(binding), value, tpf)) == Ack.Stop
) { ) {
sub.onComplete() sub.onComplete()
c.cancel() c.cancel()
} }
}
} }
inputManager.addListener(al, mappingNames: _*) inputManager.addListener(al, mappingNames: _*)
@ -502,6 +502,7 @@ package object implicits extends JavaFXMonixObservables {
c := Cancelable(() => inputManager.removeListener(al)) c := Cancelable(() => inputManager.removeListener(al))
c c
} }
}
/** /**
* <p> * <p>
@ -524,10 +525,10 @@ package object implicits extends JavaFXMonixObservables {
* @see [[EnumActionEvent]] * @see [[EnumActionEvent]]
* @see [[enumAnalogObservable]] * @see [[enumAnalogObservable]]
*/ */
@SuppressWarnings(Array("org.wartremover.warts.Equals"))
def enumObservableAction[T <: EnumEntry]( def enumObservableAction[T <: EnumEntry](
mappingEnum: Enum[T] mappingEnum: Enum[T]
): Observable[EnumActionEvent[T]] = ): Observable[EnumActionEvent[T]] = {
Observable.create(OverflowStrategy.DropOld(10)) { sub => Observable.create(OverflowStrategy.DropOld(10)) { sub =>
val c = SingleAssignCancelable() val c = SingleAssignCancelable()
val entryNames = mappingEnum.values.map(_.entryName) val entryNames = mappingEnum.values.map(_.entryName)
@ -536,13 +537,14 @@ package object implicits extends JavaFXMonixObservables {
binding: String, binding: String,
value: Boolean, value: Boolean,
tpf: Float tpf: Float
): Unit = ): Unit = {
mappingEnum.withNameOption(binding).foreach { b => mappingEnum.withNameOption(binding).foreach { b =>
if (sub.onNext(EnumActionEvent(b, value, tpf)) == Ack.Stop) { if (sub.onNext(EnumActionEvent(b, value, tpf)) == Ack.Stop) {
sub.onComplete() sub.onComplete()
c.cancel() c.cancel()
} }
} }
}
} }
inputManager.addListener(al, entryNames: _*) inputManager.addListener(al, entryNames: _*)
@ -550,11 +552,12 @@ package object implicits extends JavaFXMonixObservables {
c := Cancelable(() => inputManager.removeListener(al)) c := Cancelable(() => inputManager.removeListener(al))
c c
} }
}
@SuppressWarnings(Array("org.wartremover.warts.Equals"))
def enumEntryObservableAction[T <: EnumEntry]( def enumEntryObservableAction[T <: EnumEntry](
mappingEnumEntry: T mappingEnumEntry: T
): Observable[EnumActionEvent[T]] = ): Observable[EnumActionEvent[T]] = {
Observable.create(OverflowStrategy.DropOld(10)) { sub => Observable.create(OverflowStrategy.DropOld(10)) { sub =>
val c = SingleAssignCancelable() val c = SingleAssignCancelable()
val al = new ActionListener { val al = new ActionListener {
@ -562,7 +565,7 @@ package object implicits extends JavaFXMonixObservables {
binding: String, binding: String,
value: Boolean, value: Boolean,
tpf: Float tpf: Float
): Unit = ): Unit = {
if ( if (
sub.onNext( sub.onNext(
EnumActionEvent(mappingEnumEntry, value, tpf) EnumActionEvent(mappingEnumEntry, value, tpf)
@ -571,6 +574,7 @@ package object implicits extends JavaFXMonixObservables {
sub.onComplete() sub.onComplete()
c.cancel() c.cancel()
} }
}
} }
inputManager.addListener(al, mappingEnumEntry.entryName) inputManager.addListener(al, mappingEnumEntry.entryName)
@ -578,9 +582,10 @@ package object implicits extends JavaFXMonixObservables {
c := Cancelable(() => inputManager.removeListener(al)) c := Cancelable(() => inputManager.removeListener(al))
c c
} }
}
def analogObservable(mappingNames: String*): Observable[AnalogEvent] = {
@SuppressWarnings(Array("org.wartremover.warts.Equals"))
def analogObservable(mappingNames: String*): Observable[AnalogEvent] =
Observable.create(OverflowStrategy.DropOld(50)) { sub => Observable.create(OverflowStrategy.DropOld(50)) { sub =>
val c = SingleAssignCancelable() val c = SingleAssignCancelable()
val al = new AnalogListener { val al = new AnalogListener {
@ -588,13 +593,14 @@ package object implicits extends JavaFXMonixObservables {
binding: String, binding: String,
value: Float, value: Float,
tpf: Float tpf: Float
): Unit = ): Unit = {
if ( if (
sub.onNext(AnalogEvent(Action(binding), value, tpf)) == Ack.Stop sub.onNext(AnalogEvent(Action(binding), value, tpf)) == Ack.Stop
) { ) {
sub.onComplete() sub.onComplete()
c.cancel() c.cancel()
} }
}
} }
inputManager.addListener(al, mappingNames: _*) inputManager.addListener(al, mappingNames: _*)
@ -602,11 +608,12 @@ package object implicits extends JavaFXMonixObservables {
c := Cancelable(() => inputManager.removeListener(al)) c := Cancelable(() => inputManager.removeListener(al))
c c
} }
}
@SuppressWarnings(Array("org.wartremover.warts.Equals"))
def enumAnalogObservable[T <: EnumEntry]( def enumAnalogObservable[T <: EnumEntry](
mappingEnum: Enum[T] mappingEnum: Enum[T]
): Observable[EnumAnalogEvent[T]] = ): Observable[EnumAnalogEvent[T]] = {
Observable.create(OverflowStrategy.DropOld(50)) { sub => Observable.create(OverflowStrategy.DropOld(50)) { sub =>
val c = SingleAssignCancelable() val c = SingleAssignCancelable()
val entryNames = mappingEnum.values.map(_.entryName) val entryNames = mappingEnum.values.map(_.entryName)
@ -615,13 +622,14 @@ package object implicits extends JavaFXMonixObservables {
binding: String, binding: String,
value: Float, value: Float,
tpf: Float tpf: Float
): Unit = ): Unit = {
mappingEnum.withNameOption(binding).foreach { b => mappingEnum.withNameOption(binding).foreach { b =>
if (sub.onNext(EnumAnalogEvent(b, value, tpf)) == Ack.Stop) { if (sub.onNext(EnumAnalogEvent(b, value, tpf)) == Ack.Stop) {
sub.onComplete() sub.onComplete()
c.cancel() c.cancel()
} }
} }
}
} }
inputManager.addListener(al, entryNames: _*) inputManager.addListener(al, entryNames: _*)
@ -629,60 +637,49 @@ package object implicits extends JavaFXMonixObservables {
c := Cancelable(() => inputManager.removeListener(al)) c := Cancelable(() => inputManager.removeListener(al))
c c
} }
}
} }
implicit final class PhysicsSpaceExt(private val space: jmeb.PhysicsSpace) implicit final class PhysicsSpaceExt(private val space: PhysicsSpace)
extends AnyVal { extends AnyVal {
@SuppressWarnings(Array("org.wartremover.warts.Equals")) def collisionObservable(): Observable[PhysicsCollisionEvent] = {
def collisionObservable(): Observable[CollisionEvent] =
Observable.create(OverflowStrategy.DropOld(50)) { sub => Observable.create(OverflowStrategy.DropOld(50)) { sub =>
val c = SingleAssignCancelable() val c = SingleAssignCancelable()
val cl = new PhysicsCollisionListener { val cl = new PhysicsCollisionListener {
override def collision(event: jmePhysicsCollisionEvent): Unit = override def collision(event: PhysicsCollisionEvent): Unit = {
if (
sub.onNext(
CollisionEvent(
Option(event.getNodeA),
Option(event.getNodeB),
event.getObjectA,
event.getObjectB,
event.getAppliedImpulse _
)
) == Ack.Stop
) c.cancel()
if (sub.onNext(event) == Ack.Stop) {
sub.onComplete()
c.cancel()
}
}
} }
space.addCollisionListener(cl) space.addCollisionListener(cl)
c := Cancelable { () => c := Cancelable(() => space.removeCollisionListener(cl))
pprint.log("stopped")
space.removeCollisionListener(cl)
}
c c
} }
}
def prePhysicsTickObservable(): Observable[PrePhysicsTickEvent] = {
@SuppressWarnings(Array("org.wartremover.warts.Equals"))
def prePhysicsTickObservable(): Observable[PrePhysicsTickEvent] =
Observable.create(OverflowStrategy.DropOld(50)) { sub => Observable.create(OverflowStrategy.DropOld(50)) { sub =>
val c = SingleAssignCancelable() val c = SingleAssignCancelable()
val cl = new PhysicsTickListener { val cl = new PhysicsTickListener {
override def prePhysicsTick( override def prePhysicsTick(space: PhysicsSpace, tpf: Float): Unit = {
space: jmeb.PhysicsSpace, val event = new PrePhysicsTickEvent(space)
tpf: Float
): Unit = {
val event = new PrePhysicsTickEvent(new PhysicsSpace(space))
if (sub.onNext(event) == Ack.Stop) { if (sub.onNext(event) == Ack.Stop) {
sub.onComplete() sub.onComplete()
c.cancel() c.cancel()
} }
} }
override def physicsTick( override def physicsTick(space: PhysicsSpace, tpf: Float): Unit = {}
space: jmeb.PhysicsSpace,
tpf: Float
): Unit = {}
} }
@ -691,23 +688,21 @@ package object implicits extends JavaFXMonixObservables {
c := Cancelable(() => space.removeTickListener(cl)) c := Cancelable(() => space.removeTickListener(cl))
c c
} }
}
def physicsTickObservable(): Observable[PhysicsTickEvent] = {
@SuppressWarnings(Array("org.wartremover.warts.Equals"))
def physicsTickObservable(): Observable[PhysicsTickEvent] =
Observable.create(OverflowStrategy.DropOld(50)) { sub => Observable.create(OverflowStrategy.DropOld(50)) { sub =>
val c = SingleAssignCancelable() val c = SingleAssignCancelable()
val cl = new PhysicsTickListener { val cl = new PhysicsTickListener {
override def prePhysicsTick( override def prePhysicsTick(
space: jmeb.PhysicsSpace, space: PhysicsSpace,
tpf: Float tpf: Float
): Unit = {} ): Unit = {}
override def physicsTick( override def physicsTick(space: PhysicsSpace, tpf: Float): Unit = {
space: jmeb.PhysicsSpace, val event = new PhysicsTickEvent(space)
tpf: Float
): Unit = {
val event = new PhysicsTickEvent(new PhysicsSpace(space))
if (sub.onNext(event) == Ack.Stop) { if (sub.onNext(event) == Ack.Stop) {
sub.onComplete() sub.onComplete()
c.cancel() c.cancel()
@ -721,6 +716,7 @@ package object implicits extends JavaFXMonixObservables {
c := Cancelable(() => space.removeTickListener(cl)) c := Cancelable(() => space.removeTickListener(cl))
c c
} }
}
//TODO Consider creating a typeclass for this //TODO Consider creating a typeclass for this
def +=(anyObject: Any) = space.add(anyObject) def +=(anyObject: Any) = space.add(anyObject)
@ -729,7 +725,7 @@ package object implicits extends JavaFXMonixObservables {
space.add(anyObject) space.add(anyObject)
space space
} }
def -(anyObject: Any) = { def :-(anyObject: Any) = {
space.remove(anyObject) space.remove(anyObject)
space space
} }
@ -741,7 +737,7 @@ package object implicits extends JavaFXMonixObservables {
space space
} }
def -(spatial: Spatial) = { def :-(spatial: Spatial) = {
space.removeAll(spatial) space.removeAll(spatial)
space space
} }
@ -750,7 +746,7 @@ package object implicits extends JavaFXMonixObservables {
} }
implicit final class Vector3fOps(private val v: Vector3f) extends AnyVal { implicit final class Vector3fExt(private val v: Vector3f) extends AnyVal {
//TODO add more operations //TODO add more operations
def +=(that: Vector3f) = v.addLocal(that) def +=(that: Vector3f) = v.addLocal(that)
def +=(f: Float) = v.addLocal(f, f, f) def +=(f: Float) = v.addLocal(f, f, f)
@ -758,7 +754,6 @@ package object implicits extends JavaFXMonixObservables {
def +=:(that: ImVector3f) = v += that def +=:(that: ImVector3f) = v += that
def *=(that: Vector3f) = v.multLocal(that) def *=(that: Vector3f) = v.multLocal(that)
def *=(that: ImVector3f) = v.multLocal(that.x, that.y, that.z) def *=(that: ImVector3f) = v.multLocal(that.x, that.y, that.z)
def *=(f: Float) = v.multLocal(f, f, f)
def *=:(that: ImVector3f) = v *= that def *=:(that: ImVector3f) = v *= that
def -=(that: Vector3f) = v.subtractLocal(that) def -=(that: Vector3f) = v.subtractLocal(that)
def -=(that: ImVector3f) = v.subtractLocal(that.x, that.y, that.z) def -=(that: ImVector3f) = v.subtractLocal(that.x, that.y, that.z)
@ -768,10 +763,9 @@ package object implicits extends JavaFXMonixObservables {
def /=:(that: ImVector3f) = v *= that def /=:(that: ImVector3f) = v *= that
def unary_- = v.negateLocal() def unary_- = v.negateLocal()
def immutable = ImVector3f(v.x, v.y, v.z) def immutable = ImVector3f(v.x, v.y, v.z)
// def transformImmutable(f: Vector3f => Vector3f) = f(v).immutable
} }
implicit final class ImVector3fOps(private val v: ImVector3f) extends AnyVal { implicit final class ImVector3fExt(private val v: ImVector3f) extends AnyVal {
def +(that: ImVector3f) = v.copy(v.x + that.x, v.y + that.y, v.z + that.z) def +(that: ImVector3f) = v.copy(v.x + that.x, v.y + that.y, v.z + that.z)
def +(f: Float): ImVector3f = v.copy(v.x + f, v.y + f, v.z + f) def +(f: Float): ImVector3f = v.copy(v.x + f, v.y + f, v.z + f)
def *(that: ImVector3f) = v.copy(v.x * that.x, v.y * that.y, v.z * that.z) def *(that: ImVector3f) = v.copy(v.x * that.x, v.y * that.y, v.z * that.z)
@ -800,105 +794,4 @@ package object implicits extends JavaFXMonixObservables {
def +=(node: scalafx.scene.Node) = jfxui.attachChild(node) def +=(node: scalafx.scene.Node) = jfxui.attachChild(node)
def -=(node: scalafx.scene.Node) = jfxui.detachChild(node) def -=(node: scalafx.scene.Node) = jfxui.detachChild(node)
} }
implicit class AkkaLoggerExt(private val logger: Logger) extends AnyVal {
private def logP[T](
x: sourcecode.Text[T],
tag: String = "",
width: Int = 100,
height: Int = 500,
indent: Int = 2,
initialOffset: Int = 0
)(implicit line: sourcecode.Line, fileName: sourcecode.FileName) = {
val tagStrs =
if (tag.isEmpty) Seq.empty
else Seq(fansi.Color.Cyan(tag), fansi.Str(" "))
// "".slice(1, -1)
val prefix = Seq(
fansi.Color.Magenta(fileName.value),
fansi.Str(":"),
fansi.Color.Green(line.value.toString),
fansi.Str(" "),
fansi.Color.Cyan(x.source),
fansi.Str(": ")
) ++ tagStrs
fansi.Str.join(
prefix ++ pprint.tokenize(x.value, width, height, indent).toSeq: _*
)
}
def warnP[T](
s: sourcecode.Text[T]
)(implicit line: sourcecode.Line, fileName: sourcecode.FileName) = {
logger.warn(logP(s).render)
s
}
def errorP[T](
s: sourcecode.Text[T]
)(implicit line: sourcecode.Line, fileName: sourcecode.FileName) = {
logger.error(logP(s).render)
s
}
def infoP[T](
s: sourcecode.Text[T]
)(implicit line: sourcecode.Line, fileName: sourcecode.FileName) = {
logger.info(logP(s).render)
s
}
def debugP[T](
s: sourcecode.Text[T]
)(implicit line: sourcecode.Line, fileName: sourcecode.FileName) = {
logger.debug(logP(s).render)
s
}
def traceP[T](
s: sourcecode.Text[T]
)(implicit line: sourcecode.Line, fileName: sourcecode.FileName) = {
logger.trace(logP(s).render)
s
}
}
implicit final class OdinLoggerExt(private val logger: io.odin.Logger[Task])
extends AnyVal {
def debugU[M](msg: => M)(implicit render: Render[M], position: Position) =
logger.debug(msg).hideErrors
def infoU[M](msg: => M)(implicit render: Render[M], position: Position) =
logger.info(msg).hideErrors
def traceU[M](msg: => M)(implicit render: Render[M], position: Position) =
logger.trace(msg).hideErrors
def warnU[M](msg: => M)(implicit render: Render[M], position: Position) =
logger.warn(msg).hideErrors
def errorU[M](msg: => M)(implicit render: Render[M], position: Position) =
logger.error(msg).hideErrors
}
implicit final class TypedActorContextExt[T](private val ctx: ActorContext[T])
extends AnyVal {
def spawnN[U](behavior: Behavior[U], props: Props = Props.empty)(implicit
name: sourcecode.Name
): ActorRef[U] =
ctx.spawn(behavior, name.value, props)
}
implicit final class MonixEvalTaskExt[T](private val task: monix.eval.Task[T])
extends AnyVal {
def toIO = IO.deferAction(implicit s => IO.from(task))
}
implicit final class MonixBioTaskExt[T](private val task: monix.bio.Task[T])
extends AnyVal {
def toTask =
monix.eval.Task.deferAction(implicit s => monix.eval.Task.from(task))
}
implicit final class CoevalEitherExt[L, R](
private val coeval: Coeval[Either[L, R]]
) extends AnyVal {
def toIO = coeval.to[Task].hideErrors.rethrow
}
implicit val showForOsPath = Show.fromToString[os.Path]
} }

View File

@ -1,38 +1,35 @@
package wow.doge.mygame.launcher package wow.doge.mygame.launcher
import cats.effect.Resource import scala.concurrent.duration.FiniteDuration
import scala.concurrent.duration._
import cats.effect.concurrent.Deferred import cats.effect.concurrent.Deferred
import cats.kernel.Eq
import javafx.application.Platform import javafx.application.Platform
import monix.bio.Task import monix.bio.Task
import monix.bio.UIO
import monix.catnap.CancelableF import monix.catnap.CancelableF
import monix.execution.CancelablePromise import monix.eval.{Task => ETask}
import monix.reactive.Observable import monix.reactive.Observable
import monix.{eval => me}
import scalafx.Includes._ import scalafx.Includes._
import scalafx.application.JFXApp import scalafx.application.JFXApp
import scalafx.application.JFXApp.PrimaryStage import scalafx.application.JFXApp.PrimaryStage
import scalafx.scene.control.Button import scalafx.scene.control.Button
import scalafx.stage.StageStyle import scalafx.stage.StageStyle
import wow.doge.mygame.executors.Schedulers.FxScheduler import wow.doge.mygame.executors.Schedulers
import wow.doge.mygame.implicits._ import wow.doge.mygame.implicits.JavaFXMonixObservables._
import wow.doge.mygame.utils.IOUtils._ import wow.doge.mygame.utils.IOUtils._
object Launcher { object Launcher {
sealed trait LauncherResult sealed trait LauncherResult
object LauncherResult { object LauncherResult {
case object LaunchGame extends LauncherResult case object LaunchGame extends LauncherResult
case object Exit extends LauncherResult case object Exit extends LauncherResult
implicit val eqForLR = Eq.fromUniversalEquals[LauncherResult]
} }
class Props( class Props(
// val schedulers: Schedulers, val schedulers: Schedulers,
val fxScheduler: FxScheduler,
val signal: Deferred[Task, LauncherResult] val signal: Deferred[Task, LauncherResult]
) { ) {
val create = UIO(new Launcher(this)) val create = Task(new Launcher(this))
} }
} }
class Launcher private (props: Launcher.Props) { class Launcher private (props: Launcher.Props) {
@ -43,7 +40,8 @@ class Launcher private (props: Launcher.Props) {
} }
private lazy val launchAction = private lazy val launchAction =
launchButton.observableAction launchButton
.observableAction()
.doOnNext(_ => toTask(props.signal.complete(LauncherResult.LaunchGame))) .doOnNext(_ => toTask(props.signal.complete(LauncherResult.LaunchGame)))
private lazy val exitButton = new Button { private lazy val exitButton = new Button {
@ -51,7 +49,8 @@ class Launcher private (props: Launcher.Props) {
} }
private lazy val exitAction = private lazy val exitAction =
exitButton.observableAction exitButton
.observableAction()
.doOnNext(_ => toTask(props.signal.complete(LauncherResult.Exit))) .doOnNext(_ => toTask(props.signal.complete(LauncherResult.Exit)))
private lazy val _scene = private lazy val _scene =
@ -61,25 +60,22 @@ class Launcher private (props: Launcher.Props) {
scene = _scene scene = _scene
} }
private def internal(startSignal: CancelablePromise[Unit]) = private lazy val internal = new JFXApp {
new JFXApp { stage = _stage
stage = _stage stage.initStyle(StageStyle.Undecorated)
stage.initStyle(StageStyle.Undecorated) // ResizeHelper.addResizeListener(stage)
// ResizeHelper.addResizeListener(stage) }
startSignal.success(())
}
private lazy val sceneDragObservable = { private lazy val sceneDragObservable = {
val mpo = _scene.observableMousePressed() lazy val mpo = _scene.observableMousePressed()
val mdo = _scene.observableMouseDragged() lazy val mdo = _scene.observableMouseDragged()
mpo.concatMap(pressEvent => mpo.mergeMap(pressEvent =>
mdo.doOnNext(dragEvent => mdo.doOnNext(dragEvent =>
me.Task(pprint.log("emitted")) >> ETask(
me.Task( _stage.setX(dragEvent.screenX - pressEvent.sceneX)
_stage.setX(dragEvent.screenX - pressEvent.sceneX) ) >>
) >> ETask(
me.Task(
_stage.setY( _stage.setY(
dragEvent.screenY - pressEvent.sceneY dragEvent.screenY - pressEvent.sceneY
) )
@ -88,32 +84,21 @@ class Launcher private (props: Launcher.Props) {
) )
} }
def init = def init(delay: FiniteDuration = 2000.millis) =
Resource.make(for { for {
_ <- Task(Platform.setImplicitExit(false)) _ <- Task(Platform.setImplicitExit(false))
startSignal <- Task(CancelablePromise[Unit]())
delegate <- Task(internal(startSignal)) fib <- Task(internal.main(Array.empty)).start
combinedFib <- _ <- Task.sleep(500.millis)
Task sceneDragFib <- toIO(sceneDragObservable.completedL).start
.parZip2( fib2 <- toIO(
Task(delegate.main(Array.empty)), Observable(launchAction, exitAction).merge
Task.fromCancelablePromise(startSignal) >> toIO( .doOnNext(_ =>
me.Task.parSequence( ETask(internal.stage.close()).executeOn(props.schedulers.fx)
List(
sceneDragObservable.completedL,
Observable(launchAction, exitAction).merge
.doOnNext(_ =>
me.Task(delegate.stage.close())
.executeOn(props.fxScheduler.value)
)
.completedL
)
)
)
) )
.start .completedL
_ <- Task.fromCancelablePromise(startSignal) ).start
c <- CancelableF[Task](combinedFib.cancel) c <- CancelableF[Task](fib.cancel >> fib2.cancel >> sceneDragFib.cancel)
} yield c)(_.cancel) } yield (c)
} }

View File

@ -1,44 +1,15 @@
package wow.doge.mygame.math; package wow.doge.mygame.math;
import cats.Show import Math.{sqrt, pow}
import cats.kernel.Eq
import cats.syntax.eq._
import math.{abs, pow, sqrt} case class ImVector3f(x: Float = 0f, y: Float = 0f, z: Float = 0f)
final case class ImVector3f(x: Float, y: Float, z: Float)
object ImVector3f { object ImVector3f {
//format: off val ZERO = ImVector3f(0, 0, 0)
val Zero = ImVector3f(0, 0, 0) val UNIT_X = ImVector3f(1, 0, 0)
val UnitX = ImVector3f(1, 0, 0) val UNIT_Y = ImVector3f(0, 1, 0)
val UnitY = ImVector3f(0, 1, 0) val UNIT_Z = ImVector3f(0, 0, 1)
val UnitZ = ImVector3f(0, 0, 1)
val Unit = ImVector3f(1, 1, 1)
val Max = ImVector3f(Float.MaxValue, Float.MaxValue, Float.MaxValue)
val Min = ImVector3f(Float.MinValue, Float.MinValue, Float.MinValue)
//format: on
implicit val show = new Show[ImVector3f] {
def format(f: Float) = f.formatted("%.2f")
override def show(t: ImVector3f): String =
s"ImVector3f(${format(t.x)},${format(t.y)},${format(t.z)})"
}
implicit val eq = Eq.fromUniversalEquals[ImVector3f]
private def squareDiff(f1: Float, f2: Float) =
pow(f1.toDouble - f2.toDouble, 2)
// private def squareDiff2(f1: Float, f2: Float) = pow((f1 - f2).toDouble, 2)
def dst(v1: ImVector3f, v2: ImVector3f): Double =
if (v1 === v2) 0
else {
val total =
squareDiff(v1.x, v2.x) + squareDiff(v1.y, v2.y) +
squareDiff(v1.z, v2.z)
sqrt(total)
}
def manhattanDst(v1: ImVector3f, v2: ImVector3f) =
if (v1 === v2) 0 else abs(v1.x - v2.x) + abs(v1.y - v2.y) + abs(v1.z - v2.z)
def dst(v1: ImVector3f, v2: ImVector3f) =
sqrt(pow(v1.x - v2.x, 2) + pow(v1.y - v2.y, 2) + pow(v1.z - v2.z, 2))
} }

View File

@ -4,23 +4,8 @@ import scala.reflect.ClassTag
import akka.actor.typed.ActorRef import akka.actor.typed.ActorRef
import akka.actor.typed.Behavior import akka.actor.typed.Behavior
import akka.actor.typed.PostStop
import akka.actor.typed.Scheduler
import akka.actor.typed.SpawnProtocol
import akka.actor.typed.scaladsl.AskPattern._
import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.scaladsl.Behaviors
import akka.event.EventStream import akka.event.EventStream
import akka.util.Timeout
import cats.syntax.show._
import monix.bio.UIO
import monix.execution.Ack
import monix.execution.Cancelable
import monix.execution.cancelables.SingleAssignCancelable
import monix.reactive.Observable
import monix.reactive.OverflowStrategy
import wow.doge.mygame.implicits._
import wow.doge.mygame.subsystems.events.EventBus.ObservableSubscription
import wow.doge.mygame.utils.AkkaUtils
/** /**
* A (typed) event bus * A (typed) event bus
@ -36,103 +21,36 @@ object EventBus {
final case class Subscribe[A, E <: A](subscriber: ActorRef[E])(implicit final case class Subscribe[A, E <: A](subscriber: ActorRef[E])(implicit
classTag: ClassTag[E] classTag: ClassTag[E]
) extends Command[A] { ) extends Command[A] {
def topic: Class[_] = classTag.runtimeClass def topic: Class[_] = classTag.runtimeClass
} }
final case class Unsubscribe[A, E <: A](subscriber: ActorRef[E]) final case class Unsubscribe[A, E <: A](subscriber: ActorRef[E])
extends Command[A] extends Command[A]
final case class ObservableSubscription[A, E <: A]( def apply[A](): Behavior[EventBus.Command[A]] =
replyTo: ActorRef[Observable[E]]
)(implicit classTag: ClassTag[E])
extends Command[A] {
def ct = classTag
}
def apply[A: ClassTag]()(implicit
timeout: Timeout,
spawnProtocol: ActorRef[SpawnProtocol.Command]
): Behavior[EventBus.Command[A]] =
Behaviors.setup { ctx => Behaviors.setup { ctx =>
val eventStream = new EventStream(ctx.system.classicSystem) val eventStream = new EventStream(ctx.system.classicSystem)
implicit val scheduler = ctx.system.scheduler
new EventBus().eventStreamBehavior(eventStream) new EventBus().eventStreamBehavior(eventStream)
} }
@SuppressWarnings(Array("org.wartremover.warts.Equals"))
def observable[A, B <: A](eventBus: ActorRef[EventBus.Command[A]])(implicit
timeout: Timeout,
scheduler: Scheduler,
spawnProtocol: ActorRef[SpawnProtocol.Command],
ct: ClassTag[A],
ct2: ClassTag[B]
) =
Observable.create[B](OverflowStrategy.DropOld(50)) { sub =>
implicit val s = sub.scheduler
val c = SingleAssignCancelable()
val behavior = Behaviors
.receive[B] { (ctx, msg) =>
ctx.log.traceP(s"Emitted ${msg.toString}")
if (sub.onNext(msg) == Ack.Stop) {
c.cancel()
Behaviors.stopped
} else Behaviors.same
}
.receiveSignal {
case (_, PostStop) =>
sub.onComplete()
Behaviors.same
}
val actor =
AkkaUtils
.spawnActorL(
behavior,
actorName =
Some(show"eventBusObservable-${ct.toString.split("""\.""").last}")
)
.mapError(err => new Throwable(err.toString))
.tapError {
case ex => UIO(sub.onError(ex))
}
(for {
a <- actor
_ <- eventBus !! Subscribe(a)
_ <- UIO(c := Cancelable(() => eventBus ! Unsubscribe(a)))
} yield ()).runToFuture
c
}
} }
class EventBus[A] { class EventBus[B] {
import akka.actor.typed.scaladsl.adapter._ import akka.actor.typed.scaladsl.adapter._
private def eventStreamBehavior( private def eventStreamBehavior(
eventStream: EventStream eventStream: akka.event.EventStream
)(implicit ): Behavior[EventBus.Command[B]] =
timeout: Timeout, Behaviors.receiveMessage {
scheduler: Scheduler, case EventBus.Publish(event, name) =>
spawnProtocol: ActorRef[SpawnProtocol.Command], eventStream.publish(event)
ct: ClassTag[A] Behaviors.same
): Behavior[EventBus.Command[A]] = case s @ EventBus.Subscribe(subscriber) =>
Behaviors.setup { ctx => eventStream.subscribe(subscriber.toClassic, s.topic)
Behaviors.receiveMessage { Behaviors.same
case EventBus.Publish(event, name) => case EventBus.Unsubscribe(subscriber) =>
eventStream.publish(event) eventStream.unsubscribe(subscriber.toClassic)
Behaviors.same Behaviors.same
case s @ EventBus.Subscribe(subscriber) =>
eventStream.subscribe(subscriber.toClassic, s.topic)
Behaviors.same
case EventBus.Unsubscribe(subscriber) =>
eventStream.unsubscribe(subscriber.toClassic)
Behaviors.same
case s @ ObservableSubscription(replyTo) =>
val obs = EventBus.observable(
ctx.self
)(timeout, scheduler, spawnProtocol, ct, s.ct)
replyTo ! obs
Behaviors.same
}
} }
} }

View File

@ -1,17 +1,14 @@
package wow.doge.mygame.subsystems.events package wow.doge.mygame.subsystems.events
import cats.Show
import wow.doge.mygame.game.entities.CharacterStats
sealed trait Event sealed trait Event
case object BulletFired extends Event final case object BulletFired extends Event
// type BulletFired = BulletFired.type // type BulletFired = BulletFired.type
final case class EventWithData(data: Int) extends Event final case class EventWithData(data: Int) extends Event
sealed trait TickEvent extends Event sealed trait TickEvent extends Event
object TickEvent { object TickEvent {
case object RenderTick extends TickEvent final case object RenderTick extends TickEvent
case object PhysicsTick extends TickEvent final case object PhysicsTick extends TickEvent
} }
sealed trait EntityMovementEvent extends Event sealed trait EntityMovementEvent extends Event
@ -25,14 +22,3 @@ object EntityMovementEvent {
final case class MovedDown(name: String, pressed: Boolean) final case class MovedDown(name: String, pressed: Boolean)
extends EntityMovementEvent extends EntityMovementEvent
} }
sealed trait StatsEvent extends Event
object StatsEvent {
final case class DamageEvent(
hitBy: String,
victimName: String,
amount: CharacterStats.DamageHealth
) extends StatsEvent
implicit val show = Show.fromToString[StatsEvent]
}

View File

@ -1,9 +1,9 @@
package wow.doge.mygame.subsystems.events package wow.doge.mygame.subsystems.events
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.reflect.ClassTag
import akka.actor.typed.ActorRef import akka.actor.typed.ActorRef
import akka.actor.typed.ActorSystem
import akka.actor.typed.LogOptions import akka.actor.typed.LogOptions
import akka.actor.typed.Props import akka.actor.typed.Props
import akka.actor.typed.SpawnProtocol import akka.actor.typed.SpawnProtocol
@ -11,62 +11,46 @@ import akka.actor.typed.SupervisorStrategy
import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.scaladsl.Behaviors
import akka.util.Timeout import akka.util.Timeout
import com.typesafe.scalalogging.{Logger => SLogger} import com.typesafe.scalalogging.{Logger => SLogger}
import monix.bio.IO
import org.slf4j.event.Level import org.slf4j.event.Level
import wow.doge.mygame.AppError
import wow.doge.mygame.AppError.TimeoutError
import wow.doge.mygame.implicits._ import wow.doge.mygame.implicits._
import wow.doge.mygame.subsystems.events.Event import wow.doge.mygame.subsystems.events.Event
import wow.doge.mygame.subsystems.events.EventBus import wow.doge.mygame.subsystems.events.EventBus
import wow.doge.mygame.subsystems.events.TickEvent import wow.doge.mygame.subsystems.events.TickEvent
import wow.doge.mygame.types.AkkaScheduler
class EventsModule( class EventsModule(spawnProtocol: ActorSystem[SpawnProtocol.Command]) {
scheduler: AkkaScheduler, implicit lazy val s = spawnProtocol.scheduler
spawnProtocol: ActorRef[SpawnProtocol.Command]
) {
import EventsModule._
implicit val s = scheduler.value
implicit val sp = spawnProtocol
implicit val timeout = Timeout(1.second)
val eventBusLogger = SLogger[EventBus[_]] implicit lazy val timeout = Timeout(1.second)
val playerEventBus: IO[AppError, GameEventBus[PlayerEvent]] = lazy val eventBusLogger = SLogger[EventBus[_]]
createEventBus[PlayerEvent]()
// val playerCameraEventBusTask = lazy val playerEventBusTask =
// createEventBus[PlayerCameraEvent](Level.DEBUG) createEventBus[PlayerEvent]("playerEventBus")
val tickEventBus: IO[AppError, GameEventBus[TickEvent]] = // lazy val playerCameraEventBusTask =
createEventBus[TickEvent](Level.TRACE) // createEventBus[PlayerCameraEvent]("playerCameraEventBus", Level.DEBUG)
val mainEventBus: IO[AppError, GameEventBus[Event]] = createEventBus[Event]() lazy val tickEventBusTask =
createEventBus[TickEvent]("tickEventBus", Level.TRACE)
def createEventBus[T: ClassTag]( lazy val mainEventBusTask = createEventBus[Event]("mainEventBus")
logLevel: Level = Level.DEBUG,
busName: Option[String] = None def createEventBus[T](busName: String, logLevel: Level = Level.DEBUG) =
)(implicit name: sourcecode.Name) = spawnProtocol.askL(
spawnProtocol SpawnProtocol.Spawn[EventBus.Command[T]](
.askL( Behaviors.logMessages(
SpawnProtocol.Spawn[EventBus.Command[T]]( logOptions = LogOptions()
Behaviors.logMessages( .withLevel(logLevel)
logOptions = LogOptions() .withLogger(eventBusLogger.underlying),
.withLevel(logLevel) Behaviors
.withLogger(eventBusLogger.underlying), .supervise(EventBus[T]())
Behaviors .onFailure[Exception](SupervisorStrategy.restart)
.supervise(EventBus[T]()) ),
.onFailure[Exception]( busName,
SupervisorStrategy.restart.withLimit(2, 100.millis) Props.empty,
) _
),
busName.fold(name.value)(identity),
Props.empty,
_
)
) )
.onErrorHandleWith(TimeoutError.from) )
} }
object EventsModule { object EventsModule {

View File

@ -0,0 +1 @@
package wow.doge.mygame.subsystems.events

View File

@ -1,11 +1,9 @@
package wow.doge.mygame.subsystems.events package wow.doge.mygame.subsystems.events
import cats.Show
sealed trait PlayerEvent sealed trait PlayerEvent
sealed trait PlayerMovementEvent extends PlayerEvent sealed trait PlayerMovementEvent extends PlayerEvent
object PlayerMovementEvent { final object PlayerMovementEvent {
final case class PlayerMovedLeft(pressed: Boolean) extends PlayerMovementEvent final case class PlayerMovedLeft(pressed: Boolean) extends PlayerMovementEvent
final case class PlayerMovedRight(pressed: Boolean) final case class PlayerMovedRight(pressed: Boolean)
extends PlayerMovementEvent extends PlayerMovementEvent
@ -13,17 +11,16 @@ object PlayerMovementEvent {
extends PlayerMovementEvent extends PlayerMovementEvent
final case class PlayerMovedBackward(pressed: Boolean) final case class PlayerMovedBackward(pressed: Boolean)
extends PlayerMovementEvent extends PlayerMovementEvent
case object PlayerJumped extends PlayerMovementEvent final case object PlayerJumped extends PlayerMovementEvent
// case object PlayerTurnedRight extends PlayerMovementEvent // final case object PlayerTurnedRight extends PlayerMovementEvent
// case object PlayerTurnedLeft extends PlayerMovementEvent // final case object PlayerTurnedLeft extends PlayerMovementEvent
implicit val show = Show.fromToString[PlayerMovementEvent]
} }
sealed trait PlayerCameraEvent extends PlayerEvent sealed trait PlayerCameraEvent extends PlayerEvent
object PlayerCameraEvent { final object PlayerCameraEvent {
case object CameraLeft extends PlayerCameraEvent final case object CameraLeft extends PlayerCameraEvent
case object CameraRight extends PlayerCameraEvent final case object CameraRight extends PlayerCameraEvent
case object CameraMovedUp extends PlayerCameraEvent final case object CameraMovedUp extends PlayerCameraEvent
case object CameraMovedDown extends PlayerCameraEvent final case object CameraMovedDown extends PlayerCameraEvent
} }

View File

@ -4,10 +4,9 @@ import java.nio.file.NoSuchFileException
import scala.collection.View import scala.collection.View
import scala.collection.immutable.ArraySeq import scala.collection.immutable.ArraySeq
import scala.util.Try
import cats.Show
import cats.implicits._ import cats.implicits._
import cats.kernel.Eq
import io.circe._ import io.circe._
import io.circe.generic.JsonCodec import io.circe.generic.JsonCodec
import io.circe.generic.semiauto._ import io.circe.generic.semiauto._
@ -16,8 +15,6 @@ import monix.bio.IO
import monix.bio.UIO import monix.bio.UIO
import monix.reactive.Consumer import monix.reactive.Consumer
import monix.reactive.Observable import monix.reactive.Observable
import wow.doge.mygame.implicits._
import wow.doge.mygame.utils.readAsyncL
import wow.doge.mygame.utils.IOUtils import wow.doge.mygame.utils.IOUtils
@JsonCodec @JsonCodec
@ -26,68 +23,65 @@ final case class Test1(hello1: String, hello2: String)
final case class Test2(hello1: String) final case class Test2(hello1: String)
final case class Plugin(name: String, priority: Int) final case class Plugin(name: String, priority: Int)
object Plugin { object Plugin {
implicit val decoder: Decoder[Plugin] = deriveDecoder implicit val pluginFormat: Decoder[Plugin] = deriveDecoder
implicit val show = Show.fromToString[Plugin]
implicit val eq = Eq.fromUniversalEquals[Plugin]
} }
object ModdingSystem { object ModdingSystem {
sealed trait Error extends Product with Serializable sealed trait Error extends Serializable with Product
final case class CouldNotDecode(cause: String) extends Error final case class CouldNotDecode(cause: String) extends Error
final case class ParseFailure(cause: io.circe.ParsingFailure) extends Error final case class ParseFailure(cause: String) extends Error
final case class DecodingFailure(cause: io.circe.DecodingFailure) final case class FileNotFound(fileName: String) extends Error
extends Error case object GenericError extends Error
final case class FileNotFound(path: os.Path) extends Error
object Error {
implicit val show = Show.fromToString[Error]
implicit val eq = Eq.fromUniversalEquals[Error]
}
val x: IO[Error, String] = def readPluginsList(dir: os.Path): Try[Either[Error, ArraySeq[Plugin]]] =
IOUtils.liftErrors(readAsyncL(os.pwd / "plugins.json")) { Try(
case _: FileNotFoundException => parse(os.read(dir / "plugins.json"))
IO.raiseError(FileNotFound(os.pwd / "plugins.json")) .map(
case _: NoSuchFileException => _.as[ArraySeq[Plugin]]
IO.raiseError(FileNotFound(os.pwd / "plugins.json")) .leftMap(e => CouldNotDecode(e.getMessage()))
} )
.leftMap((e: ParsingFailure) => ParseFailure(e.message))
.flatten
)
// .toValidated
def readPluginsList(dir: os.Path): IO[Error, ArraySeq[Plugin]] = def findPluginFiles(dir: os.Path): View[os.Path] =
readAsyncL(dir / "plugins.json") os.list(dir)
.onErrorHandleWith { .view
case _: FileNotFoundException => .filter(f => f.ext == "json" && f.baseName.endsWith("plugin"))
IO.raiseError(FileNotFound(dir / "plugins.json"))
case _: NoSuchFileException =>
IO.raiseError(FileNotFound(dir / "plugins.json"))
case other => IO.terminate(other)
}
.flatMap(files =>
IO.fromEither(parse(files))
.mapError(ParseFailure)
.map(_.as[ArraySeq[Plugin]])
)
.flatMap(result => IO.fromEither(result).mapError(DecodingFailure))
def findAndReadPluginFiles( def findAndReadPluginFiles(
dir: os.Path, dir: os.Path,
plugins: ArraySeq[Plugin] plugins: ArraySeq[Plugin]
) = ) =
IO plugins
.parTraverse(plugins.sortBy(_.priority))(p => .sortBy(_.priority)
readAsyncL(dir / os.RelPath(p.name + ".plugin.json")) .view
.onErrorHandleWith { .map(p =>
case _: FileNotFoundException => p ->
IO.raiseError(FileNotFound(dir)) Either
case _: NoSuchFileException => .catchNonFatal {
IO.raiseError(FileNotFound(dir)) val path = dir / os.RelPath(p.name + ".plugin.json")
case other => IO.terminate(other) os.read(path)
} }
.attempt .leftMap {
.map(r => p -> r) case _: FileNotFoundException =>
FileNotFound(p.name)
case _: NoSuchFileException => FileNotFound(p.name)
case e => GenericError
}
) )
.map(_.partitionMap { .partitionMap {
case p -> Left(value) => Left(p -> value) case (p, either) =>
case p -> Right(value) => Right(p -> value) 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)]) = def parsePluginFiles(files: View[(Plugin, String)]) =
files files
@ -99,59 +93,74 @@ object ModdingSystem {
case (p, Right(value)) => Right(p -> value) case (p, Right(value)) => Right(p -> value)
} }
val emptyJson = Json.fromString("empty") def foldMerge(iterable: Iterable[Json]) =
iterable.foldLeft(Json.fromString("empty")) {
case (json, io.circe.Json.Null) => json //ignore null values
case (json, value) => json.deepMerge(value)
}
val foldFn: (Json, Json) => Json = { def mergePluginData(plugins: View[(Plugin, Json)]) = {
case (json, Json.Null) => json //ignore null values foldMerge(plugins.map {
case (json, value) => json.deepMerge(value) case (p, json) => json
})
} }
def mergePluginDataConsumer = def mergePluginDataConsumer =
Consumer.foldLeft[Json, Json](emptyJson)(foldFn) Consumer.foldLeft[Json, Json](Json.fromString("empty")) {
case (json, io.circe.Json.Null) => json
case (json, that) => json.deepMerge(that)
}
def loadBalancedPluginDataMerger = def loadBalancedPluginDataMerger =
Consumer Consumer
.loadBalance(parallelism = 2, mergePluginDataConsumer) .loadBalance(parallelism = 2, mergePluginDataConsumer)
.map(_.foldLeft(emptyJson)(foldFn)) .map(foldMerge)
def run(wd: os.Path = os.pwd) = // 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 { for {
plugins <- readPluginsList(wd) plugins <- IO.fromTryEither(readPluginsList(wd))
(readFailures, readSuccesses) <- findAndReadPluginFiles(wd, plugins) (readFailures, readSuccesses) <- UIO(findAndReadPluginFiles(wd, plugins))
(parseFailures, parseSuccesses) = parsePluginFiles(readSuccesses.view) (parseFailures, parseSuccesses) <- UIO(parsePluginFiles(readSuccesses))
res <- UIO.parMap5( // res <- UIO(mergePluginData(parseSuccesses))
UIO(readFailures.to(List)), res <-
UIO.pure(readSuccesses), IOUtils
UIO(parseFailures.to(List)), .toIO(
UIO(parseSuccesses.to(List)), Observable
Observable .fromIterable(parseSuccesses)
.fromIterable(parseSuccesses) .map { case (p, json) => json }
.map { case (p, json) => json } .consumeWith(loadBalancedPluginDataMerger)
.consumeWith(loadBalancedPluginDataMerger) )
.toIO .onErrorHandle(e => GenericError)
.hideErrors _ <- UIO {
)(Result.apply) println(s"Read Successes = ${readSuccesses.to(Seq)}")
} yield res 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 ()
def log(res: Result) = // monix.eval.Task.deferAction(implicit s =>
UIO { // ModdingSystem
pprint.log(show"Read Successes = ${res.readSuccesses}") // .test()
pprint.log(show"Read Failures = ${res.readFailures}") // .leftMap(e => new Throwable(e.toString()))
pprint.log(show"Parse Successes = ${res.parseSuccesses}") // .to[monix.eval.Task]
pprint.log(show"Parse Failures = ${res.parseFailures}") // )
pprint.log(show"Merged = ${res.pluginJson}")
}
final case class Result(
readFailures: List[(Plugin, Error)],
readSuccesses: List[(Plugin, String)],
parseFailures: List[(Plugin, ParsingFailure)],
parseSuccesses: List[(Plugin, Json)],
pluginJson: Json
)
object Result {
implicit val show = Show.fromToString[Result]
// implicit val eq = Eq.fromUniversalEquals[Error]
}
// def test3(wd: os.Path = os.pwd) = {
// (readPluginsList(os.pwd).toValidatedNec)
// }
} }

View File

@ -1,356 +0,0 @@
package wow.doge.mygame.subsystems.scriptsystem
import javax.script.ScriptEngine
import javax.script.ScriptEngineManager
import javax.script.ScriptException
import scala.concurrent.duration._
import ammonite.main.Defaults
import ammonite.runtime.Storage.Folder
import ammonite.util.Res
import ammonite.util.Res.Failure
import cats.Show
import cats.effect.Resource
import cats.effect.concurrent.Deferred
import cats.syntax.either._
import cats.syntax.flatMap._
import cats.syntax.show._
import com.softwaremill.macwire._
import com.softwaremill.tagging._
import groovy.util.GroovyScriptEngine
import groovy.util.ResourceException
import io.odin.Logger
import monix.bio.IO
import monix.bio.Task
import monix.bio.UIO
import monix.catnap.ConcurrentQueue
import monix.catnap.MVar
import monix.reactive.Observable
import monix.{eval => me}
import wow.doge.mygame.implicits._
import wow.doge.mygame.utils.readAsyncL
import wow.doge.mygame.executors.Schedulers
trait Requestable[A] {
protected def queue: ConcurrentQueue[Task, A]
@SuppressWarnings(Array("org.wartremover.warts.OptionPartial"))
def request[T](
compileRequest: Deferred[Task, T] => A
)(implicit timeout: FiniteDuration) =
for {
d <- Deferred[Task, T]
req = compileRequest(d)
_ <- queue.offer(req)
res <- d.get.timeout(timeout).map(_.get)
} yield res
// def request[T](
// compileRequest: Deferred[Task, T] => A
// )(implicit timeout: FiniteDuration): IO[AppError, T] =
// for {
// d <- Deferred[Task, T].hideErrors
// req = compileRequest(d)
// _ <- queue.offer(req).hideErrors
// res <-
// d.get.hideErrors
// .timeout(timeout)
// .flatMap(o => IO.fromOption(o, AppError.TimeoutError("Timed out")))
// } yield res
}
class ScriptCompiler private (
_queue: ConcurrentQueue[Task, ScriptCompiler.Command]
) extends Requestable[ScriptCompiler.Command] {
override protected def queue = _queue
// def tell(item: Command) = queue.offer(item)
}
@SuppressWarnings(Array("org.wartremover.warts.Any"))
object ScriptCompiler {
sealed trait State
case object Idle extends State
case object Active extends State
/**
* script representation
*/
sealed trait ScriptTag
type ScriptObject = Any @@ ScriptTag
sealed trait KotlinEngineTag
type KotlinScriptEngine = ScriptEngine @@ KotlinEngineTag
sealed trait Error extends Product with Serializable
final case class AmmoniteFailure(error: Res.Failure) extends Error
final case class AmmoniteException(error: Res.Exception) extends Error
final case class ScriptExceptionError(error: ScriptException) extends Error
final case class ResourceExceptionError(error: ResourceException)
extends Error
final case class GroovyScriptExceptionError(
error: groovy.util.ScriptException
) extends Error
final case class SomeError(reason: String) extends Error
sealed trait Command
final case class GetScript(
path: os.Path,
result: Deferred[Task, ScriptResult],
force: Boolean
) extends Command
// final case class GetData(result: Deferred[Task, Data])
final case class ObservableData(result: Deferred[Task, Observable[Data]])
extends Command
// final case class CompileAll(paths: Seq[os.Path]) extends Command
type ScriptsMap = Map[os.Path, Any]
type ScriptResult = Either[Error, Any]
sealed trait ScriptType
case object ScalaType extends ScriptType
case object KotlinType extends ScriptType
case object GroovyType extends ScriptType
val defaultScalaRunner =
ammonite
.Main(
storageBackend = new Folder(
// os.pwd / "target"
Defaults.ammoniteHome,
isRepl = false
)
)
val defaultKotlinRunner: KotlinScriptEngine = {
val manager = new ScriptEngineManager()
val engine = manager.getEngineByExtension("main.kts")
engine.taggedWith[KotlinEngineTag]
}
val defaultGroovyRunner: GroovyScriptEngine =
new GroovyScriptEngine(os.pwd.toString)
final case class Data(scriptsMap: ScriptsMap)
class SourceMaker(
queue: ConcurrentQueue[Task, Command],
worker: ScriptCompilerWorker
) {
import com.softwaremill.quicklens._
def get =
for {
dataVar <- MVar[Task].of(Data(Map.empty))
obs <- Task.deferAction(implicit s =>
Task(
Observable
.repeatEvalF(queue.poll)
.scanEval0(me.Task.pure((Active: State) -> Data(Map.empty))) {
case state -> data -> command =>
val nextState: IO[Error, (State, Data)] = state match {
case Idle => IO.pure(Idle -> data)
case Active =>
command match {
case GetScript(path, result, force) =>
def getAndUpdate =
worker
.request(
ScriptCompilerWorker.CompileAny(path, _)
)(20.seconds)
.flatTap(result.complete)
.hideErrors
.rethrow
.flatMap(res =>
UIO(pprint.log(res)) >>
UIO.pure(
data
.modify(_.scriptsMap)
.using(_ + (path -> res))
)
)
for {
nextData <-
if (force) getAndUpdate
else
data.scriptsMap.get(path) match {
case Some(e) =>
result
.complete(e.asRight[Error])
.hideErrors >> UIO.pure(data)
case None => getAndUpdate
}
} yield Active -> nextData
case ObservableData(result) =>
result
.complete(Observable.repeatEvalF(dataVar.take))
.hideErrors >> IO.pure(Active -> data)
}
}
nextState
.flatTap { case (_, data) => dataVar.put(data).hideErrors }
.tapError(err => UIO(pprint.log(err.toString)))
.attempt
// .mapFilter(_.toOption)
.map(_.getOrElse(state -> data))
.toTask
}
)
)
} yield obs
}
class ScriptCompileFns(
val scalaRunner: ammonite.Main,
val kotlinRunner: KotlinScriptEngine,
val groovyRunner: GroovyScriptEngine
) {
def runScala(path: os.Path): Either[Error, Any] =
scalaRunner
.runScript(path, Seq.empty)
._1 match {
case e @ Res.Exception(t, msg) => Left(AmmoniteException(e))
case f @ Failure(msg) => Left(AmmoniteFailure(f))
case Res.Success(obj) => Right(obj)
case _ => Left(SomeError("Failed to run script"))
}
def runScala2(path: os.Path): IO[Error, Any] =
for {
scriptContent <- readAsyncL(path).hideErrors
res <- IO.fromEither(
scalaRunner
.runCode(scriptContent)
._1 match {
case e @ Res.Exception(t, msg) => Left(AmmoniteException(e))
case f @ Failure(msg) => Left(AmmoniteFailure(f))
case Res.Success(obj) => Right(obj)
case _ => Left(SomeError("Failed to run script"))
}
)
} yield res
def runKotlin(path: os.Path): Either[Error, Any] =
Either
.catchNonFatal(kotlinRunner.eval(os.read(path)))
.leftMap {
case ex: ScriptException => ScriptExceptionError(ex)
}
// def runKotlin2(path: os.path): IO[Error,]
def runGroovy(path: os.Path): Either[Error, Any] =
Either
.catchNonFatal(groovyRunner.run(path.relativeTo(os.pwd).toString, ""))
.leftMap {
case ex: ResourceException => ResourceExceptionError(ex)
case ex: groovy.util.ScriptException => GroovyScriptExceptionError(ex)
}
def ensureReturnedObjectNotNull(scriptObject: Any): Either[Error, Any] =
Either.fromOption(Option(scriptObject), SomeError("unknown object"))
}
class ScriptCompileSource(
fns: ScriptCompileFns,
logger: Logger[Task],
queue: ConcurrentQueue[Task, ScriptCompilerWorker.CompileRequest],
ioScheduler: Schedulers.IoScheduler
) {
val source =
Task.deferAction(implicit s =>
Task(
Observable
.repeatEvalF(queue.poll)
.doOnNextF(el => logger.debug(show"Got $el"))
.mapParallelUnorderedF(4) {
case ScriptCompilerWorker.CompileAny(path, result) =>
for {
mbRes <- (logger.debugU("Test") >> Task(
fns
.runScala(path)
.flatMap(fns.ensureReturnedObjectNotNull)
// .map(_.taggedWith[ScriptTag])
)).executeOn(ioScheduler.value)
_ <- result.complete(mbRes)
} yield mbRes
}
)
)
}
final class ScriptCompilerWorker(
logger: Logger[Task],
_queue: ConcurrentQueue[Task, ScriptCompilerWorker.CompileRequest]
) extends Requestable[ScriptCompilerWorker.CompileRequest] {
override def queue = _queue
}
object ScriptCompilerWorker {
sealed trait CompileRequest
object CompileRequest {
implicit val show: Show[CompileRequest] = Show.fromToString
}
final case class CompileAny(
path: os.Path,
result: Deferred[Task, ScriptResult]
) extends CompileRequest
def apply(
logger: Logger[Task],
scalaRunner: ammonite.Main = defaultScalaRunner,
kotlinRunner: KotlinScriptEngine = defaultKotlinRunner,
groovyRunner: GroovyScriptEngine = defaultGroovyRunner,
ioScheduler: Schedulers.IoScheduler
) = {
val acquire = for {
queue <- ConcurrentQueue[Task].bounded[CompileRequest](10)
fns <- UIO.pure(wire[ScriptCompileFns])
worker = wire[ScriptCompilerWorker]
fib <- wire[ScriptCompileSource].source.flatMap(_.completedL.toIO.start)
// resource = Concurrent[Task].background(
// wire[ScriptCompileSource].source.flatMap(_.completedL.toIO)
// )
} yield worker -> fib
Resource
.make(acquire.hideErrors) { case worker -> fib => fib.cancel }
.map(_._1)
}
}
def apply(
logger: Logger[Task],
ioScheduler: Schedulers.IoScheduler
): Resource[UIO, ScriptCompiler] = {
def acquire(worker: ScriptCompilerWorker) =
for {
queue <- ConcurrentQueue.bounded[Task, Command](10)
fib <- wire[SourceMaker].get.flatMap(_.completedL.toIO.start)
} yield new ScriptCompiler(queue) -> fib
ScriptCompilerWorker(logger, ioScheduler = ioScheduler)
.flatMap(worker =>
Resource.make(acquire(worker).hideErrors) {
case (_, fib) => fib.cancel
}
)
.map(_._1)
}
}

View File

@ -17,9 +17,7 @@ import com.softwaremill.tagging._
import com.typesafe.scalalogging.Logger import com.typesafe.scalalogging.Logger
import groovy.util.GroovyScriptEngine import groovy.util.GroovyScriptEngine
import org.slf4j.event.Level import org.slf4j.event.Level
import wow.doge.mygame.implicits._
@SuppressWarnings(Array("org.wartremover.warts.Any"))
object ScriptActor { object ScriptActor {
/** /**
@ -43,7 +41,7 @@ object ScriptActor {
result: ActorRef[Map[os.Path, Either[Error, Any]]] result: ActorRef[Map[os.Path, Either[Error, Any]]]
) extends Command ) extends Command
val defaultScalaRunner = lazy val defaultScalaRunner =
ammonite ammonite
.Main( .Main(
storageBackend = new Folder( storageBackend = new Folder(
@ -53,13 +51,13 @@ object ScriptActor {
) )
) )
val defaultKotlinRunner: KotlinScriptEngine = { lazy val defaultKotlinRunner: KotlinScriptEngine = {
val manager = new ScriptEngineManager() val manager = new ScriptEngineManager()
val engine = manager.getEngineByExtension("main.kts") val engine = manager.getEngineByExtension("main.kts")
engine.taggedWith[Kotlin] engine.taggedWith[Kotlin]
} }
val defaultGroovyRunner: GroovyScriptEngine = lazy val defaultGroovyRunner: GroovyScriptEngine =
new GroovyScriptEngine(os.pwd.toString) new GroovyScriptEngine(os.pwd.toString)
def apply( def apply(
@ -98,7 +96,10 @@ object ScriptActor {
def runScala(path: os.Path, scalaRunner: ammonite.Main): Either[Error, Any] = def runScala(path: os.Path, scalaRunner: ammonite.Main): Either[Error, Any] =
scalaRunner scalaRunner
.runScript(path, Seq.empty) .runScript(
path,
Seq.empty
)
._1 match { ._1 match {
case ammonite.util.Res.Exception(t, msg) => Left(Error(msg)) case ammonite.util.Res.Exception(t, msg) => Left(Error(msg))
@ -128,7 +129,6 @@ object ScriptActor {
} }
@SuppressWarnings(Array("org.wartremover.warts.Any"))
class ScriptActor( class ScriptActor(
val scalaRunner: ammonite.Main, val scalaRunner: ammonite.Main,
val kotlinRunner: ScriptActor.KotlinScriptEngine, val kotlinRunner: ScriptActor.KotlinScriptEngine,
@ -140,14 +140,14 @@ class ScriptActor(
def receiveMessage: Behavior[Command] = def receiveMessage: Behavior[Command] =
Behaviors.receiveMessage { Behaviors.receiveMessage {
case CompileAny(path, requester) => case CompileAny(path, requester) =>
context.log.debug(show"Received $path") context.log.debug(s"Received $path")
val res = getScript(path) val res = getScript(path)
context.log.debug(s"result = ${res.toString}") context.log.debug(s"result = $res")
requester ! res requester ! res
Behaviors.same Behaviors.same
case CompileAll(paths, requester) => case CompileAll(paths, requester) =>
context.log.debug(show"Received $paths") context.log.debug(s"Received $paths")
requester ! compileAll(paths) requester ! compileAll(paths)
Behaviors.same Behaviors.same
} }
@ -169,7 +169,7 @@ class ScriptActor(
case l @ Left(err) => l.map(_.taggedWith[ScriptTag]) case l @ Left(err) => l.map(_.taggedWith[ScriptTag])
} }
type LOL = Map[os.Path, Either[ScriptActor.Error, Any]] type LOL = Map[os.Path, Either[wow.doge.mygame.state.ScriptActor.Error, Any]]
def compileAll( def compileAll(
paths: Seq[os.Path] paths: Seq[os.Path]
@ -179,13 +179,14 @@ class ScriptActor(
paths: Seq[os.Path], paths: Seq[os.Path],
scriptsMap: Map[ scriptsMap: Map[
os.Path, os.Path,
Either[ScriptActor.Error, Any] Either[wow.doge.mygame.state.ScriptActor.Error, Any]
] ]
): LOL = ): LOL = {
paths match { paths match {
case head :: next => loop(next, scriptsMap + (head -> getScript(head))) case head :: next => loop(next, scriptsMap + (head -> getScript(head)))
case Nil => scriptsMap case Nil => scriptsMap
} }
}
loop(paths, Map.empty) loop(paths, Map.empty)
} }

View File

@ -1,6 +1,5 @@
package wow.doge.mygame.scriptsystem package wow.doge.mygame.scriptsystem
import scala.concurrent.duration._
import scala.util.Failure import scala.util.Failure
import scala.util.Success import scala.util.Success
@ -13,12 +12,10 @@ import akka.actor.typed.scaladsl.Behaviors
import akka.actor.typed.scaladsl.PoolRouter import akka.actor.typed.scaladsl.PoolRouter
import akka.actor.typed.scaladsl.Routers import akka.actor.typed.scaladsl.Routers
import akka.util.Timeout import akka.util.Timeout
import cats.syntax.show._
import com.typesafe.scalalogging.Logger import com.typesafe.scalalogging.Logger
import org.slf4j.event.Level import org.slf4j.event.Level
import wow.doge.mygame.implicits._
import wow.doge.mygame.state.ScriptActor import wow.doge.mygame.state.ScriptActor
import scala.concurrent.duration._
import ScriptActor.ScriptObject import ScriptActor.ScriptObject
object ScriptCachingActor { object ScriptCachingActor {
@ -26,7 +23,7 @@ object ScriptCachingActor {
type ScriptsMap = Map[os.Path, ScriptObject] type ScriptsMap = Map[os.Path, ScriptObject]
type ScriptResult = Either[ScriptActor.Error, ScriptObject] type ScriptResult = Either[ScriptActor.Error, ScriptObject]
sealed trait Command extends Product with Serializable sealed trait Command
/** /**
* @param scriptPath path of the script to compile * @param scriptPath path of the script to compile
@ -46,9 +43,9 @@ object ScriptCachingActor {
final case class GetMap(requester: ActorRef[ScriptsMap]) extends Command final case class GetMap(requester: ActorRef[ScriptsMap]) extends Command
final case class Put(scriptPath: os.Path, script: ScriptObject) final case class Put(scriptPath: os.Path, script: ScriptObject)
extends Command extends Command
private case object NoOp 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], scriptActor: ActorRef[ScriptActor.Command],
scriptPath: os.Path, scriptPath: os.Path,
requester: ActorRef[ScriptResult] requester: ActorRef[ScriptResult]
@ -63,7 +60,7 @@ object ScriptCachingActor {
// requester: ActorRef[Map[os.Path, ScriptResult]] // requester: ActorRef[Map[os.Path, ScriptResult]]
// ) extends Command // ) extends Command
// class Props( // final case class Props(
// ctx: ActorContext[Command], // ctx: ActorContext[Command],
// scriptActor: ActorRef[ScriptActor.Command] // scriptActor: ActorRef[ScriptActor.Command]
// ) { // ) {
@ -100,6 +97,7 @@ class ScriptCachingActor(
) { ) {
import com.softwaremill.quicklens._ import com.softwaremill.quicklens._
import ScriptCachingActor._ import ScriptCachingActor._
import Methods._
def receiveMessage(state: State): Behavior[Command] = def receiveMessage(state: State): Behavior[Command] =
Behaviors.receiveMessage { msg => Behaviors.receiveMessage { msg =>
msg match { msg match {
@ -108,6 +106,7 @@ class ScriptCachingActor(
ctx.self ! DelegateToChild(scriptActor, scriptPath, requester) ctx.self ! DelegateToChild(scriptActor, scriptPath, requester)
else else
getOrCompileScript( getOrCompileScript(
ctx,
scriptPath, scriptPath,
state.scriptsMap, state.scriptsMap,
scriptActor, scriptActor,
@ -164,6 +163,7 @@ class ScriptCachingActor(
implicit val timeout = Timeout(15.seconds) implicit val timeout = Timeout(15.seconds)
// child ! ScriptActor.CompileAny(scriptPath, requester) // child ! ScriptActor.CompileAny(scriptPath, requester)
askChildForScriptCompilation( askChildForScriptCompilation(
ctx,
scriptActor, scriptActor,
scriptPath, scriptPath,
requester requester
@ -188,56 +188,16 @@ class ScriptCachingActor(
Behaviors.same Behaviors.same
case Put(scriptPath, script) => case Put(scriptPath, script) =>
ctx.log.debugP(show"Putting ${script.toString} at path $scriptPath") ctx.log.debug(s"Putting $script at path $scriptPath")
val newState = val newState =
state.modify(_.scriptsMap).using(_ + (scriptPath -> script)) state.modify(_.scriptsMap).using(_ + (scriptPath -> script))
ctx.log.traceP(newState.toString()) ctx.log.trace(newState.toString())
receiveMessage(state = newState) receiveMessage(state = newState)
case NoOp => Behaviors.same case NoOp => Behaviors.same
} }
} }
def getOrCompileScript(
scriptPath: os.Path,
scriptsMap: ScriptsMap,
scriptActor: ActorRef[ScriptActor.Command],
requester: ActorRef[ScriptResult]
) =
scriptsMap
.get(scriptPath)
.fold {
ctx.log.debugP("Delegating to child")
ctx.self ! DelegateToChild(
scriptActor,
scriptPath,
requester
)
} { s =>
ctx.log.debugP("Getting script from cache")
requester ! Right(s)
}
def askChildForScriptCompilation(
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.errorP(err.reason)
NoOp
},
res => Put(scriptPath, res)
)
case Failure(exception) =>
ctx.log.errorP(exception.getMessage)
NoOp
}
} }
object ScriptActorPool { object ScriptActorPool {
@ -248,8 +208,56 @@ object ScriptActorPool {
// make sure the workers are restarted if they fail // make sure the workers are restarted if they fail
Behaviors Behaviors
.supervise(ScriptActor()) .supervise(ScriptActor())
.onFailure[Exception]( .onFailure[Exception](SupervisorStrategy.restart)
SupervisorStrategy.restart.withLimit(2, 100.millis)
)
) )
} }
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

@ -5,13 +5,9 @@ import akka.actor.typed.Scheduler
import akka.actor.typed.SpawnProtocol import akka.actor.typed.SpawnProtocol
import akka.util.Timeout import akka.util.Timeout
import cats.effect.Resource import cats.effect.Resource
import cats.syntax.eq._
import io.odin.Logger
import monix.bio.Task import monix.bio.Task
import monix.bio.UIO
import wow.doge.mygame.scriptsystem.ScriptCachingActor import wow.doge.mygame.scriptsystem.ScriptCachingActor
import wow.doge.mygame.utils.AkkaUtils import wow.doge.mygame.utils.AkkaUtils
import wow.doge.mygame.executors.Schedulers
/** /**
* Scripts can either be searched and compiled at startup (Eager mode) * Scripts can either be searched and compiled at startup (Eager mode)
@ -24,36 +20,39 @@ object ScriptInitMode {
} }
class ScriptSystemResource( class ScriptSystemResource(
path: os.Path, path: os.Path,
logger: Logger[Task],
mode: ScriptInitMode = ScriptInitMode.Lazy,
ioScheduler: Schedulers.IoScheduler
)(implicit
spawnProtocol: ActorRef[SpawnProtocol.Command], spawnProtocol: ActorRef[SpawnProtocol.Command],
timeout: Timeout, mode: ScriptInitMode = ScriptInitMode.Lazy
scheduler: Scheduler )(implicit timeout: Timeout, scheduler: Scheduler) {
) { val make = {
// throw new Exception("boom")
findScriptFiles(os.pwd / "assets" / "scripts")
lazy val scriptCacheActor = AkkaUtils.spawnActorL(
spawnProtocol,
"scriptCachingActor",
ScriptCachingActor()
)
Resource.liftF(scriptCacheActor)
}
// sys.ask(ref => ScriptCachingActor.GetAll(os.pwd/'assets/'scripts/'scala/"hello2.sc",ref, false))
val init = for { val init = for {
scriptFiles <- Task( scriptFiles <- Task(findScriptFiles(os.pwd / "assets" / "scripts"))
findScriptFiles(os.pwd / "assets" / "scripts")
).hideErrors
scriptCacheActor <- AkkaUtils.spawnActorL( scriptCacheActor <- AkkaUtils.spawnActorL(
spawnProtocol,
"scriptCachingActor",
ScriptCachingActor() ScriptCachingActor()
// "scriptCachingActor"
) )
} yield scriptCacheActor } yield (scriptCacheActor)
val init2: Resource[UIO, ScriptCompiler] = for {
sc <- ScriptCompiler(logger, ioScheduler)
} yield sc
def findScriptFiles(wd: os.Path) = def findScriptFiles(wd: os.Path) =
os.walk os.walk
.stream(wd) .stream(wd)
.filter(p => .filter(p =>
os.isFile(p) && os.isFile(p) &&
(p.ext === "sc" || (p.baseName + "." + p.ext) (p.ext == "sc" || (p.baseName + "." + p.ext)
.contains(".main.kts") || p.ext === "groovy") .contains(".main.kts") || p.ext == "groovy")
) )
.toList .toList

View File

@ -1,15 +0,0 @@
package wow.doge.mygame
import akka.actor.typed.Scheduler
import com.softwaremill.tagging._
import io.estatico.newtype.macros.newtype
import monix.execution.schedulers.SchedulerService
import wow.doge.mygame.game.GameAppTags
import wow.doge.mygame.utils.wrappers.jme.AppNode2
package object types {
type RootNode = AppNode2 @@ GameAppTags.RootNode
type GuiNode = AppNode2 @@ GameAppTags.GuiNode
@newtype final case class JmeScheduler(value: SchedulerService)
@newtype final case class AkkaScheduler(value: Scheduler)
}

View File

@ -6,12 +6,10 @@ import akka.actor.typed.Props
import akka.actor.typed.Scheduler import akka.actor.typed.Scheduler
import akka.actor.typed.SpawnProtocol import akka.actor.typed.SpawnProtocol
import akka.util.Timeout import akka.util.Timeout
import wow.doge.mygame.AppError.TimeoutError
import wow.doge.mygame.implicits._ import wow.doge.mygame.implicits._
object AkkaUtils { object AkkaUtils {
def spawnActorL[T](
def spawnActorOldL[T](
spawnProtocol: ActorRef[SpawnProtocol.Command], spawnProtocol: ActorRef[SpawnProtocol.Command],
actorName: String, actorName: String,
behavior: Behavior[T] behavior: Behavior[T]
@ -24,24 +22,20 @@ object AkkaUtils {
_ _
) )
) )
def spawnActorL[T]( def spawnActorL2[T](
behavior: Behavior[T], behavior: Behavior[T],
actorName: Option[String] = None, actorName: String
props: Props = Props.empty
)(implicit )(implicit
timeout: Timeout, timeout: Timeout,
scheduler: Scheduler, scheduler: Scheduler,
spawnProtocol: ActorRef[SpawnProtocol.Command], spawnProtocol: ActorRef[SpawnProtocol.Command]
name: sourcecode.Name
) = ) =
spawnProtocol spawnProtocol.askL[ActorRef[T]](
.askL[ActorRef[T]]( SpawnProtocol.Spawn(
SpawnProtocol.Spawn( behavior,
behavior, actorName,
actorName.fold(name.value)(identity), Props.empty,
props, _
_
)
) )
.onErrorHandleWith(TimeoutError.from) )
} }

View File

@ -16,37 +16,38 @@ class GenericConsoleStream[T](
outputStream: OutputStream, outputStream: OutputStream,
val config: GenericConsoleStream.Config = val config: GenericConsoleStream.Config =
GenericConsoleStream.Config.default, GenericConsoleStream.Config.default,
// TODO make this atomic ? // TODO make this atomic
private var _streamable: Option[T] = None private var _streamable: Option[T] = None
)(implicit )(implicit
cs: ConsoleStreamable[T] cs: ConsoleStreamable[T]
) extends PrintStream(outputStream, true) { ) extends PrintStream(outputStream, true) {
private val defaultOut = System.out private lazy val defaultOut = System.out
def printToStreamable(stble: Option[T], text: String) = def printToStreamable(stble: Option[T], text: String) =
stble.foreach(s => cs.println(s, text)) stble.foreach(s => cs.println(s, text))
override def println(text: String): Unit = override def println(text: String): Unit =
if (config.exclusive) if (config.exclusive) {
printToStreamable(_streamable, text) printToStreamable(_streamable, text)
else { } else {
defaultOut.println(text) defaultOut.println(text)
printToStreamable(_streamable, text) printToStreamable(_streamable, text)
} }
override def print(text: String): Unit = override def print(text: String): Unit =
if (config.exclusive) if (config.exclusive) {
printToStreamable(_streamable, text) printToStreamable(_streamable, text)
else { } else {
defaultOut.println(text) defaultOut.println(text)
printToStreamable(_streamable, text) printToStreamable(_streamable, text)
} }
def :=(s: T) = def :=(s: T) = {
_streamable match { _streamable match {
case Some(value) => case Some(value) =>
case None => _streamable = Some(s) case None => _streamable = Some(s)
} }
}
} }
object GenericConsoleStream { object GenericConsoleStream {
@ -54,9 +55,9 @@ object GenericConsoleStream {
/** /**
* for future use * for future use
*/ */
final case class Config(exclusive: Boolean = false) case class Config(exclusive: Boolean = false)
object Config { object Config {
val default = Config() lazy val default = Config()
} }
implicit val implJFXConsoleStreamForTextArea = implicit val implJFXConsoleStreamForTextArea =

View File

@ -1,68 +0,0 @@
package wow.doge.mygame.utils
import scala.concurrent.duration.FiniteDuration
import scala.util.Random
import akka.actor.typed.ActorRef
import akka.actor.typed.Behavior
import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.scaladsl.Behaviors
import akka.actor.typed.scaladsl.TimerScheduler
import cats.syntax.show._
import wow.doge.mygame.implicits._
object GenericTimerActor {
sealed trait Command
case object Start extends Command
case object Stop extends Command
private case object Tick extends Command
final case class TimerKey(seed: Long)
final case class Props[T](
target: ActorRef[T],
messageToSend: T,
timeInterval: FiniteDuration
) {
def behavior =
Behaviors.withTimers[Command] { timers =>
Behaviors.setup { ctx =>
new GenericTimerActor(
ctx,
timers,
TimerKey(Random.nextLong()),
this
).idle
}
}
}
}
class GenericTimerActor[T](
ctx: ActorContext[GenericTimerActor.Command],
timers: TimerScheduler[GenericTimerActor.Command],
timerKey: GenericTimerActor.TimerKey,
props: GenericTimerActor.Props[T]
) {
import GenericTimerActor._
val idle: Behavior[Command] =
Behaviors.receiveMessage {
case Start =>
timers.startTimerWithFixedDelay(timerKey, Tick, props.timeInterval)
active
case _ => Behaviors.unhandled
}
val active: Behavior[Command] =
Behaviors.receiveMessage {
case Start =>
ctx.log.warnP(show"Timer-${timerKey.seed}: Timer already started")
Behaviors.same
case Tick =>
props.target ! props.messageToSend
Behaviors.same
case Stop =>
timers.cancel(timerKey)
idle
}
}

View File

@ -1,28 +1,12 @@
package wow.doge.mygame.utils package wow.doge.mygame.utils
import monix.bio.IO import monix.bio.IO
import monix.bio.Task
import monix.eval.Coeval
object IOUtils { object IOUtils {
def toTask[T](bio: IO[Throwable, T]) = def toTask[T](bio: monix.bio.IO[Throwable, T]) =
monix.eval.Task.deferAction(implicit s => bio.to[monix.eval.Task]) monix.eval.Task.deferAction(implicit s => bio.to[monix.eval.Task])
def toIO[T](task: monix.eval.Task[T]) = def toIO[T](task: monix.eval.Task[T]) =
IO.deferAction(implicit s => IO.from(task)) IO.deferAction(implicit s => IO.from(task))
def fromCoevalEither[L, R](coeval: Coeval[Either[L, R]]) =
coeval.to[Task].hideErrors.rethrow
def liftErrors[E, T](
task: Task[T]
)(pf: PartialFunction[Throwable, IO[E, T]]): IO[E, T] = {
val _ = 1
// val x = task.onErrorRecoverWith()
task.onErrorHandleWith(ex => pf.applyOrElse(ex, IO.terminate))
}
// final def mapErrorPartial[E1, B >: A](pf: PartialFunction[E, IO[E1, B]])(implicit ev: E <:< Throwable): IO[E1, B] =
// onErrorHandleWith(ex => pf.applyOrElse(ex, IO.terminate))
} }

View File

@ -1,62 +0,0 @@
package wow.doge.mygame.utils
import cats.kernel.Eq
import cats.syntax.show._
import com.typesafe.scalalogging.Logger
import monix.bio.Task
import monix.execution.Ack
import monix.execution.Cancelable
import monix.execution.cancelables.SingleAssignCancelable
import monix.reactive.Observable
import monix.reactive.OverflowStrategy
object MonixDirectoryWatcher {
import better.files._
import io.methvin.better.files._
private val logger = Logger[MonixDirectoryWatcher.type]
sealed trait WatchEvent extends Product with Serializable
final case class CreateEvent(file: File, count: Int) extends WatchEvent
final case class ModifyEvent(file: File, count: Int) extends WatchEvent
final case class DeleteEvent(file: File, count: Int) extends WatchEvent
object WatchEvent {
implicit val eq = Eq.fromUniversalEquals[WatchEvent]
}
@SuppressWarnings(Array("org.wartremover.warts.Equals"))
def apply(path: os.Path) =
Task.deferAction(implicit s =>
Task(
Observable
.create[WatchEvent](OverflowStrategy.DropNew(50)) { sub =>
import sub.scheduler
val c = SingleAssignCancelable()
val watcher =
new RecursiveFileMonitor(
File(path.toString),
logger = logger.underlying
) {
override def onCreate(file: File, count: Int) =
if (sub.onNext(CreateEvent(file, count)) == Ack.Stop)
c.cancel()
override def onModify(file: File, count: Int) =
if (sub.onNext(ModifyEvent(file, count)) == Ack.Stop)
c.cancel()
override def onDelete(file: File, count: Int) =
if (sub.onNext(DeleteEvent(file, count)) == Ack.Stop)
c.cancel()
}
watcher.start()(scheduler)
c := Cancelable(() => watcher.stop())
c
}
.publish
.refCount
)
)
}

View File

@ -1,18 +0,0 @@
package wow.doge.mygame.utils
import cats.Show
import cats.kernel.Eq
import enumeratum._
sealed trait MovementDirection extends EnumEntry
object MovementDirection extends Enum[MovementDirection] {
val values = findValues
case object Forward extends MovementDirection
case object Backward extends MovementDirection
case object Left extends MovementDirection
case object Right extends MovementDirection
implicit val eq = Eq.fromUniversalEquals[MovementDirection]
implicit val show = Show.fromToString[MovementDirection]
}

View File

@ -1,31 +0,0 @@
package wow.doge.mygame.utils
import cats.data.Reader
import cats.data.ReaderT
import monix.bio.UIO
object ReaderDemo {
type IoReaderT[S, E, A] = ReaderT[UIO, S, Either[E, A]]
val IoReaderT = ReaderT
val t =
ReaderT[UIO, String, Either[Error, Unit]](s => UIO.unit.attempt)
.run("s")
.rethrow
val r: IoReaderT[String, Error, Unit] = IoReaderT(s => UIO.unit.attempt)
val t2 = r.run("s").rethrow
// Kleisli[IO, String, Unit](s => IO.unit)
final case class Environment(str: String, num: Int)
def fun1: Reader[String, UIO[Unit]] = Reader(str => UIO(println(str)))
def fun2: Reader[Int, UIO[Unit]] = Reader(num => UIO(println(num)))
def total: Reader[Environment, UIO[Unit]] =
for {
x <- fun1.local[Environment](_.str)
y <- fun2.local[Environment](_.num)
} yield UIO.parSequence(List(x, y)).void
val io: UIO[Unit] = total.run(Environment("hello", 50))
}

View File

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

View File

@ -6,15 +6,16 @@ import monix.execution.atomic.AtomicAny
* Useless * Useless
*/ */
sealed abstract class Tree[+T] sealed abstract class Tree[+T]
final case class Node[T](data: T, children: AtomicAny[LazyList[Tree[T]]]) case class Node[T](data: T, children: AtomicAny[LazyList[Tree[T]]])
extends Tree[T] { extends Tree[T] {
def add(data: T) = def add(data: T) = {
children.transform(children => children.transform(children =>
Node(data, AtomicAny(LazyList[Tree[T]]())) #:: children Node(data, AtomicAny(LazyList[Tree[T]]())) #:: children
) )
}
} }
// case object Leaf extends Tree[Nothing] // case object Leaf extends Tree[Nothing]
final case class Data(data: Int) case class Data(data: Int)
class TreeManager[T] { class TreeManager[T] {
// val root: AtomicAny[Tree[T]] = AtomicAny(Leaf) // val root: AtomicAny[Tree[T]] = AtomicAny(Leaf)

View File

@ -1,67 +0,0 @@
package wow.doge.mygame.util.controls
import monix.bio.Task
import monix.execution.Cancelable
import monix.execution.Scheduler
import monix.execution.cancelables.CompositeCancelable
import monix.reactive.Observable
import monix.reactive.Observer
import monix.{eval => me}
final class ActionObservableExecutor[T](private val delegate: Observable[T]) {
//format: off
def -->(sub: Observer[T])(implicit s: Scheduler, c: CompositeCancelable): Unit =
//format: on
c += delegate
.doOnNext(el => me.Task.deferFuture(sub.onNext(el)).void)
.subscribe()
//format: off
def -->(f: T => Task[Unit])(implicit s: Scheduler, c: CompositeCancelable): Unit =
//format: on
c += delegate.doOnNextF(f).subscribe()
//format: off
def split(lst: (ActionObservableBuilder[T] => Cancelable)*)(implicit s: Scheduler, c: CompositeCancelable): Unit =
//format: on
c += delegate
.publishSelector(conn =>
Observable(
lst.map(f =>
Observable.unit.doOnNext(_ =>
me.Task(c += f(new ActionObservableBuilder[T](conn))).void
)
): _*
).merge
)
.subscribe()
}
//format: off
final class ActionObservableBuilder[A](private val observableAction: Observable[A]) {
//format: on
def useEval[T](v: me.Task[T]) =
new ActionObservableExecutor[T](observableAction.mapEval(_ => v))
def useEval[T](cb: A => me.Task[T]) =
new ActionObservableExecutor[T](observableAction.mapEval(cb))
def use = new ActionObservableExecutor(observableAction)
def useIterableEval[T](cb: A => collection.immutable.Iterable[T]) =
new ActionObservableExecutor[T](
observableAction.flatMap(a =>
Observable.suspend(Observable.fromIterable(cb(a)))
)
)
def doOnNext(cb: A => me.Task[Unit]): ActionObservableBuilder[A] =
new ActionObservableBuilder(observableAction.doOnNext(cb))
def mapEval[B](cb: A => me.Task[B]) =
new ActionObservableBuilder(observableAction.mapEval(cb))
def underlying = observableAction
}

View File

@ -1,47 +0,0 @@
package wow.doge.mygame.util.controls
import javafx.{scene => jfxs}
import org.kordamp.ikonli.{javafx => ikonlifx}
import scalafx.scene.paint.Paint
import scalafx.scene.text.Text
@SuppressWarnings(
Array("org.wartremover.warts.Null", "org.wartremover.warts.Equals")
)
object FontIcon {
implicit def sfxText2jfx(v: FontIcon): jfxs.text.Text =
if (v != null) v.delegate else null
}
// extends Shape(delegate)
// with PositionDelegate[ikonlifx.FontIcon]
// with SFXDelegate[ikonlifx.FontIcon]
class FontIcon(override val delegate: ikonlifx.FontIcon = new ikonlifx.FontIcon)
extends Text(delegate) {
// def iconCode_=(v: Ikon) = delegate.setIconCode(v)
def iconColor = delegate.getIconColor()
def iconColor_=(color: Paint) = delegate.setIconColor(color)
def iconSize = delegate.getIconSize()
def iconSize_=(size: Int) = delegate.setIconSize(size)
def iconLiteral = delegate.getIconLiteral()
def iconLiteral_=(literal: IconLiteral) =
delegate.setIconLiteral(literal.value)
def iconLiteral_=(literal: String) = delegate.setIconLiteral(literal)
}
sealed abstract class IconLiteral(val value: String)
object IconLiteral {
// fab-accusoft
case object Gmi10k extends IconLiteral("gmi-10k")
}

View File

@ -1,42 +0,0 @@
package wow.doge.mygame.util.controls
import com.jfoenix.{controls => jfoenixc}
import javafx.{scene => jfxs}
import scalafx.Includes._
import scalafx.beans.property.ObjectProperty
import scalafx.scene.Node
import scalafx.scene.control.Button
import wow.doge.mygame.implicits._
import jfxs.{paint => jfxsp}
@SuppressWarnings(
Array("org.wartremover.warts.Null", "org.wartremover.warts.Equals")
)
object JFXButton {
implicit def sfxButton2jfx(v: JFXButton): jfoenixc.JFXButton =
if (v != null) v.delegate else null
}
class JFXButton(
override val delegate: jfoenixc.JFXButton = new jfoenixc.JFXButton
) extends Button(delegate) {
/**
* Creates a button with the specified text as its label.
*/
def this(text: String) = this(new jfoenixc.JFXButton(text))
/**
* Creates a button with the specified text and icon for its label.
*/
def this(text: String, graphic: Node) =
this(new jfoenixc.JFXButton(text, graphic))
def ripplerFill: ObjectProperty[jfxsp.Paint] = delegate.ripplerFillProperty
def ripplerFill_=(b: jfxsp.Paint): Unit = ripplerFill() = b
def obsAction = new ActionObservableBuilder(this.observableAction)
}

View File

@ -1,22 +0,0 @@
package wow.doge.mygame.util.controls
import com.jfoenix.{controls => jfoenixc}
import scalafx.scene.layout.Region
import scalafx.scene.layout.StackPane
class JFXDialog(
override val delegate: jfoenixc.JFXDialog = new jfoenixc.JFXDialog
) extends StackPane(delegate) {
def show() = delegate.show()
def show(sp: StackPane) = delegate.show(sp)
def content = delegate.getContent()
def content_=(r: Region) = delegate.setContent(r)
def overlayClose = delegate.overlayCloseProperty()
def overlayClose_=(v: Boolean) = delegate.setOverlayClose(v)
def cacheContainer = delegate.cacheContainerProperty()
def cacheContainer_=(v: Boolean) = delegate.setCacheContainer(v)
}
object JFXDialog {
implicit def sfxJfXDialog2Jfx(v: JFXDialog): jfoenixc.JFXDialog = v.delegate
}

View File

@ -1,57 +0,0 @@
package wow.doge.mygame.util.controls
import com.jfoenix.{controls => jfoenixc}
import javafx.scene.{control => jfxsc}
import scalafx.Includes._
import scalafx.beans.property.ReadOnlyObjectProperty
import scalafx.delegate.SFXDelegate
import scalafx.scene.control.IndexedCell
import scalafx.scene.control.ListCell
import scalafx.scene.control.ListView
@SuppressWarnings(
Array("org.wartremover.warts.Null", "org.wartremover.warts.Equals")
)
object JFXListCell {
implicit def sfxListCell2jfx[T](
l: JFXListCell[T]
): ListCell[T] =
if (l != null) l.delegate else null
}
class JFXListCell[T](
override val delegate: jfoenixc.JFXListCell[T] =
new jfoenixc.JFXListCell[T] {
override def updateItem(
item: T,
empty: Boolean
): Unit = {
super.updateItem(item, empty)
// setText(null)
setText(getText())
setGraphic(getGraphic())
// setGraphic(null)
// remove empty (Trailing cells)
// setMouseTransparent(true)
// setStyle("-fx-background-color:TRANSPARENT;")
}
override def makeChildrenTransparent(): Unit = {}
}
) extends IndexedCell(delegate)
with SFXDelegate[jfoenixc.JFXListCell[T]] {
/**
* The ListView associated with this Cell.
*/
def listView: ReadOnlyObjectProperty[jfxsc.ListView[T]] =
delegate.listViewProperty
/**
* Updates the ListView associated with this Cell.
*/
def updateListView(listView: ListView[T]): Unit =
delegate.updateListView(listView)
// delegate.cell
}

View File

@ -1,40 +0,0 @@
package wow.doge.mygame.util.controls
import com.jfoenix.{controls => jfoenixc}
import monix.execution.Scheduler
import monix.reactive.Observable
import scalafx.Includes._
import scalafx.collections.ObservableBuffer
import scalafx.scene.control.ListView
@SuppressWarnings(
Array("org.wartremover.warts.Null", "org.wartremover.warts.Equals")
)
object JFXListView {
implicit def sfxListView2jfx[T](l: JFXListView[T]): jfoenixc.JFXListView[T] =
if (l != null) l.delegate else null
}
// extends Control(delegate)
// with SFXDelegate[jfoenixc.JFXListView[T]]
class JFXListView[T](
override val delegate: jfoenixc.JFXListView[T] = new jfoenixc.JFXListView[T]
) extends ListView[T] {
// def items_=(
// v: Observable[ObservableBuffer[T]]
// )(implicit s: Scheduler): Unit = {
// v.foreach { items() = _ }
// }
def items_=(v: Observable[Seq[T]])(implicit s: Scheduler): Unit =
v.map(ObservableBuffer.from).foreach(items() = _)
def depth = delegate.depthProperty()
def depth_=(depth: Int) = delegate.setDepth(depth)
def expanded = delegate.expandedProperty()
def expanded_=(v: Boolean) = expanded() = v
}

View File

@ -1,10 +0,0 @@
package wow.doge.mygame.utils.controls
import com.jfoenix.{controls => jfoenixc}
import scalafx.scene.control.ProgressBar
class JFXProgressBar(
override val delegate: jfoenixc.JFXProgressBar = new jfoenixc.JFXProgressBar
) extends ProgressBar(delegate) {
def secondaryProgress = delegate.getSecondaryProgress()
def secondaryProgress_=(v: Double) = delegate.setSecondaryProgress(v)
}

View File

@ -1,37 +0,0 @@
package wow.doge.mygame.util.controls
import com.jfoenix.{controls => jfoenixc}
import scalafx.scene.Node
import scalafx.scene.layout.StackPane
import scalafx.scene.paint.Paint
class JFXRippler(
override val delegate: jfoenixc.JFXRippler = new jfoenixc.JFXRippler
) extends StackPane(delegate) {
import JFXRippler._
def control = delegate.getControl()
def control_=(v: Node) = delegate.setControl(v)
def enabled_=(v: Boolean) = delegate.setEnabled(v)
def ripplerPos = delegate.getPosition()
def ripplerPos_=(pos: RipplerPos) = delegate.setPosition(pos)
def ripplerDisabled = delegate.ripplerDisabledProperty()
def ripplerDisabled_=(v: Boolean) = delegate.setRipplerDisabled(v)
def ripplerFill = delegate.ripplerFillProperty()
def ripplerFill_=(v: Paint) = delegate.setRipplerFill(v)
def ripplerRecenter = delegate.ripplerRecenterProperty()
def ripplerRecenter_=(v: Boolean) = delegate.setRipplerRecenter(v)
def ripplerRadius = delegate.ripplerRadiusProperty()
def ripplerRadius_=(v: Int) = delegate.setRipplerRadius(v)
}
object JFXRippler {
abstract class RipplerPos(val delegate: jfoenixc.JFXRippler.RipplerPos)
case object Front extends RipplerPos(jfoenixc.JFXRippler.RipplerPos.FRONT)
case object Back extends RipplerPos(jfoenixc.JFXRippler.RipplerPos.BACK)
object RipplerPos {
implicit def sfxRipplerPos2jfxRipplerPos(
v: RipplerPos
): jfoenixc.JFXRippler.RipplerPos = v.delegate
}
implicit def sfxRippler2jfxRippler(v: JFXRippler): jfoenixc.JFXRippler =
v.delegate
}

View File

@ -1,32 +0,0 @@
package wow.doge.mygame.util.controls
import com.jfoenix.{controls => jfoenixc}
import scalafx.scene.control.ProgressIndicator
@SuppressWarnings(
Array("org.wartremover.warts.Null", "org.wartremover.warts.Equals")
)
object JFXSpinner {
implicit def sfxSpinner2jfx(
v: JFXSpinner
): jfoenixc.JFXSpinner = if (v != null) v.delegate else null
}
// extends Control(delegate)
// with SFXDelegate[jfoenixc.JFXSpinner]
/**
* Wraps [[JFoenix JFXSpinner]]
*/
class JFXSpinner(
override val delegate: jfoenixc.JFXSpinner = new jfoenixc.JFXSpinner
) extends ProgressIndicator(delegate) {
def radius = delegate.getRadius()
def radius_=(radius: Double) = delegate.setRadius(radius)
def startingAngle = delegate.startingAngleProperty()
def startingAngle_=(angle: Double) = delegate.setStartingAngle(angle)
}

View File

@ -1,43 +0,0 @@
package wow.doge.mygame.util.controls
import com.jfoenix.{controls => jfoenixc}
import scalafx.Includes._
import scalafx.beans.property.BooleanProperty
import scalafx.scene.control.TextArea
import scalafx.scene.paint.Paint
@SuppressWarnings(
Array("org.wartremover.warts.Null", "org.wartremover.warts.Equals")
)
object JFXTextArea {
implicit def sfxTextArea2jfx(v: JFXTextArea): jfoenixc.JFXTextArea =
if (v != null) v.delegate else null
}
// extends TextInputControl(delegate)
// with SFXDelegate[jfoenixc.JFXTextArea]
class JFXTextArea(
override val delegate: jfoenixc.JFXTextArea = new jfoenixc.JFXTextArea()
) extends TextArea(delegate) {
/**
* Creates a TextArea with initial text content.
*
* @param text - A string for text content.
*/
def this(text: String) = this(new jfoenixc.JFXTextArea(text))
def labelFloat = delegate.labelFloatProperty()
def labelFloat_=(v: Boolean) = delegate.setLabelFloat(v)
def focusColor: Paint = delegate.getFocusColor()
def focusColor_=(color: Paint) = delegate.setFocusColor(color)
def unFocusColor = delegate.getUnFocusColor()
def unFocusColor_=(color: Paint) = delegate.setUnFocusColor(color)
def disableAnimation: BooleanProperty = delegate.disableAnimationProperty()
def disableAnimation_=(disable: Boolean) =
delegate.setDisableAnimation(disable)
}

View File

@ -1,38 +0,0 @@
package wow.doge.mygame.util.controls
import com.jfoenix.{controls => jfoenixc}
import scalafx.Includes._
import scalafx.beans.property.BooleanProperty
import scalafx.scene.control.TextField
import scalafx.scene.paint.Paint
@SuppressWarnings(
Array("org.wartremover.warts.Null", "org.wartremover.warts.Equals")
)
object JFXTextField {
implicit def sfxTextField2jfx(v: JFXTextField): jfoenixc.JFXTextField =
if (v != null) v.delegate else null
}
// TextInputControl(delegate)
// with AlignmentDelegate[jfoenixc.JFXTextField]
// with SFXDelegate[jfoenixc.JFXTextField] {
class JFXTextField(
override val delegate: jfoenixc.JFXTextField = new jfoenixc.JFXTextField
) extends TextField(delegate) {
def labelFloat = delegate.labelFloatProperty()
def labelFloat_=(v: Boolean) = delegate.setLabelFloat(v)
def focusColor: Paint = delegate.getFocusColor()
def focusColor_=(color: Paint) = delegate.setFocusColor(color)
def unFocusColor = delegate.getUnFocusColor()
def unFocusColor_=(color: Paint) = delegate.setUnFocusColor(color)
def disableAnimation: BooleanProperty = delegate.disableAnimationProperty()
def disableAnimation_=(disable: Boolean) =
delegate.setDisableAnimation(disable)
}

View File

@ -1,63 +0,0 @@
package wow.doge.mygame.util.controls
import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject
import com.jfoenix.{controls => jfoenixc}
import javafx.scene.{control => jfxsc}
import scalafx.collections.ObservableBuffer
import scalafx.scene.control.TreeItem
import scalafx.scene.control.TreeTableView
class RecursiveTreeItem[G <: RecursiveTreeObject[G]](
override val delegate: jfoenixc.RecursiveTreeItem[G] =
new jfoenixc.RecursiveTreeItem[G]((item: RecursiveTreeObject[G]) =>
item.getChildren()
)
) extends TreeItem[G](delegate) {
def this(value: G) =
this(
new jfoenixc.RecursiveTreeItem[G](
value,
(item: RecursiveTreeObject[G]) => item.getChildren()
)
)
def this(items: ObservableBuffer[G]) =
this(
new jfoenixc.RecursiveTreeItem[G](
items,
(item: RecursiveTreeObject[G]) => item.getChildren()
)
)
}
object RecursiveTreeItem {
implicit def sfxTreeItem2jfxTreeItem[G <: RecursiveTreeObject[G]](
v: RecursiveTreeItem[G]
): jfoenixc.RecursiveTreeItem[G] = v.delegate
}
// @formatter:off
class JFXTreeTableView[S <: RecursiveTreeObject[S]](
override val delegate: jfoenixc.JFXTreeTableView[S] = new jfoenixc.JFXTreeTableView[S]
) extends TreeTableView(delegate) {
def this(root: TreeItem[S]) = this(new jfoenixc.JFXTreeTableView[S](root))
// def this(root: TreeItem[S], items: ObservableBuffer[S]) = this(new jfoenixc.JFXTreeTableView[S](root, items))
// @formatter:on
def currentItemsCount = delegate.currentItemsCountProperty()
def predicate = delegate.predicateProperty()
// delegate.set
// override def treeColumn_=(v: TreeTableColumn[S, _]): Unit = ???
// delegate.setTreeColumn()
}
// @formatter:off
@SuppressWarnings(Array("org.wartremover.warts.Null","org.wartremover.warts.Equals"))
object JFXTreeTableView {
implicit def sfxTreeTableView2jfx[S <: RecursiveTreeObject[S]](
v: JFXTreeTableView[S]
): jfxsc.TreeTableView[S] = if (v != null) v.delegate else null
}
// @formatter:on

View File

@ -1,7 +0,0 @@
package wow.doge.mygame.util.controls
import scalafx.scene.{control => sfxc}
import wow.doge.mygame.implicits._
class MenuItem extends sfxc.MenuItem {
def obsAction = new ActionObservableBuilder(this.observableAction)
}

View File

@ -1,38 +0,0 @@
package wow.doge.mygame
import java.nio.file.StandardOpenOption
import monix.bio.UIO
import monix.execution.Scheduler
import monix.nio.file.AsyncFileChannelConsumer
import monix.nio.text.UTF8Codec.utf8Decode
import monix.nio.{file => mnf}
import wow.doge.mygame.implicits._
package object utils {
def methodName(implicit enclosing: sourcecode.Enclosing) =
enclosing.value.split(" ")(0).split("""\.""").last
def readAsync(
path: os.Path,
chunkSize: Int = 8192
) =
UIO
.deferAction(implicit s => UIO(mnf.readAsync(path.toNIO, chunkSize)))
.flatMap(bytes => UIO(bytes.pipeThrough(utf8Decode)))
@SuppressWarnings(Array("org.wartremover.warts.TraversableOps"))
def readAsyncL(
path: os.Path,
chunkSize: Int = 8192
) =
readAsync(path, chunkSize)
.flatMap(_.toListL.toIO)
.map(_.head)
def writeAsync(path: os.Path, flags: Seq[StandardOpenOption] = Seq.empty)(
implicit s: Scheduler
): AsyncFileChannelConsumer =
mnf.appendAsync(path.toNIO, 0, flags)
}

View File

@ -1,63 +0,0 @@
package wow.doge.mygame.utils.wrappers.jme
import scala.reflect.ClassTag
import cats.Show
import cats.kernel.Eq
import com.jme3.asset.AssetLoadException
import com.jme3.asset.AssetLocator
import com.jme3.asset.AssetNotFoundException
import com.jme3.scene.Spatial
import com.jme3.{asset => jmea}
import monix.bio.IO
import monix.bio.UIO
class AssetManager(assetManager: jmea.AssetManager) {
import AssetManager._
def loadModel(path: os.RelPath): IO[Error, Spatial] =
IO(assetManager.loadModel(path.toString)).onErrorHandleWith {
case ex: AssetNotFoundException =>
IO.raiseError(AssetNotFound(ex.getMessage))
case ex: AssetLoadException =>
IO.raiseError(AssetLoadError(ex.getMessage))
}
@SuppressWarnings(
Array("org.wartremover.warts.AsInstanceOf", "org.wartremover.warts.Equals")
)
def loadModelAs[T <: Spatial](
path: os.RelPath
)(implicit ct: ClassTag[T]): IO[Error, T] =
loadModel(path).flatMap(model =>
if (model.getClass == ct.runtimeClass)
UIO(model.asInstanceOf[T])
else IO.raiseError(CouldNotCastError)
)
@SuppressWarnings(
Array("org.wartremover.warts.AsInstanceOf", "org.wartremover.warts.Equals")
)
def loadAssetAs[T](path: os.RelPath)(implicit ct: ClassTag[T]): IO[Error, T] =
IO(assetManager.loadAsset(path.toString))
.onErrorHandleWith {
case ex: AssetNotFoundException =>
IO.raiseError(AssetNotFound(ex.getMessage))
case ex: AssetLoadException =>
IO.raiseError(AssetLoadError(ex.getMessage))
}
.flatMap(asset =>
if (asset.getClass == ct.runtimeClass)
UIO(asset.asInstanceOf[T])
else IO.raiseError(CouldNotCastError)
)
def registerLocator(path: os.RelPath, locator: Class[_ <: AssetLocator]) =
UIO(assetManager.registerLocator(path.toString, locator))
}
object AssetManager {
sealed trait Error extends Product with Serializable
final case class AssetNotFound(message: String) extends Error
final case class AssetLoadError(message: String) extends Error
case object CouldNotCastError extends Error
object Error {
implicit val show = Show.fromToString[Error]
implicit val eq = Eq.fromUniversalEquals[Error]
}
}

View File

@ -1,27 +0,0 @@
package wow.doge.mygame.utils.wrappers.jme
import cats.Show
import cats.kernel.Eq
import com.jme3.bullet.collision.shapes.CollisionShape
import com.jme3.bullet.{util => jmebu}
import com.jme3.scene.Spatial
import monix.bio.IO
object CollisionShapeFactory {
sealed trait Error
final case class WrongArgumentError(reason: String) extends Error
object Error {
implicit val show = Show.fromToString[Error]
implicit val eq = Eq.fromUniversalEquals[Error]
}
def createMeshShape(subtree: Spatial): IO[Error, CollisionShape] =
IO(jmebu.CollisionShapeFactory.createMeshShape(subtree))
.onErrorHandleWith {
case ex: IllegalArgumentException
if (ex.getMessage
.startsWith("The spatial must either be a Node")) =>
IO.raiseError(WrongArgumentError(ex.getMessage))
}
}

View File

@ -1,126 +0,0 @@
package wow.doge.mygame.utils.wrappers.jme
import com.jme3.input.controls.InputListener
import com.jme3.input.controls.Trigger
import com.jme3.{input => jmei}
import enumeratum._
import monix.bio.UIO
import monix.reactive.Observable
import wow.doge.mygame.ActionEvent
import wow.doge.mygame.AnalogEvent
import wow.doge.mygame.EnumActionEvent
import wow.doge.mygame.EnumAnalogEvent
import wow.doge.mygame.implicits._
final class InputManager(val delegate: jmei.InputManager) {
/**
* Create a new mapping to the given triggers.
*
* <p>
* The given mapping will be assigned to the given triggers, when
* any of the triggers given raise an event, the listeners
* registered to the mappings will receive appropriate events.
*
* @param mappingName The mapping name to assign.
* @param triggers The triggers to which the mapping is to be registered.
*/
def withMapping(mapping: String, triggers: Trigger*): UIO[Unit] =
UIO(delegate.withMapping(mapping, triggers: _*)).void
def withMapping[T <: EnumEntry](
mapping: T,
triggers: Trigger*
): UIO[Unit] = UIO(delegate.withMapping(mapping, triggers: _*)).void
def withListener(listener: InputListener, mappings: String*) =
UIO(delegate.withListener(listener, mappings: _*))
/**
* Creates new mappings from the values of the given Enum
*
* <p>
* The given mapping will be assigned to the given triggers, when
* any of the triggers given raise an event, the listeners
* registered to the mappings will receive appropriate events.
*
* @param mappingName The mapping name to assign.
* @param mappingFn Function from enum values to the sequence of trigers.
*
* @example
*
* {{{
*
* sealed trait PlayerAnalogMovementInput extends EnumEntry with UpperSnakecase
* object PlayerAnalogMovementInput extends Enum[PlayerAnalogMovementInput] {
* val values = findValues
* case object TurnRight extends PlayerAnalogMovementInput
* case object TurnLeft extends PlayerAnalogMovementInput
* }
*
* {
* inputManager.withEnumMappings(PlayerAnalogMovementInput) {
* case PlayerAnalogMovementInput.TurnRight =>
* Seq(new KeyTrigger(KeyInput.KEY_RIGHT))
* case PlayerAnalogMovementInput.TurnLeft =>
* Seq(new KeyTrigger(KeyInput.KEY_LEFT))
* }
* }
* }}}
*/
def withEnumMappings[T <: EnumEntry](
mappingEnum: Enum[T]
)(mappingFn: T => Seq[Trigger]) =
UIO(delegate.withEnumMappings(mappingEnum)(mappingFn))
/**
* Create an observable which emits the given mappings as elements of an observable
*
* @param mappingNames
* @return Observable of action events
*
* @see [[ActionEvent]]
* @see [[enumObservableAction]]
*/
def observableAction(mappingNames: String*): Observable[ActionEvent] =
delegate.observableAction(mappingNames: _*)
/**
* <p>
* Create an observable which emits the values of the given
* enum as elements of an observable
*
* @param mappingNames
* @return Observable of enum values
*
* @example {{{
* inputManager
* .enumObservableAction(PlayerMovementInput)
* .doOnNext { action =>
* action.binding match {
* case PlayerMovementInput.WalkLeft => Task {/* your actions */}
* }
* }
* }}}
*
* @see [[EnumActionEvent]]
* @see [[enumAnalogObservable]]
*/
def enumObservableAction[T <: EnumEntry](
mappingEnum: Enum[T]
): Observable[EnumActionEvent[T]] =
delegate.enumObservableAction(mappingEnum)
def enumEntryObservableAction[T <: EnumEntry](
mappingEnumEntry: T
) = delegate.enumEntryObservableAction(mappingEnumEntry)
def analogObservable(mappingNames: String*): Observable[AnalogEvent] =
delegate.analogObservable(mappingNames: _*)
def enumAnalogObservable[T <: EnumEntry](
mappingEnum: Enum[T]
): Observable[EnumAnalogEvent[T]] = delegate.enumAnalogObservable(mappingEnum)
def cursorVisible_=(value: Boolean) = UIO(delegate.setCursorVisible(value))
def cursorVisible = delegate.isCursorVisible
}

View File

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

View File

@ -1,91 +0,0 @@
package wow.doge.mygame.utils.wrappers.jme
import com.jme3.bullet.control.PhysicsControl
import com.jme3.bullet.joints.PhysicsJoint
import com.jme3.{bullet => jmeb}
import com.jme3.{scene => jmes}
import monix.bio.UIO
import wow.doge.mygame.implicits._
final class PhysicsSpace(space: jmeb.PhysicsSpace) {
def add[T](addable: T)(implicit P: PhysicsSpaceAddable[T]) =
UIO(P.addToSpace(addable, space))
def remove(anyObject: Any) = UIO(space.remove(anyObject))
def addAll(spatial: jmes.Spatial) = UIO(space.addAll(spatial))
def removeAll(spatial: jmes.Spatial) = UIO(space.removeAll(spatial))
def collisionObservable = space.collisionObservable()
// space.enqueue(() => ())
def physicsTickObservable = space.physicsTickObservable()
def prePhysicsTickObservable = space.prePhysicsTickObservable()
}
object PhysicsSpace {
implicit final class PhysicsSpaceOps(private val space: PhysicsSpace)
extends AnyVal {
def +=[T: PhysicsSpaceAddable](addable: T) = space.add(addable)
def :+[T: PhysicsSpaceAddable](addable: T) =
for {
_ <- space.add(addable)
} yield space
def -(anyObject: Any) =
for {
_ <- space.remove(anyObject)
} yield space
def -=(anyObject: Any) = space.remove(anyObject)
def +=(spatial: jmes.Spatial) = space.addAll(spatial)
// def :+(spatial: jmes.Spatial) = {
// space.addAll(spatial)
// space
// }
// def -(spatial: jmes.Spatial) = {
// space.removeAll(spatial)
// space
// }
def -=(spatial: jmes.Spatial) = space.removeAll(spatial)
}
}
trait PhysicsSpaceAddable[-T] {
def addToSpace(inst: T, space: jmeb.PhysicsSpace): Unit
}
object PhysicsSpaceAddable {
implicit val physicsSpaceAddableForPhysControl =
new PhysicsSpaceAddable[PhysicsControl] {
override def addToSpace(
inst: PhysicsControl,
space: jmeb.PhysicsSpace
): Unit = space.add(inst)
}
implicit val physicsSpaceAddableForSpatial =
new PhysicsSpaceAddable[jmes.Spatial] {
override def addToSpace(
inst: jmes.Spatial,
space: jmeb.PhysicsSpace
): Unit = space.add(inst)
}
implicit val physicsSpaceAddableForPhysJoint =
new PhysicsSpaceAddable[PhysicsJoint] {
override def addToSpace(
inst: PhysicsJoint,
space: jmeb.PhysicsSpace
): Unit = space.add(inst)
}
}

View File

@ -1,3 +0,0 @@
package wow.doge.mygame.utils.wrappers.jme
package object node {}

View File

@ -1,57 +0,0 @@
package wow.doge.mygame
import scala.concurrent.Await
import scala.concurrent.duration._
import akka.actor.typed.ActorRef
import akka.actor.typed.ActorSystem
import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.scaladsl.AskPattern._
import akka.actor.typed.scaladsl.Behaviors
import akka.util.Timeout
import org.scalatest.BeforeAndAfterAll
import org.scalatest.funsuite.AnyFunSuite
class ActorTimeoutTest extends AnyFunSuite with BeforeAndAfterAll {
import ActorTimeoutTest._
implicit val as = ActorSystem(new MyActor.Props().create, "system")
implicit val timeout = Timeout(1.millis)
test("timeoutTest") {
val fut = as.ask(MyActor.GetInt)
val res = Await.result(fut, 1.second)
assert(res == 1)
}
override protected def afterAll(): Unit =
as.terminate()
}
object ActorTimeoutTest {
object MyActor {
sealed trait Command
final case class GetInt(replyTo: ActorRef[Int]) extends Command
class Props() {
def create =
Behaviors.setup[Command] { ctx =>
new MyActor(ctx, this).receive
}
}
}
class MyActor(
ctx: ActorContext[MyActor.Command],
props: MyActor.Props
) {
import MyActor._
def receive =
Behaviors.receiveMessage[Command] {
case GetInt(replyTo) =>
// Thread.sleep(1000)
replyTo ! 1
Behaviors.same
}
}
}

View File

@ -1,32 +0,0 @@
package wow.doge.mygame
import monix.bio.Task
import wow.doge.mygame.subsystems.scriptsystem.ScriptCompiler
import monix.bio.UIO
import scala.concurrent.Future
import monix.execution.Scheduler
class AmmoniteTest extends munit.TaglessFinalSuite[Task] {
override protected def toFuture[A](f: Task[A]): Future[A] = {
implicit val s = Scheduler.global
f.runToFuture
}
val scriptCompileFns = new ScriptCompiler.ScriptCompileFns(
ScriptCompiler.defaultScalaRunner,
ScriptCompiler.defaultKotlinRunner,
ScriptCompiler.defaultGroovyRunner
)
override def afterAll(): Unit = ()
test("Basic test") {
UIO(
scriptCompileFns
.runScala(
os.pwd / "assets" / "scripts" / "scala" / "basicTestScript.sc"
)
).assertEquals(Right("hello"))
}
}

View File

@ -1,59 +0,0 @@
package wow.doge.mygame
import scala.concurrent.duration._
import cats.syntax.all._
import com.jme3.anim.AnimComposer
import com.jme3.anim.SkinningControl
import com.jme3.anim.tween.Tweens
import com.jme3.anim.tween.action.BaseAction
import com.jme3.anim.tween.action.ClipAction
import com.jme3.asset.DesktopAssetManager
import com.jme3.scene.Node
import monix.bio.UIO
import org.scalatest.funsuite.AnyFunSuite
import wow.doge.mygame.utils.wrappers.jme.AssetManager
class AnimTest extends AnyFunSuite {
import monix.execution.Scheduler.Implicits.global
val assetManager = new AssetManager(new DesktopAssetManager(true))
test("test 1") {
println((for {
_ <- UIO.unit
model <- assetManager.loadModelAs[Node](
os.rel / "Models" / "Oto" / "Oto.mesh.xml"
)
animcontrol <- UIO(model.getControlMaybe(classOf[AnimComposer]))
skinningcontrol <- UIO(model.getControlMaybe(classOf[SkinningControl]))
_ <- UIO(println(animcontrol))
_ <- UIO(println(skinningcontrol))
_ <- UIO {
animcontrol.map { ac =>
// ac.actionSequence()
// ac.makeAction()
// new BaseAction(Tweens.sequence())
// ac.getAnimClips().a
Option(ac.getAnimClip("hmm"))
.map(clip => new ClipAction(clip))
.map(Tweens.sequence(_))
.foreach { t =>
val actions = new BaseAction(t)
ac.addAction("hmm", actions)
}
val names = List("Walk", "Jump")
for {
clips <- names.traverse(name =>
Option(ac.getAnimClip(name)).map(clip => new ClipAction(clip))
)
tween <- Tweens.sequence(clips: _*).some
actions <- new BaseAction(tween).some
_ <- ac.addAction("Sequence 1", actions).some
} yield ()
()
}
}
} yield model).attempt.runSyncUnsafe(10.seconds))
}
}

View File

@ -1,75 +0,0 @@
package wow.doge.mygame
import com.jme3.asset.DesktopAssetManager
import com.jme3.material.Material
import com.jme3.material.MaterialDef
import com.jme3.scene.Geometry
import com.jme3.scene.Node
import wow.doge.mygame.utils.wrappers.jme.AssetManager
import wow.doge.mygame.utils.wrappers.jme.AssetManager.AssetNotFound
import wow.doge.mygame.utils.wrappers.jme.AssetManager.CouldNotCastError
class AssetManagerTest extends MonixBioSuite {
val fixture = FunFixture[AssetManager](
setup = _ => new AssetManager(new DesktopAssetManager(true)),
teardown = _ => ()
)
override def beforeAll(): Unit = {
import java.util.logging.{Logger => JLogger, Level}
JLogger.getLogger("").setLevel(Level.SEVERE)
}
fixture.test(
"AssetManager#loadModel should fail with AssetNotFoundError when asset does not exist"
) { assetManager =>
assetManager
.loadModel(os.rel / "doesnotexist")
.attempt
.assertEquals(Left(AssetNotFound("doesnotexist")))
}
fixture.test(
"AssetManager#loadModelAs should fail with CouldNotCastError when model is not of target type"
) { assetManager =>
val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o"
assetManager
.loadModelAs[Geometry](modelPath)
.attempt
.assertEquals(Left(CouldNotCastError))
}
fixture.test(
"AssetManager#loadModelAs should load com.jme3.Node successfully"
) { assetManager =>
val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o"
assetManager
.loadModelAs[Node](modelPath)
.attempt
.map(_.map(_.getName))
.assertEquals(Right("JaimeGeom-ogremesh"))
}
fixture.test(
"AssetManager#loadAssetAs should fail with CouldNotCastError when asset is not of target type"
) { assetManager =>
val assetPath = os.rel / "Common" / "MatDefs" / "Misc" / "Unshaded.j3md"
assetManager
.loadAssetAs[Material](assetPath)
.attempt
.assertEquals(Left(CouldNotCastError))
}
fixture.test(
"AssetManager#loadAssetAs should should load asset succesfully"
) { assetManager =>
val assetPath = os.rel / "Common" / "MatDefs" / "Misc" / "Unshaded.j3md"
assetManager
.loadAssetAs[MaterialDef](assetPath)
.attempt
.map(_.map(_.getName))
.assertEquals(Right("Unshaded"))
}
}

View File

@ -1,48 +0,0 @@
package wow.doge.mygame
import com.jme3.bounding.BoundingVolume
import com.jme3.collision.Collidable
import com.jme3.collision.CollisionResults
import com.jme3.scene.SceneGraphVisitor
import com.jme3.scene.Spatial
import com.jme3.scene.Spatial.DFSMode
import wow.doge.mygame.utils.wrappers.jme.CollisionShapeFactory
import java.util.Queue
class CollisionShapeFactoryTest extends MonixBioSuite {
test("Test for WrongArgumentError") {
CollisionShapeFactory
.createMeshShape(new TestSpatial)
.attempt
.assertEquals(
Left(
CollisionShapeFactory.WrongArgumentError(
"The spatial must either be a Node or a Geometry!"
)
)
)
}
}
class TestSpatial extends Spatial {
override def collideWith(x$1: Collidable, x$2: CollisionResults): Int = ???
override def updateModelBound(): Unit = ???
override def setModelBound(x$1: BoundingVolume): Unit = ???
override def getVertexCount(): Int = ???
override def getTriangleCount(): Int = ???
override def depthFirstTraversal(x$1: SceneGraphVisitor, x$2: DFSMode): Unit =
???
override protected def breadthFirstTraversal(
x$1: SceneGraphVisitor,
x$2: Queue[Spatial]
): Unit = ???
}

View File

@ -1,70 +0,0 @@
package wow.doge.mygame
import scala.concurrent.duration._
import cats.syntax.show._
import monix.bio.IO
import monix.reactive.Observable
import wow.doge.mygame.implicits._
import wow.doge.mygame.utils.MonixDirectoryWatcher
import scala.concurrent.Future
import monix.execution.Scheduler
import monix.bio.Task
import monix.{eval => me}
class FileWatcherTest extends munit.TaglessFinalSuite[Task] {
override protected def toFuture[A](f: Task[A]): Future[A] = {
implicit val s = Scheduler.global
f.runToFuture
}
// test("1") {
// import better.files._
// import io.methvin.better.files._
// val myDir =
// File((os.pwd / "assets" / "scripts").toString)
// val watcher = new RecursiveFileMonitor(myDir) {
// override def onCreate(file: File, count: Int) =
// println(show"$file got created")
// override def onModify(file: File, count: Int) =
// println(show"$file got modified $count times")
// override def onDelete(file: File, count: Int) =
// println(show"$file got deleted")
// }
// import monix.execution.Scheduler.Implicits.global
// CResource
// .make(Task { watcher.start(); watcher })(w => Task(w.stop()))
// .use(_ => Task.never)
// .runSyncUnsafe(10.seconds)
// }
test("2") {
val obsT = MonixDirectoryWatcher(os.pwd / "assets" / "scripts")
obsT
.flatMap(
_.takeUntil(Observable.unit.delayExecution(2.seconds))
.doOnNext {
case MonixDirectoryWatcher.CreateEvent(file, count) =>
me.Task(println(show"${file.toString} got created"))
case MonixDirectoryWatcher.DeleteEvent(file, count) =>
me.Task(println(show"${file.toString} got deleted"))
case MonixDirectoryWatcher.ModifyEvent(file, count) =>
me.Task(
pprint.log(show"${file.toString} got modified $count times")
)
}
.completedL
.flatMap(_ => me.Task.sleep(3.seconds))
.toIO
)
.onErrorHandleWith {
case ex: java.nio.file.ClosedWatchServiceException => IO.unit
case ex: java.lang.UnsupportedOperationException => IO.unit
}
}
}

Some files were not shown because too many files have changed in this diff Show More