forked from nova/jmonkey-test
Compare commits
22 Commits
master
...
developmen
Author | SHA1 | Date | |
---|---|---|---|
ece29b6b0d | |||
632bcccef3 | |||
8f3c08f271 | |||
dd01b070ff | |||
4ff54c1373 | |||
44f0538b8b | |||
9b484e895b | |||
1b9bb4265f | |||
f0ae3625bf | |||
be9acf81d5 | |||
67201c8f7e | |||
1422d91b14 | |||
92aae68254 | |||
12b232fb3c | |||
fd8b3819ff | |||
67a2bc4385 | |||
89fad19d99 | |||
2e05cb35fe | |||
64480e8e03 | |||
978215b510 | |||
6ccbf7e11d | |||
3881aac350 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -15,6 +15,7 @@ metals.sbt
|
|||||||
.metals
|
.metals
|
||||||
.bloop
|
.bloop
|
||||||
.ammonite
|
.ammonite
|
||||||
|
.bsp
|
||||||
|
|
||||||
# Scala-IDE specific
|
# Scala-IDE specific
|
||||||
.scala_dependencies
|
.scala_dependencies
|
||||||
@ -24,3 +25,5 @@ metals.sbt
|
|||||||
.vscode
|
.vscode
|
||||||
assets/
|
assets/
|
||||||
*.j3o
|
*.j3o
|
||||||
|
.attach_pid*
|
||||||
|
hs_err_pid*
|
8
.scalafix.conf
Normal file
8
.scalafix.conf
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
rules = [
|
||||||
|
# ScalalintClasses,
|
||||||
|
# ScalalintImports,
|
||||||
|
# ScalalintPackages,
|
||||||
|
# ScalalintInference,
|
||||||
|
OrganizeImports
|
||||||
|
]
|
||||||
|
# ScalalintClasses.removeEmptyConstructor = false
|
@ -1 +1,4 @@
|
|||||||
version = "2.6.4"
|
version = "2.6.4"
|
||||||
|
rewrite {
|
||||||
|
rules = [SortImports, RedundantBraces]
|
||||||
|
}
|
||||||
|
164
build.sbt
Normal file → Executable file
164
build.sbt
Normal file → Executable file
@ -1,23 +1,4 @@
|
|||||||
// 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"
|
||||||
@ -26,7 +7,7 @@ resolvers += "Jitpack" at "https://jitpack.io"
|
|||||||
resolvers += Resolver.mavenLocal
|
resolvers += Resolver.mavenLocal
|
||||||
resolvers += Resolver.sonatypeRepo("snapshots")
|
resolvers += Resolver.sonatypeRepo("snapshots")
|
||||||
|
|
||||||
lazy val jmeVersion = "3.3.2-stable"
|
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"
|
||||||
@ -37,52 +18,44 @@ 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.1.1",
|
"org.typelevel" %% "cats-core" % "2.3.0",
|
||||||
"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.1.1",
|
"org.typelevel" %% "cats-core" % "2.3.0",
|
||||||
"org.typelevel" %% "cats-effect" % "2.1.4",
|
"org.typelevel" %% "cats-effect" % "2.3.0",
|
||||||
"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.client" %% "core" % "2.2.5",
|
"com.softwaremill.sttp.client3" %% "core" % "3.0.0",
|
||||||
"com.softwaremill.sttp.client" %% "monix" % "2.2.5",
|
"com.softwaremill.sttp.client3" %% "monix" % "3.0.0",
|
||||||
"com.softwaremill.sttp.client" %% "circe" % "2.2.5",
|
"com.softwaremill.sttp.client3" %% "circe" % "3.0.0",
|
||||||
"com.softwaremill.sttp.client" %% "async-http-client-backend-monix" % "2.2.5",
|
"com.softwaremill.sttp.client3" %% "async-http-client-backend-monix" % "3.0.0",
|
||||||
|
"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",
|
||||||
@ -90,18 +63,31 @@ 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" % "14.0.1" classifier osName
|
"org.openjfx" % s"javafx-$m" % "11.0.1" classifier osName
|
||||||
),
|
),
|
||||||
scalacOptions ++= Seq(
|
scalacOptions ++= Seq(
|
||||||
"-encoding",
|
"-encoding",
|
||||||
@ -116,8 +102,6 @@ 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
|
||||||
@ -155,60 +139,40 @@ 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"
|
||||||
// Here, `libraryDependencies` is a set of dependencies, and by using `+=`,
|
|
||||||
// we're adding the scala-parser-combinators dependency to the set of dependencies
|
|
||||||
// that sbt will go and fetch when it starts up.
|
|
||||||
// Now, in any Scala file, you can import classes, objects, etc., from
|
|
||||||
// scala-parser-combinators with a regular import.
|
|
||||||
|
|
||||||
// TIP: To find the "dependency" that you need to add to the
|
|
||||||
// `libraryDependencies` set, which in the above example looks like this:
|
|
||||||
|
|
||||||
// "org.scala-lang.modules" %% "scala-parser-combinators" % "1.1.2"
|
|
||||||
|
|
||||||
// You can use Scaladex, an index of all known published Scala libraries. There,
|
|
||||||
// after you find the library you want, you can just copy/paste the dependency
|
|
||||||
// information that you need into your build file. For example, on the
|
|
||||||
// scala/scala-parser-combinators Scaladex page,
|
|
||||||
// https://index.scala-lang.org/scala/scala-parser-combinators, you can copy/paste
|
|
||||||
// the sbt dependency from the sbt box on the right-hand side of the screen.
|
|
||||||
|
|
||||||
// IMPORTANT NOTE: while build files look _kind of_ like regular Scala, it's
|
|
||||||
// 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")
|
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"
|
ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.4.3"
|
||||||
|
scalafixDependencies in ThisBuild += "org.scalalint" %% "rules" % "0.1.4"
|
||||||
|
|
||||||
|
wartremoverErrors in (Compile, compile) ++=
|
||||||
|
Warts.allBut(
|
||||||
|
Wart.Any,
|
||||||
|
Wart.NonUnitStatements,
|
||||||
|
// Wart.StringPlusAny,
|
||||||
|
Wart.Overloading,
|
||||||
|
Wart.PublicInference,
|
||||||
|
Wart.Nothing,
|
||||||
|
Wart.Var,
|
||||||
|
Wart.DefaultArguments,
|
||||||
|
// Wart.MutableDataStructures,
|
||||||
|
Wart.ImplicitConversion,
|
||||||
|
Wart.ImplicitParameter,
|
||||||
|
Wart.ToString,
|
||||||
|
Wart.Recursion,
|
||||||
|
Wart.While,
|
||||||
|
Wart.ExplicitImplicitTypes,
|
||||||
|
Wart.ListUnapply
|
||||||
|
)
|
||||||
|
// Seq(Wart.FinalCaseClass)
|
||||||
|
@ -1 +1 @@
|
|||||||
sbt.version=1.3.13
|
sbt.version=1.4.7
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
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")
|
||||||
addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1")
|
addSbtPlugin("io.spray" % "sbt-revolver" % "0.9.1")
|
||||||
|
addSbtPlugin(
|
||||||
|
"com.thoughtworks.deeplearning" % "sbt-ammonite-classpath" % "2.0.0"
|
||||||
|
)
|
||||||
|
addSbtPlugin("org.wartremover" % "sbt-wartremover" % "2.4.13")
|
||||||
|
@ -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
|
11
src/main/resources/main.css
Normal file
11
src/main/resources/main.css
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
.red-bar > .bar {
|
||||||
|
-fx-background-color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.green-bar > .bar {
|
||||||
|
-fx-background-color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yellow-bar > .bar {
|
||||||
|
-fx-background-color: yellow;
|
||||||
|
}
|
@ -1,5 +0,0 @@
|
|||||||
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?
|
|
@ -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]
|
||||||
}
|
}
|
||||||
|
|
||||||
private lazy val (defaultConsoleLogger, release1) =
|
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()
|
||||||
|
|
||||||
private lazy val (mainFileLogger, release2) =
|
val (mainFileLogger, release2) =
|
||||||
fileLogger[IO](
|
fileLogger[IO](
|
||||||
"application-log-2.log",
|
"application-log-2.log",
|
||||||
Formatter.json,
|
Formatter.json,
|
||||||
@ -47,7 +47,11 @@ class StaticLoggerBinder extends OdinLoggerBinder[IO] {
|
|||||||
.allocated
|
.allocated
|
||||||
.unsafeRunSync()
|
.unsafeRunSync()
|
||||||
|
|
||||||
private lazy val (eventBusFileLogger, release3) =
|
val mainFileLogger2 = mainFileLogger.contramap(lm =>
|
||||||
|
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,
|
||||||
@ -56,11 +60,9 @@ class StaticLoggerBinder extends OdinLoggerBinder[IO] {
|
|||||||
.allocated
|
.allocated
|
||||||
.unsafeRunSync()
|
.unsafeRunSync()
|
||||||
|
|
||||||
{
|
ArraySeq(release1, release2, release3).foreach(r =>
|
||||||
ArraySeq(release1, release2, release3).foreach(r =>
|
sys.addShutdownHook(r.unsafeRunSync())
|
||||||
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" =>
|
||||||
@ -72,15 +74,22 @@ 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) |+| mainFileLogger
|
defaultConsoleLogger.withMinimalLevel(Level.Debug) |+| mainFileLogger2
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
33
src/main/scala/wow/doge/mygame/AppError.scala
Normal file
33
src/main/scala/wow/doge/mygame/AppError.scala
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
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]
|
||||||
|
}
|
7
src/main/scala/wow/doge/mygame/Dispatchers.scala
Normal file
7
src/main/scala/wow/doge/mygame/Dispatchers.scala
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package wow.doge.mygame
|
||||||
|
|
||||||
|
import akka.actor.typed.DispatcherSelector
|
||||||
|
|
||||||
|
object Dispatchers {
|
||||||
|
val jmeDispatcher = DispatcherSelector.fromConfig("jme-dispatcher")
|
||||||
|
}
|
@ -2,68 +2,65 @@ 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 com.softwaremill.macwire._
|
import io.odin.consoleLogger
|
||||||
import io.odin._
|
import io.odin.fileLogger
|
||||||
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.game.GameAppResource
|
import wow.doge.mygame.ActorSystemResource
|
||||||
|
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 MainModule {
|
object Main extends BIOApp with ExecutorsModule {
|
||||||
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().withAsync(
|
consoleLogger(minLevel = odin.Level.Debug).withAsync(
|
||||||
timeWindow = 1.milliseconds,
|
timeWindow = 1.milliseconds,
|
||||||
maxBufferSize = Some(2000)
|
maxBufferSize = Some(100)
|
||||||
) |+|
|
) |+|
|
||||||
fileLogger(
|
fileLogger(
|
||||||
"application-log-1.log",
|
"application-log-1.log",
|
||||||
Formatter.json
|
Formatter.json
|
||||||
).withAsync(timeWindow = 1.milliseconds, maxBufferSize = Some(2000))
|
).withAsync(timeWindow = 1.milliseconds, maxBufferSize = Some(100))
|
||||||
jmeScheduler <- jMESchedulerResource
|
jmeScheduler <- jmeSchedulerResource
|
||||||
actorSystem <- actorSystemResource(logger)
|
// backend <- Resource.make(toIO(HttpClientMonixBackend()))(backend =>
|
||||||
gameApp <- {
|
// toIO(backend.close())
|
||||||
// new BulletAppState()
|
// )
|
||||||
// bas.setThreadingType(Thr)
|
actorSystem <- new ActorSystemResource(logger, schedulers.async).get
|
||||||
// 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 >> Task(consoleStream.close()))
|
.use(_ => Task.unit)
|
||||||
.onErrorHandle(_.printStackTrace())
|
.flatMap(_ => Task(consoleStream.close()))
|
||||||
|
.onErrorHandleWith(ex => UIO(ex.printStackTrace()))
|
||||||
.as(ExitCode.Success)
|
.as(ExitCode.Success)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,143 +1,228 @@
|
|||||||
package wow.doge.mygame
|
package wow.doge.mygame
|
||||||
|
|
||||||
import akka.actor.typed.ActorSystem
|
import java.util.concurrent.TimeoutException
|
||||||
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 com.jme3.app.state.AppStateManager
|
import cats.syntax.eq._
|
||||||
import com.jme3.asset.AssetManager
|
import cats.syntax.show._
|
||||||
|
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.execution.exceptions.DummyException
|
import monix.bio.UIO
|
||||||
|
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.GameAppActor
|
import wow.doge.mygame.game.GameAppResource
|
||||||
import wow.doge.mygame.game.GameAppTags
|
import wow.doge.mygame.game.controls.FollowControl
|
||||||
|
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.PlayerController
|
import wow.doge.mygame.game.entities.player.PlayerActor
|
||||||
import wow.doge.mygame.game.entities.PlayerControllerTags
|
import wow.doge.mygame.game.entities.player.PlayerController
|
||||||
import wow.doge.mygame.game.subsystems.input.GameInputHandler
|
import wow.doge.mygame.game.entities.player.PlayerMovementReducer
|
||||||
|
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 EventsModule.GameEventBus
|
import wow.doge.mygame.utils.MonixDirectoryWatcher.ModifyEvent
|
||||||
|
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],
|
||||||
gameApp: GameApp,
|
jmeThread: JmeScheduler,
|
||||||
implicit val spawnProtocol: ActorSystem[SpawnProtocol.Command],
|
|
||||||
jmeThread: monix.execution.Scheduler,
|
|
||||||
schedulers: Schedulers,
|
schedulers: Schedulers,
|
||||||
consoleStream: GenericConsoleStream[TextArea]
|
consoleStream: GenericConsoleStream[TextArea]
|
||||||
)(implicit
|
)(implicit
|
||||||
@annotation.unused timeout: Timeout,
|
spawnProtocol: ActorRef[SpawnProtocol.Command],
|
||||||
@annotation.unused scheduler: Scheduler
|
timeout: Timeout,
|
||||||
|
scheduler: AkkaScheduler
|
||||||
) {
|
) {
|
||||||
|
implicit val as = scheduler.value
|
||||||
|
|
||||||
lazy val scriptSystemInit =
|
val scriptSystemResource: Resource[UIO, ScriptCompiler] =
|
||||||
new ScriptSystemResource(os.pwd, spawnProtocol, ScriptInitMode.Eager).init
|
new ScriptSystemResource(
|
||||||
|
os.pwd,
|
||||||
|
logger,
|
||||||
|
ScriptInitMode.Eager,
|
||||||
|
schedulers.io
|
||||||
|
).init2
|
||||||
|
|
||||||
def gameInit: Task[Fiber[Throwable, Unit]] =
|
val eventsModule = new EventsModule(scheduler, spawnProtocol)
|
||||||
|
|
||||||
|
def eval(
|
||||||
|
tickEventBus: GameEventBus[TickEvent],
|
||||||
|
gameApp: GameApp,
|
||||||
|
fib: Fiber[Nothing, Unit]
|
||||||
|
) =
|
||||||
for {
|
for {
|
||||||
eventsModule <- Task(new EventsModule(spawnProtocol))
|
// g <- UIO.pure(gameApp)
|
||||||
playerEventBus <- eventsModule.playerEventBusTask
|
playerEventBus <- eventsModule.playerEventBus
|
||||||
mainEventBus <- eventsModule.mainEventBusTask
|
mainEventBus <- eventsModule.mainEventBus
|
||||||
tickEventBus <- eventsModule.tickEventBusTask
|
obs <-
|
||||||
gameAppActor <- AkkaUtils.spawnActorL2(
|
playerEventBus
|
||||||
GameAppActor.Props(tickEventBus).behavior,
|
.askL[Observable[PlayerMovementEvent]](ObservableSubscription(_))
|
||||||
"gameAppActor"
|
.onErrorHandleWith(TimeoutError.from)
|
||||||
)
|
_ <-
|
||||||
_ <- gameAppActor !! GameAppActor.Start
|
IOUtils
|
||||||
gameAppFib <- gameApp.start.executeOn(jmeThread).start
|
.toIO(
|
||||||
/**
|
obs
|
||||||
* schedule a task to run on the JME thread and wait for it's completion
|
.doOnNextF(pme =>
|
||||||
* before proceeding forward, as a signal that the JME thread has been
|
logger.trace(show"Received event $pme").toTask.void
|
||||||
* initialized, otherwise we'll get NPEs trying to access the fields
|
)
|
||||||
* of the game app
|
.completedL
|
||||||
*/
|
.startAndForget
|
||||||
res <- gameApp.enqueueL(() => "done")
|
)
|
||||||
_ <- logger.info(s"Result = $res")
|
.hideErrors
|
||||||
/**
|
|
||||||
* 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 <- gameApp.assetManager
|
assetManager <- UIO.pure(gameApp.assetManager)
|
||||||
stateManager <- gameApp.stateManager
|
|
||||||
camera <- gameApp.camera
|
camera <- gameApp.camera
|
||||||
rootNode <- gameApp.rootNode
|
rootNode <- UIO.pure(gameApp.rootNode)
|
||||||
enqueueR <- Task(gameApp.enqueue _)
|
enqueueR <- UIO(gameApp.enqueue _)
|
||||||
viewPort <- gameApp.viewPort
|
viewPort <- gameApp.viewPort
|
||||||
_ <- logger.info("before")
|
physicsSpace <- UIO.pure(gameApp.physicsSpace)
|
||||||
// jfxUI <- gameApp.jfxUI
|
_ <- logger.infoU("before")
|
||||||
consoleTextArea <- Task(new TextArea {
|
jfxUI <- gameApp.jfxUI.hideErrors
|
||||||
|
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.info("after")
|
_ <- logger.infoU("after")
|
||||||
bulletAppState <- Task(new BulletAppState())
|
_ <- logger.infoU("Initializing console stream")
|
||||||
_ <- Task(stateManager.attach(bulletAppState))
|
_ <-
|
||||||
_ <- logger.info("Initializing console stream")
|
wire[MainAppDelegate]
|
||||||
_ <- wire[MainAppDelegate].init(gameApp.scheduler)
|
.init()
|
||||||
} yield (gameAppFib)
|
.executeOn(gameApp.scheduler.value)
|
||||||
|
} yield fib
|
||||||
|
|
||||||
lazy val program = for {
|
def gameInit(
|
||||||
scriptSystem <- scriptSystemInit
|
tickEventBus: GameEventBus[TickEvent]
|
||||||
/**
|
): Resource[UIO, Either[AppError, Fiber[Nothing, Unit]]] =
|
||||||
* Signal for synchronization between the JavaFX launcher and the in-game JavaFX GUI
|
for {
|
||||||
* Without this, we get a "Toolkit already initialized" exception. The launch button
|
r1 <- wire[GameAppResource].resource.evalMap(e =>
|
||||||
* in the launcher completes the signal. The game init process which listens for this
|
IO.fromEither(e)
|
||||||
* signal can then continue
|
.flatMap {
|
||||||
*/
|
case (gameApp -> gameAppFib) =>
|
||||||
launchSignal <- Deferred[Task, Launcher.LauncherResult]
|
eval(tickEventBus, gameApp, gameAppFib)
|
||||||
launcher <- new Launcher.Props(schedulers, launchSignal).create
|
}
|
||||||
cancelToken <- launcher.init()
|
.attempt
|
||||||
launchResult <- launchSignal.get
|
)
|
||||||
_ <- cancelToken.cancel
|
dirWatcher <- Resource.liftF(
|
||||||
|
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.info("Exiting")
|
logger.infoU("Exiting")
|
||||||
/**
|
/**
|
||||||
* User chose launch. Wait for game window to close
|
* User chose launch. Wait for game window to close
|
||||||
*/
|
*/
|
||||||
else
|
else
|
||||||
gameInit.flatMap(_.join)
|
gameInit(tickEventBus).use {
|
||||||
|
case Right(fib) => fib.join >> Task.unit
|
||||||
|
case Left(error) => IO.terminate(new Exception(error.toString))
|
||||||
|
}.hideErrors
|
||||||
|
|
||||||
} yield ()
|
} yield ()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,173 +231,388 @@ 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,
|
||||||
stateManager: AppStateManager,
|
physicsSpace: PhysicsSpace,
|
||||||
camera: Camera,
|
camera: Camera,
|
||||||
viewPort: ViewPort,
|
viewPort: ViewPort,
|
||||||
enqueueR: Function1[() => Unit, Unit],
|
enqueueR: Function1[() => Unit, Unit],
|
||||||
rootNode: Node @@ GameAppTags.RootNode,
|
rootNode: RootNode,
|
||||||
bulletAppState: BulletAppState
|
schedulers: Schedulers,
|
||||||
|
jfxUI: JavaFxUI
|
||||||
)(implicit
|
)(implicit
|
||||||
@annotation.unused timeout: Timeout,
|
spawnProtocol: ActorRef[SpawnProtocol.Command],
|
||||||
@annotation.unused scheduler: Scheduler
|
timeout: Timeout,
|
||||||
|
scheduler: AkkaScheduler
|
||||||
) {
|
) {
|
||||||
lazy val physicsSpace = bulletAppState.physicsSpace
|
implicit val as = scheduler.value
|
||||||
|
|
||||||
def init(
|
def init(
|
||||||
appScheduler: monix.execution.Scheduler
|
// appScheduler: monix.execution.Scheduler
|
||||||
// consoleStream: GenericConsoleStream[TextArea]
|
// consoleStream: GenericConsoleStream[TextArea]
|
||||||
) =
|
): IO[AppError, Unit] =
|
||||||
for {
|
for {
|
||||||
_ <- loggerL.info("Initializing Systems")
|
_ <- loggerL.infoU("Initializing Systems")
|
||||||
_ <- Task(
|
_ <- assetManager.registerLocator(
|
||||||
assetManager.registerLocator(
|
os.rel / "assets" / "town.zip",
|
||||||
os.rel / "assets" / "town.zip",
|
classOf[ZipLocator]
|
||||||
classOf[ZipLocator]
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
_ <- loggerL.info("test")
|
|
||||||
// _ <- Task(consoleStream.println("text"))
|
// _ <- Task(consoleStream.println("text"))
|
||||||
_ <- DefaultGameLevel(assetManager, viewPort)
|
level <- DefaultGameLevel(assetManager, viewPort)
|
||||||
.addToGame(
|
_ <- level.addToGame(rootNode, physicsSpace)
|
||||||
rootNode,
|
playerActor <- createPlayerController()
|
||||||
bulletAppState.physicsSpace
|
// .onErrorRestart(3)
|
||||||
)
|
// _ <- wire[GameInputHandler.Props].begin
|
||||||
.executeOn(appScheduler)
|
_ <- new InputMappings(new jme.InputManager(inputManager)).setup
|
||||||
_ <- createPlayerController(appScheduler)
|
// .onErrorRestart(3)
|
||||||
.absorbWith(e => DummyException("boom"))
|
johnActor <- createTestNpc("John")
|
||||||
.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(
|
// _ <-
|
||||||
// ImVector3f(-80, 0, 100)
|
// johnActor
|
||||||
// )).executeAsync.delayExecution(2.seconds)
|
// .tellL(NpcActorSupervisor.Move(ImVector3f(-30, 0, 10)))
|
||||||
|
// .delayExecution(2.seconds)
|
||||||
_ <-
|
_ <-
|
||||||
IOUtils
|
rootNode.depthFirstTraversal
|
||||||
.toIO(
|
.doOnNextF(spat => loggerL.debug(spat.getName).toTask)
|
||||||
rootNode
|
.completedL
|
||||||
.observableBreadthFirst()
|
.toIO
|
||||||
.doOnNext(spat => IOUtils.toTask(loggerL.debug(spat.getName())))
|
.hideErrors
|
||||||
.completedL
|
damageObs <-
|
||||||
|
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
|
||||||
)
|
)
|
||||||
.executeOn(appScheduler)
|
.completedL
|
||||||
|
.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[PlayerController.Error, Unit] = {
|
): IO[AppError, PlayerActor.Ref] = {
|
||||||
val playerPos = ImVector3f.ZERO
|
val playerPos = ImVector3f.Zero
|
||||||
val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o"
|
val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o"
|
||||||
lazy val playerPhysicsControl =
|
// val modelPath = os.rel / "Models" / "Oto" / "Oto.mesh.xml"
|
||||||
|
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 {
|
||||||
playerNode <- IO.fromEither(mbPlayerNode)
|
playerModel <-
|
||||||
_ <- IO(cameraPivotNode.addControl(new FollowControl(playerNode)))
|
assetManager
|
||||||
.onErrorHandleWith(e =>
|
.loadModelAs[Node](modelPath)
|
||||||
IO.raiseError(PlayerController.GenericError(e.getMessage()))
|
.map(_.withRotate(0, FastMath.PI, 0))
|
||||||
)
|
.tapEval(m => UIO(m.center()))
|
||||||
camNode <- IO(
|
.mapError(AppError.AssetManagerError)
|
||||||
|
playerNode <- UIO(
|
||||||
PlayerController.Defaults
|
PlayerController.Defaults
|
||||||
.defaultCamerNode(camera, cameraPivotNode, playerNode, playerPos)
|
.defaultPlayerNode(
|
||||||
).onErrorHandleWith(e =>
|
playerPos,
|
||||||
IO.raiseError(PlayerController.GenericError(e.getMessage()))
|
playerModel,
|
||||||
).map(_.taggedWith[PlayerControllerTags.PlayerCameraNode])
|
playerPhysicsControl
|
||||||
// _ <- Task {
|
)
|
||||||
// val chaseCam = new ChaseCamera(camera, playerNode, inputManager)
|
)
|
||||||
// chaseCam.setSmoothMotion(false)
|
cameraPivotNode <- UIO(
|
||||||
// chaseCam.setLookAtOffset(new Vector3f(0, 1.5f, 10))
|
new Node(EntityIds.CameraPivot.value)
|
||||||
// chaseCam
|
.withControl(new FollowControl(playerNode))
|
||||||
// }
|
.taggedWith[PlayerController.Tags.PlayerCameraPivotNode]
|
||||||
// .onErrorHandleWith(e =>
|
)
|
||||||
// IO.raiseError(PlayerController.GenericError(e.getMessage()))
|
camNode <- UIO(
|
||||||
// )
|
PlayerController.Defaults
|
||||||
_ <- wire[PlayerController.Props].create
|
.defaultCamerNode(camera, playerPos)
|
||||||
} yield ()
|
.taggedWith[PlayerController.Tags.PlayerCameraNode]
|
||||||
|
)
|
||||||
|
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] = {
|
||||||
// : IO[PlayerController.Error, Unit] =
|
val initialPos = ImVector3f(50, 5, 0)
|
||||||
{
|
val npcPhysicsControl = new BetterCharacterControl(1.5f, 6f, 1f)
|
||||||
val initialPos = ImVector3f(100, 0, 0)
|
// (1f, 2.1f, 10f)
|
||||||
// val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o"
|
.withJumpForce(ImVector3f(0, 5f, 0))
|
||||||
lazy val npcPhysicsControl =
|
|
||||||
new BetterCharacterControl(1f, 2.1f, 10f)
|
val npcActorTask = AkkaUtils.spawnActorL(
|
||||||
// .withJumpForce(ImVector3f(0, 5f, 0))
|
new NpcActorSupervisor.Props(
|
||||||
// val npcMovementActor = AkkaUtils.spawnActorL2(
|
new NpcMovementActor.Props(
|
||||||
// new NpcMovementActor2.Props(
|
enqueueR,
|
||||||
// initialPos,
|
initialPos,
|
||||||
// tickEventBus,
|
npcName,
|
||||||
// npcPhysicsControl
|
npcPhysicsControl
|
||||||
// ).behavior,
|
).behavior,
|
||||||
// s"${npcName}-npcMovementActor"
|
npcName,
|
||||||
// )
|
initialPos
|
||||||
lazy val mbNpcNode = PlayerController.Defaults.defaultNpcNode(
|
).behavior,
|
||||||
assetManager,
|
actorName = Some(show"${npcName}-npcActorSupervisor")
|
||||||
|
)
|
||||||
|
|
||||||
|
(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
|
||||||
)
|
)
|
||||||
val npcActorTask = AkkaUtils.spawnActorL2(
|
_ <- (for {
|
||||||
NpcActorSupervisor
|
_ <- physicsSpace += npcPhysicsControl
|
||||||
.Props(
|
_ <- physicsSpace += npcNode
|
||||||
new NpcMovementActor.Props(
|
_ <- rootNode += npcNode
|
||||||
enqueueR,
|
} yield ()).mapError(AppError.AppNodeError)
|
||||||
initialPos,
|
npcActor <- npcActorTask
|
||||||
// tickEventBus,
|
} yield npcActor)
|
||||||
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 = {}
|
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,26 @@
|
|||||||
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.ExecutorsModule
|
import wow.doge.mygame.executors.Schedulers.AsyncScheduler
|
||||||
|
|
||||||
trait MainModule extends ExecutorsModule {
|
class ActorSystemResource(logger: Logger[Task], scheduler: AsyncScheduler) {
|
||||||
|
def get: Resource[Task, ActorSystem[SpawnProtocol.Command]] =
|
||||||
def actorSystemResource(
|
Resource.make(
|
||||||
logger: Logger[Task]
|
logger.info("Creating Actor System") >> Task(
|
||||||
): Resource[Task, ActorSystem[SpawnProtocol.Command]] =
|
ActorSystem(
|
||||||
Resource.make(logger.info("Creating Actor System") >> Task {
|
SpawnProtocol(),
|
||||||
ActorSystem(
|
name = "GameActorSystem",
|
||||||
SpawnProtocol(),
|
BootstrapSetup().withDefaultExecutionContext(scheduler.value)
|
||||||
name = "GameActorSystem"
|
)
|
||||||
)
|
)
|
||||||
})(sys =>
|
)(sys =>
|
||||||
for {
|
for {
|
||||||
_ <- Task(sys.terminate())
|
_ <- Task(sys.terminate())
|
||||||
_ <- Task.fromFuture(sys.whenTerminated)
|
_ <- Task.deferFuture(sys.whenTerminated)
|
||||||
_ <- logger.info("Actor System Terminated")
|
_ <- logger.info("Actor System Terminated")
|
||||||
} yield ()
|
} yield ()
|
||||||
)
|
)
|
||||||
|
81
src/main/scala/wow/doge/mygame/actor/GameActorSystem.scala
Normal file
81
src/main/scala/wow/doge/mygame/actor/GameActorSystem.scala
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
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
|
||||||
|
// }
|
||||||
|
// }
|
@ -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()
|
|
||||||
// Resource.make(
|
val schedulers = Schedulers.default
|
||||||
// Task(
|
|
||||||
// new Schedulers(
|
val jmeSchedulerResource = Resource.make(
|
||||||
// jme = Scheduler
|
|
||||||
// .singleThread(name = "JME-Application-Thread", daemonic = false)
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
// )(s => Task(s.jme.shutdown()))
|
|
||||||
lazy val jMESchedulerResource = Resource.make(
|
|
||||||
Task(
|
Task(
|
||||||
Scheduler
|
JmeScheduler(
|
||||||
.singleThread(name = "JME-Application-Thread", daemonic = false)
|
Scheduler
|
||||||
|
.singleThread(name = "JME-Application-Thread", daemonic = false)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)(e => Task(e.shutdown()))
|
)(s => Task(s.value.shutdown()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sealed trait Error
|
||||||
|
case object Error extends Error
|
||||||
|
@ -13,6 +13,7 @@ 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
|
||||||
@ -43,12 +44,11 @@ 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 {
|
||||||
var runner: com.jme3.app.Application = null
|
@SuppressWarnings(Array("org.wartremover.warts.Null"))
|
||||||
|
var runner: Application = null
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,9 +98,8 @@ 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)
|
||||||
|
@ -5,13 +5,9 @@ import monix.execution.Scheduler
|
|||||||
import monix.execution.UncaughtExceptionReporter
|
import monix.execution.UncaughtExceptionReporter
|
||||||
|
|
||||||
final case class Schedulers(
|
final case class Schedulers(
|
||||||
blockingIO: Scheduler = Scheduler
|
io: Schedulers.IoScheduler,
|
||||||
.io()
|
async: Schedulers.AsyncScheduler,
|
||||||
.withUncaughtExceptionReporter(Schedulers.reporter),
|
fx: Schedulers.FxScheduler
|
||||||
async: Scheduler = Scheduler.global
|
|
||||||
.withUncaughtExceptionReporter(Schedulers.reporter),
|
|
||||||
fx: Scheduler = JFXExecutionContexts.fxScheduler
|
|
||||||
.withUncaughtExceptionReporter(Schedulers.reporter)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
object Schedulers {
|
object Schedulers {
|
||||||
@ -20,4 +16,24 @@ 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)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,193 +1,130 @@
|
|||||||
package wow.doge.mygame.game
|
package wow.doge.mygame.game
|
||||||
|
|
||||||
import cats.effect.concurrent.Deferred
|
import scala.concurrent.duration._
|
||||||
import com.jme3.app.state.AppStateManager
|
|
||||||
import com.jme3.asset.AssetManager
|
import akka.actor.typed.ActorRef
|
||||||
|
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.scene.Node
|
import com.jme3.system.AppSettings
|
||||||
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.catnap.ConcurrentChannel
|
import monix.bio.UIO
|
||||||
import monix.catnap.ConsumerF
|
import wow.doge.mygame.AppError
|
||||||
import monix.catnap.Semaphore
|
import wow.doge.mygame.AppError.TimeoutError
|
||||||
import monix.eval.Coeval
|
import wow.doge.mygame.Dispatchers
|
||||||
|
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._
|
||||||
sealed trait Error
|
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
|
||||||
case object FlyCamNotExists extends Error
|
import wow.doge.mygame.subsystems.events.TickEvent
|
||||||
|
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(logger: Logger[Task], val app: SimpleAppExt) {
|
class GameApp private[game] (
|
||||||
import Ops._
|
logger: Logger[Task],
|
||||||
|
app: SimpleAppExt,
|
||||||
def stateManager: Task[AppStateManager] = Task(app.getStateManager())
|
gameActor: ActorRef[GameAppActor.Command],
|
||||||
def inputManager: Task[InputManager] = Task(app.getInputManager())
|
gameSpawnProtocol: ActorRef[SpawnProtocol.Command],
|
||||||
def assetManager: Task[AssetManager] = Task(app.getAssetManager())
|
akkaScheduler: AkkaScheduler
|
||||||
def guiNode = Task(app.getGuiNode().taggedWith[GameAppTags.GuiNode])
|
) {
|
||||||
def addToGuiNode = guiNode.flatMap(rn => Task(new AddToNode(rn)))
|
def inputManager: UIO[InputManager] = UIO(app.getInputManager())
|
||||||
def flyCam =
|
val assetManager = new AssetManager(app.getAssetManager())
|
||||||
IO(app.getFlyByCamera()).onErrorHandleWith(_ =>
|
val guiNode: GuiNode =
|
||||||
IO.raiseError(FlyCamNotExists)
|
AppNode2(app.getGuiNode()).taggedWith[GameAppTags.GuiNode]
|
||||||
)
|
// def flyCam = Option(app.getFlyByCamera())
|
||||||
def camera = Task(app.getCamera())
|
def camera = UIO(app.getCamera())
|
||||||
def viewPort = Task(app.getViewPort())
|
def viewPort = UIO(app.getViewPort())
|
||||||
def rootNode = Task(app.getRootNode().taggedWith[GameAppTags.RootNode])
|
val rootNode: RootNode =
|
||||||
// def rootNode2 = SynchedObject(app.getRootNode())
|
AppNode2(app.getRootNode()).taggedWith[GameAppTags.RootNode]
|
||||||
def addToRootNode = rootNode.flatMap(rn => Task(new AddToNode(rn)))
|
val physicsSpace =
|
||||||
def enqueue(cb: () => Unit) =
|
new PhysicsSpace(app.bulletAppState.physicsSpace)
|
||||||
app.enqueue(new Runnable {
|
def enqueue(cb: () => Unit) = app.enqueueR(() => cb())
|
||||||
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 start = Task(app.start())
|
def whenTerminated: IO[AppError, Unit] =
|
||||||
def stop = Task(app.stop())
|
IO.deferFuture(app.whenTerminated).onErrorHandleWith(TimeoutError.from)
|
||||||
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)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object GameApp {
|
class GameAppResource(
|
||||||
|
|
||||||
class WrappedNode(node: Node, lock: Semaphore[Task]) {
|
|
||||||
|
|
||||||
def +=(spat: Spatial) = lock.withPermit(Task(node.attachChild(spat)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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)))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
object Ops {
|
|
||||||
final class AddToNode[T <: Node](private val node: T) extends AnyVal {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pure version
|
|
||||||
*/
|
|
||||||
def apply(spatial: Spatial)(implicit logger: Logger[Task]) =
|
|
||||||
logger.debug(
|
|
||||||
s"Request to add spatial with name ${spatial.getName()} to node ${node.getName()}"
|
|
||||||
) >> Task(node.attachChild(spatial))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Impure version
|
|
||||||
*/
|
|
||||||
def apply(spatial: Spatial)(implicit logger: SLogger) =
|
|
||||||
Coeval {
|
|
||||||
logger.debug(
|
|
||||||
s"Request to add spatial with name ${spatial.getName()} to node ${node.getName()}"
|
|
||||||
)
|
|
||||||
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],
|
logger: Logger[Task],
|
||||||
spawnChannel: ConcurrentChannel[
|
jmeThread: JmeScheduler,
|
||||||
Task,
|
schedulers: Schedulers,
|
||||||
SpawnSystem.Complete,
|
tickEventBus: GameEventBus[TickEvent]
|
||||||
SpawnSystem.SpawnRequestWrapper
|
)(implicit
|
||||||
]
|
timeout: Timeout,
|
||||||
|
scheduler: AkkaScheduler,
|
||||||
|
spawnProtocol: ActorRef[SpawnProtocol.Command]
|
||||||
) {
|
) {
|
||||||
import SpawnSystem._
|
implicit val as = scheduler.value
|
||||||
|
def resource
|
||||||
for {
|
: Resource[UIO, Either[AppError, (GameApp, Fiber[Nothing, Unit])]] =
|
||||||
spawnSystem <- SpawnSystem(logger)
|
Resource.make(
|
||||||
res <- spawnSystem.request(SpawnSpatial(Task(new Node("Test"))))
|
(for {
|
||||||
} yield ()
|
app <- UIO(new SimpleAppExt(schedulers, new BulletAppState))
|
||||||
|
_ <- UIO(JMERunner.runner = app)
|
||||||
// val spawnChannel = ConcurrentChannel[Task].of[Result, SpawnRequest]
|
_ <- UIO {
|
||||||
|
val settings = new AppSettings(true)
|
||||||
private def receive(
|
settings.setVSync(true)
|
||||||
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)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* disables the launcher
|
||||||
|
* We'll be making our own launcher anyway
|
||||||
|
*/
|
||||||
|
app.setShowSettings(false)
|
||||||
|
app.setSettings(settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fib <- UIO(app.start).executeOn(jmeThread.value).start
|
||||||
|
_ <- Task.deferFuture(app.started).onErrorHandleWith(TimeoutError.from)
|
||||||
|
gameAppActor <- AkkaUtils.spawnActorL(
|
||||||
|
new GameAppActor.Props(tickEventBus).behavior,
|
||||||
|
Some("testGameActor"),
|
||||||
|
props = Dispatchers.jmeDispatcher
|
||||||
|
)
|
||||||
|
_ <- gameAppActor !! GameAppActor.Start
|
||||||
|
sp <-
|
||||||
|
gameAppActor
|
||||||
|
.askL(GameAppActor.GetSpawnProtocol)
|
||||||
|
.onErrorHandleWith(TimeoutError.from)
|
||||||
|
gameApp <- UIO(new GameApp(logger, app, gameAppActor, sp, scheduler))
|
||||||
|
_ <- UIO {
|
||||||
|
val fut = () => gameAppActor.ask(GameAppActor.Stop).flatten
|
||||||
|
app.cancelToken = Some(fut)
|
||||||
|
}
|
||||||
|
} yield (gameApp, fib)).attempt
|
||||||
|
) {
|
||||||
|
case Right(gameApp -> fib) => fib.cancel
|
||||||
|
case Left(error) => IO.terminate(new Exception(error.toString))
|
||||||
}
|
}
|
||||||
|
|
||||||
def request(spawnRequest: SpawnRequest) =
|
|
||||||
for {
|
|
||||||
d <- Deferred[Task, Result]
|
|
||||||
_ <- spawnChannel.push(SpawnRequestWrapper(spawnRequest, d))
|
|
||||||
res <- d.get
|
|
||||||
} yield (res)
|
|
||||||
|
|
||||||
def stop = spawnChannel.halt(Complete)
|
|
||||||
}
|
}
|
||||||
|
@ -2,61 +2,88 @@ 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.game.entities.GenericTimerActor
|
import wow.doge.mygame.implicits._
|
||||||
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.subsystems.events.TickEvent.PhysicsTick
|
import wow.doge.mygame.utils.GenericTimerActor
|
||||||
|
|
||||||
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 Stop extends Command
|
case object Ping extends Command
|
||||||
|
final case class Stop(stopSignal: ActorRef[CancelableFuture[Unit]])
|
||||||
|
extends Command
|
||||||
|
final case class GetSpawnProtocol(
|
||||||
|
replyTo: ActorRef[ActorRef[SpawnProtocol.Command]]
|
||||||
|
) extends Command
|
||||||
|
|
||||||
case class Props(
|
final case class Props(tickEventBus: GameEventBus[TickEvent]) {
|
||||||
// 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.info("Hello from GameAppActor")
|
ctx.log.infoP("Hello from GameAppActor")
|
||||||
val renderTickGenerator =
|
val renderTickGenerator =
|
||||||
ctx.spawn(
|
ctx.spawnN(
|
||||||
Behaviors
|
Behaviors
|
||||||
.supervise(renderTickGeneratorBehavior)
|
.supervise(renderTickGeneratorBehavior)
|
||||||
.onFailure[Exception](SupervisorStrategy.restart),
|
.onFailure[Exception](
|
||||||
"tickGeneratorActor"
|
SupervisorStrategy.restart.withLimit(2, 100.millis)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
val tickGeneratorTimer = ctx.spawn(
|
val tickGeneratorTimer = ctx.spawnN(
|
||||||
GenericTimerActor
|
GenericTimerActor
|
||||||
.Props(renderTickGenerator, TickGenerator.Send, 10.millis)
|
.Props(renderTickGenerator, TickGenerator.Send, 10.millis)
|
||||||
.behavior,
|
.behavior
|
||||||
"tickGeneratorTimer"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
Behaviors.receiveMessage {
|
val sp = ctx.spawn(SpawnProtocol(), "gameSpawnProtocol")
|
||||||
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 =
|
||||||
@ -75,92 +102,3 @@ 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
|
|
||||||
// )
|
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
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"))
|
|
||||||
}
|
|
@ -1,22 +1,21 @@
|
|||||||
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._
|
||||||
@ -24,34 +23,49 @@ 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 lazy val taskQueue2 = Atomic(Queue.empty[MyTask[_]])
|
private val taskQueue2 = Atomic(Queue.empty[MyTask[_]])
|
||||||
|
|
||||||
private val tickSubject =
|
private val startSignal: CancelablePromise[Unit] = CancelablePromise()
|
||||||
ConcurrentSubject[Float](multicast = MulticastStrategy.publish)(
|
private val terminationSignal: CancelablePromise[Unit] = CancelablePromise()
|
||||||
schedulers.async
|
private implicit val ec = schedulers.async.value
|
||||||
)
|
|
||||||
|
|
||||||
def tickObservable: Observable[Float] = tickSubject
|
var cancelToken: Option[() => Future[Unit]] = None
|
||||||
|
|
||||||
override def simpleInitApp(): Unit = {}
|
def started: CancelableFuture[Unit] = startSignal.future
|
||||||
|
|
||||||
override def simpleUpdate(tpf: Float): Unit = {
|
def whenTerminated: CancelableFuture[Unit] = terminationSignal.future
|
||||||
tickSubject.onNext(tpf)
|
|
||||||
|
// override def physicsSpace: PhysicsSpace = ???
|
||||||
|
|
||||||
|
override def simpleInitApp(): Unit = {
|
||||||
|
stateManager.attach(bulletAppState)
|
||||||
|
startSignal.success(())
|
||||||
}
|
}
|
||||||
|
|
||||||
override def stop(): Unit = {
|
override def simpleUpdate(tpf: Float): Unit = {}
|
||||||
tickSubject.onComplete()
|
|
||||||
super.stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
def enqueueScala[T](cb: () => T): CancelableFuture[T] = {
|
override def stop(waitFor: Boolean): Unit =
|
||||||
val p = Promise[T]()
|
cancelToken match {
|
||||||
|
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(enqueueScala(cb))
|
Task.deferFuture(enqueueFuture(cb))
|
||||||
|
|
||||||
override protected def runQueuedTasks(): Unit = {
|
override protected def runQueuedTasks(): Unit = {
|
||||||
taskQueue2.transform { current =>
|
taskQueue2.transform { current =>
|
||||||
@ -70,10 +84,10 @@ class SimpleAppExt(
|
|||||||
enqueue(command)
|
enqueue(command)
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy val scheduler = Scheduler(JMEExecutorService)
|
val scheduler = Scheduler(JMEExecutorService)
|
||||||
}
|
}
|
||||||
object SimpleAppExt {
|
object SimpleAppExt {
|
||||||
private[game] case class MyTask[T](p: Promise[T], cb: () => T)
|
private[game] final case class MyTask[T](p: CancelablePromise[T], cb: () => T)
|
||||||
}
|
}
|
||||||
|
|
||||||
// val ship = ed.createEntity()
|
// val ship = ed.createEntity()
|
||||||
|
@ -36,11 +36,11 @@ object TestActor {
|
|||||||
// )
|
// )
|
||||||
// ) {
|
// ) {
|
||||||
// case Success(value) =>
|
// case Success(value) =>
|
||||||
// ctx.log.debug("Received Value")
|
// ctx.log.debugP("Received Value")
|
||||||
// ctx.log.debug(value.toString())
|
// ctx.log.debugP(value.toString())
|
||||||
// Done
|
// Done
|
||||||
// case Failure(exception) =>
|
// case Failure(exception) =>
|
||||||
// ctx.log.debug(s"Received Error ${exception.getMessage()}")
|
// ctx.log.debugP(show"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.debug(value.toString())
|
// ctx.log.debugP(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.debug(value.toString())
|
// ctx.log.debugP(value.toString())
|
||||||
// Done
|
// Done
|
||||||
// }
|
// }
|
||||||
// case Failure(exception) =>
|
// case Failure(exception) =>
|
||||||
// ctx.log.debug(exception.getMessage())
|
// ctx.log.debugP(exception.getMessage())
|
||||||
// Done
|
// Done
|
||||||
// }
|
// }
|
||||||
// Done
|
// Done
|
||||||
// }
|
// }
|
||||||
// case Failure(exception) =>
|
// case Failure(exception) =>
|
||||||
// ctx.log.debug(exception.getMessage())
|
// ctx.log.debugP(exception.getMessage())
|
||||||
// Done
|
// Done
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
@ -1,69 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,294 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,207 +0,0 @@
|
|||||||
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,71 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,101 @@
|
|||||||
|
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 = {}
|
||||||
|
}
|
@ -0,0 +1,138 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
193
src/main/scala/wow/doge/mygame/game/entities/NpcActorSupervisor.scala
Normal file → Executable file
193
src/main/scala/wow/doge/mygame/game/entities/NpcActorSupervisor.scala
Normal file → Executable file
@ -6,28 +6,20 @@ 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.subsystems.events.EntityMovementEvent
|
import wow.doge.mygame.utils.GenericTimerActor
|
||||||
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(
|
||||||
@ -35,28 +27,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 case class MovementResponse(response: CancelableFuture[_]) extends Command
|
private final case class LogError(err: Throwable) extends Command
|
||||||
private case class LogError(err: Throwable) extends Command
|
private final case class MovementFailed(err: Throwable) extends Command
|
||||||
private case object NoOp extends Command
|
|
||||||
|
|
||||||
final case class Props(
|
class Props(
|
||||||
npcMovementActorBehavior: Behavior[NpcMovementActor.Command],
|
val npcMovementActorBehavior: Behavior[NpcMovementActor.Command],
|
||||||
npcName: String,
|
val npcName: String,
|
||||||
initialPos: ImVector3f
|
val initialPos: ImVector3f
|
||||||
) {
|
) {
|
||||||
def behavior =
|
def behavior =
|
||||||
Behaviors.setup[Command] { ctx =>
|
Behaviors.withMdc[Command](Map("actorName" -> npcName))(
|
||||||
val npcMovementActor = ctx.spawn(
|
Behaviors.setup[Command] { ctx =>
|
||||||
(npcMovementActorBehavior),
|
val npcMovementActor = ctx.spawn(
|
||||||
s"npc-${npcName}-NpcMovementActor"
|
(npcMovementActorBehavior),
|
||||||
)
|
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]
|
||||||
)
|
)
|
||||||
@ -69,7 +61,7 @@ class NpcActorSupervisor(
|
|||||||
import NpcActorSupervisor._
|
import NpcActorSupervisor._
|
||||||
implicit val timeout = Timeout(1.second)
|
implicit val timeout = Timeout(1.second)
|
||||||
|
|
||||||
private val movementTimer = ctx.spawn(
|
val movementTimer = ctx.spawn(
|
||||||
GenericTimerActor
|
GenericTimerActor
|
||||||
.Props(
|
.Props(
|
||||||
children.npcMovementActor,
|
children.npcMovementActor,
|
||||||
@ -77,12 +69,12 @@ class NpcActorSupervisor(
|
|||||||
100.millis
|
100.millis
|
||||||
)
|
)
|
||||||
.behavior,
|
.behavior,
|
||||||
s"npc-John-NpcActorTimer"
|
s"npc-${props.npcName}-NpcActorTimer"
|
||||||
)
|
)
|
||||||
|
|
||||||
def idle(state: State): Behavior[NpcActorSupervisor.Command] =
|
def idle(state: State): Behavior[Command] =
|
||||||
Behaviors.setup { _ =>
|
Behaviors.setup { _ =>
|
||||||
ctx.log.debug("Inside Idle State")
|
ctx.log.debugP(show"npcActor-${props.npcName}: Entered Idle State")
|
||||||
Behaviors.receiveMessage[Command] {
|
Behaviors.receiveMessage[Command] {
|
||||||
case m @ Move(pos) =>
|
case m @ Move(pos) =>
|
||||||
ctx.ask(
|
ctx.ask(
|
||||||
@ -97,7 +89,7 @@ class NpcActorSupervisor(
|
|||||||
moving(state, move.pos, signal)
|
moving(state, move.pos, signal)
|
||||||
|
|
||||||
case LogError(err) =>
|
case LogError(err) =>
|
||||||
ctx.log.warn(err.getMessage())
|
logError(err)
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
case _ => Behaviors.unhandled
|
case _ => Behaviors.unhandled
|
||||||
}
|
}
|
||||||
@ -107,45 +99,46 @@ class NpcActorSupervisor(
|
|||||||
state: State,
|
state: State,
|
||||||
targetPos: ImVector3f,
|
targetPos: ImVector3f,
|
||||||
signal: CancelableFuture[NpcMovementActor.DoneMoving.type]
|
signal: CancelableFuture[NpcMovementActor.DoneMoving.type]
|
||||||
): Behavior[NpcActorSupervisor.Command] =
|
): Behavior[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) => LogError(exception)
|
case Failure(exception) => MovementFailed(exception)
|
||||||
}
|
}
|
||||||
Behaviors.receiveMessagePartial[Command] {
|
Behaviors.receiveMessagePartial[Command] {
|
||||||
case LogError(err) =>
|
case LogError(err) =>
|
||||||
ctx.log.error(err.getMessage())
|
logError(err)
|
||||||
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) => LogError(exception)
|
case Failure(exception) => MovementFailed(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 {
|
||||||
@ -153,18 +146,19 @@ object NpcMovementActor {
|
|||||||
case object DoneMoving
|
case object DoneMoving
|
||||||
|
|
||||||
sealed trait Command
|
sealed trait Command
|
||||||
case class AskPosition(replyTo: ActorRef[ImVector3f]) extends Command
|
final 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
|
||||||
case class MoveTo(
|
final case class MoveTo(
|
||||||
target: ImVector3f,
|
target: ImVector3f,
|
||||||
doneSignal: ActorRef[CancelableFuture[DoneMoving.type]]
|
doneSignal: ActorRef[CancelableFuture[DoneMoving.type]]
|
||||||
) extends Command
|
) extends Command
|
||||||
|
|
||||||
final class Props[T: CanMove](
|
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 =
|
||||||
@ -183,28 +177,19 @@ class NpcMovementActor[T](
|
|||||||
|
|
||||||
def location = cm.location(props.movable)
|
def location = cm.location(props.movable)
|
||||||
|
|
||||||
def receive(
|
def receive(state: State): Behavior[NpcMovementActor.Command] =
|
||||||
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))
|
props.enqueueR(() => cm.move(props.movable, target - location, 20f))
|
||||||
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(
|
||||||
@ -214,91 +199,27 @@ class NpcMovementActor[T](
|
|||||||
): Behavior[NpcMovementActor.Command] =
|
): Behavior[NpcMovementActor.Command] =
|
||||||
Behaviors.receiveMessagePartial {
|
Behaviors.receiveMessagePartial {
|
||||||
case StopMoving =>
|
case StopMoving =>
|
||||||
ctx.log.debug(
|
ctx.log.debugP(
|
||||||
"Position at Stop = " + location.toString
|
show"npcActor-${props.npcName}: Position at Stop = $location"
|
||||||
)
|
)
|
||||||
props.enqueueR(() => cm.stop(props.movable))
|
props.enqueueR(() => cm.stop(props.movable))
|
||||||
receive(state)
|
receive(state)
|
||||||
|
|
||||||
case MovementTick =>
|
case MovementTick =>
|
||||||
val dst = ImVector3f.dst(targetPos, location)
|
val dst = ImVector3f.manhattanDst(targetPos, location)
|
||||||
if (dst <= 10f) {
|
if (dst <= 10f) {
|
||||||
ctx.self ! StopMoving
|
ctx.self ! StopMoving
|
||||||
reachDestination.success(DoneMoving)
|
reachDestination.success(DoneMoving)
|
||||||
receive(state)
|
|
||||||
} else {
|
} else {
|
||||||
ctx.log.trace("Difference = " + dst.toString())
|
// format:off
|
||||||
ctx.log.trace("Current pos = " + location.toString())
|
ctx.log.traceP(
|
||||||
Behaviors.same
|
show"npcActor-${props.npcName}: Difference = ${dst.formatted("%.2f")}"
|
||||||
|
)
|
||||||
|
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 }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
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]
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,250 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,177 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
@ -37,7 +37,7 @@ object PlayerCameraActor {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
case class State()
|
final case class State()
|
||||||
object State {
|
object State {
|
||||||
val empty = State()
|
val empty = State()
|
||||||
}
|
}
|
||||||
@ -55,28 +55,27 @@ 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.enqueueR(() => props.cameraPivotNode.rotate(rot))
|
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.enqueueR(() => props.cameraPivotNode.rotate(rot))
|
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.enqueueR(() => props.cameraPivotNode.rotate(rot))
|
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.enqueueR(() => props.cameraPivotNode.rotate(rot))
|
props.cameraPivotNode.rotate(rot)
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
case Tick =>
|
case Tick =>
|
||||||
props.enqueueR(() => {
|
val location = props.getPlayerLocation()
|
||||||
val location = props.getPlayerLocation()
|
props.cameraPivotNode.setLocalTranslation(location)
|
||||||
props.cameraPivotNode.setLocalTranslation(location)
|
|
||||||
})
|
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,129 +1,110 @@
|
|||||||
package wow.doge.mygame.game.entities
|
package wow.doge.mygame.game.entities.player
|
||||||
|
|
||||||
import akka.actor.typed.ActorRef
|
import akka.actor.typed.ActorRef
|
||||||
import akka.actor.typed.Scheduler
|
import akka.actor.typed.DispatcherSelector
|
||||||
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 wow.doge.mygame.game.GameAppTags
|
import monix.execution.AsyncQueue
|
||||||
import wow.doge.mygame.game.SimpleAppExt
|
import wow.doge.mygame.AppError
|
||||||
|
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.utils.AkkaUtils
|
import wow.doge.mygame.types._
|
||||||
|
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
|
||||||
case class CouldNotCreatePlayerNode(reason: String) extends Error
|
final case class CouldNotCreatePlayerModel(reason: AssetManager.Error)
|
||||||
case class GenericError(reason: String) extends Error
|
extends Error
|
||||||
|
|
||||||
class Props(
|
object Tags {
|
||||||
enqueueR: Function1[() => Unit, Unit],
|
sealed trait PlayerNode
|
||||||
rootNode: Node @@ GameAppTags.RootNode,
|
sealed trait PlayerCameraNode
|
||||||
loggerL: Logger[Task],
|
sealed trait PlayerCameraPivotNode
|
||||||
physicsSpace: PhysicsSpace,
|
|
||||||
initialPlayerPos: ImVector3f = ImVector3f.ZERO,
|
|
||||||
spawnProtocol: ActorRef[SpawnProtocol.Command],
|
|
||||||
playerEventBus: GameEventBus[PlayerEvent],
|
|
||||||
playerPhysicsControl: BetterCharacterControl,
|
|
||||||
appScheduler: monix.execution.Scheduler,
|
|
||||||
playerNode: Node @@ PlayerControllerTags.PlayerTag,
|
|
||||||
cameraNode: CameraNode @@ PlayerControllerTags.PlayerCameraNode,
|
|
||||||
cameraPivotNode: Node @@ PlayerControllerTags.PlayerCameraPivotNode,
|
|
||||||
tickEventBus: GameEventBus[TickEvent],
|
|
||||||
camera: Camera
|
|
||||||
)(implicit timeout: Timeout, scheduler: Scheduler) {
|
|
||||||
val create: IO[Error, Unit] =
|
|
||||||
(for {
|
|
||||||
playerActor <- AkkaUtils.spawnActorL(
|
|
||||||
spawnProtocol,
|
|
||||||
"playerActorSupervisor",
|
|
||||||
new PlayerActorSupervisor.Props(
|
|
||||||
playerEventBus,
|
|
||||||
// playerCameraEventBus,
|
|
||||||
tickEventBus,
|
|
||||||
new ImMovementActor.Props(
|
|
||||||
enqueueR,
|
|
||||||
playerPhysicsControl,
|
|
||||||
camera
|
|
||||||
).behavior,
|
|
||||||
// wireWith(PlayerCameraEventListener.apply _)
|
|
||||||
// PlayerCameraEventListener()
|
|
||||||
new PlayerCameraActor.Props(
|
|
||||||
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(
|
type PlayerNode = Node @@ Tags.PlayerNode
|
||||||
app: SimpleAppExt,
|
type PlayerCameraNode = CameraNode @@ Tags.PlayerCameraNode
|
||||||
modelPath: os.RelPath,
|
type PlayerCameraPivotNode =
|
||||||
cam: Camera
|
Node @@ Tags.PlayerCameraPivotNode
|
||||||
)(assetManager: AssetManager, bulletAppState: BulletAppState) = {
|
|
||||||
lazy val playerPos = ImVector3f.ZERO
|
class Props(
|
||||||
lazy val playerPhysicsControl = new BetterCharacterControl(1.5f, 6f, 1f)
|
gameApp: GameApp,
|
||||||
.withJumpForce(ImVector3f(0, 5f, 0))
|
enqueueR: Function1[() => Unit, Unit],
|
||||||
lazy val playerNode = new Node("PlayerNode")
|
rootNode: RootNode,
|
||||||
.withChildren(
|
loggerL: Logger[Task],
|
||||||
assetManager
|
physicsSpace: PhysicsSpace,
|
||||||
.loadModel(modelPath)
|
initialPlayerPos: ImVector3f,
|
||||||
.asInstanceOf[Node]
|
playerEventBus: GameEventBus[PlayerEvent],
|
||||||
.withRotate(0, FastMath.PI, 0)
|
playerPhysicsControl: BetterCharacterControl,
|
||||||
|
scheduler: AsyncScheduler,
|
||||||
|
fxScheduler: Schedulers.FxScheduler,
|
||||||
|
playerNode: PlayerNode,
|
||||||
|
cameraNode: PlayerCameraNode,
|
||||||
|
cameraPivotNode: PlayerCameraPivotNode,
|
||||||
|
tickEventBus: GameEventBus[TickEvent],
|
||||||
|
camera: Camera
|
||||||
|
) {
|
||||||
|
val playerActorBehavior = {
|
||||||
|
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()
|
||||||
)
|
)
|
||||||
.withLocalTranslation(playerPos)
|
val create: IO[AppError, ActorRef[PlayerActor.Command]] =
|
||||||
.withControl(playerPhysicsControl)
|
(for {
|
||||||
|
// playerActor <-
|
||||||
|
// // AkkaUtils.spawnActorL(playerActorBehavior, "playerActorSupervisor")
|
||||||
|
// gameApp.spawnGameActor(
|
||||||
|
// playerActorBehavior,
|
||||||
|
// Some("playerActorSupervisor"),
|
||||||
|
// props = DispatcherSelector.default()
|
||||||
|
// )
|
||||||
|
pa <- playerActor
|
||||||
|
_ <- (for {
|
||||||
|
_ <- rootNode += playerNode
|
||||||
|
_ <- physicsSpace += playerNode
|
||||||
|
_ <- physicsSpace += playerPhysicsControl
|
||||||
|
_ = cameraPivotNode += cameraNode
|
||||||
|
_ <- rootNode += cameraPivotNode
|
||||||
|
} yield ()).mapError(AppError.AppNodeError)
|
||||||
|
} yield pa)
|
||||||
|
|
||||||
{
|
|
||||||
bulletAppState.physicsSpace += playerNode
|
|
||||||
bulletAppState.physicsSpace += playerPhysicsControl
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
app.rootNode += playerNode
|
|
||||||
}
|
|
||||||
|
|
||||||
playerPhysicsControl
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object Defaults {
|
object Defaults {
|
||||||
@ -132,106 +113,50 @@ 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(
|
new CameraNode("CameraNode", cam)
|
||||||
"CameraNode",
|
|
||||||
cam
|
|
||||||
)
|
|
||||||
// .withControlDir(ControlDirection.SpatialToCamera)
|
// .withControlDir(ControlDirection.SpatialToCamera)
|
||||||
.withLocalTranslation(ImVector3f(0, 1.5f, 10))
|
.withLocalTranslation(ImVector3f(0, 1.5f, 10))
|
||||||
.withLookAt(playerPos, ImVector3f.UNIT_Y)
|
.withLookAt(playerPos, ImVector3f.UnitY)
|
||||||
|
|
||||||
def defaultPlayerNode(
|
def defaultPlayerNode(
|
||||||
assetManager: AssetManager,
|
|
||||||
modelPath: os.RelPath,
|
|
||||||
playerPos: ImVector3f,
|
playerPos: ImVector3f,
|
||||||
|
playerModel: Node,
|
||||||
// camNode: CameraNode,
|
// camNode: CameraNode,
|
||||||
playerPhysicsControl: BetterCharacterControl
|
playerPhysicsControl: BetterCharacterControl
|
||||||
) =
|
) =
|
||||||
Either
|
Node("PlayerNode")
|
||||||
.catchNonFatal(
|
.withChildren(playerModel)
|
||||||
Node("PlayerNode")
|
.withLocalTranslation(playerPos)
|
||||||
.withChildren(
|
.withControl(playerPhysicsControl)
|
||||||
// camNode,
|
.taggedWith[Tags.PlayerNode]
|
||||||
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,
|
||||||
// modelPath: os.RelPath,
|
npcModel: Spatial,
|
||||||
initialPos: ImVector3f,
|
initialPos: ImVector3f,
|
||||||
npcPhysicsControl: BetterCharacterControl,
|
npcPhysicsControl: BetterCharacterControl,
|
||||||
npcName: String
|
npcName: String
|
||||||
) =
|
) =
|
||||||
Either
|
// Either
|
||||||
.catchNonFatal(
|
// .catchNonFatal(
|
||||||
Node(npcName)
|
Node(npcName)
|
||||||
.withChildren(
|
.withChildren(
|
||||||
// assetManager
|
// defaultMesh.withMaterial(defaultTexture(assetManager))
|
||||||
// .loadModel(modelPath)
|
npcModel
|
||||||
// .asInstanceOf[Node]
|
|
||||||
// .withRotate(0, FastMath.PI, 0)
|
|
||||||
defaultMesh.withMaterial(defaultTexture(assetManager))
|
|
||||||
)
|
|
||||||
.withLocalTranslation(initialPos)
|
|
||||||
.withControl(npcPhysicsControl)
|
|
||||||
)
|
)
|
||||||
// .map(_.taggedWith[PlayerControllerTags.PlayerTag])
|
.withLocalTranslation(initialPos)
|
||||||
// .leftMap(e => PlayerController.CouldNotCreatePlayerNode(e.getMessage()))
|
.withControl(npcPhysicsControl)
|
||||||
|
// )
|
||||||
|
|
||||||
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)
|
|
||||||
// )
|
|
||||||
|
@ -1,41 +1,167 @@
|
|||||||
package wow.doge.mygame.game.entities
|
package wow.doge.mygame.game.entities.player
|
||||||
|
|
||||||
import akka.actor.typed.ActorRef
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
|
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.subsystems.events.PlayerCameraEvent
|
import wow.doge.mygame.executors.Schedulers
|
||||||
|
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.subsystems.movement.ImMovementActor
|
import wow.doge.mygame.utils.MovementDirection
|
||||||
|
|
||||||
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._
|
||||||
def apply(
|
import com.softwaremill.quicklens._
|
||||||
movementActor: ActorRef[ImMovementActor.Command]
|
def receive(state: State): Behavior[PlayerMovementEvent] =
|
||||||
) =
|
|
||||||
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) =>
|
||||||
movementActor ! ImMovementActor.MovedLeft(pressed)
|
// props.movementActor ! ImMovementActor.MoveLeft(pressed)
|
||||||
Behaviors.same
|
// props.playerActor ! PlayerActor.MoveLeft(pressed)
|
||||||
|
props.playerActor ! PlayerActor.Walk(
|
||||||
|
pressed,
|
||||||
|
MovementDirection.Left
|
||||||
|
)
|
||||||
|
receive(handleStamina(pressed))
|
||||||
case PlayerMovedRight(pressed) =>
|
case PlayerMovedRight(pressed) =>
|
||||||
movementActor ! ImMovementActor.MovedRight(pressed)
|
// props.playerActor ! PlayerActor.MoveRight(pressed)
|
||||||
Behaviors.same
|
props.playerActor ! PlayerActor.Walk(
|
||||||
|
pressed,
|
||||||
|
MovementDirection.Right
|
||||||
|
)
|
||||||
|
receive(handleStamina(pressed))
|
||||||
case PlayerMovedForward(pressed) =>
|
case PlayerMovedForward(pressed) =>
|
||||||
movementActor ! ImMovementActor.MovedUp(pressed)
|
// props.playerActor ! PlayerActor.MoveUp(pressed)
|
||||||
Behaviors.same
|
props.playerActor ! PlayerActor.Walk(
|
||||||
|
pressed,
|
||||||
|
MovementDirection.Forward
|
||||||
|
)
|
||||||
|
receive(handleStamina(pressed))
|
||||||
case PlayerMovedBackward(pressed) =>
|
case PlayerMovedBackward(pressed) =>
|
||||||
movementActor ! ImMovementActor.MovedDown(pressed)
|
// props.playerActor ! PlayerActor.MoveDown(pressed)
|
||||||
Behaviors.same
|
props.playerActor ! PlayerActor.Walk(
|
||||||
|
pressed,
|
||||||
|
MovementDirection.Backward
|
||||||
|
)
|
||||||
|
receive(handleStamina(pressed))
|
||||||
case PlayerJumped =>
|
case PlayerJumped =>
|
||||||
movementActor ! ImMovementActor.Jump
|
props.playerActor ! PlayerActor.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
|
||||||
@ -44,37 +170,6 @@ object 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
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
139
src/main/scala/wow/doge/mygame/game/entities/player/PlayerMovementReducer.scala
Executable file
139
src/main/scala/wow/doge/mygame/game/entities/player/PlayerMovementReducer.scala
Executable file
@ -0,0 +1,139 @@
|
|||||||
|
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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,182 @@
|
|||||||
|
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])
|
@ -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._
|
||||||
|
|
||||||
case class City(x: Float, y: Float, name: String, index: Int)
|
final case class City(x: Float, y: Float, name: String, index: Int)
|
||||||
case class Street(fromNode: City, toNode: City, cost: Float)
|
final 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
|
||||||
|
@ -18,10 +18,11 @@ 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 {
|
||||||
|
|
||||||
final class Props(
|
class Props(
|
||||||
inputManager: InputManager,
|
inputManager: InputManager,
|
||||||
playerEventBus: GameEventBus[PlayerEvent]
|
playerEventBus: GameEventBus[PlayerEvent]
|
||||||
// playerCameraEventBus: GameEventBus[PlayerCameraEvent]
|
// playerCameraEventBus: GameEventBus[PlayerCameraEvent]
|
||||||
@ -29,44 +30,44 @@ object GameInputHandler {
|
|||||||
) {
|
) {
|
||||||
def begin =
|
def begin =
|
||||||
for {
|
for {
|
||||||
_ <- UIO(setupMovementKeys(inputManager))
|
_ <- UIO(setupMovementKeys)
|
||||||
// _ <- UIO(setupAnalogMovementKeys)
|
// _ <- UIO(setupAnalogMovementKeys)
|
||||||
_ <- UIO(setupCameraKeys())
|
_ <- UIO(setupCameraKeys())
|
||||||
_ <- toIO(
|
_ <- toIO(
|
||||||
generateMovementInputEvents(
|
me.Task.parSequence(
|
||||||
inputManager,
|
Seq(
|
||||||
playerEventBus
|
// playerMovementInputEventsGenerator(
|
||||||
).completedL.startAndForget
|
// inputManager,
|
||||||
)
|
// playerEventBus
|
||||||
_ <- toIO(
|
// ).completedL,
|
||||||
generateAnalogMovementEvents(
|
// generateAnalogMovementEvents(
|
||||||
inputManager,
|
// inputManager,
|
||||||
playerEventBus
|
// playerEventBus
|
||||||
).completedL.startAndForget
|
// ).completedL,
|
||||||
)
|
// cameraMovementEventsGenerator(
|
||||||
_ <- toIO(
|
// inputManager,
|
||||||
generateCameraEvents(
|
// playerEventBus
|
||||||
inputManager,
|
// ).completedL,
|
||||||
playerEventBus
|
Ref
|
||||||
).completedL.startAndForget
|
.of[me.Task, Boolean](false)
|
||||||
)
|
.flatMap(value => cursorToggle(value))
|
||||||
_ <- toIO(
|
)
|
||||||
Ref.of[me.Task, Boolean](false).flatMap(value => cursorToggle(value))
|
)
|
||||||
)
|
).startAndForget
|
||||||
} yield ()
|
} yield ()
|
||||||
|
|
||||||
def setupMovementKeys(inputManager: InputManager) =
|
def setupMovementKeys =
|
||||||
inputManager.withEnumMappings(PlayerMovementInput) {
|
inputManager.withEnumMappings(PlayerMovementInput) {
|
||||||
case PlayerMovementInput.WalkRight =>
|
case PlayerMovementInput.WalkRight =>
|
||||||
Seq(new KeyTrigger(KeyInput.KEY_D))
|
new KeyTrigger(KeyInput.KEY_D) :: Nil
|
||||||
case PlayerMovementInput.WalkLeft =>
|
case PlayerMovementInput.WalkLeft =>
|
||||||
Seq(new KeyTrigger(KeyInput.KEY_A))
|
new KeyTrigger(KeyInput.KEY_A) :: Nil
|
||||||
case PlayerMovementInput.WalkForward =>
|
case PlayerMovementInput.WalkForward =>
|
||||||
Seq(new KeyTrigger(KeyInput.KEY_W))
|
new KeyTrigger(KeyInput.KEY_W) :: Nil
|
||||||
case PlayerMovementInput.WalkBackward =>
|
case PlayerMovementInput.WalkBackward =>
|
||||||
Seq(new KeyTrigger(KeyInput.KEY_S))
|
new KeyTrigger(KeyInput.KEY_S) :: Nil
|
||||||
case PlayerMovementInput.Jump =>
|
case PlayerMovementInput.Jump =>
|
||||||
Seq(new KeyTrigger(KeyInput.KEY_SPACE))
|
new KeyTrigger(KeyInput.KEY_SPACE) :: Nil
|
||||||
}
|
}
|
||||||
|
|
||||||
def setupAnalogMovementKeys() =
|
def setupAnalogMovementKeys() =
|
||||||
@ -124,16 +125,15 @@ object GameInputHandler {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.completedL
|
.completedL
|
||||||
.startAndForget
|
|
||||||
} yield ()
|
} yield ()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def generateMovementInputEvents(
|
def playerMovementInputEventsGenerator(
|
||||||
inputManager: InputManager,
|
inputManager: InputManager,
|
||||||
playerEventBus: GameEventBus[PlayerEvent]
|
playerEventBus: GameEventBus[PlayerEvent]
|
||||||
) = {
|
) = {
|
||||||
val name = "playerMovementInputEventsGenerator"
|
val name = methodName
|
||||||
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(pressed = action.value),
|
PlayerMovementEvent.PlayerMovedLeft(action.value),
|
||||||
name
|
name
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
case PlayerMovementInput.WalkRight =>
|
case PlayerMovementInput.WalkRight =>
|
||||||
me.Task(
|
me.Task(
|
||||||
playerEventBus ! EventBus.Publish(
|
playerEventBus ! EventBus.Publish(
|
||||||
PlayerMovementEvent.PlayerMovedRight(pressed = action.value),
|
PlayerMovementEvent.PlayerMovedRight(action.value),
|
||||||
name
|
name
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
case PlayerMovementInput.WalkForward =>
|
case PlayerMovementInput.WalkForward =>
|
||||||
me.Task(
|
me.Task(
|
||||||
playerEventBus ! EventBus.Publish(
|
playerEventBus ! EventBus.Publish(
|
||||||
PlayerMovementEvent.PlayerMovedForward(pressed = action.value),
|
PlayerMovementEvent.PlayerMovedForward(action.value),
|
||||||
name
|
name
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
case PlayerMovementInput.WalkBackward =>
|
case PlayerMovementInput.WalkBackward =>
|
||||||
me.Task(
|
me.Task(
|
||||||
playerEventBus ! EventBus.Publish(
|
playerEventBus ! EventBus.Publish(
|
||||||
PlayerMovementEvent.PlayerMovedBackward(pressed = action.value),
|
PlayerMovementEvent.PlayerMovedBackward(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,36 +183,35 @@ 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 generateCameraEvents(
|
def cameraMovementEventsGenerator(
|
||||||
inputManager: InputManager,
|
inputManager: InputManager,
|
||||||
playerEventBus: ActorRef[EventBus.Command[PlayerEvent]]
|
playerEventBus: ActorRef[EventBus.Command[PlayerEvent]]
|
||||||
) = {
|
) = {
|
||||||
val name = "cameraMovementEventsGenerator"
|
val name = methodName
|
||||||
inputManager
|
inputManager
|
||||||
.enumAnalogObservable(PlayerCameraInput)
|
.enumAnalogObservable(PlayerCameraInput)
|
||||||
.sample(1.millis)
|
.sample(1.millis)
|
||||||
|
@ -1,35 +1,48 @@
|
|||||||
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
|
||||||
final object PlayerMovementInput extends Enum[PlayerMovementInput] {
|
object PlayerMovementInput extends Enum[PlayerMovementInput] {
|
||||||
val values = findValues
|
val values = findValues
|
||||||
final case object WalkForward extends PlayerMovementInput
|
case object WalkForward extends PlayerMovementInput
|
||||||
final case object WalkRight extends PlayerMovementInput
|
case object WalkRight extends PlayerMovementInput
|
||||||
final case object WalkLeft extends PlayerMovementInput
|
case object WalkLeft extends PlayerMovementInput
|
||||||
final case object WalkBackward extends PlayerMovementInput
|
case object WalkBackward extends PlayerMovementInput
|
||||||
final case object Jump extends PlayerMovementInput
|
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
|
||||||
final object PlayerAnalogMovementInput extends Enum[PlayerAnalogMovementInput] {
|
object PlayerAnalogMovementInput extends Enum[PlayerAnalogMovementInput] {
|
||||||
val values = findValues
|
val values = findValues
|
||||||
final case object TurnRight extends PlayerAnalogMovementInput
|
case object TurnRight extends PlayerAnalogMovementInput
|
||||||
final case object TurnLeft extends PlayerAnalogMovementInput
|
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
|
||||||
final object PlayerCameraInput extends Enum[PlayerCameraInput] {
|
object PlayerCameraInput extends Enum[PlayerCameraInput] {
|
||||||
val values = findValues
|
val values = findValues
|
||||||
final case object CameraRotateLeft extends PlayerCameraInput
|
case object CameraRotateLeft extends PlayerCameraInput
|
||||||
final case object CameraRotateRight extends PlayerCameraInput
|
case object CameraRotateRight extends PlayerCameraInput
|
||||||
final case object CameraRotateUp extends PlayerCameraInput
|
case object CameraRotateUp extends PlayerCameraInput
|
||||||
final case object CameraRotateDown extends PlayerCameraInput
|
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
|
||||||
final object MiscInput extends Enum[MiscInput] {
|
object MiscInput extends Enum[MiscInput] {
|
||||||
val values = findValues
|
val values = findValues
|
||||||
final case object ToggleCursor extends MiscInput
|
case object ToggleCursor extends MiscInput
|
||||||
}
|
}
|
||||||
|
87
src/main/scala/wow/doge/mygame/game/subsystems/input/InputMappings.scala
Executable file
87
src/main/scala/wow/doge/mygame/game/subsystems/input/InputMappings.scala
Executable file
@ -0,0 +1,87 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
}
|
@ -1,50 +1,65 @@
|
|||||||
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 com.jme3.scene.Spatial
|
import monix.bio.IO
|
||||||
|
import monix.bio.UIO
|
||||||
|
import wow.doge.mygame.AppError
|
||||||
object DefaultGameLevel {
|
object DefaultGameLevel {
|
||||||
|
|
||||||
def apply(
|
def apply(
|
||||||
assetManager: AssetManager,
|
assetManager: wow.doge.mygame.utils.wrappers.jme.AssetManager,
|
||||||
viewPort: ViewPort
|
viewPort: ViewPort
|
||||||
) = {
|
): IO[AppError, GameLevel] =
|
||||||
lazy val sceneModel: Spatial = assetManager.loadModel("main.scene")
|
// for {
|
||||||
lazy val sceneShape = CollisionShapeFactory.createMeshShape(
|
// sceneModel <- assetManager.loadModelAs[Node](os.rel / "main.scene")
|
||||||
sceneModel.toNode match {
|
// sceneShape <- UIO(CollisionShapeFactory.createMeshShape(sceneModel))
|
||||||
case Right(node) => node
|
// landscape <- UIO(new RigidBodyControl(sceneShape, 0))
|
||||||
case Left(ex) =>
|
|
||||||
throw new NotImplementedError("No fallback sceneshape")
|
// _ <- UIO {
|
||||||
|
// 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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,32 +1,84 @@
|
|||||||
package wow.doge.mygame.game.subsystems.level
|
package wow.doge.mygame.game.subsystems.level
|
||||||
|
|
||||||
import com.jme3.bullet.PhysicsSpace
|
import cats.effect.Resource
|
||||||
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.Task
|
import monix.bio.IO
|
||||||
|
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.implicits._
|
import wow.doge.mygame.utils.wrappers.jme.AppNode2
|
||||||
|
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(
|
||||||
model: Spatial,
|
val model: Spatial,
|
||||||
physicsControl: RigidBodyControl,
|
val physicsControl: RigidBodyControl,
|
||||||
ambientLight: AmbientLight,
|
val ambientLight: AmbientLight,
|
||||||
directionalLight: DirectionalLight
|
val directionalLight: DirectionalLight
|
||||||
) {
|
) {
|
||||||
def addToGame(
|
def addToGame(
|
||||||
rootNode: Node @@ GameAppTags.RootNode,
|
rootNode: AppNode2 @@ 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 {
|
||||||
_ <- Task(rootNode += model)
|
_ <- rootNode -= model
|
||||||
_ <- Task(rootNode :+ ambientLight)
|
_ <- rootNode -= ambientLight
|
||||||
_ <- Task(rootNode :+ directionalLight)
|
_ <- rootNode -= directionalLight
|
||||||
_ <- Task(physicsSpace += model)
|
_ <- physicsSpace -= model
|
||||||
_ <- Task(physicsSpace += physicsControl)
|
_ <- 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
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -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 = 20f): Unit
|
def move(inst: A, direction: ImVector3f, speedFactor: Float): 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 = 20f
|
speedFactor: Float
|
||||||
): Unit = {
|
): Unit = {
|
||||||
val dir = direction.mutable.normalizeLocal()
|
val dir = direction.mutable.normalizeLocal()
|
||||||
inst.setViewDirection(dir.negate())
|
inst.setViewDirection(dir.negate())
|
||||||
@ -61,19 +61,17 @@ 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*/ }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
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
|
||||||
@ -8,7 +9,6 @@ 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,7 +19,34 @@ 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(
|
||||||
|
316
src/main/scala/wow/doge/mygame/game/subsystems/movement/MovementActor.scala
Normal file → Executable file
316
src/main/scala/wow/doge/mygame/game/subsystems/movement/MovementActor.scala
Normal file → Executable file
@ -1,15 +1,25 @@
|
|||||||
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
|
|
||||||
import wow.doge.mygame.state.CardinalDirection
|
final case class 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 {
|
||||||
@ -19,28 +29,27 @@ object RotateDir {
|
|||||||
|
|
||||||
object ImMovementActor {
|
object ImMovementActor {
|
||||||
sealed trait Command
|
sealed trait Command
|
||||||
// final case class Tick(tpf: Float) extends Command
|
case object Tick extends Command
|
||||||
final case object Tick extends Command
|
|
||||||
|
|
||||||
sealed trait Movement extends Command
|
sealed trait Movement extends Command
|
||||||
final case class MovedLeft(pressed: Boolean) extends Movement
|
final case class MoveLeft(pressed: Boolean) extends Movement
|
||||||
final case class MovedUp(pressed: Boolean) extends Movement
|
final case class MoveUp(pressed: Boolean) extends Movement
|
||||||
final case class MovedRight(pressed: Boolean) extends Movement
|
final case class MoveRight(pressed: Boolean) extends Movement
|
||||||
final case class MovedDown(pressed: Boolean) extends Movement
|
final case class MoveDown(pressed: Boolean) extends Movement
|
||||||
final case object Jump extends Movement
|
case object StopMoving extends Movement
|
||||||
// final case object RotateRight extends Movement
|
case object Jump extends Movement
|
||||||
// final case object RotateLeft extends Movement
|
|
||||||
|
|
||||||
final class Props[T: CanMove](
|
class Props(
|
||||||
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: Behavior[Command] =
|
def behavior[T: CanMove](movable: T): Behavior[Command] =
|
||||||
Behaviors.setup(ctx => new ImMovementActor(ctx, this).receive(State()))
|
Behaviors.setup(ctx =>
|
||||||
|
new ImMovementActor(ctx, this, movable).receive(State(), new Vector3f)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -48,53 +57,99 @@ object ImMovementActor {
|
|||||||
*
|
*
|
||||||
* @param cardinalDir The four directions the character can move
|
* @param cardinalDir The four directions the character can move
|
||||||
*/
|
*/
|
||||||
final case class State(cardinalDir: CardinalDirection = CardinalDirection())
|
final case class State(
|
||||||
|
cardinalDir: CardinalDirection = CardinalDirection.default
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ImMovementActor[T](
|
class ImMovementActor[T](
|
||||||
ctx: ActorContext[ImMovementActor.Command],
|
ctx: ActorContext[ImMovementActor.Command],
|
||||||
props: ImMovementActor.Props[T]
|
props: ImMovementActor.Props,
|
||||||
) {
|
val movable: T
|
||||||
|
)(implicit cm: CanMove[T]) {
|
||||||
import ImMovementActor._
|
import ImMovementActor._
|
||||||
import Methods._
|
|
||||||
|
|
||||||
def receive(
|
def receive(
|
||||||
state: ImMovementActor.State
|
state: ImMovementActor.State,
|
||||||
)(implicit cm: CanMove[T]): Behavior[Command] =
|
walkDirBuf: Vector3f
|
||||||
Behaviors.receiveMessage {
|
): Behavior[Command] =
|
||||||
case m: Movement =>
|
Behaviors
|
||||||
m match {
|
.receiveMessage[Command] {
|
||||||
case MovedLeft(pressed) =>
|
case m: Movement =>
|
||||||
props.enqueueR(() => stopIfNotPressed(pressed, props.movable))
|
m match {
|
||||||
receive(state = state.modify(_.cardinalDir.left).setTo(pressed))
|
case StopMoving =>
|
||||||
case MovedUp(pressed) =>
|
cm.stop(movable)
|
||||||
props.enqueueR(() => stopIfNotPressed(pressed, props.movable))
|
receive(State(CardinalDirection.default), new Vector3f)
|
||||||
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 Tick =>
|
case MoveLeft(pressed) =>
|
||||||
val walkDir =
|
stopIfNotPressed(pressed)
|
||||||
getDirection2(state.cardinalDir, ctx.log.trace)
|
receive(
|
||||||
// if (walkDir != ImVector3f.ZERO) {
|
state.modify(_.cardinalDir.left).setTo(pressed),
|
||||||
val tmp = walkDir * 25f * (1f / 144)
|
walkDirBuf
|
||||||
props.enqueueR { () =>
|
)
|
||||||
cm.move(props.movable, tmp)
|
case MoveUp(pressed) =>
|
||||||
}
|
stopIfNotPressed(pressed)
|
||||||
// }
|
receive(
|
||||||
Behaviors.same
|
state.modify(_.cardinalDir.up).setTo(pressed),
|
||||||
}
|
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
|
||||||
|
}
|
||||||
|
|
||||||
def getDirection2(cardinalDir: CardinalDirection, debug: String => Unit) = {
|
case Tick =>
|
||||||
|
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)
|
||||||
@ -102,19 +157,19 @@ class ImMovementActor[T](
|
|||||||
val walkDir = {
|
val walkDir = {
|
||||||
val mutWalkDir = new Vector3f()
|
val mutWalkDir = new Vector3f()
|
||||||
if (dir.up) {
|
if (dir.up) {
|
||||||
debug("up")
|
ctx.log.traceP("up")
|
||||||
mutWalkDir += camDir
|
mutWalkDir += camDir
|
||||||
}
|
}
|
||||||
if (dir.left) {
|
if (dir.left) {
|
||||||
debug("left")
|
ctx.log.traceP("left")
|
||||||
mutWalkDir += camLeft
|
mutWalkDir += camLeft
|
||||||
}
|
}
|
||||||
if (dir.right) {
|
if (dir.right) {
|
||||||
debug("right")
|
ctx.log.traceP("right")
|
||||||
mutWalkDir += -camLeft
|
mutWalkDir += -camLeft
|
||||||
}
|
}
|
||||||
if (dir.down) {
|
if (dir.down) {
|
||||||
debug("down")
|
ctx.log.traceP("down")
|
||||||
mutWalkDir += -camDir
|
mutWalkDir += -camDir
|
||||||
}
|
}
|
||||||
mutWalkDir.immutable
|
mutWalkDir.immutable
|
||||||
@ -122,50 +177,111 @@ class ImMovementActor[T](
|
|||||||
walkDir
|
walkDir
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
object Methods {
|
|
||||||
def getDirection(cardinalDir: CardinalDirection, trace: String => Unit) = {
|
|
||||||
val zero = ImVector3f.ZERO
|
|
||||||
val dir = cardinalDir
|
|
||||||
val walkDir = {
|
|
||||||
val mutWalkDir = new Vector3f()
|
|
||||||
if (dir.left) {
|
|
||||||
trace("left")
|
|
||||||
mutWalkDir += zero +=: new Vector3f(-1, 0, 0)
|
|
||||||
}
|
|
||||||
if (dir.right) {
|
|
||||||
trace("right")
|
|
||||||
mutWalkDir += zero +=: new Vector3f(1, 0, 0)
|
|
||||||
}
|
|
||||||
if (dir.up) {
|
|
||||||
trace("up")
|
|
||||||
mutWalkDir += zero +=: new Vector3f(0, 0, -1)
|
|
||||||
}
|
|
||||||
if (dir.down) {
|
|
||||||
trace("down")
|
|
||||||
mutWalkDir += zero +=: new Vector3f(0, 0, 1)
|
|
||||||
}
|
|
||||||
mutWalkDir.immutable
|
|
||||||
}
|
|
||||||
walkDir
|
|
||||||
}
|
|
||||||
|
|
||||||
def stopIfNotPressed[T](pressed: Boolean, movable: T)(implicit
|
// old/unused
|
||||||
cm: CanMove[T]
|
object MovementActor {
|
||||||
) =
|
sealed trait Command
|
||||||
if (!pressed) cm.stop(movable)
|
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
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
// props.app
|
case Tick =>
|
||||||
// .enqueueScala { () =>
|
val camDir =
|
||||||
// 1
|
props.app.getCamera.getDirection().clone().multLocal(0.6f)
|
||||||
// }
|
val camLeft = props.app.getCamera.getLeft().clone().multLocal(0.4f)
|
||||||
// .map(println)(scala.concurrent.ExecutionContext.global)
|
val walkDir = state.walkDirection.set(0, 0, 0)
|
||||||
// monix.eval.Task
|
// val walkDir = new Vector3f
|
||||||
// .fromFuture(
|
val dir = state.cardinalDir
|
||||||
// props.app
|
if (dir.up) {
|
||||||
// .enqueueScala { () =>
|
ctx.log.debugP("up")
|
||||||
// 1
|
// ctx.log.debugP(Thread.currentThread().getName())
|
||||||
// }
|
// walkDir.addLocal(0, 0, -1)
|
||||||
// )
|
walkDir += camDir
|
||||||
// .flatMap(i => monix.eval.Task(println(i)))
|
}
|
||||||
// .runToFuture(monix.execution.Scheduler.Implicits.global)
|
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))
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
61
src/main/scala/wow/doge/mygame/implicits/CatsImplicits.scala
Normal file
61
src/main/scala/wow/doge/mygame/implicits/CatsImplicits.scala
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
@ -11,11 +11,18 @@ 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
|
||||||
@ -24,6 +31,12 @@ 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
|
||||||
@ -32,6 +45,12 @@ 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
|
||||||
@ -45,9 +64,8 @@ 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 =>
|
||||||
|
@ -1,26 +1,74 @@
|
|||||||
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 {
|
||||||
|
|
||||||
implicit final class SceneObservables(private val scene: Scene)
|
final class SceneExt(private val scene: Scene) extends AnyVal {
|
||||||
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
|
||||||
@ -33,17 +81,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,
|
||||||
@ -55,17 +103,224 @@ object JavaFXMonixObservables {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
implicit final class OnActionObservable(
|
final class PropertyExt[T, J](private val prop: Property[T, J])
|
||||||
private val button: ButtonBase
|
extends AnyVal {
|
||||||
) extends AnyVal {
|
def -->[J1 >: J](sub: Observer[J1]) =
|
||||||
def observableAction(): Observable[jfxe.ActionEvent] = {
|
prop.onChange((a, b, c) => if (c != null) sub.onNext(c))
|
||||||
|
|
||||||
|
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 l = new jfxe.EventHandler[jfxe.ActionEvent] {
|
|
||||||
override def handle(event: jfxe.ActionEvent): Unit = {
|
val canc =
|
||||||
if (sub.onNext(event) == Ack.Stop) c.cancel()
|
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] {
|
||||||
|
override def handle(event: jfxe.ActionEvent): Unit =
|
||||||
|
if (sub.onNext(event) == Ack.Stop) c.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
button.onAction = l
|
button.onAction = l
|
||||||
@ -79,4 +334,27 @@ 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,12 @@ 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
|
||||||
@ -14,10 +18,12 @@ 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
|
||||||
@ -35,12 +41,17 @@ 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
|
||||||
@ -49,8 +60,9 @@ 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.state.MyBaseState
|
import wow.doge.mygame.utils.wrappers.jme.PhysicsSpace
|
||||||
|
|
||||||
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](
|
||||||
@ -65,10 +77,17 @@ final case class EnumAnalogEvent[T <: EnumEntry](
|
|||||||
value: Float,
|
value: Float,
|
||||||
tpf: Float
|
tpf: Float
|
||||||
)
|
)
|
||||||
final class PrePhysicsTickEvent(val space: PhysicsSpace) extends AnyVal
|
final case class PrePhysicsTickEvent(space: PhysicsSpace)
|
||||||
final class PhysicsTickEvent(val space: PhysicsSpace) extends AnyVal
|
final case class PhysicsTickEvent(space: PhysicsSpace)
|
||||||
|
final case class CollisionEvent(
|
||||||
|
nodeA: Option[Spatial],
|
||||||
|
nodeB: Option[Spatial],
|
||||||
|
objectA: PhysicsCollisionObject,
|
||||||
|
objectB: PhysicsCollisionObject,
|
||||||
|
appliedImpulse: Function0[Float]
|
||||||
|
)
|
||||||
|
|
||||||
package object implicits {
|
package object implicits extends JavaFXMonixObservables {
|
||||||
type PrePhysicsTickEvent = PhysicsTickEvent
|
type PrePhysicsTickEvent = PhysicsTickEvent
|
||||||
type PhysicsTickObservable =
|
type PhysicsTickObservable =
|
||||||
Observable[Either[PrePhysicsTickEvent, PhysicsTickEvent]]
|
Observable[Either[PrePhysicsTickEvent, PhysicsTickEvent]]
|
||||||
@ -83,6 +102,12 @@ package object implicits {
|
|||||||
}
|
}
|
||||||
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]])
|
||||||
|
|
||||||
@ -102,27 +127,6 @@ package object implicits {
|
|||||||
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)
|
||||||
@ -130,13 +134,11 @@ package object implicits {
|
|||||||
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)
|
||||||
@ -248,7 +250,7 @@ package object implicits {
|
|||||||
}
|
}
|
||||||
// case _ => loop(subscriber, tail)
|
// case _ => loop(subscriber, tail)
|
||||||
}
|
}
|
||||||
case LazyList() => Task.unit
|
case LazyList() => Task(subscriber.onComplete())
|
||||||
}
|
}
|
||||||
|
|
||||||
Observable.create(OverflowStrategy.Unbounded) { sub =>
|
Observable.create(OverflowStrategy.Unbounded) { sub =>
|
||||||
@ -294,7 +296,7 @@ package object implicits {
|
|||||||
Task(subscriber.onComplete())
|
Task(subscriber.onComplete())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case LazyList() => Task.unit
|
case LazyList() => Task(subscriber.onComplete())
|
||||||
}
|
}
|
||||||
|
|
||||||
Observable.create(OverflowStrategy.Unbounded) { sub =>
|
Observable.create(OverflowStrategy.Unbounded) { sub =>
|
||||||
@ -369,9 +371,8 @@ package object implicits {
|
|||||||
*/
|
*/
|
||||||
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] =
|
||||||
@ -478,8 +479,8 @@ package object implicits {
|
|||||||
* @see [[ActionEvent]]
|
* @see [[ActionEvent]]
|
||||||
* @see [[enumObservableAction]]
|
* @see [[enumObservableAction]]
|
||||||
*/
|
*/
|
||||||
def observableAction(mappingNames: String*): Observable[ActionEvent] = {
|
@SuppressWarnings(Array("org.wartremover.warts.Equals"))
|
||||||
|
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 {
|
||||||
@ -487,14 +488,13 @@ package object implicits {
|
|||||||
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,7 +502,6 @@ package object implicits {
|
|||||||
c := Cancelable(() => inputManager.removeListener(al))
|
c := Cancelable(() => inputManager.removeListener(al))
|
||||||
c
|
c
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
@ -525,10 +524,10 @@ package object implicits {
|
|||||||
* @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)
|
||||||
@ -537,14 +536,13 @@ package object implicits {
|
|||||||
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: _*)
|
||||||
@ -552,12 +550,11 @@ package object implicits {
|
|||||||
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 {
|
||||||
@ -565,7 +562,7 @@ package object implicits {
|
|||||||
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)
|
||||||
@ -574,7 +571,6 @@ package object implicits {
|
|||||||
sub.onComplete()
|
sub.onComplete()
|
||||||
c.cancel()
|
c.cancel()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inputManager.addListener(al, mappingEnumEntry.entryName)
|
inputManager.addListener(al, mappingEnumEntry.entryName)
|
||||||
@ -582,10 +578,9 @@ package object implicits {
|
|||||||
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 {
|
||||||
@ -593,14 +588,13 @@ package object implicits {
|
|||||||
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: _*)
|
||||||
@ -608,12 +602,11 @@ package object implicits {
|
|||||||
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)
|
||||||
@ -622,14 +615,13 @@ package object implicits {
|
|||||||
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: _*)
|
||||||
@ -637,72 +629,85 @@ package object implicits {
|
|||||||
c := Cancelable(() => inputManager.removeListener(al))
|
c := Cancelable(() => inputManager.removeListener(al))
|
||||||
c
|
c
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
implicit final class PhysicsSpaceExt(private val space: PhysicsSpace)
|
implicit final class PhysicsSpaceExt(private val space: jmeb.PhysicsSpace)
|
||||||
extends AnyVal {
|
extends AnyVal {
|
||||||
|
|
||||||
def collisionObservable(): Observable[PhysicsCollisionEvent] = {
|
@SuppressWarnings(Array("org.wartremover.warts.Equals"))
|
||||||
|
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: PhysicsCollisionEvent): Unit = {
|
override def collision(event: jmePhysicsCollisionEvent): 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(() => space.removeCollisionListener(cl))
|
c := Cancelable { () =>
|
||||||
c
|
pprint.log("stopped")
|
||||||
}
|
space.removeCollisionListener(cl)
|
||||||
}
|
|
||||||
|
|
||||||
def prePhysicsTickObservable(): Observable[PrePhysicsTickEvent] = {
|
|
||||||
|
|
||||||
Observable.create(OverflowStrategy.DropOld(50)) { sub =>
|
|
||||||
val c = SingleAssignCancelable()
|
|
||||||
val cl = new PhysicsTickListener {
|
|
||||||
|
|
||||||
override def prePhysicsTick(space: PhysicsSpace, tpf: Float): Unit = {
|
|
||||||
val event = new PrePhysicsTickEvent(space)
|
|
||||||
if (sub.onNext(event) == Ack.Stop) {
|
|
||||||
sub.onComplete()
|
|
||||||
c.cancel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override def physicsTick(space: PhysicsSpace, tpf: Float): Unit = {}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
space.addTickListener(cl)
|
|
||||||
|
|
||||||
c := Cancelable(() => space.removeTickListener(cl))
|
|
||||||
c
|
c
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
def physicsTickObservable(): Observable[PhysicsTickEvent] = {
|
|
||||||
|
|
||||||
|
@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,
|
space: jmeb.PhysicsSpace,
|
||||||
|
tpf: Float
|
||||||
|
): Unit = {
|
||||||
|
val event = new PrePhysicsTickEvent(new PhysicsSpace(space))
|
||||||
|
if (sub.onNext(event) == Ack.Stop) {
|
||||||
|
sub.onComplete()
|
||||||
|
c.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override def physicsTick(
|
||||||
|
space: jmeb.PhysicsSpace,
|
||||||
tpf: Float
|
tpf: Float
|
||||||
): Unit = {}
|
): Unit = {}
|
||||||
|
|
||||||
override def physicsTick(space: PhysicsSpace, tpf: Float): Unit = {
|
}
|
||||||
val event = new PhysicsTickEvent(space)
|
|
||||||
|
space.addTickListener(cl)
|
||||||
|
|
||||||
|
c := Cancelable(() => space.removeTickListener(cl))
|
||||||
|
c
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings(Array("org.wartremover.warts.Equals"))
|
||||||
|
def physicsTickObservable(): Observable[PhysicsTickEvent] =
|
||||||
|
Observable.create(OverflowStrategy.DropOld(50)) { sub =>
|
||||||
|
val c = SingleAssignCancelable()
|
||||||
|
val cl = new PhysicsTickListener {
|
||||||
|
|
||||||
|
override def prePhysicsTick(
|
||||||
|
space: jmeb.PhysicsSpace,
|
||||||
|
tpf: Float
|
||||||
|
): Unit = {}
|
||||||
|
|
||||||
|
override def physicsTick(
|
||||||
|
space: jmeb.PhysicsSpace,
|
||||||
|
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()
|
||||||
@ -716,7 +721,6 @@ package object implicits {
|
|||||||
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)
|
||||||
@ -725,7 +729,7 @@ package object implicits {
|
|||||||
space.add(anyObject)
|
space.add(anyObject)
|
||||||
space
|
space
|
||||||
}
|
}
|
||||||
def :-(anyObject: Any) = {
|
def -(anyObject: Any) = {
|
||||||
space.remove(anyObject)
|
space.remove(anyObject)
|
||||||
space
|
space
|
||||||
}
|
}
|
||||||
@ -737,7 +741,7 @@ package object implicits {
|
|||||||
space
|
space
|
||||||
}
|
}
|
||||||
|
|
||||||
def :-(spatial: Spatial) = {
|
def -(spatial: Spatial) = {
|
||||||
space.removeAll(spatial)
|
space.removeAll(spatial)
|
||||||
space
|
space
|
||||||
}
|
}
|
||||||
@ -746,7 +750,7 @@ package object implicits {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
implicit final class Vector3fExt(private val v: Vector3f) extends AnyVal {
|
implicit final class Vector3fOps(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)
|
||||||
@ -754,6 +758,7 @@ package object implicits {
|
|||||||
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)
|
||||||
@ -763,9 +768,10 @@ package object implicits {
|
|||||||
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 ImVector3fExt(private val v: ImVector3f) extends AnyVal {
|
implicit final class ImVector3fOps(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)
|
||||||
@ -794,4 +800,105 @@ package object implicits {
|
|||||||
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]
|
||||||
}
|
}
|
||||||
|
@ -1,35 +1,38 @@
|
|||||||
package wow.doge.mygame.launcher
|
package wow.doge.mygame.launcher
|
||||||
|
|
||||||
import scala.concurrent.duration.FiniteDuration
|
import cats.effect.Resource
|
||||||
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.eval.{Task => ETask}
|
import monix.execution.CancelablePromise
|
||||||
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
|
import wow.doge.mygame.executors.Schedulers.FxScheduler
|
||||||
import wow.doge.mygame.implicits.JavaFXMonixObservables._
|
import wow.doge.mygame.implicits._
|
||||||
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 = Task(new Launcher(this))
|
val create = UIO(new Launcher(this))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
class Launcher private (props: Launcher.Props) {
|
class Launcher private (props: Launcher.Props) {
|
||||||
@ -40,8 +43,7 @@ class Launcher private (props: Launcher.Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private lazy val launchAction =
|
private lazy val launchAction =
|
||||||
launchButton
|
launchButton.observableAction
|
||||||
.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 {
|
||||||
@ -49,8 +51,7 @@ class Launcher private (props: Launcher.Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private lazy val exitAction =
|
private lazy val exitAction =
|
||||||
exitButton
|
exitButton.observableAction
|
||||||
.observableAction()
|
|
||||||
.doOnNext(_ => toTask(props.signal.complete(LauncherResult.Exit)))
|
.doOnNext(_ => toTask(props.signal.complete(LauncherResult.Exit)))
|
||||||
|
|
||||||
private lazy val _scene =
|
private lazy val _scene =
|
||||||
@ -60,22 +61,25 @@ class Launcher private (props: Launcher.Props) {
|
|||||||
scene = _scene
|
scene = _scene
|
||||||
}
|
}
|
||||||
|
|
||||||
private lazy val internal = new JFXApp {
|
private def internal(startSignal: CancelablePromise[Unit]) =
|
||||||
stage = _stage
|
new JFXApp {
|
||||||
stage.initStyle(StageStyle.Undecorated)
|
stage = _stage
|
||||||
// ResizeHelper.addResizeListener(stage)
|
stage.initStyle(StageStyle.Undecorated)
|
||||||
}
|
// ResizeHelper.addResizeListener(stage)
|
||||||
|
startSignal.success(())
|
||||||
|
}
|
||||||
|
|
||||||
private lazy val sceneDragObservable = {
|
private lazy val sceneDragObservable = {
|
||||||
lazy val mpo = _scene.observableMousePressed()
|
val mpo = _scene.observableMousePressed()
|
||||||
lazy val mdo = _scene.observableMouseDragged()
|
val mdo = _scene.observableMouseDragged()
|
||||||
|
|
||||||
mpo.mergeMap(pressEvent =>
|
mpo.concatMap(pressEvent =>
|
||||||
mdo.doOnNext(dragEvent =>
|
mdo.doOnNext(dragEvent =>
|
||||||
ETask(
|
me.Task(pprint.log("emitted")) >>
|
||||||
_stage.setX(dragEvent.screenX - pressEvent.sceneX)
|
me.Task(
|
||||||
) >>
|
_stage.setX(dragEvent.screenX - pressEvent.sceneX)
|
||||||
ETask(
|
) >>
|
||||||
|
me.Task(
|
||||||
_stage.setY(
|
_stage.setY(
|
||||||
dragEvent.screenY - pressEvent.sceneY
|
dragEvent.screenY - pressEvent.sceneY
|
||||||
)
|
)
|
||||||
@ -84,21 +88,32 @@ class Launcher private (props: Launcher.Props) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
def init(delay: FiniteDuration = 2000.millis) =
|
def init =
|
||||||
for {
|
Resource.make(for {
|
||||||
_ <- Task(Platform.setImplicitExit(false))
|
_ <- Task(Platform.setImplicitExit(false))
|
||||||
|
startSignal <- Task(CancelablePromise[Unit]())
|
||||||
fib <- Task(internal.main(Array.empty)).start
|
delegate <- Task(internal(startSignal))
|
||||||
_ <- Task.sleep(500.millis)
|
combinedFib <-
|
||||||
sceneDragFib <- toIO(sceneDragObservable.completedL).start
|
Task
|
||||||
fib2 <- toIO(
|
.parZip2(
|
||||||
Observable(launchAction, exitAction).merge
|
Task(delegate.main(Array.empty)),
|
||||||
.doOnNext(_ =>
|
Task.fromCancelablePromise(startSignal) >> toIO(
|
||||||
ETask(internal.stage.close()).executeOn(props.schedulers.fx)
|
me.Task.parSequence(
|
||||||
|
List(
|
||||||
|
sceneDragObservable.completedL,
|
||||||
|
Observable(launchAction, exitAction).merge
|
||||||
|
.doOnNext(_ =>
|
||||||
|
me.Task(delegate.stage.close())
|
||||||
|
.executeOn(props.fxScheduler.value)
|
||||||
|
)
|
||||||
|
.completedL
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
.completedL
|
.start
|
||||||
).start
|
_ <- Task.fromCancelablePromise(startSignal)
|
||||||
c <- CancelableF[Task](fib.cancel >> fib2.cancel >> sceneDragFib.cancel)
|
c <- CancelableF[Task](combinedFib.cancel)
|
||||||
} yield (c)
|
} yield c)(_.cancel)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,44 @@
|
|||||||
package wow.doge.mygame.math;
|
package wow.doge.mygame.math;
|
||||||
|
|
||||||
import Math.{sqrt, pow}
|
import cats.Show
|
||||||
|
import cats.kernel.Eq
|
||||||
|
import cats.syntax.eq._
|
||||||
|
|
||||||
case class ImVector3f(x: Float = 0f, y: Float = 0f, z: Float = 0f)
|
import math.{abs, pow, sqrt}
|
||||||
|
|
||||||
|
final case class ImVector3f(x: Float, y: Float, z: Float)
|
||||||
object ImVector3f {
|
object ImVector3f {
|
||||||
val ZERO = ImVector3f(0, 0, 0)
|
//format: off
|
||||||
val UNIT_X = ImVector3f(1, 0, 0)
|
val Zero = ImVector3f(0, 0, 0)
|
||||||
val UNIT_Y = ImVector3f(0, 1, 0)
|
val UnitX = ImVector3f(1, 0, 0)
|
||||||
val UNIT_Z = ImVector3f(0, 0, 1)
|
val UnitY = ImVector3f(0, 1, 0)
|
||||||
|
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))
|
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,23 @@ 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
|
||||||
@ -21,36 +36,103 @@ 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]
|
||||||
|
|
||||||
def apply[A](): Behavior[EventBus.Command[A]] =
|
final case class ObservableSubscription[A, E <: 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[B] {
|
class EventBus[A] {
|
||||||
import akka.actor.typed.scaladsl.adapter._
|
import akka.actor.typed.scaladsl.adapter._
|
||||||
|
|
||||||
private def eventStreamBehavior(
|
private def eventStreamBehavior(
|
||||||
eventStream: akka.event.EventStream
|
eventStream: EventStream
|
||||||
): Behavior[EventBus.Command[B]] =
|
)(implicit
|
||||||
Behaviors.receiveMessage {
|
timeout: Timeout,
|
||||||
case EventBus.Publish(event, name) =>
|
scheduler: Scheduler,
|
||||||
eventStream.publish(event)
|
spawnProtocol: ActorRef[SpawnProtocol.Command],
|
||||||
Behaviors.same
|
ct: ClassTag[A]
|
||||||
case s @ EventBus.Subscribe(subscriber) =>
|
): Behavior[EventBus.Command[A]] =
|
||||||
eventStream.subscribe(subscriber.toClassic, s.topic)
|
Behaviors.setup { ctx =>
|
||||||
Behaviors.same
|
Behaviors.receiveMessage {
|
||||||
case EventBus.Unsubscribe(subscriber) =>
|
case EventBus.Publish(event, name) =>
|
||||||
eventStream.unsubscribe(subscriber.toClassic)
|
eventStream.publish(event)
|
||||||
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
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
|
||||||
final case object BulletFired extends Event
|
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 {
|
||||||
final case object RenderTick extends TickEvent
|
case object RenderTick extends TickEvent
|
||||||
final case object PhysicsTick extends TickEvent
|
case object PhysicsTick extends TickEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed trait EntityMovementEvent extends Event
|
sealed trait EntityMovementEvent extends Event
|
||||||
@ -22,3 +25,14 @@ 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]
|
||||||
|
}
|
||||||
|
@ -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,46 +11,62 @@ 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(spawnProtocol: ActorSystem[SpawnProtocol.Command]) {
|
class EventsModule(
|
||||||
implicit lazy val s = spawnProtocol.scheduler
|
scheduler: AkkaScheduler,
|
||||||
|
spawnProtocol: ActorRef[SpawnProtocol.Command]
|
||||||
|
) {
|
||||||
|
import EventsModule._
|
||||||
|
implicit val s = scheduler.value
|
||||||
|
implicit val sp = spawnProtocol
|
||||||
|
implicit val timeout = Timeout(1.second)
|
||||||
|
|
||||||
implicit lazy val timeout = Timeout(1.second)
|
val eventBusLogger = SLogger[EventBus[_]]
|
||||||
|
|
||||||
lazy val eventBusLogger = SLogger[EventBus[_]]
|
val playerEventBus: IO[AppError, GameEventBus[PlayerEvent]] =
|
||||||
|
createEventBus[PlayerEvent]()
|
||||||
|
|
||||||
lazy val playerEventBusTask =
|
// val playerCameraEventBusTask =
|
||||||
createEventBus[PlayerEvent]("playerEventBus")
|
// createEventBus[PlayerCameraEvent](Level.DEBUG)
|
||||||
|
|
||||||
// lazy val playerCameraEventBusTask =
|
val tickEventBus: IO[AppError, GameEventBus[TickEvent]] =
|
||||||
// createEventBus[PlayerCameraEvent]("playerCameraEventBus", Level.DEBUG)
|
createEventBus[TickEvent](Level.TRACE)
|
||||||
|
|
||||||
lazy val tickEventBusTask =
|
val mainEventBus: IO[AppError, GameEventBus[Event]] = createEventBus[Event]()
|
||||||
createEventBus[TickEvent]("tickEventBus", Level.TRACE)
|
|
||||||
|
|
||||||
lazy val mainEventBusTask = createEventBus[Event]("mainEventBus")
|
def createEventBus[T: ClassTag](
|
||||||
|
logLevel: Level = Level.DEBUG,
|
||||||
def createEventBus[T](busName: String, logLevel: Level = Level.DEBUG) =
|
busName: Option[String] = None
|
||||||
spawnProtocol.askL(
|
)(implicit name: sourcecode.Name) =
|
||||||
SpawnProtocol.Spawn[EventBus.Command[T]](
|
spawnProtocol
|
||||||
Behaviors.logMessages(
|
.askL(
|
||||||
logOptions = LogOptions()
|
SpawnProtocol.Spawn[EventBus.Command[T]](
|
||||||
.withLevel(logLevel)
|
Behaviors.logMessages(
|
||||||
.withLogger(eventBusLogger.underlying),
|
logOptions = LogOptions()
|
||||||
Behaviors
|
.withLevel(logLevel)
|
||||||
.supervise(EventBus[T]())
|
.withLogger(eventBusLogger.underlying),
|
||||||
.onFailure[Exception](SupervisorStrategy.restart)
|
Behaviors
|
||||||
),
|
.supervise(EventBus[T]())
|
||||||
busName,
|
.onFailure[Exception](
|
||||||
Props.empty,
|
SupervisorStrategy.restart.withLimit(2, 100.millis)
|
||||||
_
|
)
|
||||||
|
),
|
||||||
|
busName.fold(name.value)(identity),
|
||||||
|
Props.empty,
|
||||||
|
_
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
.onErrorHandleWith(TimeoutError.from)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object EventsModule {
|
object EventsModule {
|
||||||
|
@ -1 +0,0 @@
|
|||||||
package wow.doge.mygame.subsystems.events
|
|
@ -1,9 +1,11 @@
|
|||||||
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
|
||||||
final object PlayerMovementEvent {
|
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
|
||||||
@ -11,16 +13,17 @@ final object PlayerMovementEvent {
|
|||||||
extends PlayerMovementEvent
|
extends PlayerMovementEvent
|
||||||
final case class PlayerMovedBackward(pressed: Boolean)
|
final case class PlayerMovedBackward(pressed: Boolean)
|
||||||
extends PlayerMovementEvent
|
extends PlayerMovementEvent
|
||||||
final case object PlayerJumped extends PlayerMovementEvent
|
case object PlayerJumped extends PlayerMovementEvent
|
||||||
// final case object PlayerTurnedRight extends PlayerMovementEvent
|
// case object PlayerTurnedRight extends PlayerMovementEvent
|
||||||
// final case object PlayerTurnedLeft extends PlayerMovementEvent
|
// case object PlayerTurnedLeft extends PlayerMovementEvent
|
||||||
|
implicit val show = Show.fromToString[PlayerMovementEvent]
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed trait PlayerCameraEvent extends PlayerEvent
|
sealed trait PlayerCameraEvent extends PlayerEvent
|
||||||
|
|
||||||
final object PlayerCameraEvent {
|
object PlayerCameraEvent {
|
||||||
final case object CameraLeft extends PlayerCameraEvent
|
case object CameraLeft extends PlayerCameraEvent
|
||||||
final case object CameraRight extends PlayerCameraEvent
|
case object CameraRight extends PlayerCameraEvent
|
||||||
final case object CameraMovedUp extends PlayerCameraEvent
|
case object CameraMovedUp extends PlayerCameraEvent
|
||||||
final case object CameraMovedDown extends PlayerCameraEvent
|
case object CameraMovedDown extends PlayerCameraEvent
|
||||||
}
|
}
|
||||||
|
203
src/main/scala/wow/doge/mygame/subsystems/moddingsystem/ModdingSystem.scala
Normal file → Executable file
203
src/main/scala/wow/doge/mygame/subsystems/moddingsystem/ModdingSystem.scala
Normal file → Executable file
@ -4,9 +4,10 @@ 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._
|
||||||
@ -15,6 +16,8 @@ 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
|
||||||
@ -23,65 +26,68 @@ 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 pluginFormat: Decoder[Plugin] = deriveDecoder
|
implicit val decoder: Decoder[Plugin] = deriveDecoder
|
||||||
|
implicit val show = Show.fromToString[Plugin]
|
||||||
|
implicit val eq = Eq.fromUniversalEquals[Plugin]
|
||||||
}
|
}
|
||||||
|
|
||||||
object ModdingSystem {
|
object ModdingSystem {
|
||||||
sealed trait Error extends Serializable with Product
|
sealed trait Error extends Product with Serializable
|
||||||
final case class CouldNotDecode(cause: String) extends Error
|
final case class CouldNotDecode(cause: String) extends Error
|
||||||
final case class ParseFailure(cause: String) extends Error
|
final case class ParseFailure(cause: io.circe.ParsingFailure) extends Error
|
||||||
final case class FileNotFound(fileName: String) extends Error
|
final case class DecodingFailure(cause: io.circe.DecodingFailure)
|
||||||
case object GenericError extends Error
|
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]
|
||||||
|
}
|
||||||
|
|
||||||
def readPluginsList(dir: os.Path): Try[Either[Error, ArraySeq[Plugin]]] =
|
val x: IO[Error, String] =
|
||||||
Try(
|
IOUtils.liftErrors(readAsyncL(os.pwd / "plugins.json")) {
|
||||||
parse(os.read(dir / "plugins.json"))
|
case _: FileNotFoundException =>
|
||||||
.map(
|
IO.raiseError(FileNotFound(os.pwd / "plugins.json"))
|
||||||
_.as[ArraySeq[Plugin]]
|
case _: NoSuchFileException =>
|
||||||
.leftMap(e => CouldNotDecode(e.getMessage()))
|
IO.raiseError(FileNotFound(os.pwd / "plugins.json"))
|
||||||
)
|
}
|
||||||
.leftMap((e: ParsingFailure) => ParseFailure(e.message))
|
|
||||||
.flatten
|
|
||||||
)
|
|
||||||
// .toValidated
|
|
||||||
|
|
||||||
def findPluginFiles(dir: os.Path): View[os.Path] =
|
def readPluginsList(dir: os.Path): IO[Error, ArraySeq[Plugin]] =
|
||||||
os.list(dir)
|
readAsyncL(dir / "plugins.json")
|
||||||
.view
|
.onErrorHandleWith {
|
||||||
.filter(f => f.ext == "json" && f.baseName.endsWith("plugin"))
|
case _: FileNotFoundException =>
|
||||||
|
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]
|
||||||
) =
|
) =
|
||||||
plugins
|
IO
|
||||||
.sortBy(_.priority)
|
.parTraverse(plugins.sortBy(_.priority))(p =>
|
||||||
.view
|
readAsyncL(dir / os.RelPath(p.name + ".plugin.json"))
|
||||||
.map(p =>
|
.onErrorHandleWith {
|
||||||
p ->
|
case _: FileNotFoundException =>
|
||||||
Either
|
IO.raiseError(FileNotFound(dir))
|
||||||
.catchNonFatal {
|
case _: NoSuchFileException =>
|
||||||
val path = dir / os.RelPath(p.name + ".plugin.json")
|
IO.raiseError(FileNotFound(dir))
|
||||||
os.read(path)
|
case other => IO.terminate(other)
|
||||||
}
|
|
||||||
.leftMap {
|
|
||||||
case _: FileNotFoundException =>
|
|
||||||
FileNotFound(p.name)
|
|
||||||
case _: NoSuchFileException => FileNotFound(p.name)
|
|
||||||
case e => GenericError
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.partitionMap {
|
|
||||||
case (p, either) =>
|
|
||||||
either match {
|
|
||||||
case Left(value) => Left(p -> value)
|
|
||||||
case Right(value) => Right(p -> value)
|
|
||||||
}
|
}
|
||||||
}
|
.attempt
|
||||||
|
.map(r => p -> r)
|
||||||
def readPluginFiles(filePaths: View[os.Path]) = {
|
)
|
||||||
filePaths.map(path => os.read(path))
|
.map(_.partitionMap {
|
||||||
}
|
case p -> Left(value) => Left(p -> value)
|
||||||
|
case p -> Right(value) => Right(p -> value)
|
||||||
|
})
|
||||||
|
|
||||||
def parsePluginFiles(files: View[(Plugin, String)]) =
|
def parsePluginFiles(files: View[(Plugin, String)]) =
|
||||||
files
|
files
|
||||||
@ -93,74 +99,59 @@ object ModdingSystem {
|
|||||||
case (p, Right(value)) => Right(p -> value)
|
case (p, Right(value)) => Right(p -> value)
|
||||||
}
|
}
|
||||||
|
|
||||||
def foldMerge(iterable: Iterable[Json]) =
|
val emptyJson = Json.fromString("empty")
|
||||||
iterable.foldLeft(Json.fromString("empty")) {
|
|
||||||
case (json, io.circe.Json.Null) => json //ignore null values
|
|
||||||
case (json, value) => json.deepMerge(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
def mergePluginData(plugins: View[(Plugin, Json)]) = {
|
val foldFn: (Json, Json) => Json = {
|
||||||
foldMerge(plugins.map {
|
case (json, Json.Null) => json //ignore null values
|
||||||
case (p, json) => json
|
case (json, value) => json.deepMerge(value)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def mergePluginDataConsumer =
|
def mergePluginDataConsumer =
|
||||||
Consumer.foldLeft[Json, Json](Json.fromString("empty")) {
|
Consumer.foldLeft[Json, Json](emptyJson)(foldFn)
|
||||||
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(foldMerge)
|
.map(_.foldLeft(emptyJson)(foldFn))
|
||||||
|
|
||||||
// def test =
|
def run(wd: os.Path = os.pwd) =
|
||||||
// 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 <- IO.fromTryEither(readPluginsList(wd))
|
plugins <- readPluginsList(wd)
|
||||||
(readFailures, readSuccesses) <- UIO(findAndReadPluginFiles(wd, plugins))
|
(readFailures, readSuccesses) <- findAndReadPluginFiles(wd, plugins)
|
||||||
(parseFailures, parseSuccesses) <- UIO(parsePluginFiles(readSuccesses))
|
(parseFailures, parseSuccesses) = parsePluginFiles(readSuccesses.view)
|
||||||
// res <- UIO(mergePluginData(parseSuccesses))
|
res <- UIO.parMap5(
|
||||||
res <-
|
UIO(readFailures.to(List)),
|
||||||
IOUtils
|
UIO.pure(readSuccesses),
|
||||||
.toIO(
|
UIO(parseFailures.to(List)),
|
||||||
Observable
|
UIO(parseSuccesses.to(List)),
|
||||||
.fromIterable(parseSuccesses)
|
Observable
|
||||||
.map { case (p, json) => json }
|
.fromIterable(parseSuccesses)
|
||||||
.consumeWith(loadBalancedPluginDataMerger)
|
.map { case (p, json) => json }
|
||||||
)
|
.consumeWith(loadBalancedPluginDataMerger)
|
||||||
.onErrorHandle(e => GenericError)
|
.toIO
|
||||||
_ <- UIO {
|
.hideErrors
|
||||||
println(s"Read Successes = ${readSuccesses.to(Seq)}")
|
)(Result.apply)
|
||||||
println(s"Read Failures = ${readFailures.to(Seq)}")
|
} yield res
|
||||||
println(s"Parse Successes = ${parseSuccesses.to(Seq)}")
|
|
||||||
println(s"Parse Failures = ${parseFailures.to(Seq)}")
|
|
||||||
println(s"Merged = $res")
|
|
||||||
}
|
|
||||||
} yield ()
|
|
||||||
|
|
||||||
// monix.eval.Task.deferAction(implicit s =>
|
def log(res: Result) =
|
||||||
// ModdingSystem
|
UIO {
|
||||||
// .test()
|
pprint.log(show"Read Successes = ${res.readSuccesses}")
|
||||||
// .leftMap(e => new Throwable(e.toString()))
|
pprint.log(show"Read Failures = ${res.readFailures}")
|
||||||
// .to[monix.eval.Task]
|
pprint.log(show"Parse Successes = ${res.parseSuccesses}")
|
||||||
// )
|
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)
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,356 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -17,7 +17,9 @@ 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 {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -41,7 +43,7 @@ object ScriptActor {
|
|||||||
result: ActorRef[Map[os.Path, Either[Error, Any]]]
|
result: ActorRef[Map[os.Path, Either[Error, Any]]]
|
||||||
) extends Command
|
) extends Command
|
||||||
|
|
||||||
lazy val defaultScalaRunner =
|
val defaultScalaRunner =
|
||||||
ammonite
|
ammonite
|
||||||
.Main(
|
.Main(
|
||||||
storageBackend = new Folder(
|
storageBackend = new Folder(
|
||||||
@ -51,13 +53,13 @@ object ScriptActor {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
lazy val defaultKotlinRunner: KotlinScriptEngine = {
|
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]
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy val defaultGroovyRunner: GroovyScriptEngine =
|
val defaultGroovyRunner: GroovyScriptEngine =
|
||||||
new GroovyScriptEngine(os.pwd.toString)
|
new GroovyScriptEngine(os.pwd.toString)
|
||||||
|
|
||||||
def apply(
|
def apply(
|
||||||
@ -96,10 +98,7 @@ 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(
|
.runScript(path, Seq.empty)
|
||||||
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))
|
||||||
|
|
||||||
@ -129,6 +128,7 @@ 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(s"Received $path")
|
context.log.debug(show"Received $path")
|
||||||
val res = getScript(path)
|
val res = getScript(path)
|
||||||
context.log.debug(s"result = $res")
|
context.log.debug(s"result = ${res.toString}")
|
||||||
requester ! res
|
requester ! res
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
|
|
||||||
case CompileAll(paths, requester) =>
|
case CompileAll(paths, requester) =>
|
||||||
context.log.debug(s"Received $paths")
|
context.log.debug(show"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[wow.doge.mygame.state.ScriptActor.Error, Any]]
|
type LOL = Map[os.Path, Either[ScriptActor.Error, Any]]
|
||||||
|
|
||||||
def compileAll(
|
def compileAll(
|
||||||
paths: Seq[os.Path]
|
paths: Seq[os.Path]
|
||||||
@ -179,14 +179,13 @@ class ScriptActor(
|
|||||||
paths: Seq[os.Path],
|
paths: Seq[os.Path],
|
||||||
scriptsMap: Map[
|
scriptsMap: Map[
|
||||||
os.Path,
|
os.Path,
|
||||||
Either[wow.doge.mygame.state.ScriptActor.Error, Any]
|
Either[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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
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
|
||||||
|
|
||||||
@ -12,10 +13,12 @@ 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 {
|
||||||
@ -23,7 +26,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
|
sealed trait Command extends Product with Serializable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param scriptPath path of the script to compile
|
* @param scriptPath path of the script to compile
|
||||||
@ -43,9 +46,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[scriptsystem] final case object NoOp extends Command
|
private case object NoOp extends Command
|
||||||
|
|
||||||
private[scriptsystem] final case class DelegateToChild(
|
private final case class DelegateToChild(
|
||||||
scriptActor: ActorRef[ScriptActor.Command],
|
scriptActor: ActorRef[ScriptActor.Command],
|
||||||
scriptPath: os.Path,
|
scriptPath: os.Path,
|
||||||
requester: ActorRef[ScriptResult]
|
requester: ActorRef[ScriptResult]
|
||||||
@ -60,7 +63,7 @@ object ScriptCachingActor {
|
|||||||
// requester: ActorRef[Map[os.Path, ScriptResult]]
|
// requester: ActorRef[Map[os.Path, ScriptResult]]
|
||||||
// ) extends Command
|
// ) extends Command
|
||||||
|
|
||||||
// final case class Props(
|
// class Props(
|
||||||
// ctx: ActorContext[Command],
|
// ctx: ActorContext[Command],
|
||||||
// scriptActor: ActorRef[ScriptActor.Command]
|
// scriptActor: ActorRef[ScriptActor.Command]
|
||||||
// ) {
|
// ) {
|
||||||
@ -97,7 +100,6 @@ 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 {
|
||||||
@ -106,7 +108,6 @@ 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,
|
||||||
@ -163,7 +164,6 @@ 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,16 +188,56 @@ class ScriptCachingActor(
|
|||||||
Behaviors.same
|
Behaviors.same
|
||||||
|
|
||||||
case Put(scriptPath, script) =>
|
case Put(scriptPath, script) =>
|
||||||
ctx.log.debug(s"Putting $script at path $scriptPath")
|
ctx.log.debugP(show"Putting ${script.toString} at path $scriptPath")
|
||||||
val newState =
|
val newState =
|
||||||
state.modify(_.scriptsMap).using(_ + (scriptPath -> script))
|
state.modify(_.scriptsMap).using(_ + (scriptPath -> script))
|
||||||
ctx.log.trace(newState.toString())
|
ctx.log.traceP(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 {
|
||||||
@ -208,56 +248,8 @@ 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](SupervisorStrategy.restart)
|
.onFailure[Exception](
|
||||||
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -5,9 +5,13 @@ 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)
|
||||||
@ -20,39 +24,36 @@ 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],
|
||||||
mode: ScriptInitMode = ScriptInitMode.Lazy
|
timeout: Timeout,
|
||||||
)(implicit timeout: Timeout, scheduler: Scheduler) {
|
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(findScriptFiles(os.pwd / "assets" / "scripts"))
|
scriptFiles <- Task(
|
||||||
|
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
|
||||||
|
|
||||||
|
15
src/main/scala/wow/doge/mygame/types/package.scala
Normal file
15
src/main/scala/wow/doge/mygame/types/package.scala
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
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)
|
||||||
|
}
|
@ -6,10 +6,12 @@ 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]
|
||||||
@ -22,20 +24,24 @@ object AkkaUtils {
|
|||||||
_
|
_
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
def spawnActorL2[T](
|
def spawnActorL[T](
|
||||||
behavior: Behavior[T],
|
behavior: Behavior[T],
|
||||||
actorName: String
|
actorName: Option[String] = None,
|
||||||
|
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.askL[ActorRef[T]](
|
spawnProtocol
|
||||||
SpawnProtocol.Spawn(
|
.askL[ActorRef[T]](
|
||||||
behavior,
|
SpawnProtocol.Spawn(
|
||||||
actorName,
|
behavior,
|
||||||
Props.empty,
|
actorName.fold(name.value)(identity),
|
||||||
_
|
props,
|
||||||
|
_
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
.onErrorHandleWith(TimeoutError.from)
|
||||||
}
|
}
|
||||||
|
@ -16,38 +16,37 @@ 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 lazy val defaultOut = System.out
|
private 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 {
|
||||||
@ -55,9 +54,9 @@ object GenericConsoleStream {
|
|||||||
/**
|
/**
|
||||||
* for future use
|
* for future use
|
||||||
*/
|
*/
|
||||||
case class Config(exclusive: Boolean = false)
|
final case class Config(exclusive: Boolean = false)
|
||||||
object Config {
|
object Config {
|
||||||
lazy val default = Config()
|
val default = Config()
|
||||||
}
|
}
|
||||||
|
|
||||||
implicit val implJFXConsoleStreamForTextArea =
|
implicit val implJFXConsoleStreamForTextArea =
|
||||||
|
68
src/main/scala/wow/doge/mygame/utils/GenericTimerActor.scala
Normal file
68
src/main/scala/wow/doge/mygame/utils/GenericTimerActor.scala
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,28 @@
|
|||||||
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: monix.bio.IO[Throwable, T]) =
|
def toTask[T](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))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,62 @@
|
|||||||
|
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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
18
src/main/scala/wow/doge/mygame/utils/MovementDirection.scala
Normal file
18
src/main/scala/wow/doge/mygame/utils/MovementDirection.scala
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
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]
|
||||||
|
}
|
31
src/main/scala/wow/doge/mygame/utils/ReaderDemo.scala
Normal file
31
src/main/scala/wow/doge/mygame/utils/ReaderDemo.scala
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
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))
|
||||||
|
|
||||||
|
}
|
@ -1,14 +1,21 @@
|
|||||||
package wow.doge.mygame.utils
|
package wow.doge.mygame.utils
|
||||||
|
|
||||||
case class Display(
|
final case class Display(
|
||||||
width: Int = 640,
|
width: Int,
|
||||||
height: Int = 480,
|
height: Int,
|
||||||
title: String = "JME-Game",
|
title: String,
|
||||||
fullScren: Boolean = false,
|
fullScren: Boolean,
|
||||||
vsync: Boolean = false,
|
vsync: Boolean,
|
||||||
frameRate: Int = -1
|
frameRate: Int
|
||||||
)
|
)
|
||||||
object Display {
|
object Display {
|
||||||
val default = Display()
|
val default = Display(
|
||||||
|
width = 640,
|
||||||
|
height = 480,
|
||||||
|
title = "JME-Game",
|
||||||
|
fullScren = false,
|
||||||
|
vsync = false,
|
||||||
|
frameRate = -1
|
||||||
|
)
|
||||||
}
|
}
|
||||||
case class GlobalSettings(display: Display = Display.default)
|
final case class GlobalSettings(display: Display = Display.default)
|
||||||
|
@ -6,16 +6,15 @@ import monix.execution.atomic.AtomicAny
|
|||||||
* Useless
|
* Useless
|
||||||
*/
|
*/
|
||||||
sealed abstract class Tree[+T]
|
sealed abstract class Tree[+T]
|
||||||
case class Node[T](data: T, children: AtomicAny[LazyList[Tree[T]]])
|
final 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]
|
||||||
case class Data(data: Int)
|
final 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)
|
||||||
|
@ -0,0 +1,67 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
}
|
47
src/main/scala/wow/doge/mygame/utils/controls/FontIcon.scala
Normal file
47
src/main/scala/wow/doge/mygame/utils/controls/FontIcon.scala
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
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")
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
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)
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
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
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
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)
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
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
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
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)
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
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)
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
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)
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
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
|
@ -0,0 +1,7 @@
|
|||||||
|
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)
|
||||||
|
}
|
38
src/main/scala/wow/doge/mygame/utils/package.scala
Executable file
38
src/main/scala/wow/doge/mygame/utils/package.scala
Executable file
@ -0,0 +1,38 @@
|
|||||||
|
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)
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
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]
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,126 @@
|
|||||||
|
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
|
||||||
|
}
|
130
src/main/scala/wow/doge/mygame/utils/wrappers/jme/Node.scala
Normal file
130
src/main/scala/wow/doge/mygame/utils/wrappers/jme/Node.scala
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
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)
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,91 @@
|
|||||||
|
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)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
package wow.doge.mygame.utils.wrappers.jme
|
||||||
|
|
||||||
|
package object node {}
|
57
src/test/scala/wow/doge/mygame/ActorTimeoutTest.scala
Normal file
57
src/test/scala/wow/doge/mygame/ActorTimeoutTest.scala
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
32
src/test/scala/wow/doge/mygame/AmmoniteTest.scala
Normal file
32
src/test/scala/wow/doge/mygame/AmmoniteTest.scala
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
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"))
|
||||||
|
}
|
||||||
|
}
|
59
src/test/scala/wow/doge/mygame/AnimTest.scala
Normal file
59
src/test/scala/wow/doge/mygame/AnimTest.scala
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
75
src/test/scala/wow/doge/mygame/AssetManagerTest.scala
Normal file
75
src/test/scala/wow/doge/mygame/AssetManagerTest.scala
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
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"))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
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 = ???
|
||||||
|
|
||||||
|
}
|
70
src/test/scala/wow/doge/mygame/FileWatcherTest.scala
Normal file
70
src/test/scala/wow/doge/mygame/FileWatcherTest.scala
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
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
Loading…
Reference in New Issue
Block a user