many changes
This commit is contained in:
parent
67a2bc4385
commit
fd8b3819ff
@ -66,7 +66,8 @@ lazy val root = (project in file(".")).settings(
|
||||
"com.lihaoyi" %% "pprint" % "0.6.0",
|
||||
"org.scalatest" %% "scalatest" % "3.2.2" % "test",
|
||||
"org.typelevel" %% "cats-mtl" % "1.1.1",
|
||||
"io.estatico" %% "newtype" % "0.4.4"
|
||||
"io.estatico" %% "newtype" % "0.4.4",
|
||||
"io.methvin" %% "directory-watcher-better-files" % "0.14.0"
|
||||
),
|
||||
// Determine OS version of JavaFX binaries
|
||||
|
||||
|
@ -14,14 +14,16 @@ import io.odin._
|
||||
import io.odin.json.Formatter
|
||||
import io.odin.syntax._
|
||||
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
|
||||
|
||||
object Main extends BIOApp with MainModule {
|
||||
object Main extends BIOApp with ExecutorsModule {
|
||||
import java.util.logging.{Logger => JLogger, Level}
|
||||
JLogger.getLogger("").setLevel(Level.SEVERE)
|
||||
implicit val timeout = Timeout(1.second)
|
||||
|
||||
override def scheduler: Scheduler = schedulers.async
|
||||
override def scheduler: Scheduler = schedulers.async.value
|
||||
|
||||
def appResource(consoleStream: GenericConsoleStream[TextArea]) =
|
||||
for {
|
||||
@ -34,18 +36,18 @@ object Main extends BIOApp with MainModule {
|
||||
"application-log-1.log",
|
||||
Formatter.json
|
||||
).withAsync(timeWindow = 1.milliseconds, maxBufferSize = Some(2000))
|
||||
jmeScheduler <- jMESchedulerResource
|
||||
jmeScheduler <- jmeSchedulerResource
|
||||
// backend <- Resource.make(toIO(HttpClientMonixBackend()))(backend =>
|
||||
// toIO(backend.close())
|
||||
// )
|
||||
actorSystem <- actorSystemResource(logger, schedulers.async)
|
||||
actorSystem <- new ActorSystemResource(logger, schedulers.async).get
|
||||
_ <- Resource.liftF(
|
||||
new MainApp(
|
||||
logger,
|
||||
jmeScheduler,
|
||||
schedulers,
|
||||
consoleStream
|
||||
)(actorSystem, timeout, actorSystem.scheduler).program
|
||||
)(actorSystem, timeout, AkkaScheduler(actorSystem.scheduler)).program
|
||||
)
|
||||
|
||||
} yield ()
|
||||
|
@ -3,7 +3,6 @@ package wow.doge.mygame
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import akka.actor.typed.ActorRef
|
||||
import akka.actor.typed.Scheduler
|
||||
import akka.actor.typed.SpawnProtocol
|
||||
import akka.util.Timeout
|
||||
import cats.effect.Resource
|
||||
@ -16,6 +15,8 @@ import com.jme3.material.Material
|
||||
import com.jme3.material.MaterialDef
|
||||
import com.jme3.math.ColorRGBA
|
||||
import com.jme3.math.FastMath
|
||||
import com.jme3.math.Quaternion
|
||||
import com.jme3.math.Vector3f
|
||||
import com.jme3.renderer.Camera
|
||||
import com.jme3.renderer.ViewPort
|
||||
import com.jme3.scene.Node
|
||||
@ -27,19 +28,22 @@ import monix.bio.IO
|
||||
import monix.bio.Task
|
||||
import monix.bio.UIO
|
||||
import monix.eval.Coeval
|
||||
import monix.execution.exceptions.DummyException
|
||||
import monix.reactive.Observable
|
||||
import scalafx.scene.control.TextArea
|
||||
import wow.doge.mygame.AppError.TimeoutError
|
||||
import wow.doge.mygame.executors.Schedulers
|
||||
import wow.doge.mygame.game.GameApp
|
||||
import wow.doge.mygame.game.GameAppActor
|
||||
import wow.doge.mygame.game.GameAppResource
|
||||
import wow.doge.mygame.game.controls.FollowControl
|
||||
import wow.doge.mygame.game.entities.CharacterStats
|
||||
import wow.doge.mygame.game.entities.EntityIds
|
||||
import wow.doge.mygame.game.entities.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.subsystems.input.PlayerCameraInput
|
||||
import wow.doge.mygame.game.subsystems.level.DefaultGameLevel
|
||||
import wow.doge.mygame.implicits._
|
||||
import wow.doge.mygame.launcher.Launcher
|
||||
@ -50,50 +54,45 @@ import wow.doge.mygame.subsystems.events.EventBus
|
||||
import wow.doge.mygame.subsystems.events.EventBus.ObservableSubscription
|
||||
import wow.doge.mygame.subsystems.events.EventsModule
|
||||
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
|
||||
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.subsystems.events.StatsEvent.DamageEvent
|
||||
import wow.doge.mygame.subsystems.events.TickEvent
|
||||
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.wrappers.jme.AssetManager
|
||||
import wow.doge.mygame.utils.wrappers.jme.PhysicsSpace
|
||||
import wow.doge.mygame.subsystems.events.PlayerCameraEvent
|
||||
import com.jme3.math.Quaternion
|
||||
import com.jme3.math.Vector3f
|
||||
import wow.doge.mygame.types._
|
||||
import wow.doge.mygame.game.controls.FollowControl
|
||||
import wow.doge.mygame.game.subsystems.input.PlayerCameraInput
|
||||
class MainApp(
|
||||
logger: Logger[Task],
|
||||
jmeThread: monix.execution.Scheduler,
|
||||
jmeThread: JmeScheduler,
|
||||
schedulers: Schedulers,
|
||||
consoleStream: GenericConsoleStream[TextArea]
|
||||
)(implicit
|
||||
spawnProtocol: ActorRef[SpawnProtocol.Command],
|
||||
timeout: Timeout,
|
||||
scheduler: Scheduler
|
||||
scheduler: AkkaScheduler
|
||||
) {
|
||||
implicit val as = scheduler.value
|
||||
|
||||
val scriptSystemInit =
|
||||
new ScriptSystemResource(os.pwd, ScriptInitMode.Eager).init
|
||||
|
||||
val eventsModule = new EventsModule(scheduler, spawnProtocol)
|
||||
|
||||
class TestClass(
|
||||
playerEventBus: GameEventBus[PlayerEvent],
|
||||
tickEventBus: GameEventBus[TickEvent]
|
||||
)
|
||||
|
||||
def eval(gameApp: GameApp, fib: Fiber[Nothing, Unit]) =
|
||||
def eval(
|
||||
tickEventBus: GameEventBus[TickEvent],
|
||||
gameApp: GameApp,
|
||||
fib: Fiber[Nothing, Unit]
|
||||
) =
|
||||
for {
|
||||
// g <- UIO.pure(gameApp)
|
||||
playerEventBus <- eventsModule.playerEventBus
|
||||
mainEventBus <- eventsModule.mainEventBus
|
||||
tickEventBus <- eventsModule.tickEventBus
|
||||
obs <-
|
||||
playerEventBus
|
||||
.askL[Observable[PlayerMovementEvent]](ObservableSubscription(_))
|
||||
@ -116,11 +115,6 @@ class MainApp(
|
||||
physicsSpace <- UIO.pure(gameApp.physicsSpace)
|
||||
_ <- logger.infoU("before")
|
||||
// jfxUI <- gameApp.jfxUI
|
||||
gameAppActor <- gameApp.spawnGameActor(
|
||||
GameAppActor.Props(tickEventBus).behavior,
|
||||
Some("gameAppActor")
|
||||
)
|
||||
_ <- gameAppActor !! GameAppActor.Start
|
||||
consoleTextArea <- UIO(new TextArea {
|
||||
text = "hello \n"
|
||||
editable = false
|
||||
@ -138,25 +132,25 @@ class MainApp(
|
||||
.executeOn(gameApp.scheduler.value)
|
||||
} yield fib
|
||||
|
||||
def gameInit: Resource[UIO, Either[AppError, Fiber[Nothing, Unit]]] =
|
||||
def gameInit(
|
||||
tickEventBus: GameEventBus[TickEvent]
|
||||
): Resource[UIO, Either[AppError, Fiber[Nothing, Unit]]] =
|
||||
wire[GameAppResource].resource.evalMap {
|
||||
case Right(gameApp -> gameAppFib) =>
|
||||
eval(gameApp, gameAppFib).attempt
|
||||
eval(tickEventBus, gameApp, gameAppFib).attempt
|
||||
case Left(error) => IO.terminate(new Exception(error.toString))
|
||||
}
|
||||
|
||||
// val x: Task[Unit] = for {
|
||||
// tickEventBus <- eventsModule.tickEventBusTask
|
||||
// playerEventBus <- eventsModule.playerEventBusTask
|
||||
// _ <- UIO(wire[TestClass])
|
||||
// _ <- gameInit(tickEventBus).use(_.join)
|
||||
// } yield ()
|
||||
|
||||
val program = for {
|
||||
// scriptSystem <- scriptSystemInit
|
||||
launchSignal <- Deferred[Task, Launcher.LauncherResult].hideErrors
|
||||
launcher <- new Launcher.Props(schedulers, launchSignal).create
|
||||
launchResult <- launcher.init.use(_ => launchSignal.get).hideErrors
|
||||
launcher <- new Launcher.Props(schedulers.fx, launchSignal).create
|
||||
launchResult <-
|
||||
launcher.init
|
||||
.use(_ => launchSignal.get)
|
||||
.hideErrors
|
||||
tickEventBus <-
|
||||
eventsModule.tickEventBus.hideErrorsWith(e => DummyException(e.toString))
|
||||
_ <-
|
||||
/**
|
||||
* User chose to quit
|
||||
@ -167,7 +161,7 @@ class MainApp(
|
||||
* User chose launch. Wait for game window to close
|
||||
*/
|
||||
else
|
||||
gameInit.use {
|
||||
gameInit(tickEventBus).use {
|
||||
case Right(fib) => fib.join >> Task.unit
|
||||
case Left(error) => IO.terminate(new Exception(error.toString))
|
||||
}.hideErrors
|
||||
@ -195,8 +189,9 @@ class MainAppDelegate(
|
||||
)(implicit
|
||||
spawnProtocol: ActorRef[SpawnProtocol.Command],
|
||||
timeout: Timeout,
|
||||
scheduler: Scheduler
|
||||
scheduler: AkkaScheduler
|
||||
) {
|
||||
implicit val as = scheduler.value
|
||||
|
||||
def init(
|
||||
// appScheduler: monix.execution.Scheduler
|
||||
@ -237,9 +232,12 @@ class MainAppDelegate(
|
||||
damageObs
|
||||
.doOnNextF(event =>
|
||||
(loggerL.debug(s"Received Damage Event $event") >>
|
||||
IO(
|
||||
playerActor ! PlayerActorSupervisor.TakeDamage(event.amount)
|
||||
)).toTask
|
||||
(if (event.victimName === "PlayerNode")
|
||||
// playerActor !! PlayerActorSupervisor.TakeDamage(event.amount)
|
||||
playerActor.askL(
|
||||
PlayerActorSupervisor.TakeDamage2(event.amount, _)
|
||||
)
|
||||
else IO.unit)).toTask
|
||||
)
|
||||
.completedL
|
||||
.toIO
|
||||
@ -251,10 +249,8 @@ class MainAppDelegate(
|
||||
.doOnNextF(_ =>
|
||||
playerActor
|
||||
.askL(PlayerActorSupervisor.GetStatus)
|
||||
.flatMap(s =>
|
||||
loggerL.debug(s"Player actor status: $s") >> UIO.pure(s)
|
||||
)
|
||||
.void
|
||||
.flatMap(s => loggerL.debug(s"Player actor status: $s"))
|
||||
|
||||
// .flatMap(s =>
|
||||
// if (s == Status.Alive)
|
||||
// playerActor
|
||||
@ -276,19 +272,21 @@ class MainAppDelegate(
|
||||
.startAndForget
|
||||
_ <-
|
||||
physicsSpace.collisionObservable
|
||||
.filter(event =>
|
||||
(for {
|
||||
nodeA <- event.nodeA
|
||||
nodeB <- event.nodeB
|
||||
} yield nodeA.getName === "PlayerNode" && nodeB.getName === "John" ||
|
||||
nodeB.getName === "PlayerNode" && nodeA.getName === "John")
|
||||
.getOrElse(false)
|
||||
)
|
||||
.doOnNextF(event =>
|
||||
loggerL
|
||||
.debug(s"$event ${event.appliedImpulse()}")
|
||||
.toTask
|
||||
)
|
||||
// .filter(event =>
|
||||
// (for {
|
||||
// nodeA <- event.nodeA
|
||||
// nodeB <- event.nodeB
|
||||
// } yield nodeA.getName === "PlayerNode" && nodeB.getName === "John" ||
|
||||
// nodeB.getName === "PlayerNode" && nodeA.getName === "John")
|
||||
// .getOrElse(false)
|
||||
// )
|
||||
// .doOnNextF(event =>
|
||||
// loggerL
|
||||
// .debug(s"$event ${event.appliedImpulse()}")
|
||||
// .toTask
|
||||
// )
|
||||
.filter(_.nodeA.map(_.getName =!= "main-scene_node").getOrElse(false))
|
||||
.filter(_.nodeB.map(_.getName =!= "main-scene_node").getOrElse(false))
|
||||
.doOnNextF(event =>
|
||||
(for {
|
||||
victim <- Coeval(for {
|
||||
@ -299,7 +297,11 @@ class MainAppDelegate(
|
||||
victim.foreach { v =>
|
||||
pprint.log(s"emitted event ${v.getName}")
|
||||
mainEventBus ! EventBus.Publish(
|
||||
DamageEvent("John", v.getName, 10),
|
||||
DamageEvent(
|
||||
"John",
|
||||
v.getName,
|
||||
CharacterStats.DamageHealth(10)
|
||||
),
|
||||
"damageHandler"
|
||||
)
|
||||
}
|
||||
@ -327,6 +329,7 @@ class MainAppDelegate(
|
||||
): IO[AppError, PlayerActorSupervisor.Ref] = {
|
||||
val playerPos = ImVector3f.Zero
|
||||
val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o"
|
||||
// val modelPath = os.rel / "Models" / "Oto" / "Oto.mesh.xml"
|
||||
val playerPhysicsControl =
|
||||
PlayerController.Defaults.defaultPlayerPhysicsControl
|
||||
|
||||
@ -335,6 +338,7 @@ class MainAppDelegate(
|
||||
assetManager
|
||||
.loadModelAs[Node](modelPath)
|
||||
.map(_.withRotate(0, FastMath.PI, 0))
|
||||
.tapEval(m => UIO(m.center()))
|
||||
.mapError(AppError.AssetManagerError)
|
||||
playerNode <- UIO(
|
||||
PlayerController.Defaults
|
||||
@ -346,9 +350,7 @@ class MainAppDelegate(
|
||||
)
|
||||
cameraPivotNode <- UIO(
|
||||
new Node(EntityIds.CameraPivot.value)
|
||||
.withControl(
|
||||
new FollowControl(playerNode)
|
||||
)
|
||||
.withControl(new FollowControl(playerNode))
|
||||
.taggedWith[PlayerController.Tags.PlayerCameraPivotNode]
|
||||
)
|
||||
camNode <- UIO(
|
||||
@ -405,24 +407,24 @@ class MainAppDelegate(
|
||||
.toIO
|
||||
.hideErrors
|
||||
.startAndForget
|
||||
_ <-
|
||||
Observable
|
||||
.interval(10.millis)
|
||||
.doOnNextF(_ =>
|
||||
Coeval {
|
||||
val location = playerNode.getWorldTranslation()
|
||||
cameraPivotNode.setLocalTranslation(location)
|
||||
}
|
||||
)
|
||||
.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)
|
||||
playerActor <- wire[PlayerController.Props].create
|
||||
obs <-
|
||||
playerActor
|
||||
.askL(PlayerActorSupervisor.GetStatsObservable)
|
||||
.askL(PlayerActorSupervisor.GetStatsObservable2)
|
||||
.onErrorHandleWith(TimeoutError.from)
|
||||
_ <-
|
||||
obs
|
||||
|
@ -5,21 +5,16 @@ import akka.actor.typed.SpawnProtocol
|
||||
import cats.effect.Resource
|
||||
import io.odin.Logger
|
||||
import monix.bio.Task
|
||||
import monix.execution.Scheduler
|
||||
import wow.doge.mygame.executors.ExecutorsModule
|
||||
import wow.doge.mygame.executors.Schedulers.AsyncScheduler
|
||||
|
||||
trait MainModule extends ExecutorsModule {
|
||||
|
||||
def actorSystemResource(
|
||||
logger: Logger[Task],
|
||||
scheduler: Scheduler
|
||||
): Resource[Task, ActorSystem[SpawnProtocol.Command]] =
|
||||
class ActorSystemResource(logger: Logger[Task], scheduler: AsyncScheduler) {
|
||||
def get: Resource[Task, ActorSystem[SpawnProtocol.Command]] =
|
||||
Resource.make(
|
||||
logger.info("Creating Actor System") >> Task(
|
||||
ActorSystem(
|
||||
SpawnProtocol(),
|
||||
name = "GameActorSystem",
|
||||
BootstrapSetup().withDefaultExecutionContext(scheduler)
|
||||
BootstrapSetup().withDefaultExecutionContext(scheduler.value)
|
||||
)
|
||||
)
|
||||
)(sys =>
|
||||
|
81
src/main/scala/wow/doge/mygame/actor/GameActorSystem.scala
Normal file
81
src/main/scala/wow/doge/mygame/actor/GameActorSystem.scala
Normal file
@ -0,0 +1,81 @@
|
||||
package wow.doge.mygame.actor
|
||||
|
||||
import akka.actor.typed.ActorRef
|
||||
import akka.actor.typed.SpawnProtocol
|
||||
import akka.actor.typed.scaladsl.ActorContext
|
||||
import akka.actor.typed.scaladsl.Behaviors
|
||||
import wow.doge.mygame.implicits._
|
||||
// import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
|
||||
// import wow.doge.mygame.subsystems.events.Event
|
||||
// import scala.reflect.ClassTag
|
||||
// import akka.actor.typed.LogOptions
|
||||
// import wow.doge.mygame.subsystems.events.EventBus
|
||||
// import scala.concurrent.duration._
|
||||
// import akka.util.Timeout
|
||||
// import akka.actor.typed.SupervisorStrategy
|
||||
|
||||
object GameActorSystem {
|
||||
sealed trait Command
|
||||
case class GetSpawnProtocol(
|
||||
replyTo: ActorRef[ActorRef[SpawnProtocol.Command]]
|
||||
) extends Command
|
||||
|
||||
class Props() {
|
||||
def create =
|
||||
Behaviors.setup[Command] { ctx =>
|
||||
val systemSpawnProtocol = ctx.spawnN(SpawnProtocol())
|
||||
new GameActorSystem(ctx, this, systemSpawnProtocol).receive
|
||||
}
|
||||
}
|
||||
}
|
||||
class GameActorSystem(
|
||||
ctx: ActorContext[GameActorSystem.Command],
|
||||
props: GameActorSystem.Props,
|
||||
sp: ActorRef[SpawnProtocol.Command]
|
||||
) {
|
||||
import GameActorSystem._
|
||||
def receive =
|
||||
Behaviors.receiveMessage[Command] {
|
||||
case GetSpawnProtocol(replyTo) =>
|
||||
replyTo ! sp
|
||||
Behaviors.same
|
||||
}
|
||||
}
|
||||
|
||||
// object EventBusSupervisor {
|
||||
// sealed trait Command
|
||||
// case class GetMainEventBus(replyTo: ActorRef[GameEventBus[Event]])
|
||||
// extends Command
|
||||
// case class GetEventBus[T](replyTo: ActorRef[GameEventBus[T]])(implicit
|
||||
// classTag: ClassTag[T]
|
||||
// ) extends Command {
|
||||
// def ct = classTag
|
||||
// }
|
||||
|
||||
// class Props(val spawnProtocol: ActorRef[SpawnProtocol.Command]) {
|
||||
// def create =
|
||||
// Behaviors.setup[Command] { ctx =>
|
||||
// new EventBusSupervisor(ctx, this).receive
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// class EventBusSupervisor(
|
||||
// ctx: ActorContext[EventBusSupervisor.Command],
|
||||
// props: EventBusSupervisor.Props
|
||||
// ) {
|
||||
// import EventBusSupervisor._
|
||||
// implicit val timeout = Timeout(1.second)
|
||||
// implicit val sp = props.spawnProtocol
|
||||
// def receive =
|
||||
// Behaviors.receiveMessage[Command] {
|
||||
// case g @ GetEventBus(replyTo) =>
|
||||
// implicit val ct = g.ct
|
||||
// Behaviors
|
||||
// .supervise(EventBus())
|
||||
// .onFailure[Exception](
|
||||
// SupervisorStrategy.restart.withLimit(2, 100.millis)
|
||||
// )
|
||||
// Behaviors.same
|
||||
// case _ => Behaviors.same
|
||||
// }
|
||||
// }
|
@ -1,31 +1,22 @@
|
||||
package wow.doge.mygame.executors
|
||||
|
||||
import cats.effect.Resource
|
||||
import monix.bio.IO
|
||||
import monix.bio.Task
|
||||
import monix.bio.UIO
|
||||
import monix.execution.Scheduler
|
||||
import wow.doge.mygame.types.JmeScheduler
|
||||
|
||||
trait ExecutorsModule {
|
||||
val schedulers = Schedulers()
|
||||
val acquire: UIO[Either[Error, Int]] =
|
||||
IO.pure(1).onErrorHandleWith(_ => IO.raiseError(Error)).attempt
|
||||
// : Resource[IO[Error, Unit], Unit]
|
||||
val res = Resource.make(acquire)(_ => IO.unit)
|
||||
val x: Task[Either[Error, Unit]] = res.use {
|
||||
case Right(value) => Task(Right(println(s"got $value")))
|
||||
case Left(value) => Task(Left(value))
|
||||
}
|
||||
val z = x.onErrorHandleWith(ex => UIO(Right(ex.printStackTrace())))
|
||||
val y: IO[Error, Unit] = z >>
|
||||
x.hideErrors.rethrow
|
||||
|
||||
val jMESchedulerResource = Resource.make(
|
||||
val schedulers = Schedulers.default
|
||||
|
||||
val jmeSchedulerResource = Resource.make(
|
||||
Task(
|
||||
Scheduler
|
||||
.singleThread(name = "JME-Application-Thread", daemonic = false)
|
||||
JmeScheduler(
|
||||
Scheduler
|
||||
.singleThread(name = "JME-Application-Thread", daemonic = false)
|
||||
)
|
||||
)
|
||||
)(e => Task(e.shutdown()))
|
||||
)(s => Task(s.value.shutdown()))
|
||||
}
|
||||
|
||||
sealed trait Error
|
||||
|
@ -13,6 +13,7 @@ import scala.concurrent.ExecutionContext
|
||||
import akka.dispatch.DispatcherPrerequisites
|
||||
import akka.dispatch.ExecutorServiceConfigurator
|
||||
import akka.dispatch.ExecutorServiceFactory
|
||||
import com.jme3.app.Application
|
||||
import com.typesafe.config.Config
|
||||
import javafx.application.Platform
|
||||
import monix.execution.Scheduler
|
||||
@ -42,13 +43,11 @@ object SwingExecutorService extends GUIExecutorService {
|
||||
|
||||
object JMEExecutorService extends GUIExecutorService {
|
||||
override def execute(command: Runnable) =
|
||||
JMERunner.runner.get.apply(command)
|
||||
// new SingleThreadEventExecutor()
|
||||
sys.addShutdownHook(JMEExecutorService.shutdown())
|
||||
JMERunner.runner.enqueue(command)
|
||||
}
|
||||
|
||||
object JMERunner {
|
||||
var runner: Option[Runnable => Unit] = None
|
||||
var runner: Application = null
|
||||
|
||||
}
|
||||
|
||||
@ -98,9 +97,8 @@ class SwingEventThreadExecutorServiceConfigurator(
|
||||
object JFXExecutionContexts {
|
||||
val javaFxExecutionContext: ExecutionContext =
|
||||
ExecutionContext.fromExecutor(new Executor {
|
||||
def execute(command: Runnable): Unit = {
|
||||
def execute(command: Runnable): Unit =
|
||||
Platform.runLater(command)
|
||||
}
|
||||
})
|
||||
val fxScheduler =
|
||||
Scheduler(javaFxExecutionContext)
|
||||
|
@ -5,13 +5,9 @@ import monix.execution.Scheduler
|
||||
import monix.execution.UncaughtExceptionReporter
|
||||
|
||||
final case class Schedulers(
|
||||
blockingIO: Scheduler = Scheduler
|
||||
.io()
|
||||
.withUncaughtExceptionReporter(Schedulers.reporter),
|
||||
async: Scheduler = Scheduler.global
|
||||
.withUncaughtExceptionReporter(Schedulers.reporter),
|
||||
fx: Scheduler = JFXExecutionContexts.fxScheduler
|
||||
.withUncaughtExceptionReporter(Schedulers.reporter)
|
||||
blockingIO: Schedulers.IoScheduler,
|
||||
async: Schedulers.AsyncScheduler,
|
||||
fx: Schedulers.FxScheduler
|
||||
)
|
||||
|
||||
object Schedulers {
|
||||
@ -20,4 +16,24 @@ object Schedulers {
|
||||
logger.error("Uncaught exception", ex)
|
||||
}
|
||||
|
||||
val default = Schedulers(
|
||||
IoScheduler(
|
||||
Scheduler
|
||||
.io()
|
||||
.withUncaughtExceptionReporter(Schedulers.reporter)
|
||||
),
|
||||
AsyncScheduler(
|
||||
Scheduler.global
|
||||
.withUncaughtExceptionReporter(Schedulers.reporter)
|
||||
),
|
||||
FxScheduler(
|
||||
JFXExecutionContexts.fxScheduler
|
||||
.withUncaughtExceptionReporter(Schedulers.reporter)
|
||||
)
|
||||
)
|
||||
|
||||
case class AsyncScheduler(value: Scheduler)
|
||||
case class IoScheduler(value: Scheduler)
|
||||
case class FxScheduler(value: Scheduler)
|
||||
|
||||
}
|
||||
|
@ -25,9 +25,6 @@ import monix.bio.UIO
|
||||
import monix.catnap.ConcurrentChannel
|
||||
import monix.catnap.ConsumerF
|
||||
import monix.eval.Coeval
|
||||
import monix.execution.CancelableFuture
|
||||
import monix.execution.CancelablePromise
|
||||
import monix.execution.Scheduler
|
||||
import wow.doge.mygame.AppError
|
||||
import wow.doge.mygame.AppError.TimeoutError
|
||||
import wow.doge.mygame.Dispatchers
|
||||
@ -35,10 +32,11 @@ import wow.doge.mygame.executors.JMERunner
|
||||
import wow.doge.mygame.executors.Schedulers
|
||||
import wow.doge.mygame.game.subsystems.ui.JFxUI
|
||||
import wow.doge.mygame.implicits._
|
||||
import wow.doge.mygame.utils.AkkaUtils
|
||||
import wow.doge.mygame.utils.GenericTimerActor
|
||||
import wow.doge.mygame.utils.wrappers.jme._
|
||||
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
|
||||
import wow.doge.mygame.subsystems.events.TickEvent
|
||||
import wow.doge.mygame.types._
|
||||
import wow.doge.mygame.utils.AkkaUtils
|
||||
import wow.doge.mygame.utils.wrappers.jme._
|
||||
object GameAppTags {
|
||||
sealed trait RootNode
|
||||
sealed trait GuiNode
|
||||
@ -47,9 +45,9 @@ object GameAppTags {
|
||||
class GameApp private[game] (
|
||||
logger: Logger[Task],
|
||||
app: SimpleAppExt,
|
||||
gameActor: ActorRef[TestGameActor.Command],
|
||||
gameActor: ActorRef[GameAppActor.Command],
|
||||
gameSpawnProtocol: ActorRef[SpawnProtocol.Command],
|
||||
scheduler: akka.actor.typed.Scheduler
|
||||
akkaScheduler: AkkaScheduler
|
||||
) {
|
||||
def inputManager: UIO[InputManager] = UIO(app.getInputManager())
|
||||
val assetManager = new AssetManager(app.getAssetManager())
|
||||
@ -75,7 +73,7 @@ class GameApp private[game] (
|
||||
)(implicit name: sourcecode.Name) =
|
||||
AkkaUtils.spawnActorL(behavior, actorName, props)(
|
||||
2.seconds,
|
||||
scheduler,
|
||||
akkaScheduler.value,
|
||||
gameSpawnProtocol,
|
||||
name
|
||||
)
|
||||
@ -87,19 +85,21 @@ class GameApp private[game] (
|
||||
|
||||
class GameAppResource(
|
||||
logger: Logger[Task],
|
||||
jmeThread: Scheduler,
|
||||
schedulers: Schedulers
|
||||
jmeThread: JmeScheduler,
|
||||
schedulers: Schedulers,
|
||||
tickEventBus: GameEventBus[TickEvent]
|
||||
)(implicit
|
||||
timeout: Timeout,
|
||||
scheduler: akka.actor.typed.Scheduler,
|
||||
scheduler: AkkaScheduler,
|
||||
spawnProtocol: ActorRef[SpawnProtocol.Command]
|
||||
) {
|
||||
implicit val as = scheduler.value
|
||||
def resource
|
||||
: Resource[UIO, Either[AppError, (GameApp, Fiber[Nothing, Unit])]] =
|
||||
Resource.make(
|
||||
(for {
|
||||
app <- UIO(new SimpleAppExt(schedulers, new BulletAppState))
|
||||
_ <- UIO(JMERunner.runner = Some(app.enqueue _))
|
||||
_ <- UIO(JMERunner.runner = app)
|
||||
_ <- UIO {
|
||||
val settings = new AppSettings(true)
|
||||
settings.setVSync(true)
|
||||
@ -112,20 +112,21 @@ class GameAppResource(
|
||||
app.setSettings(settings)
|
||||
}
|
||||
|
||||
fib <- UIO(app.start).executeOn(jmeThread).start
|
||||
fib <- UIO(app.start).executeOn(jmeThread.value).start
|
||||
_ <- Task.deferFuture(app.started).onErrorHandleWith(TimeoutError.from)
|
||||
testGameActor <- AkkaUtils.spawnActorL(
|
||||
new TestGameActor.Props().create,
|
||||
gameAppActor <- AkkaUtils.spawnActorL(
|
||||
new GameAppActor.Props(tickEventBus).behavior,
|
||||
Some("testGameActor"),
|
||||
props = Dispatchers.jmeDispatcher
|
||||
)
|
||||
_ <- gameAppActor !! GameAppActor.Start
|
||||
sp <-
|
||||
testGameActor
|
||||
.askL(TestGameActor.GetSpawnProtocol)
|
||||
gameAppActor
|
||||
.askL(GameAppActor.GetSpawnProtocol)
|
||||
.onErrorHandleWith(TimeoutError.from)
|
||||
gameApp <- UIO(new GameApp(logger, app, testGameActor, sp, scheduler))
|
||||
gameApp <- UIO(new GameApp(logger, app, gameAppActor, sp, scheduler))
|
||||
_ <- UIO {
|
||||
val fut = () => testGameActor.ask(TestGameActor.Stop).flatten
|
||||
val fut = () => gameAppActor.ask(GameAppActor.Stop).flatten
|
||||
app.cancelToken = Some(fut)
|
||||
}
|
||||
} yield (gameApp, fib)).attempt
|
||||
@ -137,58 +138,6 @@ class GameAppResource(
|
||||
|
||||
object GameApp {}
|
||||
|
||||
import akka.actor.typed.scaladsl.Behaviors
|
||||
import akka.actor.typed.scaladsl.ActorContext
|
||||
|
||||
object TestGameActor {
|
||||
sealed trait Command
|
||||
case object Ping extends Command
|
||||
case class Stop(stopSignal: ActorRef[CancelableFuture[Unit]]) extends Command
|
||||
case class GetSpawnProtocol(
|
||||
replyTo: ActorRef[ActorRef[SpawnProtocol.Command]]
|
||||
) extends Command
|
||||
import scala.concurrent.duration._
|
||||
class Props() {
|
||||
def create =
|
||||
Behaviors.setup[Command] { ctx =>
|
||||
ctx.spawn(
|
||||
GenericTimerActor
|
||||
.Props(ctx.self, Ping, 1000.millis)
|
||||
.behavior,
|
||||
"pingTimer"
|
||||
) ! GenericTimerActor.Start
|
||||
new TestGameActor(ctx, this).receive
|
||||
}
|
||||
}
|
||||
}
|
||||
class TestGameActor(
|
||||
ctx: ActorContext[TestGameActor.Command],
|
||||
props: TestGameActor.Props
|
||||
) {
|
||||
import TestGameActor._
|
||||
val stopPromise = CancelablePromise[Unit]()
|
||||
def receive =
|
||||
Behaviors
|
||||
.receiveMessage[Command] {
|
||||
case Stop(replyTo) =>
|
||||
ctx.log.infoP("stopping")
|
||||
replyTo ! stopPromise.future
|
||||
Behaviors.stopped
|
||||
case Ping =>
|
||||
ctx.log.debugP("ping")
|
||||
Behaviors.same
|
||||
case GetSpawnProtocol(replyTo) =>
|
||||
val sp = ctx.spawn(SpawnProtocol(), "gameSpawnProtocol")
|
||||
replyTo ! sp
|
||||
Behaviors.same
|
||||
}
|
||||
.receiveSignal {
|
||||
case (_, akka.actor.typed.PostStop) =>
|
||||
stopPromise.success(())
|
||||
Behaviors.same
|
||||
}
|
||||
}
|
||||
|
||||
object Ops {
|
||||
final class AddToNode[T <: Node](private val node: T) extends AnyVal {
|
||||
|
||||
|
@ -2,14 +2,18 @@ package wow.doge.mygame.game
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import akka.actor.typed.ActorRef
|
||||
import akka.actor.typed.PostStop
|
||||
import akka.actor.typed.SpawnProtocol
|
||||
import akka.actor.typed.SupervisorStrategy
|
||||
import akka.actor.typed.scaladsl.Behaviors
|
||||
import monix.execution.CancelableFuture
|
||||
import monix.execution.CancelablePromise
|
||||
import wow.doge.mygame.game.TickGenerator.Send
|
||||
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.TickEvent
|
||||
import wow.doge.mygame.subsystems.events.TickEvent.PhysicsTick
|
||||
import wow.doge.mygame.utils.GenericTimerActor
|
||||
|
||||
object GameAppActor {
|
||||
@ -17,7 +21,11 @@ object GameAppActor {
|
||||
sealed trait Command
|
||||
case object Start extends Command
|
||||
case object Pause extends Command
|
||||
case object Stop extends Command
|
||||
case object Ping extends Command
|
||||
case class Stop(stopSignal: ActorRef[CancelableFuture[Unit]]) extends Command
|
||||
case class GetSpawnProtocol(
|
||||
replyTo: ActorRef[ActorRef[SpawnProtocol.Command]]
|
||||
) extends Command
|
||||
|
||||
case class Props(tickEventBus: GameEventBus[TickEvent]) {
|
||||
def behavior =
|
||||
@ -38,19 +46,43 @@ object GameAppActor {
|
||||
.behavior
|
||||
)
|
||||
|
||||
Behaviors.receiveMessage {
|
||||
case Start =>
|
||||
tickGeneratorTimer ! GenericTimerActor.Start
|
||||
Behaviors.same
|
||||
case Pause =>
|
||||
tickGeneratorTimer ! GenericTimerActor.Stop
|
||||
Behaviors.same
|
||||
case Stop =>
|
||||
ctx.log.info("Received stop")
|
||||
tickGeneratorTimer ! GenericTimerActor.Stop
|
||||
Behaviors.stopped
|
||||
val sp = ctx.spawn(SpawnProtocol(), "gameSpawnProtocol")
|
||||
|
||||
}
|
||||
ctx.spawn(
|
||||
GenericTimerActor
|
||||
.Props(ctx.self, Ping, 1000.millis)
|
||||
.behavior,
|
||||
"pingTimer"
|
||||
) ! GenericTimerActor.Start
|
||||
|
||||
val stopPromise = CancelablePromise[Unit]()
|
||||
|
||||
Behaviors
|
||||
.receiveMessage[Command] {
|
||||
case Start =>
|
||||
tickGeneratorTimer ! GenericTimerActor.Start
|
||||
Behaviors.same
|
||||
case Pause =>
|
||||
tickGeneratorTimer ! GenericTimerActor.Stop
|
||||
Behaviors.same
|
||||
case Stop(replyTo) =>
|
||||
ctx.log.infoP("Received stop")
|
||||
tickGeneratorTimer ! GenericTimerActor.Stop
|
||||
replyTo ! stopPromise.future
|
||||
Behaviors.stopped
|
||||
case Ping =>
|
||||
ctx.log.debugP("ping")
|
||||
Behaviors.same
|
||||
case GetSpawnProtocol(replyTo) =>
|
||||
replyTo ! sp
|
||||
Behaviors.same
|
||||
|
||||
}
|
||||
.receiveSignal {
|
||||
case (_, PostStop) =>
|
||||
stopPromise.success(())
|
||||
Behaviors.same
|
||||
}
|
||||
}
|
||||
|
||||
val renderTickGeneratorBehavior =
|
||||
@ -69,92 +101,3 @@ object TickGenerator {
|
||||
sealed trait Command
|
||||
case object Send extends Command
|
||||
}
|
||||
object SubscribingActor {
|
||||
def apply() =
|
||||
Behaviors.receive[PhysicsTick.type] { (ctx, msg) =>
|
||||
ctx.log.debugP(s"received event $msg")
|
||||
Behaviors.same
|
||||
}
|
||||
}
|
||||
object Methods {
|
||||
|
||||
def old() = {
|
||||
// val movementActor =
|
||||
// ctx.spawn(
|
||||
// MovementActor(MovementActor.Props(app, geom)),
|
||||
// "movementActor"
|
||||
// // DispatcherSelector.fromConfig("jme-dispatcher")
|
||||
// )
|
||||
|
||||
// val movementActorTimer = ctx.spawn(
|
||||
// MovementActorTimer(movementActor),
|
||||
// "movementActorTimer"
|
||||
// )
|
||||
}
|
||||
|
||||
def old2() = {
|
||||
// ctx.log.info("here")
|
||||
|
||||
// {
|
||||
// implicit val s = schedulers.async
|
||||
// Task
|
||||
// .parZip2(
|
||||
// loggerL.info("Test").executeOn(app.scheduler),
|
||||
// app
|
||||
// .enqueueL(() => loggerL.info("here 2").executeOn(app.scheduler))
|
||||
// .flatten
|
||||
// )
|
||||
// .runToFuture
|
||||
// }
|
||||
|
||||
// app
|
||||
// .getRootNode()
|
||||
// .depthFirst(s =>
|
||||
// // s match {
|
||||
// // case node: Node =>
|
||||
// // println("node" + s.getName() + " children " + node.getChildren())
|
||||
// // case g: Geometry => println(s.getName())
|
||||
// // }
|
||||
// println(s.getName())
|
||||
// )
|
||||
|
||||
// println("----------------")
|
||||
|
||||
// {
|
||||
// app
|
||||
// .getRootNode()
|
||||
// .observableDepthFirst()
|
||||
// .map(s => s.getName())
|
||||
// // .takeWhileInclusive(_.getName() != "level")
|
||||
// .onErrorHandle(e => e.getMessage())
|
||||
// .foreach(println)(schedulers.async)
|
||||
|
||||
// }
|
||||
|
||||
// println("----------------")
|
||||
|
||||
// {
|
||||
// app
|
||||
// .getRootNode()
|
||||
// .observableBreadthFirst()
|
||||
// .map(s => s.getName())
|
||||
// // .takeWhileInclusive(_.getName() != "level")
|
||||
// .onErrorHandle(e => e.getMessage())
|
||||
// .foreach(println)(schedulers.async)
|
||||
|
||||
// }
|
||||
|
||||
// app.start()
|
||||
// Behaviors.same
|
||||
}
|
||||
}
|
||||
|
||||
// new PlayerMovementState(
|
||||
// // movementActor,
|
||||
// // movementActorTimer,
|
||||
// imMovementActor,
|
||||
// // geom,
|
||||
// // camNode,
|
||||
// playerNode
|
||||
// // ctx.self
|
||||
// )
|
||||
|
@ -1,19 +1,31 @@
|
||||
package wow.doge.mygame.game.controls
|
||||
|
||||
import scala.concurrent.Future
|
||||
|
||||
import com.jme3.math.FastMath
|
||||
import com.jme3.math.Quaternion
|
||||
import com.jme3.scene.Node
|
||||
import com.jme3.scene.control.AbstractControl
|
||||
import com.jme3.math.Vector3f
|
||||
import com.jme3.renderer.RenderManager
|
||||
import com.jme3.renderer.ViewPort
|
||||
import monix.reactive.Observable
|
||||
import wow.doge.mygame.game.subsystems.input.PlayerCameraInput
|
||||
import com.jme3.scene.Node
|
||||
import com.jme3.scene.Spatial
|
||||
import monix.{eval => me}
|
||||
import com.jme3.math.FastMath
|
||||
import com.jme3.math.Vector3f
|
||||
import com.jme3.scene.control.AbstractControl
|
||||
import monix.execution.Ack
|
||||
import monix.execution.Cancelable
|
||||
import monix.execution.Scheduler
|
||||
import monix.reactive.Observable
|
||||
import monix.reactive.Observer
|
||||
import wow.doge.mygame.game.subsystems.input.PlayerCameraInput
|
||||
|
||||
/**
|
||||
* A very low level (and error prone) camera movement control implementation.
|
||||
* Not used currently
|
||||
*
|
||||
* @param rotationBuf
|
||||
* @param obs
|
||||
* @param rotateFn
|
||||
* @param s
|
||||
*/
|
||||
class CameraMovementControl(
|
||||
rotationBuf: Quaternion,
|
||||
obs: Observable[PlayerCameraInput],
|
||||
@ -23,8 +35,21 @@ class CameraMovementControl(
|
||||
private var _event: PlayerCameraInput = null
|
||||
private var _subscriptionToken: Cancelable = null
|
||||
|
||||
private val sink = new Observer[PlayerCameraInput] {
|
||||
|
||||
override def onNext(event: PlayerCameraInput): Future[Ack] = {
|
||||
_event = event
|
||||
Ack.Continue
|
||||
}
|
||||
|
||||
override def onError(ex: Throwable): Unit = {}
|
||||
|
||||
override def onComplete(): Unit = {}
|
||||
|
||||
}
|
||||
|
||||
override def controlUpdate(tpf: Float): Unit =
|
||||
if (_event != null)
|
||||
if (_event != null) {
|
||||
_event match {
|
||||
case PlayerCameraInput.CameraRotateLeft =>
|
||||
val rot = rotationBuf
|
||||
@ -43,6 +68,8 @@ class CameraMovementControl(
|
||||
.fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X)
|
||||
rotateFn(rot)
|
||||
}
|
||||
_event = null
|
||||
}
|
||||
|
||||
override def controlRender(
|
||||
x$1: RenderManager,
|
||||
@ -51,8 +78,7 @@ class CameraMovementControl(
|
||||
override def setSpatial(spatial: Spatial): Unit = {
|
||||
super.setSpatial(spatial)
|
||||
if (this.spatial != null)
|
||||
_subscriptionToken =
|
||||
obs.doOnNext(event => me.Task { _event = event }).subscribe()
|
||||
_subscriptionToken = obs.subscribe(sink)
|
||||
else {
|
||||
_subscriptionToken.cancel()
|
||||
_subscriptionToken = null
|
||||
|
@ -0,0 +1,116 @@
|
||||
package wow.doge.mygame.game.entities
|
||||
|
||||
import akka.actor.typed.ActorRef
|
||||
import akka.actor.typed.Behavior
|
||||
import akka.actor.typed.scaladsl.ActorContext
|
||||
import akka.actor.typed.scaladsl.Behaviors
|
||||
import io.estatico.newtype.macros.newtype
|
||||
import wow.doge.mygame.game.entities.CharacterStats.HealHealth
|
||||
|
||||
case class CharacterStats(hp: CharacterStats.Health, stamina: Int)
|
||||
object CharacterStats {
|
||||
@newtype case class HealHealth(toInt: Int)
|
||||
@newtype case class DamageHealth(toInt: Int)
|
||||
@newtype 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)
|
||||
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)
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
object StatsActor {
|
||||
|
||||
sealed trait Command
|
||||
// case class TakeDamage(value: Int) extends Command
|
||||
case class TakeDamageResult(
|
||||
value: CharacterStats.DamageHealth,
|
||||
replyTo: ActorRef[(Boolean, CharacterStats)]
|
||||
) extends Command
|
||||
case class HealResult(value: HealHealth) extends Command
|
||||
case class CurrentStats(replyTo: ActorRef[CharacterStats]) extends Command
|
||||
|
||||
class Props(
|
||||
startingHealth: CharacterStats.Health,
|
||||
startingStamina: CharacterStats.Stamina
|
||||
) {
|
||||
def behavior =
|
||||
Behaviors.setup[Command] { ctx =>
|
||||
new StatsActor(ctx, this)
|
||||
.receive(
|
||||
State(CharacterStats(startingHealth, startingStamina.toInt))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
case class State(stats: CharacterStats)
|
||||
}
|
||||
class StatsActor(
|
||||
ctx: ActorContext[StatsActor.Command],
|
||||
props: StatsActor.Props
|
||||
) {
|
||||
import StatsActor._
|
||||
import CharacterStats._
|
||||
import com.softwaremill.quicklens._
|
||||
def receive(state: State): Behavior[Command] =
|
||||
Behaviors.receiveMessage[Command] {
|
||||
// Todo add min max values
|
||||
// case TakeDamage(value) =>
|
||||
// val nextState =
|
||||
// if (state.stats.hp - value <= 0)
|
||||
// state.modify(_.stats.hp).setTo(0)
|
||||
// else
|
||||
// state.modify(_.stats.hp).using(_ - value)
|
||||
// receive(nextState)
|
||||
case TakeDamageResult(value, replyTo) =>
|
||||
val nextState = if ((state.stats.hp - value).toInt <= 0) {
|
||||
replyTo ! true -> state.stats
|
||||
state.modify(_.stats.hp).setTo(Health(0))
|
||||
} else {
|
||||
replyTo ! false -> state.stats
|
||||
state.modify(_.stats.hp).using(_ - value)
|
||||
}
|
||||
receive(nextState)
|
||||
case HealResult(value) =>
|
||||
receive(state.modify(_.stats.hp).using(_ :+ value))
|
||||
case CurrentStats(replyTo) =>
|
||||
replyTo ! state.stats
|
||||
Behaviors.same
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
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
|
||||
@ -9,9 +11,15 @@ 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
|
||||
@ -19,13 +27,8 @@ 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 scala.util.Success
|
||||
import scala.util.Failure
|
||||
import akka.util.Timeout
|
||||
import monix.reactive.Observable
|
||||
import monix.reactive.subjects.ConcurrentSubject
|
||||
import monix.execution.Scheduler
|
||||
import monix.reactive.OverflowStrategy
|
||||
import monix.eval.Coeval
|
||||
import monix.execution.AsyncQueue
|
||||
object PlayerActorSupervisor {
|
||||
|
||||
type Ref = ActorRef[PlayerActorSupervisor.Command]
|
||||
@ -37,23 +40,33 @@ object PlayerActorSupervisor {
|
||||
}
|
||||
|
||||
sealed trait Command
|
||||
case class TakeDamage(value: Int) extends Command
|
||||
case class Heal(value: Int) extends Command
|
||||
case class CurrentStats(replyTo: ActorRef[StatsActor.State]) extends 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[StatsActor.State]])
|
||||
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, StatsActor.State))
|
||||
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: Scheduler
|
||||
val scheduler: AsyncScheduler
|
||||
) {
|
||||
def behavior =
|
||||
Behaviors.logMessages(
|
||||
@ -78,7 +91,12 @@ object PlayerActorSupervisor {
|
||||
)
|
||||
|
||||
val playerStatsActor =
|
||||
ctx.spawnN(new StatsActor.Props(100, 100).behavior)
|
||||
ctx.spawnN(
|
||||
new StatsActor.Props(
|
||||
CharacterStats.Health(100),
|
||||
CharacterStats.Stamina(100)
|
||||
).behavior
|
||||
)
|
||||
|
||||
val playerMovementEl = ctx.spawnN(
|
||||
Behaviors
|
||||
@ -115,7 +133,13 @@ object PlayerActorSupervisor {
|
||||
ctx,
|
||||
this,
|
||||
Children(playerMovementActor, playerStatsActor),
|
||||
ConcurrentSubject.publish(OverflowStrategy.DropOld(50))(scheduler)
|
||||
ConcurrentSubject.publish(
|
||||
OverflowStrategy.DropOldAndSignal(
|
||||
50,
|
||||
dropped => Coeval.pure(None)
|
||||
)
|
||||
)(scheduler.value),
|
||||
AsyncQueue.bounded(10)(scheduler.value)
|
||||
).aliveState
|
||||
}
|
||||
)
|
||||
@ -131,7 +155,8 @@ class PlayerActorSupervisor(
|
||||
ctx: ActorContext[PlayerActorSupervisor.Command],
|
||||
props: PlayerActorSupervisor.Props,
|
||||
children: PlayerActorSupervisor.Children,
|
||||
statsSubject: ConcurrentSubject[StatsActor.State, StatsActor.State]
|
||||
statsSubject: ConcurrentSubject[CharacterStats, CharacterStats],
|
||||
statsQueue: AsyncQueue[CharacterStats]
|
||||
) {
|
||||
import PlayerActorSupervisor._
|
||||
implicit val timeout = Timeout(1.second)
|
||||
@ -149,12 +174,23 @@ class PlayerActorSupervisor(
|
||||
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.Heal(value)
|
||||
children.statsActor ! StatsActor.HealResult(value)
|
||||
Behaviors.same
|
||||
case GetStatus(replyTo) =>
|
||||
replyTo ! Status.Alive
|
||||
@ -169,6 +205,12 @@ class PlayerActorSupervisor(
|
||||
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) =>
|
||||
@ -176,6 +218,15 @@ class PlayerActorSupervisor(
|
||||
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)
|
||||
@ -215,54 +266,3 @@ class PlayerActorSupervisor(
|
||||
Behaviors.same
|
||||
}
|
||||
}
|
||||
|
||||
object StatsActor {
|
||||
|
||||
sealed trait Command
|
||||
case class TakeDamage(value: Int) extends Command
|
||||
case class TakeDamageResult(value: Int, replyTo: ActorRef[(Boolean, State)])
|
||||
extends Command
|
||||
case class Heal(value: Int) extends Command
|
||||
case class CurrentStats(replyTo: ActorRef[State]) extends Command
|
||||
|
||||
class Props(startingHealth: Int, startingStamina: Int) {
|
||||
def behavior =
|
||||
Behaviors.setup[Command] { ctx =>
|
||||
new StatsActor(ctx, this)
|
||||
.receive(State(startingHealth, startingStamina))
|
||||
}
|
||||
}
|
||||
|
||||
case class State(hp: Int, stamina: Int)
|
||||
}
|
||||
class StatsActor(
|
||||
ctx: ActorContext[StatsActor.Command],
|
||||
props: StatsActor.Props
|
||||
) {
|
||||
import StatsActor._
|
||||
import com.softwaremill.quicklens._
|
||||
def receive(state: State): Behavior[Command] =
|
||||
Behaviors.receiveMessage[Command] {
|
||||
// Todo add min max values
|
||||
case TakeDamage(value) =>
|
||||
val nextState =
|
||||
if (state.hp - value <= 0)
|
||||
state.modify(_.hp).setTo(0)
|
||||
else
|
||||
state.modify(_.hp).using(_ - value)
|
||||
receive(nextState)
|
||||
case TakeDamageResult(value, replyTo) =>
|
||||
val nextState = if (state.hp - value <= 0) {
|
||||
replyTo ! true -> state
|
||||
state
|
||||
} else {
|
||||
replyTo ! false -> state
|
||||
state.modify(_.hp).using(_ - value)
|
||||
}
|
||||
receive(nextState)
|
||||
case Heal(value) => receive(state.modify(_.hp).using(_ + value))
|
||||
case CurrentStats(replyTo) =>
|
||||
replyTo ! state
|
||||
Behaviors.same
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import io.odin.Logger
|
||||
import monix.bio.IO
|
||||
import monix.bio.Task
|
||||
import wow.doge.mygame.AppError
|
||||
import wow.doge.mygame.executors.Schedulers.AsyncScheduler
|
||||
import wow.doge.mygame.game.GameApp
|
||||
import wow.doge.mygame.implicits._
|
||||
import wow.doge.mygame.math.ImVector3f
|
||||
@ -21,8 +22,8 @@ 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.movement.ImMovementActor
|
||||
import wow.doge.mygame.utils.wrappers.jme._
|
||||
import wow.doge.mygame.types._
|
||||
import wow.doge.mygame.utils.wrappers.jme._
|
||||
|
||||
object PlayerController {
|
||||
sealed trait Error
|
||||
@ -49,7 +50,7 @@ object PlayerController {
|
||||
playerEventBus: GameEventBus[PlayerEvent],
|
||||
playerPhysicsControl: BetterCharacterControl,
|
||||
// appScheduler: monix.execution.Scheduler,
|
||||
scheduler: monix.execution.Scheduler,
|
||||
scheduler: AsyncScheduler,
|
||||
playerNode: PlayerNode,
|
||||
cameraNode: PlayerCameraNode,
|
||||
cameraPivotNode: PlayerCameraPivotNode,
|
||||
|
@ -46,6 +46,7 @@ object PlayerMovementEventListener {
|
||||
)
|
||||
}
|
||||
|
||||
//not used
|
||||
object PlayerCameraEventListener {
|
||||
import PlayerCameraEvent._
|
||||
def apply(playerCameraActor: ActorRef[PlayerCameraActor.Command]) =
|
||||
|
@ -10,7 +10,6 @@ import com.jme3.scene.Geometry
|
||||
import com.softwaremill.quicklens._
|
||||
import wow.doge.mygame.game.subsystems.movement.CanMove
|
||||
import wow.doge.mygame.implicits._
|
||||
import wow.doge.mygame.math.ImVector3f
|
||||
|
||||
final case class CardinalDirection(
|
||||
left: Boolean = false,
|
||||
|
@ -756,7 +756,7 @@ package object implicits {
|
||||
|
||||
}
|
||||
|
||||
implicit final class Vector3fExt(private val v: Vector3f) extends AnyVal {
|
||||
implicit final class Vector3fOps(private val v: Vector3f) extends AnyVal {
|
||||
//TODO add more operations
|
||||
def +=(that: Vector3f) = v.addLocal(that)
|
||||
def +=(f: Float) = v.addLocal(f, f, f)
|
||||
@ -777,7 +777,7 @@ package object implicits {
|
||||
// def transformImmutable(f: Vector3f => Vector3f) = f(v).immutable
|
||||
}
|
||||
|
||||
implicit final class ImVector3fExt(private val v: ImVector3f) extends AnyVal {
|
||||
implicit final class ImVector3fOps(private val v: ImVector3f) extends AnyVal {
|
||||
def +(that: ImVector3f) = v.copy(v.x + that.x, v.y + that.y, v.z + that.z)
|
||||
def +(f: Float): ImVector3f = v.copy(v.x + f, v.y + f, v.z + f)
|
||||
def *(that: ImVector3f) = v.copy(v.x * that.x, v.y * that.y, v.z * that.z)
|
||||
@ -867,7 +867,7 @@ package object implicits {
|
||||
|
||||
}
|
||||
|
||||
implicit class OdinLoggerExt(private val logger: io.odin.Logger[Task])
|
||||
implicit final class OdinLoggerExt(private val logger: io.odin.Logger[Task])
|
||||
extends AnyVal {
|
||||
def debugU[M](msg: => M)(implicit render: Render[M], position: Position) =
|
||||
logger.debug(msg).hideErrors
|
||||
@ -881,7 +881,7 @@ package object implicits {
|
||||
logger.error(msg).hideErrors
|
||||
}
|
||||
|
||||
implicit class TypedActorContextExt[T](private val ctx: ActorContext[T])
|
||||
implicit final class TypedActorContextExt[T](private val ctx: ActorContext[T])
|
||||
extends AnyVal {
|
||||
def spawnN[U](behavior: Behavior[U], props: Props = Props.empty)(implicit
|
||||
name: sourcecode.Name
|
||||
@ -889,19 +889,20 @@ package object implicits {
|
||||
ctx.spawn(behavior, name.value, props)
|
||||
}
|
||||
|
||||
implicit class MonixEvalTaskExt[T](private val task: monix.eval.Task[T])
|
||||
implicit final class MonixEvalTaskExt[T](private val task: monix.eval.Task[T])
|
||||
extends AnyVal {
|
||||
def toIO = IO.deferAction(implicit s => IO.from(task))
|
||||
}
|
||||
|
||||
implicit class MonixBioTaskExt[T](private val task: monix.bio.Task[T])
|
||||
implicit final class MonixBioTaskExt[T](private val task: monix.bio.Task[T])
|
||||
extends AnyVal {
|
||||
def toTask =
|
||||
monix.eval.Task.deferAction(implicit s => monix.eval.Task.from(task))
|
||||
}
|
||||
|
||||
implicit class CoevalEitherExt[L, R](private val coeval: Coeval[Either[L, R]])
|
||||
extends AnyVal {
|
||||
implicit final class CoevalEitherExt[L, R](
|
||||
private val coeval: Coeval[Either[L, R]]
|
||||
) extends AnyVal {
|
||||
def toIO = coeval.to[Task].hideErrors.rethrow
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ import scalafx.application.JFXApp
|
||||
import scalafx.application.JFXApp.PrimaryStage
|
||||
import scalafx.scene.control.Button
|
||||
import scalafx.stage.StageStyle
|
||||
import wow.doge.mygame.executors.Schedulers
|
||||
import wow.doge.mygame.executors.Schedulers.FxScheduler
|
||||
import wow.doge.mygame.implicits.JavaFXMonixObservables._
|
||||
import wow.doge.mygame.utils.IOUtils._
|
||||
object Launcher {
|
||||
@ -28,7 +28,8 @@ object Launcher {
|
||||
}
|
||||
|
||||
class Props(
|
||||
val schedulers: Schedulers,
|
||||
// val schedulers: Schedulers,
|
||||
val fxScheduler: FxScheduler,
|
||||
val signal: Deferred[Task, LauncherResult]
|
||||
) {
|
||||
val create = UIO(new Launcher(this))
|
||||
@ -105,7 +106,7 @@ class Launcher private (props: Launcher.Props) {
|
||||
Observable(launchAction, exitAction).merge
|
||||
.doOnNext(_ =>
|
||||
me.Task(delegate.stage.close())
|
||||
.executeOn(props.schedulers.fx)
|
||||
.executeOn(props.fxScheduler.value)
|
||||
)
|
||||
.completedL
|
||||
)
|
||||
@ -113,6 +114,7 @@ class Launcher private (props: Launcher.Props) {
|
||||
)
|
||||
)
|
||||
.start
|
||||
_ <- Task.fromCancelablePromise(startSignal)
|
||||
c <- CancelableF[Task](combinedFib.cancel)
|
||||
} yield c)(_.cancel)
|
||||
|
||||
|
@ -43,9 +43,8 @@ object EventBus {
|
||||
|
||||
final case class ObservableSubscription[A, E <: A](
|
||||
replyTo: ActorRef[Observable[E]]
|
||||
)(implicit
|
||||
classTag: ClassTag[E]
|
||||
) extends Command[A] {
|
||||
)(implicit classTag: ClassTag[E])
|
||||
extends Command[A] {
|
||||
def ct = classTag
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
package wow.doge.mygame.subsystems.events
|
||||
|
||||
import wow.doge.mygame.game.entities.CharacterStats
|
||||
|
||||
sealed trait Event
|
||||
case object BulletFired extends Event
|
||||
// type BulletFired = BulletFired.type
|
||||
@ -25,6 +27,9 @@ object EntityMovementEvent {
|
||||
|
||||
sealed trait StatsEvent extends Event
|
||||
object StatsEvent {
|
||||
case class DamageEvent(hitBy: String, victimName: String, amount: Int)
|
||||
extends StatsEvent
|
||||
case class DamageEvent(
|
||||
hitBy: String,
|
||||
victimName: String,
|
||||
amount: CharacterStats.DamageHealth
|
||||
) extends StatsEvent
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import scala.reflect.ClassTag
|
||||
import akka.actor.typed.ActorRef
|
||||
import akka.actor.typed.LogOptions
|
||||
import akka.actor.typed.Props
|
||||
import akka.actor.typed.Scheduler
|
||||
import akka.actor.typed.SpawnProtocol
|
||||
import akka.actor.typed.SupervisorStrategy
|
||||
import akka.actor.typed.scaladsl.Behaviors
|
||||
@ -20,13 +19,14 @@ import wow.doge.mygame.implicits._
|
||||
import wow.doge.mygame.subsystems.events.Event
|
||||
import wow.doge.mygame.subsystems.events.EventBus
|
||||
import wow.doge.mygame.subsystems.events.TickEvent
|
||||
import wow.doge.mygame.types.AkkaScheduler
|
||||
|
||||
class EventsModule(
|
||||
scheduler: Scheduler,
|
||||
scheduler: AkkaScheduler,
|
||||
spawnProtocol: ActorRef[SpawnProtocol.Command]
|
||||
) {
|
||||
import EventsModule._
|
||||
implicit val s = scheduler
|
||||
implicit val s = scheduler.value
|
||||
implicit val sp = spawnProtocol
|
||||
implicit val timeout = Timeout(1.second)
|
||||
|
||||
|
@ -0,0 +1,310 @@
|
||||
package wow.doge.mygame.subsystems.scriptsystem
|
||||
|
||||
import javax.script.ScriptEngine
|
||||
import javax.script.ScriptEngineManager
|
||||
import javax.script.ScriptException
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import ammonite.main.Defaults
|
||||
import ammonite.runtime.Storage.Folder
|
||||
import ammonite.util.Res
|
||||
import ammonite.util.Res.Failure
|
||||
import cats.effect.Resource
|
||||
import cats.effect.concurrent.Deferred
|
||||
import cats.syntax.either._
|
||||
import cats.syntax.flatMap._
|
||||
import com.softwaremill.macwire._
|
||||
import com.softwaremill.tagging._
|
||||
import groovy.util.GroovyScriptEngine
|
||||
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
|
||||
|
||||
trait Requestable[A] {
|
||||
|
||||
protected def queue: ConcurrentQueue[Task, A]
|
||||
|
||||
def request[T](
|
||||
compileRequest: Deferred[Task, T] => A
|
||||
)(implicit timeout: FiniteDuration) =
|
||||
for {
|
||||
d <- Deferred[Task, T]
|
||||
req = compileRequest(d)
|
||||
_ <- queue.offer(req)
|
||||
res <- d.get.timeout(timeout).map(_.get)
|
||||
} yield res
|
||||
}
|
||||
|
||||
class ScriptCompiler private (
|
||||
_queue: ConcurrentQueue[Task, ScriptCompiler.Command]
|
||||
) extends Requestable[ScriptCompiler.Command] {
|
||||
|
||||
override protected def queue = _queue
|
||||
|
||||
// def tell(item: Command) = queue.offer(item)
|
||||
|
||||
}
|
||||
object ScriptCompiler {
|
||||
|
||||
sealed trait State
|
||||
case object Idle extends State
|
||||
case object Active extends State
|
||||
|
||||
/**
|
||||
* script representation
|
||||
*/
|
||||
sealed trait ScriptTag
|
||||
type ScriptObject = Any @@ ScriptTag
|
||||
|
||||
sealed trait KotlinEngineTag
|
||||
type KotlinScriptEngine = ScriptEngine @@ KotlinEngineTag
|
||||
|
||||
sealed trait Error
|
||||
final case class AmmoniteFailure(error: Res.Failure) extends Error
|
||||
final case class AmmoniteException(error: Res.Exception) extends Error
|
||||
final case class ScriptExceptionError(error: ScriptException) extends Error
|
||||
final case class ResourceExceptionError(error: ResourceException)
|
||||
extends Error
|
||||
final case class GroovyScriptExceptionError(
|
||||
error: groovy.util.ScriptException
|
||||
) extends Error
|
||||
final case class SomeError(reason: String) extends Error
|
||||
|
||||
sealed trait Command
|
||||
final case class Get(
|
||||
path: os.Path,
|
||||
result: Deferred[Task, ScriptResult],
|
||||
force: Boolean
|
||||
) extends Command
|
||||
final case class GetData(result: Deferred[Task, Data])
|
||||
final case class ObservableData(result: Deferred[Task, Observable[Data]])
|
||||
extends Command
|
||||
// extends Command
|
||||
// final case class CompileAll(paths: Seq[os.Path]) extends Command
|
||||
|
||||
type ScriptsMap = Map[os.Path, Any]
|
||||
type ScriptResult = Either[Error, Any]
|
||||
|
||||
sealed trait ScriptType
|
||||
case object ScalaType extends ScriptType
|
||||
case object KotlinType extends ScriptType
|
||||
case object GroovyType extends ScriptType
|
||||
|
||||
val defaultScalaRunner =
|
||||
ammonite
|
||||
.Main(
|
||||
storageBackend = new Folder(
|
||||
// os.pwd / "target"
|
||||
Defaults.ammoniteHome,
|
||||
isRepl = false
|
||||
)
|
||||
)
|
||||
|
||||
val defaultKotlinRunner: KotlinScriptEngine = {
|
||||
val manager = new ScriptEngineManager()
|
||||
val engine = manager.getEngineByExtension("main.kts")
|
||||
engine.taggedWith[KotlinEngineTag]
|
||||
}
|
||||
|
||||
val defaultGroovyRunner: GroovyScriptEngine =
|
||||
new GroovyScriptEngine(os.pwd.toString)
|
||||
|
||||
case class Data(scriptsMap: ScriptsMap)
|
||||
|
||||
class SourceMaker(
|
||||
queue: ConcurrentQueue[Task, Command],
|
||||
worker: ScriptCompilerWorker
|
||||
) {
|
||||
import com.softwaremill.quicklens._
|
||||
def get =
|
||||
for {
|
||||
dataVar <- MVar[Task].of(Data(Map.empty))
|
||||
obs <- Task.deferAction(implicit s =>
|
||||
Task(
|
||||
Observable
|
||||
.repeatEvalF(queue.poll)
|
||||
.scanEval0(me.Task.pure((Active: State) -> Data(Map.empty))) {
|
||||
case state -> data -> command =>
|
||||
val nextState: IO[Error, (State, Data)] = state match {
|
||||
case Idle => IO.pure(Idle -> data)
|
||||
case Active =>
|
||||
command match {
|
||||
case Get(path, result, force) =>
|
||||
def getAndUpdate =
|
||||
worker
|
||||
.request(
|
||||
ScriptCompilerWorker.CompileAny(path, _)
|
||||
)(
|
||||
20.seconds
|
||||
)
|
||||
.flatTap(result.complete)
|
||||
.hideErrors
|
||||
.rethrow
|
||||
.flatMap(res =>
|
||||
UIO(pprint.log(res)) >>
|
||||
UIO.pure(
|
||||
data
|
||||
.modify(_.scriptsMap)
|
||||
.using(_ + (path -> res))
|
||||
)
|
||||
)
|
||||
for {
|
||||
nextData <-
|
||||
if (force) getAndUpdate
|
||||
else
|
||||
data.scriptsMap.get(path) match {
|
||||
case Some(e) =>
|
||||
result
|
||||
.complete(e.asRight[Error])
|
||||
.hideErrors >> UIO.pure(data)
|
||||
case None => getAndUpdate
|
||||
}
|
||||
} yield Active -> nextData
|
||||
case ObservableData(result) =>
|
||||
result
|
||||
.complete(Observable.repeatEvalF(dataVar.take))
|
||||
.hideErrors >> IO.pure(Active -> data)
|
||||
}
|
||||
|
||||
}
|
||||
nextState
|
||||
.flatTap { case (_, data) => dataVar.put(data).hideErrors }
|
||||
.tapError(err => UIO(pprint.log(err.toString)))
|
||||
.attempt
|
||||
// .mapFilter(_.toOption)
|
||||
.map(_.getOrElse(state -> data))
|
||||
.toTask
|
||||
}
|
||||
)
|
||||
)
|
||||
} yield obs
|
||||
|
||||
}
|
||||
|
||||
class ScriptCompileFns(
|
||||
val scalaRunner: ammonite.Main,
|
||||
val kotlinRunner: KotlinScriptEngine,
|
||||
val groovyRunner: GroovyScriptEngine
|
||||
) {
|
||||
def runScala(path: os.Path): Either[Error, Any] =
|
||||
scalaRunner
|
||||
.runScript(path, Seq.empty)
|
||||
._1 match {
|
||||
case e @ Res.Exception(t, msg) => Left(AmmoniteException(e))
|
||||
|
||||
case f @ Failure(msg) => Left(AmmoniteFailure(f))
|
||||
|
||||
case Res.Success(obj) => Right(obj)
|
||||
|
||||
case _ => Left(SomeError("Failed to run script"))
|
||||
}
|
||||
|
||||
def runKotlin(path: os.Path): Either[Error, Any] =
|
||||
Either
|
||||
.catchNonFatal(kotlinRunner.eval(os.read(path)))
|
||||
.leftMap {
|
||||
case ex: ScriptException => ScriptExceptionError(ex)
|
||||
}
|
||||
|
||||
def runGroovy(path: os.Path): Either[Error, Any] =
|
||||
Either
|
||||
.catchNonFatal(groovyRunner.run(path.relativeTo(os.pwd).toString, ""))
|
||||
.leftMap {
|
||||
case ex: ResourceException => ResourceExceptionError(ex)
|
||||
case ex: groovy.util.ScriptException => GroovyScriptExceptionError(ex)
|
||||
}
|
||||
|
||||
def ensureReturnedObjectNotNull(scriptObject: Any): Either[Error, Any] =
|
||||
Either.fromOption(Option(scriptObject), SomeError("unknown object"))
|
||||
}
|
||||
|
||||
class ScriptCompileSource(
|
||||
fns: ScriptCompileFns,
|
||||
logger: Logger[Task],
|
||||
queue: ConcurrentQueue[Task, ScriptCompilerWorker.CompileRequest]
|
||||
) {
|
||||
import fns._
|
||||
|
||||
val source =
|
||||
Task.deferAction(implicit s =>
|
||||
Task(
|
||||
Observable
|
||||
.repeatEvalF(queue.poll)
|
||||
.doOnNextF(el => logger.debug(s"Got $el"))
|
||||
.mapParallelUnorderedF(4) {
|
||||
case ScriptCompilerWorker.CompileAny(path, result) =>
|
||||
for {
|
||||
mbRes <- Task(
|
||||
runScala(path)
|
||||
.flatMap(ensureReturnedObjectNotNull)
|
||||
// .map(_.taggedWith[ScriptTag])
|
||||
)
|
||||
_ <- result.complete(mbRes)
|
||||
} yield mbRes
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
// override private val
|
||||
final class ScriptCompilerWorker(
|
||||
logger: Logger[Task],
|
||||
_queue: ConcurrentQueue[Task, ScriptCompilerWorker.CompileRequest]
|
||||
) extends Requestable[ScriptCompilerWorker.CompileRequest] {
|
||||
|
||||
override def queue = _queue
|
||||
|
||||
}
|
||||
|
||||
object ScriptCompilerWorker {
|
||||
|
||||
sealed trait CompileRequest
|
||||
final case class CompileAny(
|
||||
path: os.Path,
|
||||
result: Deferred[Task, ScriptResult]
|
||||
) extends CompileRequest
|
||||
|
||||
def apply(
|
||||
logger: Logger[Task],
|
||||
scalaRunner: ammonite.Main = defaultScalaRunner,
|
||||
kotlinRunner: KotlinScriptEngine = defaultKotlinRunner,
|
||||
groovyRunner: GroovyScriptEngine = defaultGroovyRunner
|
||||
) = {
|
||||
val acquire = for {
|
||||
queue <- ConcurrentQueue[Task].bounded[CompileRequest](10)
|
||||
fns <- UIO.pure(wire[ScriptCompileFns])
|
||||
worker = wire[ScriptCompilerWorker]
|
||||
fib <- wire[ScriptCompileSource].source.flatMap(_.completedL.toIO.start)
|
||||
// resource = Concurrent[Task].background(
|
||||
// wire[ScriptCompileSource].source.flatMap(_.completedL.toIO)
|
||||
// )
|
||||
} yield worker -> fib
|
||||
Resource
|
||||
.make(acquire) { case worker -> fib => fib.cancel }
|
||||
.map(_._1)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def apply(logger: Logger[Task]) = {
|
||||
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)
|
||||
.flatMap(worker =>
|
||||
Resource.make(acquire(worker)) { case (_, fib) => fib.cancel }
|
||||
)
|
||||
.map(_._1)
|
||||
}
|
||||
|
||||
}
|
@ -96,10 +96,7 @@ object ScriptActor {
|
||||
|
||||
def runScala(path: os.Path, scalaRunner: ammonite.Main): Either[Error, Any] =
|
||||
scalaRunner
|
||||
.runScript(
|
||||
path,
|
||||
Seq.empty
|
||||
)
|
||||
.runScript(path, Seq.empty)
|
||||
._1 match {
|
||||
case ammonite.util.Res.Exception(t, msg) => Left(Error(msg))
|
||||
|
||||
|
@ -45,9 +45,9 @@ object ScriptCachingActor {
|
||||
final case class GetMap(requester: ActorRef[ScriptsMap]) extends Command
|
||||
final case class Put(scriptPath: os.Path, script: ScriptObject)
|
||||
extends Command
|
||||
private[scriptsystem] case object NoOp extends Command
|
||||
private case object NoOp extends Command
|
||||
|
||||
private[scriptsystem] final case class DelegateToChild(
|
||||
private final case class DelegateToChild(
|
||||
scriptActor: ActorRef[ScriptActor.Command],
|
||||
scriptPath: os.Path,
|
||||
requester: ActorRef[ScriptResult]
|
||||
|
@ -1,17 +1,15 @@
|
||||
package wow.doge.mygame
|
||||
|
||||
import wow.doge.mygame.utils.wrappers.jme.AppNode2
|
||||
import akka.actor.typed.Scheduler
|
||||
import com.softwaremill.tagging._
|
||||
import wow.doge.mygame.game.GameAppTags
|
||||
import monix.execution.Scheduler
|
||||
import io.estatico.newtype.macros.newtype
|
||||
import monix.execution.schedulers.SchedulerService
|
||||
import wow.doge.mygame.game.GameAppTags
|
||||
import wow.doge.mygame.utils.wrappers.jme.AppNode2
|
||||
|
||||
package object types {
|
||||
type RootNode = AppNode2 @@ GameAppTags.RootNode
|
||||
type GuiNode = AppNode2 @@ GameAppTags.GuiNode
|
||||
@newtype case class AsyncScheduler(value: Scheduler)
|
||||
@newtype case class IoScheduler(value: Scheduler)
|
||||
@newtype case class FxScheduler(value: Scheduler)
|
||||
@newtype case class JmeScheduler(value: Scheduler)
|
||||
@newtype case class AkkaScheduler(value: akka.actor.typed.Scheduler)
|
||||
@newtype case class JmeScheduler(value: SchedulerService)
|
||||
@newtype case class AkkaScheduler(value: Scheduler)
|
||||
}
|
||||
|
@ -0,0 +1,33 @@
|
||||
package wow.doge.mygame.utils
|
||||
|
||||
import monix.reactive.Observable
|
||||
import monix.reactive.OverflowStrategy
|
||||
import monix.execution.Cancelable
|
||||
import monix.execution.cancelables.SingleAssignCancelable
|
||||
import monix.execution.Ack
|
||||
|
||||
object MonixDirectoryWatcher {
|
||||
import better.files._
|
||||
import io.methvin.better.files._
|
||||
def apply(path: os.Path) =
|
||||
Observable.create[String](OverflowStrategy.DropNew(50)) { sub =>
|
||||
import sub.scheduler
|
||||
|
||||
val c = SingleAssignCancelable()
|
||||
|
||||
val myDir = File(path.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")
|
||||
if (sub.onNext(file.name) == Ack.Stop) c.cancel()
|
||||
override def onDelete(file: File, count: Int) =
|
||||
println(s"$file got deleted")
|
||||
}
|
||||
watcher.start()(scheduler)
|
||||
c := Cancelable(() => watcher.stop())
|
||||
c
|
||||
|
||||
}
|
||||
}
|
63
src/test/scala/wow/doge/mygame/AnimTest.scala
Normal file
63
src/test/scala/wow/doge/mygame/AnimTest.scala
Normal file
@ -0,0 +1,63 @@
|
||||
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 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.ClipAction
|
||||
import cats.syntax.all._
|
||||
|
||||
class AnimTest extends AnyFunSuite {
|
||||
import monix.execution.Scheduler.Implicits.global
|
||||
val assetManager = new AssetManager(new DesktopAssetManager(true))
|
||||
|
||||
test("test 1") {
|
||||
println((for {
|
||||
_ <- UIO.unit
|
||||
model <- assetManager.loadModelAs[Node](
|
||||
os.rel / "Models" / "Oto" / "Oto.mesh.xml"
|
||||
)
|
||||
animcontrol <- UIO(model.getControlMaybe(classOf[AnimComposer]))
|
||||
skinningcontrol <- UIO(model.getControlMaybe(classOf[SkinningControl]))
|
||||
_ <- UIO(println(animcontrol))
|
||||
_ <- UIO(println(skinningcontrol))
|
||||
_ <- UIO {
|
||||
animcontrol.map { ac =>
|
||||
// ac.actionSequence()
|
||||
// ac.makeAction()
|
||||
// new BaseAction(Tweens.sequence())
|
||||
// ac.getAnimClips().a
|
||||
Option(ac.getAnimClip("hmm"))
|
||||
.map(clip => new ClipAction(clip))
|
||||
.map(Tweens.sequence(_))
|
||||
.foreach { t =>
|
||||
val actions = new BaseAction(t)
|
||||
ac.addAction("hmm", actions)
|
||||
}
|
||||
|
||||
val names = List("Walk", "Jump")
|
||||
for {
|
||||
clips <- names.traverse(name =>
|
||||
Option(ac.getAnimClip(name)).map(clip => new ClipAction(clip))
|
||||
)
|
||||
tween <- Tweens.sequence(clips: _*).some
|
||||
actions <- new BaseAction(tween).some
|
||||
_ <- ac.addAction("Sequence 1", actions).some
|
||||
} yield ()
|
||||
|
||||
()
|
||||
}
|
||||
}
|
||||
} yield model).attempt.runSyncUnsafe(10.seconds))
|
||||
}
|
||||
}
|
30
src/test/scala/wow/doge/mygame/FileWatcherTest.scala
Normal file
30
src/test/scala/wow/doge/mygame/FileWatcherTest.scala
Normal file
@ -0,0 +1,30 @@
|
||||
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._
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
import monix.execution.Scheduler.Implicits.global
|
||||
CResource
|
||||
.make(Task { watcher.start(); watcher })(w => Task(w.stop()))
|
||||
.use(_ => Task.never)
|
||||
.runSyncUnsafe(10.seconds)
|
||||
}
|
||||
}
|
44
src/test/scala/wow/doge/mygame/MonixScriptCompilerTest.scala
Normal file
44
src/test/scala/wow/doge/mygame/MonixScriptCompilerTest.scala
Normal file
@ -0,0 +1,44 @@
|
||||
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 {
|
||||
|
||||
test("some-test") {
|
||||
ScriptCompiler(consoleLogger[Task]())
|
||||
.use(scriptCompiler =>
|
||||
for {
|
||||
// _ <-
|
||||
// scriptCompiler.source
|
||||
// .doOnNextF(el => Task(println(s"Got $el")))
|
||||
// .completedL
|
||||
// .toIO
|
||||
// .hideErrors
|
||||
// .startAndForget
|
||||
response <- scriptCompiler.request(
|
||||
ScriptCompiler.Get(
|
||||
os.pwd / "assets" / "scripts" / "scala" / "hello2.sc",
|
||||
_,
|
||||
false
|
||||
)
|
||||
)(20.seconds)
|
||||
_ <- Task(pprint.log(response.toString))
|
||||
// _ <- Task.sleep(4.seconds)
|
||||
// _ <- scriptCompiler.tell(
|
||||
// ScriptCompiler.CompileAny(
|
||||
// os.pwd / "assets" / "scripts" / "scala" / "hello2.sc"
|
||||
// )
|
||||
// )
|
||||
// _ <- Task.sleep(4.seconds)
|
||||
// _ <- Task.sleep(8.seconds)
|
||||
} yield ()
|
||||
)
|
||||
.runSyncUnsafe(20.seconds)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user