many changes

This commit is contained in:
Rohan Sircar 2021-01-30 13:58:42 +05:30
parent 67a2bc4385
commit fd8b3819ff
29 changed files with 1012 additions and 409 deletions

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -46,6 +46,7 @@ object PlayerMovementEventListener {
)
}
//not used
object PlayerCameraEventListener {
import PlayerCameraEvent._
def apply(playerCameraActor: ActorRef[PlayerCameraActor.Command]) =

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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