Compare commits

...

14 Commits

Author SHA1 Message Date
ece29b6b0d many changes 2021-04-19 19:51:58 +05:30
632bcccef3 Add monix-nio and make modding sys use it 2021-03-24 18:41:18 +05:30
8f3c08f271 minor misc changes 2021-03-24 14:21:28 +05:30
dd01b070ff Add IO wrapper for InputManager 2021-03-10 19:25:12 +05:30
4ff54c1373 Use monix observable based player movement reducer 2021-03-10 19:24:52 +05:30
44f0538b8b Player cannot walk when stamina is 0 2021-03-09 19:14:17 +05:30
9b484e895b Add stamina regen logic 2021-03-08 21:17:14 +05:30
1b9bb4265f Myriad changes 2021-03-08 19:18:43 +05:30
f0ae3625bf Add rest of the player movement messages 2021-03-08 19:18:28 +05:30
be9acf81d5 Player actor changes
Rename PlayerActorSupervisor to PlayerActor
Make PlayerEventListener able to handle multiple inputs
2021-03-06 19:33:27 +05:30
67201c8f7e Many changes
Add player actor substates
Added color logic to hud stats bars
2021-03-05 00:39:57 +05:30
1422d91b14 Update player actor
Make stats observable hot
Remove concurrentsubject
2021-02-28 23:45:43 +05:30
92aae68254 Add stamina consumption logic 2021-02-28 19:32:49 +05:30
12b232fb3c many changes 2021-02-27 11:36:32 +05:30
93 changed files with 2830 additions and 1700 deletions

1
.gitignore vendored
View File

@ -15,6 +15,7 @@ metals.sbt
.metals
.bloop
.ammonite
.bsp
# Scala-IDE specific
.scala_dependencies

8
.scalafix.conf Normal file
View File

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

47
build.sbt Normal file → Executable file
View File

@ -7,7 +7,7 @@ resolvers += "Jitpack" at "https://jitpack.io"
resolvers += Resolver.mavenLocal
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 {
case n if n.startsWith("Linux") => "linux"
@ -18,6 +18,7 @@ lazy val osName = System.getProperty("os.name") match {
lazy val javaFXModules =
Seq("base", "controls", "fxml", "graphics", "media", "swing", "web")
testFrameworks += new TestFramework("munit.Framework")
lazy val root = (project in file(".")).settings(
name := "mygame",
organization := "wow.doge",
@ -33,6 +34,7 @@ lazy val root = (project in file(".")).settings(
"com.simsilica" % "zay-es" % "1.2.1",
"org.typelevel" %% "cats-core" % "2.3.0",
"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-scripting-jsr223" % "1.4.10",
"org.codehaus.groovy" % "groovy-all" % "3.0.6" pomOnly (),
@ -42,6 +44,7 @@ lazy val root = (project in file(".")).settings(
"org.typelevel" %% "cats-effect" % "2.3.0",
"io.monix" %% "monix" % "3.2.2",
"io.monix" %% "monix-bio" % "1.1.0",
"io.monix" %% "monix-nio" % "0.0.9",
"io.circe" %% "circe-core" % "0.13.0",
"io.circe" %% "circe-generic" % "0.13.0",
"com.softwaremill.sttp.client3" %% "core" % "3.0.0",
@ -67,13 +70,24 @@ lazy val root = (project in file(".")).settings(
"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"
"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
// Add JavaFX dependencies
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(
"-encoding",
@ -126,7 +140,8 @@ lazy val root = (project in file(".")).settings(
// oldStrategy(x)
}
)
initialCommands in (console) := """ammonite.Main.main(Array.empty)"""
// initialCommands in (console) := """ammonite.Main.main(Array.empty)"""
ammoniteVersion := "2.2.0"
addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1")
addCompilerPlugin(
"org.typelevel" %% "kind-projector" % "0.11.1" cross CrossVersion.full
@ -135,7 +150,29 @@ inThisBuild(
List(
scalaVersion := scalaVersion.value, // 2.11.12, or 2.13.3
semanticdbEnabled := true, // enable SemanticDB
semanticdbVersion := "4.3.24" // use Scalafix compatible version
semanticdbVersion := "4.4.10" // use Scalafix compatible version
)
)
ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.4.3"
scalafixDependencies in ThisBuild += "org.scalalint" %% "rules" % "0.1.4"
wartremoverErrors in (Compile, compile) ++=
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)

View File

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

View File

@ -1,2 +1,7 @@
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.0")
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.23")
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")

View 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;
}

View File

@ -60,11 +60,9 @@ class StaticLoggerBinder extends OdinLoggerBinder[IO] {
.allocated
.unsafeRunSync()
{
ArraySeq(release1, release2, release3).foreach(r =>
sys.addShutdownHook(r.unsafeRunSync())
)
}
val loggers: PartialFunction[String, Logger[IO]] = {
case "some.external.package.SpecificClass" =>

View File

@ -9,7 +9,7 @@ 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
sealed trait AppError extends Product with Serializable
object AppError {
final case class TimeoutError(reason: String) extends AppError
object TimeoutError {

View File

@ -2,22 +2,24 @@ package wow.doge.mygame
import scala.concurrent.duration._
import _root_.monix.bio.BIOApp
import _root_.monix.bio.Task
import _root_.monix.bio.UIO
import _root_.monix.execution.Scheduler
import akka.util.Timeout
import cats.effect.ExitCode
import cats.effect.Resource
import cats.implicits._
import io.odin._
import io.odin.consoleLogger
import io.odin.fileLogger
import io.odin.json.Formatter
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 wow.doge.mygame.ActorSystemResource
import wow.doge.mygame.executors.ExecutorsModule
import wow.doge.mygame.types.AkkaScheduler
import wow.doge.mygame.utils.GenericConsoleStream
import io.odin
object Main extends BIOApp with ExecutorsModule {
import java.util.logging.{Logger => JLogger, Level}
JLogger.getLogger("").setLevel(Level.SEVERE)
@ -28,14 +30,14 @@ object Main extends BIOApp with ExecutorsModule {
def appResource(consoleStream: GenericConsoleStream[TextArea]) =
for {
logger <-
consoleLogger().withAsync(
consoleLogger(minLevel = odin.Level.Debug).withAsync(
timeWindow = 1.milliseconds,
maxBufferSize = Some(100)
) |+|
fileLogger(
"application-log-1.log",
Formatter.json
).withAsync(timeWindow = 1.milliseconds, maxBufferSize = Some(2000))
).withAsync(timeWindow = 1.milliseconds, maxBufferSize = Some(100))
jmeScheduler <- jmeSchedulerResource
// backend <- Resource.make(toIO(HttpClientMonixBackend()))(backend =>
// toIO(backend.close())

View File

@ -1,5 +1,8 @@
package wow.doge.mygame
import java.util.concurrent.TimeoutException
import scala.annotation.switch
import scala.concurrent.duration._
import akka.actor.typed.ActorRef
@ -8,6 +11,8 @@ import akka.util.Timeout
import cats.effect.Resource
import cats.effect.concurrent.Deferred
import cats.syntax.eq._
import cats.syntax.show._
import com.jayfella.jme.jfx.JavaFxUI
import com.jme3.asset.plugins.ZipLocator
import com.jme3.bullet.control.BetterCharacterControl
import com.jme3.input.InputManager
@ -28,9 +33,15 @@ import monix.bio.IO
import monix.bio.Task
import monix.bio.UIO
import monix.eval.Coeval
import monix.execution.exceptions.DummyException
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.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.game.GameApp
@ -40,10 +51,12 @@ import wow.doge.mygame.game.entities.CharacterStats
import wow.doge.mygame.game.entities.EntityIds
import wow.doge.mygame.game.entities.NpcActorSupervisor
import wow.doge.mygame.game.entities.NpcMovementActor
import wow.doge.mygame.game.entities.PlayerActorSupervisor
import wow.doge.mygame.game.entities.PlayerController
import wow.doge.mygame.game.subsystems.input.GameInputHandler
import wow.doge.mygame.game.entities.player.PlayerActor
import wow.doge.mygame.game.entities.player.PlayerController
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.implicits._
import wow.doge.mygame.launcher.Launcher
@ -59,14 +72,20 @@ 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.scriptsystem.ScriptCompiler
import wow.doge.mygame.subsystems.scriptsystem.ScriptInitMode
import wow.doge.mygame.subsystems.scriptsystem.ScriptSystemResource
import wow.doge.mygame.types._
import wow.doge.mygame.utils.AkkaUtils
import wow.doge.mygame.utils.GenericConsoleStream
import wow.doge.mygame.utils.IOUtils
import wow.doge.mygame.utils.MonixDirectoryWatcher
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(
logger: Logger[Task],
jmeThread: JmeScheduler,
@ -79,8 +98,13 @@ class MainApp(
) {
implicit val as = scheduler.value
val scriptSystemInit =
new ScriptSystemResource(os.pwd, ScriptInitMode.Eager).init
val scriptSystemResource: Resource[UIO, ScriptCompiler] =
new ScriptSystemResource(
os.pwd,
logger,
ScriptInitMode.Eager,
schedulers.io
).init2
val eventsModule = new EventsModule(scheduler, spawnProtocol)
@ -101,7 +125,9 @@ class MainApp(
IOUtils
.toIO(
obs
.doOnNextF(pme => Coeval(pprint.log(s"Received event $pme")).void)
.doOnNextF(pme =>
logger.trace(show"Received event $pme").toTask.void
)
.completedL
.startAndForget
)
@ -114,7 +140,7 @@ class MainApp(
viewPort <- gameApp.viewPort
physicsSpace <- UIO.pure(gameApp.physicsSpace)
_ <- logger.infoU("before")
// jfxUI <- gameApp.jfxUI
jfxUI <- gameApp.jfxUI.hideErrors
consoleTextArea <- UIO(new TextArea {
text = "hello \n"
editable = false
@ -122,6 +148,7 @@ class MainApp(
// maxHeight = 150
// maxWidth = 300
})
// _ <- Task(consoleStream := consoleTextArea)
// _ <- Task(jfxUI += consoleTextArea)
_ <- logger.infoU("after")
@ -135,11 +162,41 @@ class MainApp(
def gameInit(
tickEventBus: GameEventBus[TickEvent]
): Resource[UIO, Either[AppError, Fiber[Nothing, Unit]]] =
wire[GameAppResource].resource.evalMap {
case Right(gameApp -> gameAppFib) =>
eval(tickEventBus, gameApp, gameAppFib).attempt
case Left(error) => IO.terminate(new Exception(error.toString))
for {
r1 <- wire[GameAppResource].resource.evalMap(e =>
IO.fromEither(e)
.flatMap {
case (gameApp -> gameAppFib) =>
eval(tickEventBus, gameApp, gameAppFib)
}
.attempt
)
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
@ -150,7 +207,7 @@ class MainApp(
.use(_ => launchSignal.get)
.hideErrors
tickEventBus <-
eventsModule.tickEventBus.hideErrorsWith(e => DummyException(e.toString))
eventsModule.tickEventBus.hideErrorsWith(e => new Exception(e.toString))
_ <-
/**
* User chose to quit
@ -185,7 +242,8 @@ class MainAppDelegate(
viewPort: ViewPort,
enqueueR: Function1[() => Unit, Unit],
rootNode: RootNode,
schedulers: Schedulers
schedulers: Schedulers,
jfxUI: JavaFxUI
)(implicit
spawnProtocol: ActorRef[SpawnProtocol.Command],
timeout: Timeout,
@ -203,13 +261,14 @@ class MainAppDelegate(
os.rel / "assets" / "town.zip",
classOf[ZipLocator]
)
_ <- loggerL.infoU("test")
// _ <- Task(consoleStream.println("text"))
level <- DefaultGameLevel(assetManager, viewPort)
_ <- level.addToGame(rootNode, physicsSpace)
playerActor <- createPlayerController()
// .onErrorRestart(3)
_ <- wire[GameInputHandler.Props].begin
// _ <- wire[GameInputHandler.Props].begin
_ <- new InputMappings(new jme.InputManager(inputManager)).setup
// .onErrorRestart(3)
johnActor <- createTestNpc("John")
// _ <- johnActor !! NpcActorSupervisor.Move(ImVector3f(0, 0, 20))
@ -231,45 +290,45 @@ class MainAppDelegate(
_ <-
damageObs
.doOnNextF(event =>
(loggerL.debug(s"Received Damage Event $event") >>
(loggerL.debug(show"Received Damage Event $event") >>
(if (event.victimName === "PlayerNode")
// playerActor !! PlayerActorSupervisor.TakeDamage(event.amount)
playerActor.askL(
PlayerActorSupervisor.TakeDamage2(event.amount, _)
)
playerActor
.askL(PlayerActor.TakeDamage(event.amount, _))
.void
.onErrorHandle { case ex: TimeoutException => () }
else IO.unit)).toTask
)
.completedL
.toIO
.hideErrors
.startAndForget
_ <-
Observable
.interval(1.second)
.doOnNextF(_ =>
playerActor
.askL(PlayerActorSupervisor.GetStatus)
.flatMap(s => loggerL.debug(s"Player actor status: $s"))
// .flatMap(s =>
// if (s == Status.Alive)
// playerActor
// .askL(PlayerActorSupervisor.CurrentStats )
// .flatMap(s => loggerL.debug(s"Got state $s"))
// else IO.unit
// )
.toTask
)
// _ <-
// Observable
// .interval(1.second)
// .doOnNextF(_ =>
// playerActor
// .askL(PlayerActorSupervisor.GetStatus)
// .flatMap(s => loggerL.debug(s"Player actor status: $s"))
// .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
// )
.completedL
.toIO
.hideErrors
.startAndForget
// // .doOnNextF(_ =>
// // playerActor
// // .askL(PlayerActorSupervisor.GetStatus )
// // .flatMap(s => loggerL.debug(show"Player actor status: $s"))
// // .toTask
// // )
// .completedL
// .toIO
// .hideErrors
// .startAndForget
_ <-
physicsSpace.collisionObservable
// .filter(event =>
@ -282,7 +341,7 @@ class MainAppDelegate(
// )
// .doOnNextF(event =>
// loggerL
// .debug(s"$event ${event.appliedImpulse()}")
// .debug(show"$event ${event.appliedImpulse()}")
// .toTask
// )
.filter(_.nodeA.map(_.getName =!= "main-scene_node").getOrElse(false))
@ -295,7 +354,7 @@ class MainAppDelegate(
} yield if (nodeB.getName === "John") nodeA else nodeB)
_ <- Coeval(
victim.foreach { v =>
pprint.log(s"emitted event ${v.getName}")
pprint.log(show"emitted event ${v.getName}")
mainEventBus ! EventBus.Publish(
DamageEvent(
"John",
@ -322,11 +381,104 @@ class MainAppDelegate(
// )
// .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 ()
def createPlayerController(
// appScheduler: monix.execution.Scheduler
): IO[AppError, PlayerActorSupervisor.Ref] = {
): IO[AppError, PlayerActor.Ref] = {
val playerPos = ImVector3f.Zero
val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o"
// val modelPath = os.rel / "Models" / "Oto" / "Oto.mesh.xml"
@ -369,66 +521,44 @@ class MainAppDelegate(
.scan(new Quaternion) {
case (rotationBuf, action) =>
action.binding match {
// case PlayerCameraEvent.CameraLeft =>
case PlayerCameraInput.CameraRotateLeft =>
// me.Task {
val rot = rotationBuf
.fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y)
cameraPivotNode.rotate(rot)
rotationBuf
// }
// case PlayerCameraEvent.CameraRight =>
case PlayerCameraInput.CameraRotateRight =>
// me.Task {
val rot = rotationBuf
.fromAngleAxis(-1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y)
cameraPivotNode.rotate(rot)
rotationBuf
// }
// case PlayerCameraEvent.CameraMovedUp =>
case PlayerCameraInput.CameraRotateUp =>
// me.Task {
val rot = rotationBuf
.fromAngleAxis(-1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X)
cameraPivotNode.rotate(rot)
rotationBuf
// }
// case PlayerCameraEvent.CameraMovedDown =>
case PlayerCameraInput.CameraRotateDown =>
// me.Task {
val rot = rotationBuf
.fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X)
cameraPivotNode.rotate(rot)
rotationBuf
// }
}
}
.completedL
.toIO
.hideErrors
.startAndForget
// _ <-
// Observable
// .interval(10.millis)
// .doOnNextF(_ =>
// Coeval {
// val location = playerNode.getWorldTranslation()
// cameraPivotNode.setLocalTranslation(location)
// }
// )
// .completedL
// .toIO
// .hideErrors
// .startAndForget
sched <- UIO.pure(schedulers.async)
fxSched <- UIO.pure(schedulers.fx)
playerActor <- wire[PlayerController.Props].create
obs <-
playerActor
.askL(PlayerActorSupervisor.GetStatsObservable2)
.onErrorHandleWith(TimeoutError.from)
playerMovementReducer = new PlayerMovementReducer(playerActor, loggerL)
_ <-
obs
.doOnNext(s => loggerL.debug(s"Got state $s").toTask)
inputManager
.enumObservableAction(PlayerMovementInput)
.sample(1.millis)
.scanEval(me.Task.pure(PlayerMovementReducer.State.empty))(
playerMovementReducer.value
)
.completedL
.toIO
.hideErrors
@ -456,7 +586,7 @@ class MainAppDelegate(
npcName,
initialPos
).behavior,
actorName = Some(s"${npcName}-npcActorSupervisor")
actorName = Some(show"${npcName}-npcActorSupervisor")
)
(for {

View File

@ -16,11 +16,11 @@ import wow.doge.mygame.implicits._
object GameActorSystem {
sealed trait Command
case class GetSpawnProtocol(
final case class GetSpawnProtocol(
replyTo: ActorRef[ActorRef[SpawnProtocol.Command]]
) extends Command
class Props() {
class Props {
def create =
Behaviors.setup[Command] { ctx =>
val systemSpawnProtocol = ctx.spawnN(SpawnProtocol())
@ -44,9 +44,9 @@ class GameActorSystem(
// object EventBusSupervisor {
// sealed trait Command
// case class GetMainEventBus(replyTo: ActorRef[GameEventBus[Event]])
// final case class GetMainEventBus(replyTo: ActorRef[GameEventBus[Event]])
// extends Command
// case class GetEventBus[T](replyTo: ActorRef[GameEventBus[T]])(implicit
// final case class GetEventBus[T](replyTo: ActorRef[GameEventBus[T]])(implicit
// classTag: ClassTag[T]
// ) extends Command {
// def ct = classTag

View File

@ -47,6 +47,7 @@ object JMEExecutorService extends GUIExecutorService {
}
object JMERunner {
@SuppressWarnings(Array("org.wartremover.warts.Null"))
var runner: Application = null
}

View File

@ -5,7 +5,7 @@ import monix.execution.Scheduler
import monix.execution.UncaughtExceptionReporter
final case class Schedulers(
blockingIO: Schedulers.IoScheduler,
io: Schedulers.IoScheduler,
async: Schedulers.AsyncScheduler,
fx: Schedulers.FxScheduler
)
@ -32,8 +32,8 @@ object Schedulers {
)
)
case class AsyncScheduler(value: Scheduler)
case class IoScheduler(value: Scheduler)
case class FxScheduler(value: Scheduler)
final case class AsyncScheduler(value: Scheduler)
final case class IoScheduler(value: Scheduler)
final case class FxScheduler(value: Scheduler)
}

View File

@ -9,22 +9,15 @@ import akka.actor.typed.SpawnProtocol
import akka.actor.typed.scaladsl.AskPattern._
import akka.util.Timeout
import cats.effect.Resource
import cats.effect.concurrent.Deferred
import com.jme3.bullet.BulletAppState
import com.jme3.input.InputManager
import com.jme3.scene.Node
import com.jme3.scene.Spatial
import com.jme3.system.AppSettings
import com.softwaremill.tagging._
import com.typesafe.scalalogging.{Logger => SLogger}
import io.odin.Logger
import monix.bio.Fiber
import monix.bio.IO
import monix.bio.Task
import monix.bio.UIO
import monix.catnap.ConcurrentChannel
import monix.catnap.ConsumerF
import monix.eval.Coeval
import wow.doge.mygame.AppError
import wow.doge.mygame.AppError.TimeoutError
import wow.doge.mygame.Dispatchers
@ -135,112 +128,3 @@ class GameAppResource(
case Left(error) => IO.terminate(new Exception(error.toString))
}
}
object GameApp {}
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
case object Ok extends Result
sealed trait Complete
case object Complete extends Complete
sealed trait SpawnRequest
final case class SpawnSpatial(nodeTask: Task[Node]) extends SpawnRequest
final case class SpawnRequestWrapper(
spawnRequest: SpawnRequest,
result: Deferred[Task, Result]
)
def apply(logger: Logger[Task]) =
for {
spawnChannel <- ConcurrentChannel[Task].of[Complete, SpawnRequestWrapper]
spawnSystem <- Task(new SpawnSystem(logger, spawnChannel))
consumer <-
spawnChannel.consume
.use(consumer => spawnSystem.receive(consumer))
.startAndForget
} yield (spawnSystem)
}
class SpawnSystem(
logger: Logger[Task],
spawnChannel: ConcurrentChannel[
Task,
SpawnSystem.Complete,
SpawnSystem.SpawnRequestWrapper
]
) {
import SpawnSystem._
for {
spawnSystem <- SpawnSystem(logger)
res <- spawnSystem.request(SpawnSpatial(Task(new Node("Test"))))
} yield ()
// val spawnChannel = ConcurrentChannel[Task].of[Result, SpawnRequest]
private def receive(
consumer: ConsumerF[Task, Complete, SpawnRequestWrapper]
): Task[Unit] =
consumer.pull.flatMap {
case Right(message) =>
for {
_ <-
logger
.debug(s"Received spawn request $message")
_ <- handleSpawn(message)
} yield receive(consumer)
case Left(r) =>
logger.info("Closing Spawn System")
}
private def handleSpawn(spawnRequestWrapper: SpawnRequestWrapper) =
spawnRequestWrapper match {
case SpawnRequestWrapper(spawnRequest, result) =>
spawnRequest match {
case SpawnSpatial(spatialTask) =>
spatialTask.flatMap(spatial =>
logger.debug(
s"Spawning spatial with name ${spatial.getName()}"
) >> result
.complete(Ok)
)
}
}
def request(spawnRequest: SpawnRequest) =
for {
d <- Deferred[Task, Result]
_ <- spawnChannel.push(SpawnRequestWrapper(spawnRequest, d))
res <- d.get
} yield (res)
def stop = spawnChannel.halt(Complete)
}

View File

@ -22,12 +22,13 @@ object GameAppActor {
case object Start extends Command
case object Pause extends Command
case object Ping extends Command
case class Stop(stopSignal: ActorRef[CancelableFuture[Unit]]) extends Command
case class GetSpawnProtocol(
final case class Stop(stopSignal: ActorRef[CancelableFuture[Unit]])
extends Command
final case class GetSpawnProtocol(
replyTo: ActorRef[ActorRef[SpawnProtocol.Command]]
) extends Command
case class Props(tickEventBus: GameEventBus[TickEvent]) {
final case class Props(tickEventBus: GameEventBus[TickEvent]) {
def behavior =
Behaviors.setup[Command] { ctx =>
ctx.log.infoP("Hello from GameAppActor")
@ -48,12 +49,12 @@ object GameAppActor {
val sp = ctx.spawn(SpawnProtocol(), "gameSpawnProtocol")
ctx.spawn(
GenericTimerActor
.Props(ctx.self, Ping, 1000.millis)
.behavior,
"pingTimer"
) ! GenericTimerActor.Start
// ctx.spawn(
// GenericTimerActor
// .Props(ctx.self, Ping, 1000.millis)
// .behavior,
// "pingTimer"
// ) ! GenericTimerActor.Start
val stopPromise = CancelablePromise[Unit]()

View File

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

View File

@ -1,7 +1,6 @@
package wow.doge.mygame.game
import scala.collection.immutable.Queue
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import com.jme3.app.SimpleApplication
@ -28,6 +27,7 @@ class SimpleAppExt(
private val startSignal: CancelablePromise[Unit] = CancelablePromise()
private val terminationSignal: CancelablePromise[Unit] = CancelablePromise()
private implicit val ec = schedulers.async.value
var cancelToken: Option[() => Future[Unit]] = None
@ -87,7 +87,7 @@ class SimpleAppExt(
val scheduler = Scheduler(JMEExecutorService)
}
object SimpleAppExt {
private[game] case class MyTask[T](p: CancelablePromise[T], cb: () => T)
private[game] final case class MyTask[T](p: CancelablePromise[T], cb: () => T)
}
// val ship = ed.createEntity()

View File

@ -40,7 +40,7 @@ object TestActor {
// ctx.log.debugP(value.toString())
// Done
// case Failure(exception) =>
// ctx.log.debugP(s"Received Error ${exception.getMessage()}")
// ctx.log.debugP(show"Received Error ${exception.getMessage()}")
// Done
// }
}

View File

@ -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)
}
}
}

View File

@ -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")
}
}
}
}

View File

@ -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
}
}

View File

@ -2,6 +2,7 @@ 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
@ -26,6 +27,7 @@ import wow.doge.mygame.game.subsystems.input.PlayerCameraInput
* @param rotateFn
* @param s
*/
@SuppressWarnings(Array("org.wartremover.warts.Null"))
class CameraMovementControl(
rotationBuf: Quaternion,
obs: Observable[PlayerCameraInput],
@ -49,7 +51,7 @@ class CameraMovementControl(
}
override def controlUpdate(tpf: Float): Unit =
if (_event != null) {
if (_event =!= null) {
_event match {
case PlayerCameraInput.CameraRotateLeft =>
val rot = rotationBuf
@ -75,6 +77,8 @@ class CameraMovementControl(
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)

View File

@ -4,67 +4,68 @@ 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
import wow.doge.mygame.game.entities.CharacterStats.HealHealth
case class CharacterStats(hp: CharacterStats.Health, stamina: Int)
final case class CharacterStats(
hp: CharacterStats.Health,
stamina: CharacterStats.Stamina
)
object CharacterStats {
@newtype case class HealHealth(toInt: Int)
@newtype case class DamageHealth(toInt: Int)
@newtype case class Health(toInt: Int)
@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: Int): Health = Health(h.toInt + v)
// def -(v: Int): Health = Health(h.toInt - v)
// def *(v: Int): Health = Health(h.toInt * v)
// def /(v: Int): Health = Health(h.toInt / v)
def :+(v: HealHealth): Health = Health(h.toInt + v.toInt)
def -(v: DamageHealth): Health = Health(h.toInt - v.toInt)
}
}
@newtype case class HealStamina(toInt: Int)
@newtype case class DamageStamina(toInt: Int)
@newtype case class Stamina(toInt: Int)
@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: Int): Stamina = Stamina(h.toInt + v)
// def -(v: Int): Stamina = Stamina(h.toInt - v)
// def *(v: Int): Stamina = Stamina(h.toInt * v)
// def /(v: Int): Stamina = Stamina(h.toInt / v)
def :+(v: HealStamina): Stamina = Stamina(h.toInt + v.toInt)
def -(v: DamageStamina): Stamina = Stamina(h.toInt - v.toInt)
}
}
// object Stamina {
// implicit class StaminaOps(private val h: Stamina) extends AnyVal {
// def +(v: Health): Stamina = Stamina(h.toInt + v.toInt)
// def -(v: Health): Stamina = Stamina(h.toInt - v.toInt)
// def *(v: Health): Stamina = Stamina(h.toInt * v.toInt)
// def /(v: Health): Stamina = Stamina(h.toInt / v.toInt)
// }
// }
// object Damage {
// implicit class DamageOps(private val h: Damage) extends AnyVal {
// def +(v: Health): Damage = Damage(h.toInt + v.toInt)
// def -(v: Health): Damage = Damage(h.toInt - v.toInt)
// def *(v: Health): Damage = Damage(h.toInt * v.toInt)
// def /(v: Health): Damage = Damage(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
// case class TakeDamage(value: Int) extends Command
case class TakeDamageResult(
final case class TakeDamageResult(
value: CharacterStats.DamageHealth,
replyTo: ActorRef[(Boolean, CharacterStats)]
replyTo: ActorRef[(Status, CharacterStats)]
) extends Command
case class HealResult(value: HealHealth) extends Command
case class CurrentStats(replyTo: ActorRef[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,
@ -74,12 +75,12 @@ object StatsActor {
Behaviors.setup[Command] { ctx =>
new StatsActor(ctx, this)
.receive(
State(CharacterStats(startingHealth, startingStamina.toInt))
State(CharacterStats(startingHealth, startingStamina))
)
}
}
case class State(stats: CharacterStats)
final case class State(stats: CharacterStats)
}
class StatsActor(
ctx: ActorContext[StatsActor.Command],
@ -100,15 +101,36 @@ class StatsActor(
// receive(nextState)
case TakeDamageResult(value, replyTo) =>
val nextState = if ((state.stats.hp - value).toInt <= 0) {
replyTo ! true -> state.stats
state.modify(_.stats.hp).setTo(Health(0))
val s = state.modify(_.stats.hp).setTo(Health(0))
replyTo ! Status.Dead -> s.stats
s
} else {
replyTo ! false -> state.stats
state.modify(_.stats.hp).using(_ - value)
val s = state.modify(_.stats.hp).using(_ - value)
replyTo ! Status.Alive -> s.stats
s
}
receive(nextState)
case HealResult(value) =>
receive(state.modify(_.stats.hp).using(_ :+ value))
case ConsumeStaminaResult(value, replyTo) =>
val nextState = if ((state.stats.stamina - value).toInt <= 0) {
val s = state.modify(_.stats.stamina).setTo(Stamina(0))
replyTo ! Status.Alive -> s.stats
s
} else {
val s = state.modify(_.stats.stamina).using(_ - value)
replyTo ! Status.Alive -> s.stats
s
}
receive(nextState)
case HealHealthResult(value, replyTo) =>
val nextState = receive(state.modify(_.stats.hp).using(_ :+ value))
replyTo ! state.stats
nextState
case HealStaminaResult(value, replyTo) =>
val nextState = receive(state.modify(_.stats.stamina).using(_ :+ value))
replyTo ! state.stats
nextState
case CurrentStats(replyTo) =>
replyTo ! state.stats
Behaviors.same

View File

@ -6,7 +6,6 @@ import scala.util.Success
import akka.actor.typed.ActorRef
import akka.actor.typed.Behavior
import akka.actor.typed.SupervisorStrategy
import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.scaladsl.Behaviors
import akka.util.Timeout
@ -16,17 +15,6 @@ import monix.execution.CancelablePromise
import wow.doge.mygame.game.subsystems.movement.CanMove
import wow.doge.mygame.implicits._
import wow.doge.mygame.math.ImVector3f
import wow.doge.mygame.subsystems.events.EntityMovementEvent
import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedDown
import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedLeft
import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedRight
import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedUp
import wow.doge.mygame.subsystems.events.Event
import wow.doge.mygame.subsystems.events.EventBus
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
import wow.doge.mygame.subsystems.events.TickEvent
import wow.doge.mygame.subsystems.events.TickEvent.RenderTick
import wow.doge.mygame.subsystems.movement.ImMovementActor
import wow.doge.mygame.utils.GenericTimerActor
object NpcActorSupervisor {
@ -39,8 +27,8 @@ object NpcActorSupervisor {
signal: CancelableFuture[NpcMovementActor.DoneMoving.type]
) extends Command
private case object DoneMoving extends Command
private case class LogError(err: Throwable) extends Command
private case class MovementFailed(err: Throwable) extends Command
private final case class LogError(err: Throwable) extends Command
private final case class MovementFailed(err: Throwable) extends Command
class Props(
val npcMovementActorBehavior: Behavior[NpcMovementActor.Command],
@ -84,9 +72,9 @@ class NpcActorSupervisor(
s"npc-${props.npcName}-NpcActorTimer"
)
def idle(state: State): Behavior[NpcActorSupervisor.Command] =
def idle(state: State): Behavior[Command] =
Behaviors.setup { _ =>
ctx.log.debugP(s"npcActor-${props.npcName}: Entered Idle State")
ctx.log.debugP(show"npcActor-${props.npcName}: Entered Idle State")
Behaviors.receiveMessage[Command] {
case m @ Move(pos) =>
ctx.ask(
@ -111,9 +99,9 @@ class NpcActorSupervisor(
state: State,
targetPos: ImVector3f,
signal: CancelableFuture[NpcMovementActor.DoneMoving.type]
): Behavior[NpcActorSupervisor.Command] =
): Behavior[Command] =
Behaviors.setup { _ =>
ctx.log.debugP(s"npcActor-${props.npcName}: Entered Moving State")
ctx.log.debugP(show"npcActor-${props.npcName}: Entered Moving State")
movementTimer ! GenericTimerActor.Start
ctx.pipeToSelf(signal) {
case Success(value) => DoneMoving
@ -150,7 +138,7 @@ class NpcActorSupervisor(
}
def logError(err: Throwable) =
ctx.log.errorP(s"npcActor-${props.npcName}: " + err.getMessage)
ctx.log.errorP(show"npcActor-${props.npcName}: " + err.getMessage)
}
object NpcMovementActor {
@ -158,10 +146,10 @@ object NpcMovementActor {
case object DoneMoving
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 StopMoving extends Command
case class MoveTo(
final case class MoveTo(
target: ImVector3f,
doneSignal: ActorRef[CancelableFuture[DoneMoving.type]]
) extends Command
@ -212,7 +200,7 @@ class NpcMovementActor[T](
Behaviors.receiveMessagePartial {
case StopMoving =>
ctx.log.debugP(
show"npcActor-${props.npcName}: Position at Stop = " + location
show"npcActor-${props.npcName}: Position at Stop = $location"
)
props.enqueueR(() => cm.stop(props.movable))
receive(state)
@ -235,76 +223,3 @@ class NpcMovementActor[T](
Behaviors.same
}
}
object NpcMovementActorNotUsed {
sealed trait Command
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.withLimit(2, 100.millis)
),
s"npc-${npcName}-MovementActorChild"
)
val npcMovementElBehavior = Behaviors.receiveMessagePartial[Event] {
case event: EntityMovementEvent =>
event match {
case MovedLeft(name, pressed) =>
if (name == npcName)
movementActor ! ImMovementActor.MoveLeft(pressed)
Behaviors.same
case MovedUp(name, pressed) =>
if (name == npcName)
movementActor ! ImMovementActor.MoveUp(pressed)
Behaviors.same
case MovedRight(name, pressed) =>
if (name == npcName)
movementActor ! ImMovementActor.MoveRight(pressed)
Behaviors.same
case MovedDown(name, pressed) =>
if (name == npcName)
movementActor ! ImMovementActor.MoveDown(pressed)
Behaviors.same
}
}
val npcMovementEl = ctx.spawn(
Behaviors
.supervise(npcMovementElBehavior)
.onFailure[Exception](
SupervisorStrategy.restart.withLimit(2, 100.millis)
),
s"npc-${npcName}-MovementEventHandler"
)
val renderTickElBehavior =
Behaviors.receiveMessage[RenderTick.type] {
case RenderTick =>
movementActor ! ImMovementActor.Tick
Behaviors.same
}
val renderTickEl =
ctx.spawn(
renderTickElBehavior,
s"npc-${npcName}-MovementTickListener"
)
mainEventBus ! EventBus.Subscribe(npcMovementEl)
tickEventBus ! EventBus.Subscribe(renderTickEl)
Behaviors.receiveMessage(msg => Behaviors.same)
}
}
}

View File

@ -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]
}
}

View File

@ -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
}
}

View File

@ -1,268 +0,0 @@
package wow.doge.mygame.game.entities
import scala.concurrent.duration._
import scala.util.Failure
import scala.util.Success
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.reactive.Observable
import monix.reactive.OverflowStrategy
import monix.reactive.subjects.ConcurrentSubject
import org.slf4j.event.Level
import wow.doge.mygame.Dispatchers
import wow.doge.mygame.executors.Schedulers.AsyncScheduler
import wow.doge.mygame.game.entities.StatsActor
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 monix.eval.Coeval
import monix.execution.AsyncQueue
object PlayerActorSupervisor {
type Ref = ActorRef[PlayerActorSupervisor.Command]
sealed trait Status
object Status {
case object Alive extends Status
case object Dead extends Status
}
sealed trait Command
case class TakeDamage(value: CharacterStats.DamageHealth) extends Command
case class TakeDamage2(
value: CharacterStats.DamageHealth,
replyTo: ActorRef[Unit]
) extends Command
case class Heal(value: CharacterStats.HealHealth) extends Command
case class CurrentStats(replyTo: ActorRef[CharacterStats]) extends Command
case class GetStatus(replyTo: ActorRef[Status]) extends Command
case class GetStatsObservable(replyTo: ActorRef[Observable[CharacterStats]])
extends Command
case class GetStatsObservable2(replyTo: ActorRef[Observable[CharacterStats]])
extends Command
private case object Die extends Command
private case class DamageResponse(response: (Boolean, CharacterStats))
extends Command
private case class DamageResponse2(
response: (Boolean, CharacterStats),
replyTo: ActorRef[Unit]
) extends Command
// private case class InternalTakeDamage(old: Int, value: Int) extends Command
private case class LogError(ex: Throwable) extends Command
class Props(
val playerEventBus: GameEventBus[PlayerEvent],
val tickEventBus: GameEventBus[TickEvent],
val imMovementActorBehavior: Behavior[ImMovementActor.Command],
val scheduler: AsyncScheduler
) {
def behavior =
Behaviors.logMessages(
LogOptions()
.withLevel(Level.DEBUG)
.withLogger(
Logger[PlayerActorSupervisor].underlying
),
Behaviors
.setup[Command] { ctx =>
ctx.log.infoP("Starting PlayerActor")
// spawn children actors
val playerMovementActor =
ctx.spawnN(
Behaviors
.supervise(imMovementActorBehavior)
.onFailure[Exception](
SupervisorStrategy.restart.withLimit(2, 100.millis)
),
Dispatchers.jmeDispatcher
)
val playerStatsActor =
ctx.spawnN(
new StatsActor.Props(
CharacterStats.Health(100),
CharacterStats.Stamina(100)
).behavior
)
val playerMovementEl = ctx.spawnN(
Behaviors
.supervise(PlayerMovementEventListener(playerMovementActor))
.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 PlayerActorSupervisor(
ctx,
this,
Children(playerMovementActor, playerStatsActor),
ConcurrentSubject.publish(
OverflowStrategy.DropOldAndSignal(
50,
dropped => Coeval.pure(None)
)
)(scheduler.value),
AsyncQueue.bounded(10)(scheduler.value)
).aliveState
}
)
}
case class Children(
movementActor: ActorRef[ImMovementActor.Command],
statsActor: ActorRef[StatsActor.Command]
)
}
class PlayerActorSupervisor(
ctx: ActorContext[PlayerActorSupervisor.Command],
props: PlayerActorSupervisor.Props,
children: PlayerActorSupervisor.Children,
statsSubject: ConcurrentSubject[CharacterStats, CharacterStats],
statsQueue: AsyncQueue[CharacterStats]
) {
import PlayerActorSupervisor._
implicit val timeout = Timeout(1.second)
val aliveState =
Behaviors
.receiveMessage[Command] {
case TakeDamage(value) =>
// children.movementActor ! ImMovementActor.MovedDown(true)
// ctx.ask(children.statsActor, StatsActor.CurrentStats(_)) {
// case Success(status) => InternalTakeDamage(status.hp, value)
// case Failure(ex) => LogError(ex)
// }
ctx.ask(children.statsActor, StatsActor.TakeDamageResult(value, _)) {
case Success(response) => DamageResponse(response)
case Failure(ex) => LogError(ex)
}
Behaviors.same
case TakeDamage2(value, replyTo) =>
// children.movementActor ! ImMovementActor.MovedDown(true)
// ctx.ask(children.statsActor, StatsActor.CurrentStats(_)) {
// case Success(status) => InternalTakeDamage(status.hp, value)
// case Failure(ex) => LogError(ex)
// }
ctx.ask(children.statsActor, StatsActor.TakeDamageResult(value, _)) {
case Success(response) => DamageResponse2(response, replyTo)
case Failure(ex) => LogError(ex)
}
Behaviors.same
case CurrentStats(replyTo) =>
// ctx.ask(children.statsActor, StatsActor.CurrentStats())
children.statsActor ! StatsActor.CurrentStats(replyTo)
Behaviors.same
case Heal(value) =>
children.statsActor ! StatsActor.HealResult(value)
Behaviors.same
case GetStatus(replyTo) =>
replyTo ! Status.Alive
Behaviors.same
// case _ => Behaviors.unhandled
// case InternalTakeDamage(hp, damage) =>
// if (hp - damage <= 0) dead
// else {
// children.statsActor ! StatsActor.TakeDamage(damage)
// Behaviors.same
// }
case GetStatsObservable(replyTo) =>
replyTo ! statsSubject
Behaviors.same
case GetStatsObservable2(replyTo) =>
import monix.{eval => me}
replyTo ! Observable.repeatEvalF(
me.Task.deferFuture(statsQueue.poll())
)
Behaviors.same
case DamageResponse(response) =>
response match {
case (dead, state) =>
if (dead) ctx.self ! Die
statsSubject.onNext(state)
}
Behaviors.same
case DamageResponse2(response, replyTo) =>
response match {
case (dead, stats) =>
if (dead) ctx.self ! Die
statsQueue
.offer(stats)
.foreach(_ => replyTo ! ())(props.scheduler.value)
}
Behaviors.same
case Die => deadState
case LogError(ex) =>
ctx.log.error(ex.getMessage)
Behaviors.same
}
.receiveSignal {
case (_, PostStop) =>
ctx.log.infoP("stopped")
statsSubject.onComplete()
Behaviors.same
}
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")
statsSubject.onComplete()
Behaviors.same
}
}

View File

@ -1,149 +0,0 @@
package wow.doge.mygame.game.entities.player
import scala.concurrent.duration._
import akka.actor.typed.ActorRef
import akka.actor.typed.Behavior
import akka.actor.typed.LogOptions
import akka.actor.typed.PostStop
import akka.actor.typed.SupervisorStrategy
import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.scaladsl.Behaviors
import com.typesafe.scalalogging.Logger
import org.slf4j.event.Level
import wow.doge.mygame.Dispatchers
import wow.doge.mygame.game.entities.PlayerCameraActor
import wow.doge.mygame.game.entities.PlayerCameraEventListener
import wow.doge.mygame.game.entities.PlayerMovementEventListener
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
object PlayerActorSupervisor2 {
sealed trait Command
case object Tick extends Command
sealed trait Movement extends Command
final case class MoveLeft(pressed: Boolean) extends Movement
final case class MoveUp(pressed: Boolean) extends Movement
final case class MoveRight(pressed: Boolean) extends Movement
final case class MoveDown(pressed: Boolean) extends Movement
case object Jump extends Movement
sealed trait Camera
case object RotateLeft extends Camera
case object RotateRight extends Camera
case object RotateUp extends Camera
case object RotateDown extends Camera
class Props(
val playerEventBus: GameEventBus[PlayerEvent],
val tickEventBus: GameEventBus[TickEvent],
val imMovementActorBehavior: Behavior[ImMovementActor.Command],
val playerCameraActorBehavior: Behavior[PlayerCameraActor.Command]
) {
def behavior =
Behaviors.logMessages(
LogOptions()
.withLevel(Level.TRACE)
.withLogger(
Logger[PlayerActorSupervisor2].underlying
),
Behaviors
.setup[Command] { ctx =>
ctx.log.infoP("Starting PlayerActor")
// spawn children actors
val movementActor =
ctx.spawn(
Behaviors
.supervise(imMovementActorBehavior)
.onFailure[Exception](
SupervisorStrategy.restart.withLimit(2, 100.millis)
),
"playerMovementActor",
Dispatchers.jmeDispatcher
)
val playerCameraActor =
ctx.spawn(
playerCameraActorBehavior,
"playerCameraActor",
Dispatchers.jmeDispatcher
)
val playerCameraEl = ctx.spawn(
PlayerCameraEventListener(playerCameraActor),
"playerCameraActorEl"
)
val playerMovementEl = ctx.spawn(
Behaviors
.supervise(PlayerMovementEventListener(movementActor))
.onFailure[Exception](
SupervisorStrategy.restart.withLimit(2, 100.millis)
),
"playerMovementEventHandler"
)
val renderTickEl = {
val behavior: Behavior[RenderTick.type] =
Behaviors.setup(ctx =>
Behaviors
.receiveMessage[RenderTick.type] {
case RenderTick =>
movementActor ! 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)
playerEventBus ! EventBus.Subscribe(playerCameraEl)
new PlayerActorSupervisor2(
ctx,
this,
Children(movementActor)
).receive
}
)
}
case class Children(
movementActor: ActorRef[ImMovementActor.Command]
)
}
class PlayerActorSupervisor2(
ctx: ActorContext[PlayerActorSupervisor2.Command],
props: PlayerActorSupervisor2.Props,
children: PlayerActorSupervisor2.Children
) {
import PlayerActorSupervisor2._
def receive =
Behaviors
.receiveMessage[Command] {
case m @ MoveDown(pressed) =>
// children.movementActor ! m
Behaviors.same
case _ =>
// children.movementActor ! ImMovementActor.MovedDown(true)
Behaviors.same
}
.receiveSignal {
case (_, PostStop) =>
ctx.log.infoP("stopped")
Behaviors.same
}
}

View File

@ -37,7 +37,7 @@ object PlayerCameraActor {
)
}
case class State()
final case class State()
object State {
val empty = State()
}

View File

@ -1,4 +1,4 @@
package wow.doge.mygame.game.entities
package wow.doge.mygame.game.entities.player
import akka.actor.typed.ActorRef
import akka.actor.typed.DispatcherSelector
@ -13,9 +13,13 @@ import com.softwaremill.tagging._
import io.odin.Logger
import monix.bio.IO
import monix.bio.Task
import monix.execution.AsyncQueue
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.math.ImVector3f
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
@ -27,7 +31,8 @@ import wow.doge.mygame.utils.wrappers.jme._
object PlayerController {
sealed trait Error
case class CouldNotCreatePlayerModel(reason: AssetManager.Error) extends Error
final case class CouldNotCreatePlayerModel(reason: AssetManager.Error)
extends Error
object Tags {
sealed trait PlayerNode
@ -49,8 +54,8 @@ object PlayerController {
initialPlayerPos: ImVector3f,
playerEventBus: GameEventBus[PlayerEvent],
playerPhysicsControl: BetterCharacterControl,
// appScheduler: monix.execution.Scheduler,
scheduler: AsyncScheduler,
fxScheduler: Schedulers.FxScheduler,
playerNode: PlayerNode,
cameraNode: PlayerCameraNode,
cameraPivotNode: PlayerCameraPivotNode,
@ -62,20 +67,26 @@ object PlayerController {
enqueueR,
camera
).behavior(playerPhysicsControl)
new PlayerActorSupervisor.Props(
val statsActorBeh = new StatsActor.Props(
CharacterStats.Health(100),
CharacterStats.Stamina(100)
).behavior
new PlayerActor.Props(
playerEventBus,
tickEventBus,
movementActorBeh,
scheduler
statsActorBeh,
scheduler,
fxScheduler,
AsyncQueue.bounded(50)(scheduler.value)
).behavior
}
val playerActor =
gameApp.spawnGameActor(
playerActorBehavior,
// Some("playerActorSupervisor"),
props = DispatcherSelector.default()
)
val create: IO[AppError, ActorRef[PlayerActorSupervisor.Command]] =
val create: IO[AppError, ActorRef[PlayerActor.Command]] =
(for {
// playerActor <-
// // AkkaUtils.spawnActorL(playerActorBehavior, "playerActorSupervisor")

View File

@ -1,39 +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.scaladsl.ActorContext
import akka.actor.typed.scaladsl.AskPattern._
import akka.actor.typed.scaladsl.Behaviors
import akka.util.Timeout
import cats.syntax.eq._
import com.typesafe.scalalogging.Logger
import monix.eval.Task
import monix.execution.CancelableFuture
import monix.reactive.Observable
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.movement.ImMovementActor
import wow.doge.mygame.utils.MovementDirection
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._
def apply(movementActor: ActorRef[ImMovementActor.Command]) =
import com.softwaremill.quicklens._
def receive(state: State): Behavior[PlayerMovementEvent] =
Behaviors.logMessages(
LogOptions()
.withLevel(Level.TRACE)
.withLogger(
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 {
case PlayerMovedLeft(pressed) =>
movementActor ! ImMovementActor.MoveLeft(pressed)
Behaviors.same
// props.movementActor ! ImMovementActor.MoveLeft(pressed)
// props.playerActor ! PlayerActor.MoveLeft(pressed)
props.playerActor ! PlayerActor.Walk(
pressed,
MovementDirection.Left
)
receive(handleStamina(pressed))
case PlayerMovedRight(pressed) =>
movementActor ! ImMovementActor.MoveRight(pressed)
Behaviors.same
// props.playerActor ! PlayerActor.MoveRight(pressed)
props.playerActor ! PlayerActor.Walk(
pressed,
MovementDirection.Right
)
receive(handleStamina(pressed))
case PlayerMovedForward(pressed) =>
movementActor ! ImMovementActor.MoveUp(pressed)
Behaviors.same
// props.playerActor ! PlayerActor.MoveUp(pressed)
props.playerActor ! PlayerActor.Walk(
pressed,
MovementDirection.Forward
)
receive(handleStamina(pressed))
case PlayerMovedBackward(pressed) =>
movementActor ! ImMovementActor.MoveDown(pressed)
Behaviors.same
// props.playerActor ! PlayerActor.MoveDown(pressed)
props.playerActor ! PlayerActor.Walk(
pressed,
MovementDirection.Backward
)
receive(handleStamina(pressed))
case PlayerJumped =>
movementActor ! ImMovementActor.Jump
props.playerActor ! PlayerActor.Jump
// props.playerActor
// ctx.ask(
// props.playerActor,
// PlayerActor
// .ConsumeStamina(CharacterStats.DamageStamina(1), _)
// )(_ => ())
Behaviors.same
// case PlayerTurnedRight =>
// movementActor ! ImMovementActor.RotateRight
@ -42,35 +170,6 @@ object PlayerMovementEventListener {
// movementActor ! ImMovementActor.RotateLeft
// Behaviors.same
}
)
)
}
//not used
object PlayerCameraEventListener {
import PlayerCameraEvent._
def apply(playerCameraActor: ActorRef[PlayerCameraActor.Command]) =
Behaviors.logMessages(
LogOptions()
.withLevel(Level.TRACE)
.withLogger(
Logger[PlayerCameraEventListener.type].underlying
),
Behaviors.setup[PlayerCameraEvent](ctx =>
Behaviors.receiveMessagePartial {
case CameraMovedUp =>
playerCameraActor ! PlayerCameraActor.RotateUp
Behaviors.same
case CameraMovedDown =>
playerCameraActor ! PlayerCameraActor.RotateDown
Behaviors.same
case CameraLeft =>
playerCameraActor ! PlayerCameraActor.RotateLeft
Behaviors.same
case CameraRight =>
playerCameraActor ! PlayerCameraActor.RotateRight
Behaviors.same
}
)
)
}

View File

@ -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)
)
}
}

View File

@ -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])

View File

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

View File

@ -18,6 +18,7 @@ import wow.doge.mygame.subsystems.events.PlayerCameraEvent
import wow.doge.mygame.subsystems.events.PlayerEvent
import wow.doge.mygame.subsystems.events.PlayerMovementEvent
import wow.doge.mygame.utils.IOUtils._
import wow.doge.mygame.utils.methodName
object GameInputHandler {
@ -29,16 +30,16 @@ object GameInputHandler {
) {
def begin =
for {
_ <- UIO(setupMovementKeys(inputManager))
_ <- UIO(setupMovementKeys)
// _ <- UIO(setupAnalogMovementKeys)
_ <- UIO(setupCameraKeys())
_ <- toIO(
me.Task.parSequence(
Seq(
playerMovementInputEventsGenerator(
inputManager,
playerEventBus
).completedL,
// playerMovementInputEventsGenerator(
// inputManager,
// playerEventBus
// ).completedL,
// generateAnalogMovementEvents(
// inputManager,
// playerEventBus
@ -55,7 +56,7 @@ object GameInputHandler {
).startAndForget
} yield ()
def setupMovementKeys(inputManager: InputManager) =
def setupMovementKeys =
inputManager.withEnumMappings(PlayerMovementInput) {
case PlayerMovementInput.WalkRight =>
new KeyTrigger(KeyInput.KEY_D) :: Nil
@ -128,9 +129,6 @@ object GameInputHandler {
}
def methodName(implicit enclosing: sourcecode.Enclosing) =
enclosing.value.split(" ")(0).split("""\.""").last
def playerMovementInputEventsGenerator(
inputManager: InputManager,
playerEventBus: GameEventBus[PlayerEvent]
@ -144,28 +142,28 @@ object GameInputHandler {
case PlayerMovementInput.WalkLeft =>
me.Task(
playerEventBus ! EventBus.Publish(
PlayerMovementEvent.PlayerMovedLeft(pressed = action.value),
PlayerMovementEvent.PlayerMovedLeft(action.value),
name
)
)
case PlayerMovementInput.WalkRight =>
me.Task(
playerEventBus ! EventBus.Publish(
PlayerMovementEvent.PlayerMovedRight(pressed = action.value),
PlayerMovementEvent.PlayerMovedRight(action.value),
name
)
)
case PlayerMovementInput.WalkForward =>
me.Task(
playerEventBus ! EventBus.Publish(
PlayerMovementEvent.PlayerMovedForward(pressed = action.value),
PlayerMovementEvent.PlayerMovedForward(action.value),
name
)
)
case PlayerMovementInput.WalkBackward =>
me.Task(
playerEventBus ! EventBus.Publish(
PlayerMovementEvent.PlayerMovedBackward(pressed = action.value),
PlayerMovementEvent.PlayerMovedBackward(action.value),
name
)
)

View File

@ -1,6 +1,9 @@
package wow.doge.mygame.game.subsystems.input
import cats.Show
import cats.kernel.Eq
import enumeratum.EnumEntry._
import enumeratum._
import io.circe.generic.semiauto._
sealed trait PlayerMovementInput extends EnumEntry with UpperSnakecase
object PlayerMovementInput extends Enum[PlayerMovementInput] {
@ -10,6 +13,9 @@ object PlayerMovementInput extends Enum[PlayerMovementInput] {
case object WalkLeft extends PlayerMovementInput
case object WalkBackward 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
@ -17,6 +23,9 @@ object PlayerAnalogMovementInput extends Enum[PlayerAnalogMovementInput] {
val values = findValues
case object TurnRight 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
@ -26,6 +35,10 @@ object PlayerCameraInput extends Enum[PlayerCameraInput] {
case object CameraRotateRight extends PlayerCameraInput
case object CameraRotateUp 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

View 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
}

View File

@ -1,55 +1,14 @@
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.DirectionalLight
import com.jme3.math.ColorRGBA
import com.jme3.math.Vector3f
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 {
def apply(
assetManager: AssetManager,
viewPort: ViewPort
) = {
val sceneModel: Spatial = assetManager.loadModel("main.scene")
val sceneShape = CollisionShapeFactory.createMeshShape(
sceneModel.toNode match {
case Right(node) => node
case Left(ex) =>
throw new NotImplementedError("No fallback sceneshape")
}
)
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
)
}
def apply(
assetManager: wow.doge.mygame.utils.wrappers.jme.AssetManager,
viewPort: ViewPort

View File

@ -15,6 +15,8 @@ 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(
val model: Spatial,
val physicsControl: RigidBodyControl,

View File

@ -12,11 +12,14 @@ import wow.doge.mygame.game.subsystems.movement.CanMove
import wow.doge.mygame.implicits._
final case class CardinalDirection(
left: Boolean = false,
right: Boolean = false,
up: Boolean = false,
down: Boolean = false
left: Boolean,
right: Boolean,
up: Boolean,
down: Boolean
)
object CardinalDirection {
val default = CardinalDirection(false, false, false, false)
}
sealed trait RotateDir
object RotateDir {
@ -33,6 +36,7 @@ object ImMovementActor {
final case class MoveUp(pressed: Boolean) extends Movement
final case class MoveRight(pressed: Boolean) extends Movement
final case class MoveDown(pressed: Boolean) extends Movement
case object StopMoving extends Movement
case object Jump extends Movement
class Props(
@ -53,7 +57,9 @@ object ImMovementActor {
*
* @param cardinalDir The four directions the character can move
*/
final case class State(cardinalDir: CardinalDirection = CardinalDirection())
final case class State(
cardinalDir: CardinalDirection = CardinalDirection.default
)
}
@ -72,28 +78,32 @@ class ImMovementActor[T](
.receiveMessage[Command] {
case m: Movement =>
m match {
case StopMoving =>
cm.stop(movable)
receive(State(CardinalDirection.default), new Vector3f)
case MoveLeft(pressed) =>
stopIfNotPressed(pressed)
receive(
state = state.modify(_.cardinalDir.left).setTo(pressed),
state.modify(_.cardinalDir.left).setTo(pressed),
walkDirBuf
)
case MoveUp(pressed) =>
stopIfNotPressed(pressed)
receive(
state = state.modify(_.cardinalDir.up).setTo(pressed),
state.modify(_.cardinalDir.up).setTo(pressed),
walkDirBuf
)
case MoveRight(pressed) =>
stopIfNotPressed(pressed)
receive(
state = state.modify(_.cardinalDir.right).setTo(pressed),
state.modify(_.cardinalDir.right).setTo(pressed),
walkDirBuf
)
case MoveDown(pressed) =>
stopIfNotPressed(pressed)
receive(
state = state.modify(_.cardinalDir.down).setTo(pressed),
state.modify(_.cardinalDir.down).setTo(pressed),
walkDirBuf
)
case Jump =>
@ -188,7 +198,7 @@ object MovementActor {
* @param walkDirection scratch space to avoid allocations on every tick. Do not share outside the actor
*/
final case class State(
cardinalDir: CardinalDirection = CardinalDirection(),
cardinalDir: CardinalDirection = CardinalDirection.default,
walkDirection: Vector3f = Vector3f.UNIT_X
)

View File

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

View File

@ -1,30 +1,75 @@
package wow.doge.mygame.implicits
import javafx.beans.value.ObservableValue
import javafx.beans.{value => jfxbv}
import javafx.beans.property.ObjectProperty
import javafx.collections.ObservableList
import javafx.event.ActionEvent
import javafx.event.EventHandler
import javafx.scene.{input => jfxsi}
import javafx.{event => jfxe}
import monix.bio.Task
import monix.eval.Coeval
import monix.execution.Ack
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.Observer
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.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 {
implicit final class SceneObservables(private val scene: Scene)
extends AnyVal {
final class SceneExt(private val scene: Scene) extends AnyVal {
def observableMousePressed(): Observable[jfxsi.MouseEvent] = {
import monix.execution.cancelables.SingleAssignCancelable
Observable.create(OverflowStrategy.Unbounded) { sub =>
val c = SingleAssignCancelable()
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()
}
}
scene.onMousePressed = l
c := Cancelable(() =>
@ -36,17 +81,17 @@ object JavaFXMonixObservables {
c
}
}
def observableMouseDragged(): Observable[jfxsi.MouseEvent] = {
import monix.execution.cancelables.SingleAssignCancelable
Observable.create(OverflowStrategy.Unbounded) { sub =>
val c = SingleAssignCancelable()
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()
}
}
scene.onMouseDragged = l
scene.onMouseDragged = l;
c := Cancelable(() =>
scene.removeEventHandler(
jfxsi.MouseEvent.MOUSE_DRAGGED,
@ -58,47 +103,225 @@ object JavaFXMonixObservables {
}
}
implicit final class BindObs[A, B](private val prop: Property[A, B])
final class PropertyExt[T, J](private val prop: Property[T, J])
extends AnyVal {
def <--[T](op: Observable[(ObservableValue[_ <: B], B, B)] => T) = {
op(prop.observableChange())
}
def -->[J1 >: J](sub: Observer[J1]) =
prop.onChange((a, b, c) => if (c != null) sub.onNext(c))
def observableChange(): Observable[(ObservableValue[_ <: B], B, B)] = {
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
Observable.create(OverflowStrategy.Unbounded) { sub =>
val c = SingleAssignCancelable()
val listener = new jfxbv.ChangeListener[B] {
override def changed(
observable: ObservableValue[_ <: B],
oldValue: B,
newValue: B
): Unit = {
sub.onNext((observable, oldValue, newValue))
}
}
val canc =
prop.onChange((a, b, c1) =>
if (c1 != null && sub.onNext(c1) == Ack.Stop) c.cancel()
)
prop.addListener(listener)
c := Cancelable(() => prop.removeListener(listener))
c := Cancelable(() => canc.cancel())
c
}
}
}
implicit final class OnActionObservable(
private val button: ButtonBase
) extends AnyVal {
def observableAction(): Observable[jfxe.ActionEvent] = {
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 l = new jfxe.EventHandler[jfxe.ActionEvent] {
override def handle(event: jfxe.ActionEvent): Unit = {
if (sub.onNext(event) == Ack.Stop) c.cancel()
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
c := Cancelable(() =>
@ -111,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
}
}
}
}

View File

@ -9,6 +9,7 @@ import akka.actor.typed.Props
import akka.actor.typed.Scheduler
import akka.actor.typed.scaladsl.ActorContext
import akka.util.Timeout
import cats.Show
import com.jayfella.jme.jfx.JavaFxUI
import com.jme3.app.Application
import com.jme3.app.SimpleApplication
@ -61,7 +62,6 @@ import monix.reactive.OverflowStrategy
import monix.reactive.observers.Subscriber
import org.slf4j.Logger
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)
@ -87,7 +87,7 @@ final case class CollisionEvent(
appliedImpulse: Function0[Float]
)
package object implicits {
package object implicits extends JavaFXMonixObservables {
type PrePhysicsTickEvent = PhysicsTickEvent
type PhysicsTickObservable =
Observable[Either[PrePhysicsTickEvent, PhysicsTickEvent]]
@ -102,6 +102,12 @@ package object implicits {
}
implicit final class StateManagerExt(private val asm: AppStateManager)
extends AnyVal {
@SuppressWarnings(
Array(
"org.wartremover.warts.AsInstanceOf",
"org.wartremover.warts.Equals"
)
)
def state[S <: AppState]()(implicit c: ClassTag[S]): S =
asm.getState(c.runtimeClass.asInstanceOf[Class[S]])
@ -121,26 +127,6 @@ package object implicits {
def viewPort = sa.getViewPort()
def rootNode = sa.getRootNode()
def observableTick: Observable[Float] =
Observable.create(OverflowStrategy.Unbounded) { sub =>
val c = SingleAssignCancelable()
val as = new MyBaseState {
override def init(): Unit = {}
override def update(tpf: Float) =
if (sub.onNext(tpf) == Ack.Stop)
c.cancel()
override def stop(): Unit = {}
override def onEnable() = {}
override def onDisable() = {}
}
sa.stateManager.attach(as)
c := Cancelable(() => sa.stateManager.detach(as))
c
}
}
implicit final class AssetManagerExt(private val am: AssetManager)
@ -493,6 +479,7 @@ package object implicits {
* @see [[ActionEvent]]
* @see [[enumObservableAction]]
*/
@SuppressWarnings(Array("org.wartremover.warts.Equals"))
def observableAction(mappingNames: String*): Observable[ActionEvent] =
Observable.create(OverflowStrategy.DropOld(10)) { sub =>
val c = SingleAssignCancelable()
@ -537,6 +524,7 @@ package object implicits {
* @see [[EnumActionEvent]]
* @see [[enumAnalogObservable]]
*/
@SuppressWarnings(Array("org.wartremover.warts.Equals"))
def enumObservableAction[T <: EnumEntry](
mappingEnum: Enum[T]
): Observable[EnumActionEvent[T]] =
@ -563,6 +551,7 @@ package object implicits {
c
}
@SuppressWarnings(Array("org.wartremover.warts.Equals"))
def enumEntryObservableAction[T <: EnumEntry](
mappingEnumEntry: T
): Observable[EnumActionEvent[T]] =
@ -590,6 +579,7 @@ package object implicits {
c
}
@SuppressWarnings(Array("org.wartremover.warts.Equals"))
def analogObservable(mappingNames: String*): Observable[AnalogEvent] =
Observable.create(OverflowStrategy.DropOld(50)) { sub =>
val c = SingleAssignCancelable()
@ -613,6 +603,7 @@ package object implicits {
c
}
@SuppressWarnings(Array("org.wartremover.warts.Equals"))
def enumAnalogObservable[T <: EnumEntry](
mappingEnum: Enum[T]
): Observable[EnumAnalogEvent[T]] =
@ -643,6 +634,7 @@ package object implicits {
implicit final class PhysicsSpaceExt(private val space: jmeb.PhysicsSpace)
extends AnyVal {
@SuppressWarnings(Array("org.wartremover.warts.Equals"))
def collisionObservable(): Observable[CollisionEvent] =
Observable.create(OverflowStrategy.DropOld(50)) { sub =>
val c = SingleAssignCancelable()
@ -670,6 +662,7 @@ package object implicits {
c
}
@SuppressWarnings(Array("org.wartremover.warts.Equals"))
def prePhysicsTickObservable(): Observable[PrePhysicsTickEvent] =
Observable.create(OverflowStrategy.DropOld(50)) { sub =>
val c = SingleAssignCancelable()
@ -699,6 +692,7 @@ package object implicits {
c
}
@SuppressWarnings(Array("org.wartremover.warts.Equals"))
def physicsTickObservable(): Observable[PhysicsTickEvent] =
Observable.create(OverflowStrategy.DropOld(50)) { sub =>
val c = SingleAssignCancelable()
@ -905,4 +899,6 @@ package object implicits {
) extends AnyVal {
def toIO = coeval.to[Task].hideErrors.rethrow
}
implicit val showForOsPath = Show.fromToString[os.Path]
}

View File

@ -16,7 +16,7 @@ import scalafx.application.JFXApp.PrimaryStage
import scalafx.scene.control.Button
import scalafx.stage.StageStyle
import wow.doge.mygame.executors.Schedulers.FxScheduler
import wow.doge.mygame.implicits.JavaFXMonixObservables._
import wow.doge.mygame.implicits._
import wow.doge.mygame.utils.IOUtils._
object Launcher {
sealed trait LauncherResult
@ -43,8 +43,7 @@ class Launcher private (props: Launcher.Props) {
}
private lazy val launchAction =
launchButton
.observableAction()
launchButton.observableAction
.doOnNext(_ => toTask(props.signal.complete(LauncherResult.LaunchGame)))
private lazy val exitButton = new Button {
@ -52,8 +51,7 @@ class Launcher private (props: Launcher.Props) {
}
private lazy val exitAction =
exitButton
.observableAction()
exitButton.observableAction
.doOnNext(_ => toTask(props.signal.complete(LauncherResult.Exit)))
private lazy val _scene =

View File

@ -6,7 +6,7 @@ import cats.syntax.eq._
import math.{abs, pow, sqrt}
case class ImVector3f(x: Float, y: Float, z: Float)
final case class ImVector3f(x: Float, y: Float, z: Float)
object ImVector3f {
//format: off
val Zero = ImVector3f(0, 0, 0)

View File

@ -11,6 +11,7 @@ import akka.actor.typed.scaladsl.AskPattern._
import akka.actor.typed.scaladsl.Behaviors
import akka.event.EventStream
import akka.util.Timeout
import cats.syntax.show._
import monix.bio.UIO
import monix.execution.Ack
import monix.execution.Cancelable
@ -58,6 +59,7 @@ object EventBus {
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,
@ -70,7 +72,7 @@ object EventBus {
val c = SingleAssignCancelable()
val behavior = Behaviors
.receive[B] { (ctx, msg) =>
ctx.log.traceP(s"Emitted $msg")
ctx.log.traceP(s"Emitted ${msg.toString}")
if (sub.onNext(msg) == Ack.Stop) {
c.cancel()
Behaviors.stopped
@ -86,7 +88,7 @@ object EventBus {
.spawnActorL(
behavior,
actorName =
Some(s"eventBusObservable-${ct.toString.split("""\.""").last}")
Some(show"eventBusObservable-${ct.toString.split("""\.""").last}")
)
.mapError(err => new Throwable(err.toString))
.tapError {

View File

@ -1,5 +1,6 @@
package wow.doge.mygame.subsystems.events
import cats.Show
import wow.doge.mygame.game.entities.CharacterStats
sealed trait Event
@ -27,9 +28,11 @@ object EntityMovementEvent {
sealed trait StatsEvent extends Event
object StatsEvent {
case class DamageEvent(
final case class DamageEvent(
hitBy: String,
victimName: String,
amount: CharacterStats.DamageHealth
) extends StatsEvent
implicit val show = Show.fromToString[StatsEvent]
}

View File

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

View File

@ -1,5 +1,7 @@
package wow.doge.mygame.subsystems.events
import cats.Show
sealed trait PlayerEvent
sealed trait PlayerMovementEvent extends PlayerEvent
@ -14,6 +16,7 @@ object PlayerMovementEvent {
case object PlayerJumped extends PlayerMovementEvent
// case object PlayerTurnedRight extends PlayerMovementEvent
// case object PlayerTurnedLeft extends PlayerMovementEvent
implicit val show = Show.fromToString[PlayerMovementEvent]
}
sealed trait PlayerCameraEvent extends PlayerEvent

View File

@ -16,10 +16,10 @@ import monix.bio.IO
import monix.bio.UIO
import monix.reactive.Consumer
import monix.reactive.Observable
import wow.doge.mygame.implicits._
import wow.doge.mygame.utils.readAsyncL
import wow.doge.mygame.utils.IOUtils
import IOUtils.toIO
@JsonCodec
final case class Test1(hello1: String, hello2: String)
@JsonCodec
@ -32,7 +32,7 @@ object Plugin {
}
object ModdingSystem {
sealed trait Error
sealed trait Error extends Product with Serializable
final case class CouldNotDecode(cause: String) extends Error
final case class ParseFailure(cause: io.circe.ParsingFailure) extends Error
final case class DecodingFailure(cause: io.circe.DecodingFailure)
@ -43,125 +43,52 @@ object ModdingSystem {
implicit val eq = Eq.fromUniversalEquals[Error]
}
val x: IO[Error, String] =
IOUtils.liftErrors(readAsyncL(os.pwd / "plugins.json")) {
case _: FileNotFoundException =>
IO.raiseError(FileNotFound(os.pwd / "plugins.json"))
case _: NoSuchFileException =>
IO.raiseError(FileNotFound(os.pwd / "plugins.json"))
}
def readPluginsList(dir: os.Path): IO[Error, ArraySeq[Plugin]] =
IO(parse(os.read(dir / "plugins.json")))
readAsyncL(dir / "plugins.json")
.onErrorHandleWith {
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(files)
.map(_.as[ArraySeq[Plugin]])
IO.fromEither(parse(files))
.mapError(ParseFailure)
.map(_.as[ArraySeq[Plugin]])
)
.flatMap(result => IO.fromEither(result).mapError(DecodingFailure))
def findPluginFiles(dir: os.Path): View[os.Path] =
os.list(dir)
.view
.filter(f => f.ext == "json" && f.baseName.endsWith("plugin"))
def findAndReadPluginFiles(
dir: os.Path,
plugins: ArraySeq[Plugin]
): UIO[(View[(Plugin, Error)], View[(Plugin, String)])] =
UIO(
plugins
.sortBy(_.priority)
.view
.map { p =>
val path = dir / os.RelPath(p.name + ".plugin.json")
p ->
Either
.catchNonFatal(os.read(path))
.leftMap {
case _: FileNotFoundException => FileNotFound(path)
case _: NoSuchFileException => FileNotFound(path)
}
}
.partitionMap {
case (p, either) =>
either match {
case Left(value) => Left(p -> value)
case Right(value) => Right(p -> value)
}
}
)
// : (View[(Plugin, Error)], View[(Plugin, String)])
def findAndReadPluginFiles2(
dir: os.Path,
plugins: ArraySeq[Plugin]
) =
// IO.parTraverse(plugins.sortBy(_.priority))(p =>
// IO {
// val path = dir / os.RelPath(p.name + ".plugin.json")
// os.read(path)
// }
// .onErrorHandleWith {
// case _: FileNotFoundException =>
// IO.raiseError(FileNotFound(dir.toString))
// case _: NoSuchFileException =>
// IO.raiseError(FileNotFound(dir.toString))
// }
// .flatMap(r => UIO(p -> r))
// ).map {
// _.partitionMap {
// case (p, either) =>
// either match {
// case Left(value) => Left(p -> value)
// case Right(value) => Right(p -> value)
// }
// }
// }
plugins
.sortBy(_.priority)
.view
.map { p =>
val path = dir / os.RelPath(p.name + ".plugin.json")
p -> IO(os.read(path))
IO
.parTraverse(plugins.sortBy(_.priority))(p =>
readAsyncL(dir / os.RelPath(p.name + ".plugin.json"))
.onErrorHandleWith {
case _: FileNotFoundException => IO.raiseError(FileNotFound(path))
case _: NoSuchFileException => IO.raiseError(FileNotFound(path))
case _: FileNotFoundException =>
IO.raiseError(FileNotFound(dir))
case _: NoSuchFileException =>
IO.raiseError(FileNotFound(dir))
case other => IO.terminate(other)
}
// .map(r => p -> r)
.attempt
.map(r => p -> r)
)
.map(_.partitionMap {
case p -> Left(value) => Left(p -> value)
case p -> Right(value) => Right(p -> value)
})
}
.map {
case (p, io) =>
io.attempt.map {
case Left(value) => Left(p -> value)
case Right(value) => Right(p -> value)
}
}
.to(List)
.parSequence
// .partitionMap {
// _.map {
// case l @ Left(value) => l
// case r @ Right(value) => r
// }
// }
// .sequence
// def readPluginFiles(filePaths: View[os.Path]) =
// filePaths.map(path => os.read(path))
// def readPluginFiles2(filePaths: View[os.Path]) =
// filePaths
// .map(path =>
// IO(os.read(path)).onErrorHandleWith {
// case _: FileNotFoundException =>
// IO.raiseError(FileNotFound(path))
// case _: NoSuchFileException =>
// IO.raiseError(FileNotFound(path))
// }
// )
// .to(List)
// .parSequence
def parsePluginFiles(files: View[(Plugin, String)]) =
files
.map {
@ -191,18 +118,18 @@ object ModdingSystem {
for {
plugins <- readPluginsList(wd)
(readFailures, readSuccesses) <- findAndReadPluginFiles(wd, plugins)
(parseFailures, parseSuccesses) <- UIO(parsePluginFiles(readSuccesses))
(parseFailures, parseSuccesses) = parsePluginFiles(readSuccesses.view)
res <- UIO.parMap5(
UIO(readFailures.to(List)),
UIO(readSuccesses.to(List)),
UIO.pure(readSuccesses),
UIO(parseFailures.to(List)),
UIO(parseSuccesses.to(List)),
toIO(
Observable
.fromIterable(parseSuccesses)
.map { case (p, json) => json }
.consumeWith(loadBalancedPluginDataMerger)
).hideErrors
.toIO
.hideErrors
)(Result.apply)
} yield res
@ -215,7 +142,7 @@ object ModdingSystem {
pprint.log(show"Merged = ${res.pluginJson}")
}
case class Result(
final case class Result(
readFailures: List[(Plugin, Error)],
readSuccesses: List[(Plugin, String)],
parseFailures: List[(Plugin, ParsingFailure)],

View File

@ -10,28 +10,33 @@ 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.reactive.Observable
import wow.doge.mygame.implicits._
import monix.{eval => me}
import groovy.util.ResourceException
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) =
@ -41,6 +46,19 @@ trait Requestable[A] {
_ <- 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 (
@ -52,6 +70,7 @@ class ScriptCompiler private (
// def tell(item: Command) = queue.offer(item)
}
@SuppressWarnings(Array("org.wartremover.warts.Any"))
object ScriptCompiler {
sealed trait State
@ -67,7 +86,7 @@ object ScriptCompiler {
sealed trait KotlinEngineTag
type KotlinScriptEngine = ScriptEngine @@ KotlinEngineTag
sealed trait Error
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
@ -79,15 +98,14 @@ object ScriptCompiler {
final case class SomeError(reason: String) extends Error
sealed trait Command
final case class Get(
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 GetData(result: Deferred[Task, Data])
final case class ObservableData(result: Deferred[Task, Observable[Data]])
extends Command
// extends Command
// final case class CompileAll(paths: Seq[os.Path]) extends Command
type ScriptsMap = Map[os.Path, Any]
@ -117,7 +135,7 @@ object ScriptCompiler {
val defaultGroovyRunner: GroovyScriptEngine =
new GroovyScriptEngine(os.pwd.toString)
case class Data(scriptsMap: ScriptsMap)
final case class Data(scriptsMap: ScriptsMap)
class SourceMaker(
queue: ConcurrentQueue[Task, Command],
@ -137,14 +155,12 @@ object ScriptCompiler {
case Idle => IO.pure(Idle -> data)
case Active =>
command match {
case Get(path, result, force) =>
case GetScript(path, result, force) =>
def getAndUpdate =
worker
.request(
ScriptCompilerWorker.CompileAny(path, _)
)(
20.seconds
)
)(20.seconds)
.flatTap(result.complete)
.hideErrors
.rethrow
@ -207,6 +223,24 @@ object ScriptCompiler {
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)))
@ -214,6 +248,8 @@ object ScriptCompiler {
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, ""))
@ -229,31 +265,31 @@ object ScriptCompiler {
class ScriptCompileSource(
fns: ScriptCompileFns,
logger: Logger[Task],
queue: ConcurrentQueue[Task, ScriptCompilerWorker.CompileRequest]
queue: ConcurrentQueue[Task, ScriptCompilerWorker.CompileRequest],
ioScheduler: Schedulers.IoScheduler
) {
import fns._
val source =
Task.deferAction(implicit s =>
Task(
Observable
.repeatEvalF(queue.poll)
.doOnNextF(el => logger.debug(s"Got $el"))
.doOnNextF(el => logger.debug(show"Got $el"))
.mapParallelUnorderedF(4) {
case ScriptCompilerWorker.CompileAny(path, result) =>
for {
mbRes <- Task(
runScala(path)
.flatMap(ensureReturnedObjectNotNull)
mbRes <- (logger.debugU("Test") >> Task(
fns
.runScala(path)
.flatMap(fns.ensureReturnedObjectNotNull)
// .map(_.taggedWith[ScriptTag])
)
)).executeOn(ioScheduler.value)
_ <- result.complete(mbRes)
} yield mbRes
}
)
)
}
// override private val
final class ScriptCompilerWorker(
logger: Logger[Task],
_queue: ConcurrentQueue[Task, ScriptCompilerWorker.CompileRequest]
@ -266,6 +302,10 @@ object ScriptCompiler {
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]
@ -275,7 +315,8 @@ object ScriptCompiler {
logger: Logger[Task],
scalaRunner: ammonite.Main = defaultScalaRunner,
kotlinRunner: KotlinScriptEngine = defaultKotlinRunner,
groovyRunner: GroovyScriptEngine = defaultGroovyRunner
groovyRunner: GroovyScriptEngine = defaultGroovyRunner,
ioScheduler: Schedulers.IoScheduler
) = {
val acquire = for {
queue <- ConcurrentQueue[Task].bounded[CompileRequest](10)
@ -287,22 +328,27 @@ object ScriptCompiler {
// )
} yield worker -> fib
Resource
.make(acquire) { case worker -> fib => fib.cancel }
.make(acquire.hideErrors) { case worker -> fib => fib.cancel }
.map(_._1)
}
}
def apply(logger: Logger[Task]) = {
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)
ScriptCompilerWorker(logger, ioScheduler = ioScheduler)
.flatMap(worker =>
Resource.make(acquire(worker)) { case (_, fib) => fib.cancel }
Resource.make(acquire(worker).hideErrors) {
case (_, fib) => fib.cancel
}
)
.map(_._1)
}

View File

@ -17,7 +17,9 @@ import com.softwaremill.tagging._
import com.typesafe.scalalogging.Logger
import groovy.util.GroovyScriptEngine
import org.slf4j.event.Level
import wow.doge.mygame.implicits._
@SuppressWarnings(Array("org.wartremover.warts.Any"))
object ScriptActor {
/**
@ -126,6 +128,7 @@ object ScriptActor {
}
@SuppressWarnings(Array("org.wartremover.warts.Any"))
class ScriptActor(
val scalaRunner: ammonite.Main,
val kotlinRunner: ScriptActor.KotlinScriptEngine,
@ -137,14 +140,14 @@ class ScriptActor(
def receiveMessage: Behavior[Command] =
Behaviors.receiveMessage {
case CompileAny(path, requester) =>
context.log.debug(s"Received $path")
context.log.debug(show"Received $path")
val res = getScript(path)
context.log.debug(s"result = $res")
context.log.debug(s"result = ${res.toString}")
requester ! res
Behaviors.same
case CompileAll(paths, requester) =>
context.log.debug(s"Received $paths")
context.log.debug(show"Received $paths")
requester ! compileAll(paths)
Behaviors.same
}
@ -166,7 +169,7 @@ class ScriptActor(
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(
paths: Seq[os.Path]
@ -176,7 +179,7 @@ class ScriptActor(
paths: Seq[os.Path],
scriptsMap: Map[
os.Path,
Either[wow.doge.mygame.state.ScriptActor.Error, Any]
Either[ScriptActor.Error, Any]
]
): LOL =
paths match {

View File

@ -13,6 +13,7 @@ import akka.actor.typed.scaladsl.Behaviors
import akka.actor.typed.scaladsl.PoolRouter
import akka.actor.typed.scaladsl.Routers
import akka.util.Timeout
import cats.syntax.show._
import com.typesafe.scalalogging.Logger
import org.slf4j.event.Level
import wow.doge.mygame.implicits._
@ -25,7 +26,7 @@ object ScriptCachingActor {
type ScriptsMap = Map[os.Path, 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
@ -187,7 +188,7 @@ class ScriptCachingActor(
Behaviors.same
case Put(scriptPath, script) =>
ctx.log.debugP(s"Putting $script at path $scriptPath")
ctx.log.debugP(show"Putting ${script.toString} at path $scriptPath")
val newState =
state.modify(_.scriptsMap).using(_ + (scriptPath -> script))
ctx.log.traceP(newState.toString())

View File

@ -4,9 +4,14 @@ import akka.actor.typed.ActorRef
import akka.actor.typed.Scheduler
import akka.actor.typed.SpawnProtocol
import akka.util.Timeout
import cats.effect.Resource
import cats.syntax.eq._
import io.odin.Logger
import monix.bio.Task
import monix.bio.UIO
import wow.doge.mygame.scriptsystem.ScriptCachingActor
import wow.doge.mygame.utils.AkkaUtils
import wow.doge.mygame.executors.Schedulers
/**
* Scripts can either be searched and compiled at startup (Eager mode)
@ -19,7 +24,9 @@ object ScriptInitMode {
}
class ScriptSystemResource(
path: os.Path,
mode: ScriptInitMode = ScriptInitMode.Lazy
logger: Logger[Task],
mode: ScriptInitMode = ScriptInitMode.Lazy,
ioScheduler: Schedulers.IoScheduler
)(implicit
spawnProtocol: ActorRef[SpawnProtocol.Command],
timeout: Timeout,
@ -36,13 +43,17 @@ class ScriptSystemResource(
)
} yield scriptCacheActor
val init2: Resource[UIO, ScriptCompiler] = for {
sc <- ScriptCompiler(logger, ioScheduler)
} yield sc
def findScriptFiles(wd: os.Path) =
os.walk
.stream(wd)
.filter(p =>
os.isFile(p) &&
(p.ext == "sc" || (p.baseName + "." + p.ext)
.contains(".main.kts") || p.ext == "groovy")
(p.ext === "sc" || (p.baseName + "." + p.ext)
.contains(".main.kts") || p.ext === "groovy")
)
.toList

View File

@ -10,6 +10,6 @@ import wow.doge.mygame.utils.wrappers.jme.AppNode2
package object types {
type RootNode = AppNode2 @@ GameAppTags.RootNode
type GuiNode = AppNode2 @@ GameAppTags.GuiNode
@newtype case class JmeScheduler(value: SchedulerService)
@newtype case class AkkaScheduler(value: Scheduler)
@newtype final case class JmeScheduler(value: SchedulerService)
@newtype final case class AkkaScheduler(value: Scheduler)
}

View File

@ -54,7 +54,7 @@ object GenericConsoleStream {
/**
* for future use
*/
case class Config(exclusive: Boolean = false)
final case class Config(exclusive: Boolean = false)
object Config {
val default = Config()
}

View File

@ -8,6 +8,7 @@ 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 {
@ -15,9 +16,9 @@ object GenericTimerActor {
case object Start extends Command
case object Stop extends Command
private case object Tick extends Command
case class TimerKey(seed: Long)
final case class TimerKey(seed: Long)
case class Props[T](
final case class Props[T](
target: ActorRef[T],
messageToSend: T,
timeInterval: FiniteDuration
@ -55,7 +56,7 @@ class GenericTimerActor[T](
val active: Behavior[Command] =
Behaviors.receiveMessage {
case Start =>
ctx.log.warnP(s"Timer-${timerKey.seed}: Timer already started")
ctx.log.warnP(show"Timer-${timerKey.seed}: Timer already started")
Behaviors.same
case Tick =>
props.target ! props.messageToSend

View File

@ -14,4 +14,15 @@ object IOUtils {
def fromCoevalEither[L, R](coeval: Coeval[Either[L, R]]) =
coeval.to[Task].hideErrors.rethrow
def liftErrors[E, T](
task: Task[T]
)(pf: PartialFunction[Throwable, IO[E, T]]): IO[E, T] = {
val _ = 1
// val x = task.onErrorRecoverWith()
task.onErrorHandleWith(ex => pf.applyOrElse(ex, IO.terminate))
}
// final def mapErrorPartial[E1, B >: A](pf: PartialFunction[E, IO[E1, B]])(implicit ev: E <:< Throwable): IO[E1, B] =
// onErrorHandleWith(ex => pf.applyOrElse(ex, IO.terminate))
}

View File

@ -1,33 +1,62 @@
package wow.doge.mygame.utils
import monix.reactive.Observable
import monix.reactive.OverflowStrategy
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.execution.Ack
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) =
Observable.create[String](OverflowStrategy.DropNew(50)) { sub =>
Task.deferAction(implicit s =>
Task(
Observable
.create[WatchEvent](OverflowStrategy.DropNew(50)) { sub =>
import sub.scheduler
val c = SingleAssignCancelable()
val myDir = File(path.toString)
val watcher = new RecursiveFileMonitor(myDir) {
val watcher =
new RecursiveFileMonitor(
File(path.toString),
logger = logger.underlying
) {
override def onCreate(file: File, count: Int) =
println(s"$file got created")
if (sub.onNext(CreateEvent(file, count)) == Ack.Stop)
c.cancel()
override def onModify(file: File, count: Int) =
// println(s"$file got modified $count times")
if (sub.onNext(file.name) == Ack.Stop) c.cancel()
if (sub.onNext(ModifyEvent(file, count)) == Ack.Stop)
c.cancel()
override def onDelete(file: File, count: Int) =
println(s"$file got deleted")
if (sub.onNext(DeleteEvent(file, count)) == Ack.Stop)
c.cancel()
}
watcher.start()(scheduler)
c := Cancelable(() => watcher.stop())
c
}
.publish
.refCount
)
)
}

View 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]
}

View File

@ -15,7 +15,7 @@ object ReaderDemo {
val t2 = r.run("s").rethrow
// Kleisli[IO, String, Unit](s => IO.unit)
case class Environment(str: String, num: Int)
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)))

View File

@ -1,6 +1,6 @@
package wow.doge.mygame.utils
case class Display(
final case class Display(
width: Int,
height: Int,
title: String,
@ -18,4 +18,4 @@ object Display {
frameRate = -1
)
}
case class GlobalSettings(display: Display = Display.default)
final case class GlobalSettings(display: Display = Display.default)

View File

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

View File

@ -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
}

View 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")
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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

View File

@ -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)
}

35
src/main/scala/wow/doge/mygame/utils/package.scala Normal file → Executable file
View File

@ -1,7 +1,38 @@
package wow.doge.mygame
// import wow.doge.mygame.utils.wrappers.Node
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 {
// type AppNode = Node
def methodName(implicit enclosing: sourcecode.Enclosing) =
enclosing.value.split(" ")(0).split("""\.""").last
def readAsync(
path: os.Path,
chunkSize: Int = 8192
) =
UIO
.deferAction(implicit s => UIO(mnf.readAsync(path.toNIO, chunkSize)))
.flatMap(bytes => UIO(bytes.pipeThrough(utf8Decode)))
@SuppressWarnings(Array("org.wartremover.warts.TraversableOps"))
def readAsyncL(
path: os.Path,
chunkSize: Int = 8192
) =
readAsync(path, chunkSize)
.flatMap(_.toListL.toIO)
.map(_.head)
def writeAsync(path: os.Path, flags: Seq[StandardOpenOption] = Seq.empty)(
implicit s: Scheduler
): AsyncFileChannelConsumer =
mnf.appendAsync(path.toNIO, 0, flags)
}

View File

@ -20,6 +20,9 @@ class AssetManager(assetManager: jmea.AssetManager) {
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] =
@ -28,6 +31,9 @@ class AssetManager(assetManager: jmea.AssetManager) {
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 {
@ -46,9 +52,9 @@ class AssetManager(assetManager: jmea.AssetManager) {
}
object AssetManager {
sealed trait Error
case class AssetNotFound(message: String) extends Error
case class AssetLoadError(message: String) extends Error
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]

View File

@ -9,7 +9,7 @@ import monix.bio.IO
object CollisionShapeFactory {
sealed trait Error
case class WrongArgumentError(reason: String) extends Error
final case class WrongArgumentError(reason: String) extends Error
object Error {
implicit val show = Show.fromToString[Error]

View File

@ -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
}

View File

@ -1,15 +1,16 @@
package wow.doge.mygame
import akka.actor.typed.scaladsl.Behaviors
import akka.actor.typed.scaladsl.ActorContext
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.BeforeAndAfterAll
import akka.actor.typed.ActorSystem
import scala.concurrent.duration._
import akka.actor.typed.ActorRef
import akka.actor.typed.scaladsl.AskPattern._
import akka.util.Timeout
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._
@ -30,7 +31,7 @@ class ActorTimeoutTest extends AnyFunSuite with BeforeAndAfterAll {
object ActorTimeoutTest {
object MyActor {
sealed trait Command
case class GetInt(replyTo: ActorRef[Int]) extends Command
final case class GetInt(replyTo: ActorRef[Int]) extends Command
class Props() {
def create =

View 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"))
}
}

View File

@ -1,21 +1,17 @@
package wow.doge.mygame
import org.scalatest.funsuite.AnyFunSuite
import com.jme3.anim.AnimClip
import wow.doge.mygame.implicits._
import wow.doge.mygame.utils.wrappers.jme.AssetManager
import com.jme3.asset.DesktopAssetManager
import com.jme3.scene.Spatial
import monix.bio.UIO
import scala.concurrent.duration._
import com.jme3.scene.Node
import cats.syntax.all._
import com.jme3.anim.AnimComposer
import com.jme3.anim.SkinningControl
import com.jme3.anim.tween.action.BaseAction
import com.jme3.anim.tween.Tweens
import scala.jdk.javaapi.CollectionConverters._
import com.jme3.anim.tween.Tween
import com.jme3.anim.tween.action.BaseAction
import com.jme3.anim.tween.action.ClipAction
import cats.syntax.all._
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

View File

@ -1,66 +1,75 @@
package wow.doge.mygame
import org.scalatest.funsuite.AnyFunSuite
import monix.execution.Scheduler.Implicits.global
import cats.syntax.eq._
import com.jme3.{asset => jmea}
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 com.jme3.scene.Geometry
import wow.doge.mygame.utils.wrappers.jme.AssetManager.CouldNotCastError
import com.jme3.scene.Node
import com.jme3.material.MaterialDef
import com.jme3.material.Material
import scala.annotation.nowarn
@nowarn("msg=method get in class LeftProjection is deprecated")
@nowarn("msg=method right in class Either is deprecated")
@nowarn("msg=method get in class RightProjection is deprecated")
class AssetManagerTest extends AnyFunSuite {
class AssetManagerTest extends MonixBioSuite {
val _assetManager: jmea.AssetManager = new DesktopAssetManager(true)
val assetManager = new AssetManager(_assetManager)
val fixture = FunFixture[AssetManager](
setup = _ => new AssetManager(new DesktopAssetManager(true)),
teardown = _ => ()
)
test("Test for AssetNotFound error") {
val res =
assetManager.loadModel(os.rel / "doesnotexist").attempt.runSyncUnsafe()
assert(res.isLeft)
assert(res.left.get eqv AssetNotFound("doesnotexist"))
override def beforeAll(): Unit = {
import java.util.logging.{Logger => JLogger, Level}
JLogger.getLogger("").setLevel(Level.SEVERE)
}
test("Test for Model CouldNotCastError") {
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"
val res1 = assetManager
assetManager
.loadModelAs[Geometry](modelPath)
.attempt
.runSyncUnsafe()
assert(res1.isLeft)
assert(res1.left.get eqv CouldNotCastError)
.assertEquals(Left(CouldNotCastError))
val res2 = assetManager
}
fixture.test(
"AssetManager#loadModelAs should load com.jme3.Node successfully"
) { assetManager =>
val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o"
assetManager
.loadModelAs[Node](modelPath)
.attempt
.runSyncUnsafe()
assert(res2.isRight)
assert(res2.map(_.getName).right.get eqv "JaimeGeom-ogremesh")
.map(_.map(_.getName))
.assertEquals(Right("JaimeGeom-ogremesh"))
}
test("Test for Asset CouldNotCastError") {
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"
val res1 = assetManager
assetManager
.loadAssetAs[Material](assetPath)
.attempt
.runSyncUnsafe()
assert(res1.isLeft)
assert(res1.left.get eqv CouldNotCastError)
.assertEquals(Left(CouldNotCastError))
}
val res2 = assetManager
fixture.test(
"AssetManager#loadAssetAs should should load asset succesfully"
) { assetManager =>
val assetPath = os.rel / "Common" / "MatDefs" / "Misc" / "Unshaded.j3md"
assetManager
.loadAssetAs[MaterialDef](assetPath)
.attempt
.runSyncUnsafe()
assert(res2.isRight)
assert(res2.map(_.getName).right.get eqv "Unshaded")
.map(_.map(_.getName))
.assertEquals(Right("Unshaded"))
}
}

View File

@ -1,31 +1,27 @@
package wow.doge.mygame
import org.scalatest.funsuite.AnyFunSuite
import com.jme3.scene.Spatial
import com.jme3.collision.{Collidable, CollisionResults}
import com.jme3.bounding.BoundingVolume
import com.jme3.scene.Spatial.DFSMode
import com.jme3.collision.Collidable
import com.jme3.collision.CollisionResults
import com.jme3.scene.SceneGraphVisitor
import java.util.Queue
import com.jme3.scene.Spatial
import com.jme3.scene.Spatial.DFSMode
import wow.doge.mygame.utils.wrappers.jme.CollisionShapeFactory
import monix.execution.Scheduler.Implicits.global
import cats.syntax.eq._
class CollisionShapeFactoryTest extends AnyFunSuite {
import java.util.Queue
class CollisionShapeFactoryTest extends MonixBioSuite {
test("Test for WrongArgumentError") {
val res = CollisionShapeFactory
CollisionShapeFactory
.createMeshShape(new TestSpatial)
.attempt
.runSyncUnsafe()
assert(res.isLeft)
assert(
res.left.get eqv
.assertEquals(
Left(
CollisionShapeFactory.WrongArgumentError(
"The spatial must either be a Node or a Geometry!"
)
)
)
}
}

View File

@ -1,30 +1,70 @@
package wow.doge.mygame
import org.scalatest.funsuite.AnyFunSuite
import cats.effect.{Resource => CResource}
import monix.eval.Task
import scala.concurrent.duration._
class FileWatcherTest extends AnyFunSuite {
test("1") {
import better.files._
import io.methvin.better.files._
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}
val myDir =
File((os.pwd / "assets" / "scripts").toString)
val watcher = new RecursiveFileMonitor(myDir) {
override def onCreate(file: File, count: Int) =
println(s"$file got created")
override def onModify(file: File, count: Int) =
println(s"$file got modified $count times")
override def onDelete(file: File, count: Int) =
println(s"$file got deleted")
class FileWatcherTest extends munit.TaglessFinalSuite[Task] {
override protected def toFuture[A](f: Task[A]): Future[A] = {
implicit val s = Scheduler.global
f.runToFuture
}
import monix.execution.Scheduler.Implicits.global
CResource
.make(Task { watcher.start(); watcher })(w => Task(w.stop()))
.use(_ => Task.never)
.runSyncUnsafe(10.seconds)
// 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
}
}
}

View File

@ -1,12 +1,41 @@
package wow.doge.mygame
import org.scalatest.funsuite.AnyFunSuite
import munit.ScalaCheckSuite
import org.scalacheck.Prop._
import munit.FunSuite
import wow.doge.mygame.math.ImVector3f
import com.typesafe.scalalogging.LazyLogging
import cats.syntax.eq._
import cats.syntax.show._
import cats.syntax.eq._
import org.scalacheck.Gen
import org.scalacheck.Arbitrary.arbitrary
class ImVector3fSuite extends FunSuite with ScalaCheckSuite with LazyLogging {
val floatGen = for {
a <- arbitrary[Float]
b <- arbitrary[Float]
c <- arbitrary[Float]
} yield ImVector3f(a, b, c)
val intGen = for {
a <- arbitrary[Int]
b <- arbitrary[Int]
c <- arbitrary[Int]
} yield ImVector3f(a.toFloat, b.toFloat, c.toFloat)
property("imvector3f dst float") {
forAll(floatGen, floatGen) { (v1: ImVector3f, v2: ImVector3f) =>
assertEquals(ImVector3f.dst(v1, v2), ImVector3f.dst(v2, v1))
}
}
property("imvector3f dst int") {
forAll(intGen, intGen) { (v1: ImVector3f, v2: ImVector3f) =>
assertEquals(ImVector3f.dst(v1, v2), ImVector3f.dst(v2, v1))
}
}
class ImVector3fTest extends AnyFunSuite with LazyLogging {
test("maxvalue") {
val v1 = ImVector3f.Max
val v2 = ImVector3f.Max
@ -33,19 +62,4 @@ class ImVector3fTest extends AnyFunSuite with LazyLogging {
assert(ImVector3f.dst(v1, v2) eqv ImVector3f.dst(v2, v1))
}
test("another") {
{
val v1 = ImVector3f(1, 0, 0)
val v2 = ImVector3f(1, 1, 1)
logger.info(ImVector3f.dst(v1, v2).show)
assert(ImVector3f.dst(v1, v2) eqv ImVector3f.dst(v2, v1))
}
{
val v1 = ImVector3f(1, 1, 0)
val v2 = ImVector3f(1, 1, 1)
logger.info(ImVector3f.dst(v1, v2).show)
assert(ImVector3f.dst(v1, v2) eqv ImVector3f.dst(v2, v1))
}
}
}

View File

@ -1,13 +1,20 @@
package wow.doge.mygame
import org.scalatest.funsuite.AnyFunSuite
import wow.doge.mygame.subsystems.moddingsystem.ModdingSystem
import monix.execution.Scheduler.Implicits.global
import cats.syntax.eq._
import io.circe.Printer
import monix.bio.UIO
import cats.syntax.eq._
import org.scalatest.funsuite.AnyFunSuite
import wow.doge.mygame.subsystems.moddingsystem.ModdingSystem
import monix.bio.Task
import scala.concurrent.Future
import monix.execution.Scheduler
class ModdingSystemTest extends AnyFunSuite {
class ModdingSystemTest extends munit.TaglessFinalSuite[Task] {
override protected def toFuture[A](f: Task[A]): Future[A] = {
implicit val s = Scheduler.global
f.runToFuture
}
val printer = Printer.spaces2
test("main") {
val io = for {
@ -19,9 +26,6 @@ class ModdingSystemTest extends AnyFunSuite {
)
_ <- ModdingSystem.log(res)
} yield res
io.attempt.runSyncUnsafe() match {
case Left(value) => pprint.log(value); ()
case Right(value) => ()
}
io.attempt
}
}

View File

@ -0,0 +1,13 @@
package wow.doge.mygame
import scala.concurrent.Future
import monix.bio.Task
import monix.execution.Scheduler
trait MonixBioSuite extends munit.TaglessFinalSuite[Task] {
override protected def toFuture[A](f: Task[A]): Future[A] = {
implicit val s = Scheduler.global
f.runToFuture
}
}

View File

@ -1,29 +1,35 @@
package wow.doge.mygame
import org.scalatest.funsuite.AnyFunSuite
import monix.bio.Task
import scala.concurrent.duration._
import monix.execution.Scheduler.Implicits.global
import wow.doge.mygame.subsystems.scriptsystem.ScriptCompiler
import io.odin.consoleLogger
import wow.doge.mygame.implicits._
class MonixScriptCompilerTest extends AnyFunSuite {
import io.odin.consoleLogger
import monix.bio.Task
import wow.doge.mygame.subsystems.scriptsystem.ScriptCompiler
import wow.doge.mygame.executors.Schedulers
import scala.concurrent.Future
import monix.execution.Scheduler
class MonixScriptCompilerTest extends munit.TaglessFinalSuite[Task] {
override protected def toFuture[A](f: Task[A]): Future[A] = {
implicit val s = Scheduler.global
f.runToFuture
}
test("some-test") {
ScriptCompiler(consoleLogger[Task]())
ScriptCompiler(consoleLogger[Task](), Schedulers.default.io)
.use(scriptCompiler =>
for {
// _ <-
// scriptCompiler.source
// .doOnNextF(el => Task(println(s"Got $el")))
// .doOnNextF(el => Task(println(show"Got $el")))
// .completedL
// .toIO
// .hideErrors
// .startAndForget
response <- scriptCompiler.request(
ScriptCompiler.Get(
os.pwd / "assets" / "scripts" / "scala" / "hello2.sc",
ScriptCompiler.GetScript(
os.pwd / "assets" / "scripts" / "scala" / "hello.sc",
_,
false
)
@ -39,6 +45,5 @@ class MonixScriptCompilerTest extends AnyFunSuite {
// _ <- Task.sleep(8.seconds)
} yield ()
)
.runSyncUnsafe(20.seconds)
}
}

View File

@ -1,16 +1,14 @@
package wow.doge.mygame
import cats.data.ReaderT
import monix.bio.UIO
import org.scalatest.funsuite.AnyFunSuite
import monix.execution.Scheduler.Implicits.global
import monix.bio.IO
import cats.mtl.Ask
import cats.mtl.implicits._
import cats.effect.LiftIO
import monix.bio.Task
import cats.data.Kleisli
import cats.mtl.Ask
import monix.bio.IO
import monix.bio.IOLift
import monix.execution.Scheduler.Implicits.global
import org.scalatest.funsuite.AnyFunSuite
final case class Environment(str: String, num: Int)
class ReaderT_Test extends AnyFunSuite {
@ -24,8 +22,6 @@ class ReaderT_Test extends AnyFunSuite {
// val t2 = r.run("s").rethrow
// Kleisli[IO, String, Unit](s => IO.unit)
case class Environment(str: String, num: Int)
// test("runReaderT_Test") {
// def fun1: ReaderT[UIO, String, Unit] = ReaderT(str => UIO(println(str)))
// def fun2: ReaderT[UIO, Int, Unit] = ReaderT(num => UIO(println(num)))

View File

@ -2,8 +2,8 @@ package wow.doge.mygame
import cats.data.Reader
import monix.bio.UIO
import org.scalatest.funsuite.AnyFunSuite
import monix.execution.Scheduler.Implicits.global
import org.scalatest.funsuite.AnyFunSuite
class ReaderTest extends AnyFunSuite {
@ -17,8 +17,6 @@ class ReaderTest extends AnyFunSuite {
// val t2 = r.run("s").rethrow
// Kleisli[IO, String, Unit](s => IO.unit)
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)))

View File

@ -0,0 +1,41 @@
package wow.doge.mygame
import sttp.capabilities.monix.MonixStreams
import sttp.client3._
import sttp.client3.asynchttpclient.monix._
import monix.eval.Task
import monix.reactive.Observable
import scala.concurrent.duration.Duration
import scala.collection.immutable.ArraySeq
import monix.reactive.Consumer
class WebsocketTest {
// : Task[Response[Either[String, Observable[Array[Byte]]]]]
val s = AsyncHttpClientMonixBackend().flatMap { backend =>
val response =
basicRequest
.post(uri"...")
.response(asStreamUnsafe(MonixStreams))
.readTimeout(Duration.Inf)
.send(backend)
response.map(_.body.map(_.map(ArraySeq.unsafeWrapArray)))
}
val MB = 1024 * 1024
def consumer(contentLength: Long): Consumer[ArraySeq[Byte], Long] =
Consumer.foldLeftEval(0L) {
case (sum, data) =>
val newSum = sum + data.length
for {
_ <- Task(
pprint.log(
s"Bytes downloaded = ${newSum * 1f / MB}MB . Percent done = ${(newSum * 100f / contentLength)
.formatted("%.2f")}"
)
)
} yield newSum
}
}