Browse Source

daghgsre

development
Rohan Sircar 4 months ago
parent
commit
89fad19d99
  1. 87
      build.sbt
  2. 36
      src/main/scala/wow/doge/mygame/AppError.scala
  3. 3
      src/main/scala/wow/doge/mygame/Main.scala
  4. 177
      src/main/scala/wow/doge/mygame/MainApp.scala
  5. 4
      src/main/scala/wow/doge/mygame/MainModule.scala
  6. 52
      src/main/scala/wow/doge/mygame/game/GameApp.scala
  7. 2
      src/main/scala/wow/doge/mygame/game/GameAppActor.scala
  8. 3
      src/main/scala/wow/doge/mygame/game/entities/player/PlayerActorSupervisor.scala
  9. 35
      src/main/scala/wow/doge/mygame/game/entities/player/PlayerController.scala
  10. 3
      src/main/scala/wow/doge/mygame/game/subsystems/input/GameInputHandler.scala
  11. 4
      src/main/scala/wow/doge/mygame/game/subsystems/level/DefaultGameLevel.scala
  12. 37
      src/main/scala/wow/doge/mygame/game/subsystems/level/GameLevel.scala
  13. 7
      src/main/scala/wow/doge/mygame/game/subsystems/movement/CanMove2.scala
  14. 2
      src/main/scala/wow/doge/mygame/game/subsystems/movement/MovementActor.scala
  15. 11
      src/main/scala/wow/doge/mygame/implicits/CatsImplicits.scala
  16. 3
      src/main/scala/wow/doge/mygame/launcher/Launcher.scala
  17. 45
      src/main/scala/wow/doge/mygame/math/ImVector3f.scala
  18. 22
      src/main/scala/wow/doge/mygame/subsystems/events/EventBus.scala
  19. 50
      src/main/scala/wow/doge/mygame/subsystems/events/EventsModule.scala
  20. 260
      src/main/scala/wow/doge/mygame/subsystems/moddingsystem/ModdingSystem.scala
  21. 6
      src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptSystemModule.scala
  22. 8
      src/main/scala/wow/doge/mygame/utils/AkkaUtils.scala
  23. 7
      src/main/scala/wow/doge/mygame/utils/GenericTimerActor.scala
  24. 31
      src/main/scala/wow/doge/mygame/utils/ReaderDemo.scala
  25. 16
      src/main/scala/wow/doge/mygame/utils/wrappers/jme/AssetManager.scala
  26. 19
      src/main/scala/wow/doge/mygame/utils/wrappers/jme/CollisionShapeFactory.scala
  27. 14
      src/main/scala/wow/doge/mygame/utils/wrappers/jme/Node.scala
  28. 14
      src/main/scala/wow/doge/mygame/utils/wrappers/jme/PhysicsSpace.scala
  29. 21
      src/test/scala/wow/doge/mygame/AssetManagerTest.scala
  30. 5
      src/test/scala/wow/doge/mygame/CollisionShapeFactoryTest.scala
  31. 51
      src/test/scala/wow/doge/mygame/ImVector3fTest.scala
  32. 27
      src/test/scala/wow/doge/mygame/ModdingSystemTest.scala
  33. 67
      src/test/scala/wow/doge/mygame/ReaderT_Test.scala
  34. 37
      src/test/scala/wow/doge/mygame/ReaderTest.scala

87
build.sbt

@ -1,23 +1,4 @@
// The simplest possible sbt build file is just one line:
scalaVersion := "2.13.3"
// That is, to create a valid sbt build, all you've got to do is define the
// version of Scala you'd like your project to use.
// ============================================================================
// Lines like the above defining `scalaVersion` are called "settings". Settings
// are key/value pairs. In the case of `scalaVersion`, the key is "scalaVersion"
// and the value is "2.13.1"
// It's possible to define many kinds of settings, such as:
// Note, it's not required for you to define these three settings. These are
// mostly only necessary if you intend to publish your library's binaries on a
// place like Sonatype or Bintray.
// Want to use a published library in your project?
// You can define other libraries as dependencies in your build like this:
resolvers += "Jcenter" at "https://jcenter.bintray.com/"
resolvers += "JME Bintray" at "https://bintray.com/jmonkeyengine/com.jme3"
@ -49,12 +30,8 @@ lazy val root = (project in file(".")).settings(
organization := "wow.doge",
version := "1.0-SNAPSHOT",
libraryDependencies ++= Seq(
// "org.scala-lang.modules" %% "scala-parser-combinators" % "1.1.2",
// https://mvnrepository.com/artifact/org.jmonkeyengine/jme3-core
"org.jmonkeyengine" % "jme3-core" % jmeVersion,
// https://mvnrepository.com/artifact/org.jmonkeyengine/jme3-desktop
"org.jmonkeyengine" % "jme3-desktop" % jmeVersion,
// https://mvnrepository.com/artifact/org.jmonkeyengine/jme3-lwjgl3
"org.jmonkeyengine" % "jme3-lwjgl3" % jmeVersion,
"org.jmonkeyengine" % "jme3-effects" % jmeVersion,
"org.jmonkeyengine" % "jme3-plugins" % jmeVersion,
@ -74,15 +51,15 @@ lazy val root = (project in file(".")).settings(
"io.monix" %% "monix-bio" % "1.1.0",
"io.circe" %% "circe-core" % "0.13.0",
"io.circe" %% "circe-generic" % "0.13.0",
"com.softwaremill.sttp.client" %% "core" % "2.2.5",
"com.softwaremill.sttp.client" %% "monix" % "2.2.5",
"com.softwaremill.sttp.client" %% "circe" % "2.2.5",
"com.softwaremill.sttp.client" %% "async-http-client-backend-monix" % "2.2.5",
"com.softwaremill.sttp.client3" %% "core" % "3.0.0",
"com.softwaremill.sttp.client3" %% "monix" % "3.0.0",
"com.softwaremill.sttp.client3" %% "circe" % "3.0.0",
"com.softwaremill.sttp.client3" %% "async-http-client-backend-monix" % "3.0.0",
"com.softwaremill.sttp.client3" %% "httpclient-backend-monix" % "3.0.0",
"com.github.valskalla" %% "odin-monix" % "0.8.1",
"com.github.valskalla" %% "odin-json" % "0.9.1",
"com.softwaremill.macwire" %% "util" % "2.3.7",
"com.softwaremill.macwire" %% "macros" % "2.3.7" % "provided",
// "com.softwaremill.macwire" %% "macrosakka" % "2.3.6" % "provided",
"com.github.valskalla" %% "odin-slf4j" % "0.8.1",
"com.softwaremill.quicklens" %% "quicklens" % "1.6.1",
"org.scala-lang.modules" %% "scala-parallel-collections" % "1.0.0-RC1",
@ -90,9 +67,6 @@ lazy val root = (project in file(".")).settings(
"io.circe" %% "circe-config" % "0.8.0",
"com.beachape" %% "enumeratum-circe" % "1.6.1",
"com.lihaoyi" %% "os-lib" % "0.7.1",
// "com.jayfella" % "jme-jfx-11" % "1.1.5",
// "com.github.goxr3plus" % "FX-BorderlessScene" % "4.4.0",
// "com.github.Oshan96" % "CustomStage" % "v1.3.1",
"com.badlogicgames.gdx" % "gdx-ai" % "1.8.2",
"org.recast4j" % "recast" % "1.2.5",
"org.recast4j" % "detour" % "1.2.5",
@ -118,8 +92,6 @@ lazy val root = (project in file(".")).settings(
"-Xlint",
"-Ywarn-numeric-widen",
"-Ymacro-annotations",
// "-Xlint:byname-implicit",
// "utf-8", // Specify character encoding used by source files.
//silence warnings for by-name implicits
"-Wconf:cat=lint-byname-implicit:s",
//give errors on non exhaustive matches
@ -159,55 +131,6 @@ lazy val root = (project in file(".")).settings(
}
)
initialCommands in (console) := """ammonite.Main.main(Array.empty)"""
// Here, `libraryDependencies` is a set of dependencies, and by using `+=`,
// we're adding the scala-parser-combinators dependency to the set of dependencies
// that sbt will go and fetch when it starts up.
// Now, in any Scala file, you can import classes, objects, etc., from
// scala-parser-combinators with a regular import.
// TIP: To find the "dependency" that you need to add to the
// `libraryDependencies` set, which in the above example looks like this:
// "org.scala-lang.modules" %% "scala-parser-combinators" % "1.1.2"
// You can use Scaladex, an index of all known published Scala libraries. There,
// after you find the library you want, you can just copy/paste the dependency
// information that you need into your build file. For example, on the
// scala/scala-parser-combinators Scaladex page,
// https://index.scala-lang.org/scala/scala-parser-combinators, you can copy/paste
// the sbt dependency from the sbt box on the right-hand side of the screen.
// IMPORTANT NOTE: while build files look _kind of_ like regular Scala, it's
// important to note that syntax in *.sbt files doesn't always behave like
// regular Scala. For example, notice in this build file that it's not required
// to put our settings into an enclosing object or class. Always remember that
// sbt is a bit different, semantically, than vanilla Scala.
// ============================================================================
// Most moderately interesting Scala projects don't make use of the very simple
// build file style (called "bare style") used in this build.sbt file. Most
// intermediate Scala projects make use of so-called "multi-project" builds. A
// multi-project build makes it possible to have different folders which sbt can
// be configured differently for. That is, you may wish to have different
// dependencies or different testing frameworks defined for different parts of
// your codebase. Multi-project builds make this possible.
// Here's a quick glimpse of what a multi-project build looks like for this
// build, with only one "subproject" defined, called `root`:
// lazy val root = (project in file(".")).
// settings(
// inThisBuild(List(
// organization := "ch.epfl.scala",
// scalaVersion := "2.13.1"
// )),
// name := "hello-world"
// )
// To learn more about multi-project builds, head over to the official sbt
// documentation at http://www.scala-sbt.org/documentation.html
addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1")
addCompilerPlugin(
"org.typelevel" %% "kind-projector" % "0.11.1" cross CrossVersion.full

36
src/main/scala/wow/doge/mygame/AppError.scala

@ -1,6 +1,40 @@
package wow.doge.mygame
import java.util.concurrent.TimeoutException
import cats.data.Reader
import monix.bio.IO
import wow.doge.mygame.utils.wrappers.jme.AssetManager
import wow.doge.mygame.utils.wrappers.jme.CollisionShapeFactory
import wow.doge.mygame.utils.wrappers.jme.NodeWrapper2
sealed trait AppError
object AppError {
case class TimeoutError(reason: String) extends AppError
final case class TimeoutError(reason: String) extends AppError
object TimeoutError {
def reader =
Reader[Throwable, TimeoutError] {
case ex: TimeoutException => TimeoutError(ex.getMessage)
}
def from: PartialFunction[Throwable, IO[TimeoutError, Nothing]] = {
case ex: TimeoutException => IO.raiseError(TimeoutError(ex.getMessage))
}
}
final case class DummyError(reason: String) extends AppError
object DummyError {
def reader =
Reader[Throwable, DummyError] {
case ex: NullPointerException => DummyError(ex.getMessage)
}
}
final case class AssetManagerError(error: AssetManager.Error) extends AppError
final case class AppNodeError(error: NodeWrapper2.Error) extends AppError
final case class CollisionShapeCreationFailed(
err: CollisionShapeFactory.Error
) extends AppError
def fromThrowable: PartialFunction[Throwable, IO[AppError, Nothing]] = {
case ex: TimeoutException => IO.raiseError(TimeoutError(ex.getMessage))
}
}

3
src/main/scala/wow/doge/mygame/Main.scala

@ -35,6 +35,9 @@ object Main extends BIOApp with MainModule {
Formatter.json
).withAsync(timeWindow = 1.milliseconds, maxBufferSize = Some(2000))
jmeScheduler <- jMESchedulerResource
// backend <- Resource.make(toIO(HttpClientMonixBackend()))(backend =>
// toIO(backend.close())
// )
actorSystem <- actorSystemResource(logger, schedulers.async)
_ <- Resource.liftF(
new MainApp(

177
src/main/scala/wow/doge/mygame/MainApp.scala

@ -1,19 +1,22 @@
package wow.doge.mygame
import java.util.concurrent.TimeoutException
import scala.concurrent.duration._
import akka.actor.typed.ActorRef
import akka.actor.typed.ActorSystem
import akka.actor.typed.Scheduler
import akka.actor.typed.SpawnProtocol
import akka.util.Timeout
import cats.effect.Resource
import cats.effect.concurrent.Deferred
import cats.syntax.eq._
import com.jme3.app.state.AppStateManager
import com.jme3.asset.plugins.ZipLocator
import com.jme3.bullet.control.BetterCharacterControl
import com.jme3.input.InputManager
import com.jme3.material.Material
import com.jme3.material.MaterialDef
import com.jme3.math.ColorRGBA
import com.jme3.math.FastMath
import com.jme3.renderer.Camera
import com.jme3.renderer.RenderManager
@ -27,6 +30,8 @@ import monix.bio.Fiber
import monix.bio.IO
import monix.bio.Task
import monix.bio.UIO
import monix.eval.Coeval
import monix.reactive.Observable
import scalafx.scene.control.TextArea
import wow.doge.mygame.executors.Schedulers
import wow.doge.mygame.game.GameApp
@ -45,23 +50,20 @@ import wow.doge.mygame.implicits._
import wow.doge.mygame.launcher.Launcher
import wow.doge.mygame.launcher.Launcher.LauncherResult
import wow.doge.mygame.math.ImVector3f
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.PlayerEvent
import wow.doge.mygame.subsystems.events.PlayerMovementEvent
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.utils.AkkaUtils
import wow.doge.mygame.utils.GenericConsoleStream
import wow.doge.mygame.utils.wrappers.jme.AppNode
import wow.doge.mygame.utils.IOUtils
import wow.doge.mygame.utils.wrappers.jme.AppNode2
import wow.doge.mygame.utils.wrappers.jme.AssetManager
import wow.doge.mygame.utils.wrappers.jme.PhysicsSpace
import wow.doge.mygame.subsystems.events.EventBus.ObservableSubscription
import wow.doge.mygame.subsystems.events.PlayerMovementEvent
import monix.reactive.Observable
import monix.eval.Coeval
import wow.doge.mygame.utils.IOUtils
import com.jme3.math.ColorRGBA
class MainApp(
logger: Logger[Task],
@ -84,53 +86,66 @@ class MainApp(
tickEventBus: GameEventBus[TickEvent]
)
def gameInit: Resource[Task, Fiber[Throwable, Unit]] =
wire[GameAppResource].resource.evalMap {
case gameApp -> gameAppFib =>
for {
playerEventBus <- eventsModule.playerEventBusTask
mainEventBus <- eventsModule.mainEventBusTask
tickEventBus <- eventsModule.tickEventBusTask
obs <- playerEventBus.askL[Observable[PlayerMovementEvent]](
def eval(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(_)
)
_ <- IOUtils.toIO(
.onErrorHandleWith {
case ex: TimeoutException =>
IO.raiseError(AppError.TimeoutError(ex.getMessage()))
}
_ <-
IOUtils
.toIO(
obs
.doOnNextF(pme => Coeval(pprint.log(s"Received event $pme")).void)
.completedL
.startAndForget
)
inputManager <- gameApp.inputManager
assetManager <- UIO.pure(gameApp.assetManager)
stateManager <- gameApp.stateManager
camera <- gameApp.camera
rootNode <- UIO.pure(gameApp.rootNode)
enqueueR <- UIO(gameApp.enqueue _)
viewPort <- gameApp.viewPort
physicsSpace <- UIO.pure(gameApp.physicsSpace)
_ <- logger.info("before")
// jfxUI <- gameApp.jfxUI
gameAppActor <- gameApp.spawnGameActor(
GameAppActor.Props(tickEventBus).behavior,
"gameAppActor"
)
_ <- gameAppActor !! GameAppActor.Start
consoleTextArea <- Task(new TextArea {
text = "hello \n"
editable = false
wrapText = true
// maxHeight = 150
// maxWidth = 300
})
// _ <- Task(consoleStream := consoleTextArea)
// _ <- Task(jfxUI += consoleTextArea)
_ <- logger.info("after")
_ <- logger.info("Initializing console stream")
_ <-
wire[MainAppDelegate]
.init()
.executeOn(gameApp.scheduler)
} yield gameAppFib
.hideErrors
inputManager <- gameApp.inputManager
assetManager <- UIO.pure(gameApp.assetManager)
camera <- gameApp.camera
rootNode <- UIO.pure(gameApp.rootNode)
enqueueR <- UIO(gameApp.enqueue _)
viewPort <- gameApp.viewPort
physicsSpace <- UIO.pure(gameApp.physicsSpace)
_ <- logger.infoU("before")
// jfxUI <- gameApp.jfxUI
gameAppActor <- gameApp.spawnGameActor(
GameAppActor.Props(tickEventBus).behavior,
"gameAppActor"
)
_ <- gameAppActor !! GameAppActor.Start
consoleTextArea <- UIO(new TextArea {
text = "hello \n"
editable = false
wrapText = true
// maxHeight = 150
// maxWidth = 300
})
// _ <- Task(consoleStream := consoleTextArea)
// _ <- Task(jfxUI += consoleTextArea)
_ <- logger.infoU("after")
_ <- logger.infoU("Initializing console stream")
_ <-
wire[MainAppDelegate]
.init()
.executeOn(gameApp.scheduler)
} yield fib
def gameInit: Resource[UIO, Either[AppError, Fiber[Nothing, Unit]]] =
wire[GameAppResource].resource.evalMap {
case Right(gameApp -> gameAppFib) =>
eval(gameApp, gameAppFib).attempt
case Left(error) => IO.terminate(new Exception(error.toString))
}
// val x: Task[Unit] = for {
@ -141,21 +156,24 @@ class MainApp(
// } yield ()
val program = for {
scriptSystem <- scriptSystemInit
launchSignal <- Deferred[Task, Launcher.LauncherResult]
// scriptSystem <- scriptSystemInit
launchSignal <- Deferred[Task, Launcher.LauncherResult].hideErrors
launcher <- new Launcher.Props(schedulers, launchSignal).create
launchResult <- launcher.init.use(_ => launchSignal.get)
launchResult <- launcher.init.use(_ => launchSignal.get).hideErrors
_ <-
/**
* User chose to quit
*/
if (launchResult === LauncherResult.Exit)
logger.info("Exiting")
logger.infoU("Exiting")
/**
* User chose launch. Wait for game window to close
*/
else
gameInit.use(_.join)
gameInit.use {
case Right(fib) => fib.join >> Task.unit
case Left(error) => IO.terminate(new Exception(error.toString))
}.hideErrors
} yield ()
}
@ -170,12 +188,11 @@ class MainAppDelegate(
tickEventBus: GameEventBus[TickEvent],
inputManager: InputManager,
assetManager: AssetManager,
stateManager: AppStateManager,
physicsSpace: PhysicsSpace[Task],
physicsSpace: PhysicsSpace,
camera: Camera,
viewPort: ViewPort,
enqueueR: Function1[() => Unit, Unit],
rootNode: AppNode[Task] @@ GameAppTags.RootNode
rootNode: AppNode2 @@ GameAppTags.RootNode
)(implicit
spawnProtocol: ActorRef[SpawnProtocol.Command],
timeout: Timeout,
@ -185,31 +202,28 @@ class MainAppDelegate(
def init(
// appScheduler: monix.execution.Scheduler
// consoleStream: GenericConsoleStream[TextArea]
) =
): IO[AppError, Unit] =
for {
_ <- loggerL.info("Initializing Systems")
_ <- loggerL.infoU("Initializing Systems")
_ <- assetManager.registerLocator(
os.rel / "assets" / "town.zip",
classOf[ZipLocator]
)
_ <- loggerL.info("test")
_ <- loggerL.infoU("test")
// _ <- Task(consoleStream.println("text"))
_ <- DefaultGameLevel(assetManager, viewPort)
.flatMap(_.addToGame(rootNode, physicsSpace).hideErrors)
.onErrorHandleWith(e => loggerL.error(e.toString))
level <- DefaultGameLevel(assetManager, viewPort)
_ <- level.addToGame(rootNode, physicsSpace)
_ <- createPlayerController()
.onErrorHandleWith(e => loggerL.error(e.toString))
// .onErrorRestart(3)
_ <- wire[GameInputHandler.Props].begin
// .onErrorRestart(3)
johnActor <- createTestNpc("John")
.onErrorHandleWith(e => IO.raiseError(new Throwable(e.toString)))
// _ <- johnActor !! NpcActorSupervisor.Move(ImVector3f(0, 0, 20))
// _ <-
// johnActor
// .tellL(NpcActorSupervisor.Move(ImVector3f(-30, 0, 10)))
// .delayExecution(2.seconds)
_ <-
johnActor
.tellL(NpcActorSupervisor.Move(ImVector3f(-30, 0, 10)))
.delayExecution(2.seconds)
// _ <-
// IOUtils
// .toIO(
@ -224,8 +238,8 @@ class MainAppDelegate(
def createPlayerController(
// appScheduler: monix.execution.Scheduler
): IO[PlayerController.Error, ActorRef[PlayerActorSupervisor.Command]] = {
val playerPos = ImVector3f.ZERO
): IO[AppError, ActorRef[PlayerActorSupervisor.Command]] = {
val playerPos = ImVector3f.Zero
val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o"
val playerPhysicsControl =
PlayerController.Defaults.defaultPlayerPhysicsControl
@ -235,7 +249,7 @@ class MainAppDelegate(
assetManager
.loadModelAs[Node](modelPath)
.map(_.withRotate(0, FastMath.PI, 0))
.mapError(PlayerController.CouldNotCreatePlayerModel)
.mapError(AppError.AssetManagerError)
playerNode <- UIO(
PlayerController.Defaults
.defaultPlayerNode(
@ -261,7 +275,7 @@ class MainAppDelegate(
def createTestNpc(
// appScheduler: monix.execution.Scheduler,
npcName: String
) = {
): IO[AppError, ActorRef[NpcActorSupervisor.Command]] = {
val initialPos = ImVector3f(50, 5, 0)
val npcPhysicsControl = new BetterCharacterControl(1.5f, 6f, 1f)
// (1f, 2.1f, 10f)
@ -284,10 +298,13 @@ class MainAppDelegate(
s"${npcName}-npcActorSupervisor"
)
for {
materialDef <- assetManager.loadAssetAs[MaterialDef](
os.rel / "Common" / "MatDefs" / "Misc" / "Unshaded.j3md"
)
(for {
materialDef <-
assetManager
.loadAssetAs[MaterialDef](
os.rel / "Common" / "MatDefs" / "Misc" / "Unshaded.j3md"
)
.mapError(AppError.AssetManagerError)
material = new Material(materialDef)
_ = material.setColor("Color", ColorRGBA.Blue)
mesh = PlayerController.Defaults.defaultMesh.withMaterial(material)
@ -297,13 +314,13 @@ class MainAppDelegate(
npcPhysicsControl,
npcName
)
npcActor <- npcActorTask.hideErrors
_ <- (for {
_ <- physicsSpace += npcPhysicsControl
_ <- physicsSpace += npcNode
_ <- rootNode += npcNode
} yield ()).hideErrors
} yield npcActor
} yield ()).mapError(AppError.AppNodeError)
npcActor <- npcActorTask
} yield npcActor)
}

4
src/main/scala/wow/doge/mygame/MainModule.scala

@ -1,12 +1,12 @@
package wow.doge.mygame
import akka.actor.BootstrapSetup
import akka.actor.typed.ActorSystem
import akka.actor.typed.SpawnProtocol
import cats.effect.Resource
import io.odin.Logger
import monix.bio.Task
import wow.doge.mygame.executors.ExecutorsModule
import akka.actor.BootstrapSetup
import monix.execution.Scheduler
import wow.doge.mygame.executors.ExecutorsModule
trait MainModule extends ExecutorsModule {

52
src/main/scala/wow/doge/mygame/game/GameApp.scala

@ -10,7 +10,6 @@ import akka.actor.typed.scaladsl.AskPattern._
import akka.util.Timeout
import cats.effect.Resource
import cats.effect.concurrent.Deferred
import com.jme3.app.state.AppStateManager
import com.jme3.bullet.BulletAppState
import com.jme3.input.InputManager
import com.jme3.scene.Node
@ -20,6 +19,7 @@ import com.softwaremill.tagging._
import com.typesafe.scalalogging.{Logger => SLogger}
import io.odin.Logger
import monix.bio.Fiber
import monix.bio.IO
import monix.bio.Task
import monix.bio.UIO
import monix.catnap.ConcurrentChannel
@ -28,14 +28,16 @@ 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
import wow.doge.mygame.executors.JMERunner
import wow.doge.mygame.executors.Schedulers
import wow.doge.mygame.utils.GenericTimerActor
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.Dispatchers
object GameAppTags {
sealed trait RootNode
@ -49,18 +51,17 @@ class GameApp private[game] (
gameActor: ActorRef[TestGameActor.Command],
gameSpawnProtocol: ActorRef[SpawnProtocol.Command]
) {
def stateManager: Task[AppStateManager] = Task(app.getStateManager())
def inputManager: Task[InputManager] = Task(app.getInputManager())
def inputManager: UIO[InputManager] = UIO(app.getInputManager())
def assetManager = new AssetManager(app.getAssetManager())
def guiNode = Task(app.getGuiNode().taggedWith[GameAppTags.GuiNode])
val guiNode2 = AppNode[Task](app.getGuiNode()).taggedWith[GameAppTags.GuiNode]
def guiNode = UIO(app.getGuiNode().taggedWith[GameAppTags.GuiNode])
val guiNode2 = AppNode2(app.getGuiNode()).taggedWith[GameAppTags.GuiNode]
def flyCam = Option(app.getFlyByCamera())
def camera = Task(app.getCamera())
def viewPort = Task(app.getViewPort())
// def rootNode = Task(app.getRootNode().taggedWith[GameAppTags.RootNode])
def camera = UIO(app.getCamera())
def viewPort = UIO(app.getViewPort())
// def rootNode = UIO(app.getRootNode().taggedWith[GameAppTags.RootNode])
val rootNode =
AppNode[Task](app.getRootNode()).taggedWith[GameAppTags.RootNode]
val physicsSpace = new PhysicsSpace[Task](app.bulletAppState.physicsSpace)
AppNode2(app.getRootNode()).taggedWith[GameAppTags.RootNode]
val physicsSpace = new PhysicsSpace(app.bulletAppState.physicsSpace)
def enqueue(cb: () => Unit) = app.enqueueR(() => cb())
def enqueueL[T](cb: () => T): Task[T] = app.enqueueL(cb)
@ -89,13 +90,14 @@ class GameAppResource(
scheduler: akka.actor.typed.Scheduler,
spawnProtocol: ActorRef[SpawnProtocol.Command]
) {
def resource: Resource[Task, (GameApp, Fiber[Throwable, Unit])] =
def resource
: Resource[UIO, Either[AppError, (GameApp, Fiber[Nothing, Unit])]] =
Resource.make {
lazy val bullet = new BulletAppState
for {
app <- Task(new SimpleAppExt(schedulers, bullet))
(for {
app <- UIO(new SimpleAppExt(schedulers, bullet))
_ <- UIO(JMERunner.runner = Some(app.enqueue _))
_ <- Task {
_ <- UIO {
val settings = new AppSettings(true)
settings.setVSync(true)
@ -107,23 +109,27 @@ class GameAppResource(
app.setSettings(settings)
}
fib <- Task(app.start).executeOn(jmeThread).start
_ <- Task.deferFuture(app.started)
fib <- UIO(app.start).executeOn(jmeThread).start
_ <- Task.deferFuture(app.started).onErrorHandleWith(TimeoutError.from)
testGameActor <- AkkaUtils.spawnActorL(
new TestGameActor.Props().create,
"testGameActor",
Dispatchers.jmeDispatcher
)
sp <- testGameActor.askL(TestGameActor.GetSpawnProtocol(_))
gameApp <- Task(new GameApp(logger, app, testGameActor, sp))
_ <- Task {
sp <-
testGameActor
.askL(TestGameActor.GetSpawnProtocol(_))
.onErrorHandleWith(TimeoutError.from)
gameApp <- UIO(new GameApp(logger, app, testGameActor, sp))
_ <- UIO {
val fut = () => testGameActor.ask(TestGameActor.Stop).flatten
app.cancelToken = Some(fut)
}
} yield (gameApp, fib)
} yield (gameApp, fib)).attempt
} {
case (gameApp, fib) =>
case Right(gameApp -> fib) =>
fib.cancel >> UIO(JMERunner.runner = None)
case Left(error) => IO.terminate(new Exception(error.toString))
}
}

2
src/main/scala/wow/doge/mygame/game/GameAppActor.scala

@ -5,12 +5,12 @@ import scala.concurrent.duration._
import akka.actor.typed.SupervisorStrategy
import akka.actor.typed.scaladsl.Behaviors
import wow.doge.mygame.game.TickGenerator.Send
import wow.doge.mygame.utils.GenericTimerActor
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 {

3
src/main/scala/wow/doge/mygame/game/entities/player/PlayerActorSupervisor.scala

@ -8,14 +8,13 @@ import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.scaladsl.Behaviors
import com.typesafe.scalalogging.Logger
import org.slf4j.event.Level
import wow.doge.mygame.game.subsystems.movement.CanMove
import wow.doge.mygame.implicits._
import wow.doge.mygame.subsystems.events.EventBus
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
import wow.doge.mygame.subsystems.events.PlayerEvent
import wow.doge.mygame.subsystems.events.TickEvent
import wow.doge.mygame.subsystems.events.TickEvent.RenderTick
import wow.doge.mygame.subsystems.movement.ImMovementActor
import wow.doge.mygame.implicits._
object PlayerActorSupervisor {
sealed trait Command
final case class Props(

35
src/main/scala/wow/doge/mygame/game/entities/player/PlayerController.scala

@ -4,7 +4,6 @@ import akka.actor.typed.ActorRef
import akka.actor.typed.Scheduler
import akka.actor.typed.SpawnProtocol
import akka.util.Timeout
import com.jme3.asset.AssetManager
import com.jme3.bullet.BulletAppState
import com.jme3.bullet.control.BetterCharacterControl
import com.jme3.math.FastMath
@ -16,8 +15,9 @@ import com.jme3.scene.Spatial
import com.jme3.scene.shape.Box
import com.softwaremill.tagging._
import io.odin.Logger
import monix.bio.IO
import monix.bio.Task
import monix.bio.UIO
import wow.doge.mygame.AppError
import wow.doge.mygame.game.GameAppTags
import wow.doge.mygame.game.SimpleAppExt
import wow.doge.mygame.implicits._
@ -36,18 +36,15 @@ object PlayerControllerTags {
object PlayerController {
sealed trait Error
case class CouldNotCreatePlayerNode(reason: String) extends Error
case class CouldNotCreatePlayerModel(
reason: wow.doge.mygame.utils.wrappers.jme.AssetManager.Error
) extends Error
case class CouldNotCreatePlayerModel(reason: AssetManager.Error) extends Error
class Props(
enqueueR: Function1[() => Unit, Unit],
rootNode: AppNode[Task] @@ GameAppTags.RootNode,
rootNode: AppNode2 @@ GameAppTags.RootNode,
loggerL: Logger[Task],
// physicsSpace: com.jme3.bullet.PhysicsSpace,
physicsSpace: PhysicsSpace[Task],
initialPlayerPos: ImVector3f = ImVector3f.ZERO,
physicsSpace: PhysicsSpace,
initialPlayerPos: ImVector3f = ImVector3f.Zero,
playerEventBus: GameEventBus[PlayerEvent],
playerPhysicsControl: BetterCharacterControl,
// appScheduler: monix.execution.Scheduler,
@ -78,16 +75,18 @@ object PlayerController {
cameraActorBeh
).behavior
}
val create: UIO[ActorRef[PlayerActorSupervisor.Command]] =
val create: IO[AppError, ActorRef[PlayerActorSupervisor.Command]] =
(for {
playerActor <-
AkkaUtils.spawnActorL(playerActorBehavior, "playerActorSupervisor")
_ <- rootNode += playerNode
_ <- physicsSpace += playerNode
_ <- physicsSpace += playerPhysicsControl
_ = cameraPivotNode += cameraNode
_ <- rootNode += cameraPivotNode
} yield playerActor).hideErrors
_ <- (for {
_ <- rootNode += playerNode
_ <- physicsSpace += playerNode
_ <- physicsSpace += playerPhysicsControl
_ = cameraPivotNode += cameraNode
_ <- rootNode += cameraPivotNode
} yield ()).mapError(AppError.AppNodeError)
} yield playerActor)
}
@ -96,7 +95,7 @@ object PlayerController {
modelPath: os.RelPath,
cam: Camera
)(assetManager: AssetManager, bulletAppState: BulletAppState) = {
val playerPos = ImVector3f.ZERO
val playerPos = ImVector3f.Zero
val playerPhysicsControl = new BetterCharacterControl(1.5f, 6f, 1f)
.withJumpForce(ImVector3f(0, 5f, 0))
val playerNode = new Node("PlayerNode")
@ -152,7 +151,7 @@ object PlayerController {
new CameraNode("CameraNode", cam)
// .withControlDir(ControlDirection.SpatialToCamera)
.withLocalTranslation(ImVector3f(0, 1.5f, 10))
.withLookAt(playerPos, ImVector3f.UNIT_Y)
.withLookAt(playerPos, ImVector3f.UnitY)
def defaultPlayerNode(
playerPos: ImVector3f,

3
src/main/scala/wow/doge/mygame/game/subsystems/input/GameInputHandler.scala

@ -9,7 +9,7 @@ import com.jme3.input.KeyInput
import com.jme3.input.MouseInput
import com.jme3.input.controls.KeyTrigger
import com.jme3.input.controls.MouseAxisTrigger
import monix.bio.Task
import monix.bio.UIO
import monix.{eval => me}
import wow.doge.mygame.implicits._
import wow.doge.mygame.subsystems.events.EventBus
@ -18,7 +18,6 @@ import wow.doge.mygame.subsystems.events.PlayerCameraEvent
import wow.doge.mygame.subsystems.events.PlayerEvent
import wow.doge.mygame.subsystems.events.PlayerMovementEvent
import wow.doge.mygame.utils.IOUtils._
import monix.bio.UIO
object GameInputHandler {

4
src/main/scala/wow/doge/mygame/game/subsystems/level/DefaultGameLevel.scala

@ -8,7 +8,9 @@ import com.jme3.math.ColorRGBA
import com.jme3.math.Vector3f
import com.jme3.renderer.ViewPort
import com.jme3.scene.Spatial
import monix.bio.IO
import monix.bio.UIO
import wow.doge.mygame.AppError
object DefaultGameLevel {
def apply(
@ -51,7 +53,7 @@ object DefaultGameLevel {
def apply(
assetManager: wow.doge.mygame.utils.wrappers.jme.AssetManager,
viewPort: ViewPort
) =
): IO[AppError, GameLevel] =
// for {
// sceneModel <- assetManager.loadModelAs[Node](os.rel / "main.scene")
// sceneShape <- UIO(CollisionShapeFactory.createMeshShape(sceneModel))

37
src/main/scala/wow/doge/mygame/game/subsystems/level/GameLevel.scala

@ -8,11 +8,10 @@ import com.jme3.scene.Node
import com.jme3.scene.Spatial
import com.softwaremill.tagging._
import monix.bio.IO
import monix.bio.Task
import monix.bio.UIO
import wow.doge.mygame.AppError
import wow.doge.mygame.game.GameAppTags
import wow.doge.mygame.utils.wrappers.jme.AppNode
import wow.doge.mygame.utils.wrappers.jme.AssetManager
import wow.doge.mygame.utils.wrappers.jme.AppNode2
import wow.doge.mygame.utils.wrappers.jme.CollisionShapeFactory
import wow.doge.mygame.utils.wrappers.jme.PhysicsSpace
@ -23,21 +22,20 @@ class GameLevel(
val directionalLight: DirectionalLight
) {
def addToGame(
rootNode: AppNode[Task] @@ GameAppTags.RootNode,
physicsSpace: PhysicsSpace[Task]
) = {
for {
rootNode: AppNode2 @@ GameAppTags.RootNode,
physicsSpace: PhysicsSpace
) =
(for {
_ <- rootNode += model
_ <- rootNode += ambientLight
_ <- rootNode += directionalLight
_ <- physicsSpace += model
_ <- physicsSpace += physicsControl
} yield ()
}
} yield ()).mapError(AppError.AppNodeError)
def removeFromGame(
rootNode: AppNode[Task] @@ GameAppTags.RootNode,
physicsSpace: PhysicsSpace[Task]
) = {
rootNode: AppNode2 @@ GameAppTags.RootNode,
physicsSpace: PhysicsSpace
) =
for {
_ <- rootNode -= model
_ <- rootNode -= ambientLight
@ -45,11 +43,10 @@ class GameLevel(
_ <- physicsSpace -= model
_ <- physicsSpace -= physicsControl
} yield ()
}
def resource(
rootNode: AppNode[Task] @@ GameAppTags.RootNode,
physicsSpace: PhysicsSpace[Task]
rootNode: AppNode2 @@ GameAppTags.RootNode,
physicsSpace: PhysicsSpace
) =
Resource.make(this.addToGame(rootNode, physicsSpace))(_ =>
this.removeFromGame(rootNode, physicsSpace)
@ -57,10 +54,6 @@ class GameLevel(
}
object GameLevel {
sealed trait Error
case class AssetLoadError(err: AssetManager.Error) extends Error
case class CollisionShapeCreationFailed(err: CollisionShapeFactory.Error)
extends Error
def apply(
modelPath: os.RelPath,
@ -68,16 +61,16 @@ object GameLevel {
dl: DirectionalLight
)(implicit
assetManager: wow.doge.mygame.utils.wrappers.jme.AssetManager
): IO[Error, GameLevel] =
): IO[AppError, GameLevel] =
for {
sceneModel <-
assetManager
.loadModelAs[Node](modelPath)
.mapError(AssetLoadError)
.mapError(AppError.AssetManagerError)
sceneShape <-
CollisionShapeFactory
.createMeshShape(sceneModel)
.mapError(CollisionShapeCreationFailed)
.mapError(AppError.CollisionShapeCreationFailed)
landscape <- UIO(new RigidBodyControl(sceneShape, 0))
} yield new GameLevel(

7
src/main/scala/wow/doge/mygame/game/subsystems/movement/CanMove2.scala

@ -21,9 +21,8 @@ trait CanMove2[-A, F[_]] {
object Test {
val x = new BetterCharacterControl(4, 10, 5)
def test[T](x: T)(implicit cm: CanMove2[T, Id]) = {
cm.move(x, ImVector3f.ZERO)
}
def test[T](x: T)(implicit cm: CanMove2[T, Id]) =
cm.move(x, ImVector3f.Zero)
}
object CanMove2 {
@ -36,7 +35,7 @@ object CanMove2 {
): Id[Unit] = {}
override def location(inst: BetterCharacterControl): Id[ImVector3f] =
ImVector3f.ZERO
ImVector3f.Zero
override def jump(inst: BetterCharacterControl): Id[Unit] = ???

2
src/main/scala/wow/doge/mygame/game/subsystems/movement/MovementActor.scala

@ -129,7 +129,7 @@ class ImMovementActor[T](
}
object Methods {
def getDirection(cardinalDir: CardinalDirection, trace: String => Unit) = {
val zero = ImVector3f.ZERO
val zero = ImVector3f.Zero
val dir = cardinalDir
val walkDir = {
val mutWalkDir = new Vector3f()

11
src/main/scala/wow/doge/mygame/implicits/CatsImplicits.scala

@ -1,14 +1,13 @@
package wow.doge.mygame.implicits
import cats.data.Kleisli
import monix.bio.IO
import monix.bio.UIO
import cats.effect.Resource
import cats.Functor
import cats.effect.Bracket
import monix.bio.Task
import cats.Show
import cats.data.Kleisli
import cats.effect.Resource
import cats.syntax.show._
import monix.bio.IO
import monix.bio.Task
import monix.bio.UIO
trait CatsExtensions {
implicit class KleisliCompanionExt(k: Kleisli.type) {

3
src/main/scala/wow/doge/mygame/launcher/Launcher.scala

@ -5,6 +5,7 @@ import cats.effect.concurrent.Deferred
import cats.kernel.Eq
import javafx.application.Platform
import monix.bio.Task
import monix.bio.UIO
import monix.catnap.CancelableF
import monix.execution.CancelablePromise
import monix.reactive.Observable
@ -30,7 +31,7 @@ object Launcher {
val schedulers: Schedulers,
val signal: Deferred[Task, LauncherResult]
) {
val create = Task(new Launcher(this))
val create = UIO(new Launcher(this))
}
}
class Launcher private (props: Launcher.Props) {

45
src/main/scala/wow/doge/mygame/math/ImVector3f.scala

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

22
src/main/scala/wow/doge/mygame/subsystems/events/EventBus.scala

@ -1,25 +1,25 @@
package wow.doge.mygame.subsystems.events
import scala.reflect.ClassTag
import scala.util.Random
import akka.actor.typed.ActorRef
import akka.actor.typed.Behavior
import akka.actor.typed.scaladsl.Behaviors
import akka.event.EventStream
import monix.reactive.Observable
import monix.reactive.OverflowStrategy
import monix.execution.cancelables.SingleAssignCancelable
import monix.execution.Ack
import akka.util.Timeout
import akka.actor.typed.Scheduler
import akka.actor.typed.SpawnProtocol
import scala.util.Random
import akka.actor.typed.scaladsl.AskPattern._
import akka.actor.typed.scaladsl.Behaviors
import akka.event.EventStream
import akka.util.Timeout
import monix.bio.UIO
import monix.execution.Ack
import monix.execution.Cancelable
import wow.doge.mygame.utils.AkkaUtils
import monix.execution.cancelables.SingleAssignCancelable
import monix.reactive.Observable
import monix.reactive.OverflowStrategy
import wow.doge.mygame.implicits._
import monix.bio.UIO
import wow.doge.mygame.subsystems.events.EventBus.ObservableSubscription
import wow.doge.mygame.utils.AkkaUtils
/**
* A (typed) event bus
@ -81,6 +81,7 @@ object EventBus {
behavior,
s"eventBusObservable-${ct.toString}-${Random.nextLong()}"
)
.mapError(err => new Exception(err.toString))
.tapError {
case ex => UIO(sub.onError(ex))
}
@ -116,6 +117,7 @@ object EventBus {
behavior,
s"eventBusObservable-${ct.toString}-${math.abs(Random.nextLong())}"
)
.mapError(err => new Throwable(err.toString))
.tapError {
case ex => UIO(sub.onError(ex))
}

50
src/main/scala/wow/doge/mygame/subsystems/events/EventsModule.scala

@ -1,63 +1,75 @@
package wow.doge.mygame.subsystems.events
import java.util.concurrent.TimeoutException
import scala.concurrent.duration._
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
import akka.util.Timeout
import com.typesafe.scalalogging.{Logger => SLogger}
import monix.bio.IO
import org.slf4j.event.Level
import wow.doge.mygame.AppError
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 scala.reflect.ClassTag
import akka.actor.typed.Scheduler
class EventsModule(
scheduler: Scheduler,
spawnProtocol: ActorRef[SpawnProtocol.Command]
) {
import EventsModule._
implicit val s = scheduler
implicit val sp = spawnProtocol
implicit val timeout = Timeout(1.second)
val eventBusLogger = SLogger[EventBus[_]]
val playerEventBusTask =
val playerEventBus: IO[AppError, GameEventBus[PlayerEvent]] =
createEventBus[PlayerEvent]("playerEventBus")
// val playerCameraEventBusTask =
// createEventBus[PlayerCameraEvent]("playerCameraEventBus", Level.DEBUG)
val tickEventBusTask =
val tickEventBus: IO[AppError, GameEventBus[TickEvent]] =
createEventBus[TickEvent]("tickEventBus", Level.TRACE)
val mainEventBusTask = createEventBus[Event]("mainEventBus")
val mainEventBus: IO[AppError, GameEventBus[Event]] =
createEventBus[Event]("mainEventBus")
def createEventBus[T: ClassTag](
busName: String,
logLevel: Level = Level.DEBUG
) =
spawnProtocol.askL(
SpawnProtocol.Spawn[EventBus.Command[T]](
Behaviors.logMessages(
logOptions = LogOptions()
.withLevel(logLevel)
.withLogger(eventBusLogger.underlying),
Behaviors
.supervise(EventBus[T]())
.onFailure[Exception](SupervisorStrategy.restart)
),
busName,
Props.empty,
_
spawnProtocol
.askL(
SpawnProtocol.Spawn[EventBus.Command[T]](
Behaviors.logMessages(
logOptions = LogOptions()
.withLevel(logLevel)
.withLogger(eventBusLogger.underlying),
Behaviors
.supervise(EventBus[T]())
.onFailure[Exception](SupervisorStrategy.restart)
),
busName,
Props.empty,
_
)
)
)
.onErrorHandleWith {
case ex: TimeoutException =>
IO.raiseError(AppError.TimeoutError(ex.getMessage))
}
}
object EventsModule {

260
src/main/scala/wow/doge/mygame/subsystems/moddingsystem/ModdingSystem.scala

@ -4,9 +4,10 @@ import java.nio.file.NoSuchFileException
import scala.collection.View
import scala.collection.immutable.ArraySeq
import scala.util.Try
import cats.Show
import cats.implicits._
import cats.kernel.Eq
import io.circe._
import io.circe.generic.JsonCodec
import io.circe.generic.semiauto._
@ -17,33 +18,45 @@ import monix.reactive.Consumer
import monix.reactive.Observable
import wow.doge.mygame.utils.IOUtils
import IOUtils.toIO
@JsonCodec
final case class Test1(hello1: String, hello2: String)
@JsonCodec
final case class Test2(hello1: String)
final case class Plugin(name: String, priority: Int)
object Plugin {
implicit val pluginFormat: Decoder[Plugin] = deriveDecoder
implicit val decoder: Decoder[Plugin] = deriveDecoder
implicit val show = Show.fromToString[Plugin]
implicit val eq = Eq.fromUniversalEquals[Plugin]
}
object ModdingSystem {
sealed trait Error
final case class CouldNotDecode(cause: String) extends Error
final case class ParseFailure(cause: String) extends Error
final case class FileNotFound(fileName: String) extends Error
case object GenericError extends Error
def readPluginsList(dir: os.Path): Try[Either[Error, ArraySeq[Plugin]]] =
Try(
parse(os.read(dir / "plugins.json"))
.map(
_.as[ArraySeq[Plugin]]
.leftMap(e => CouldNotDecode(e.getMessage()))
)
.leftMap((e: ParsingFailure) => ParseFailure(e.message))
.flatten
)
// .toValidated
final case class ParseFailure(cause: io.circe.ParsingFailure) extends Error
final case class DecodingFailure(cause: io.circe.DecodingFailure)
extends Error
final case class FileNotFound(path: os.Path) extends Error
object Error {
implicit val show = Show.fromToString[Error]
implicit val eq = Eq.fromUniversalEquals[Error]
}
def readPluginsList(dir: os.Path): IO[Error, ArraySeq[Plugin]] =
IO(parse(os.read(dir / "plugins.json")))
.onErrorHandleWith {
case _: FileNotFoundException =>
IO.raiseError(FileNotFound(dir / "plugins.json"))
case _: NoSuchFileException =>
IO.raiseError(FileNotFound(dir / "plugins.json"))
}
.flatMap(files =>
IO.fromEither(files)
.map(_.as[ArraySeq[Plugin]])
.mapError(ParseFailure)
)
.flatMap(result => IO.fromEither(result).mapError(DecodingFailure))
def findPluginFiles(dir: os.Path): View[os.Path] =
os.list(dir)
@ -53,35 +66,102 @@ object ModdingSystem {
def findAndReadPluginFiles(
dir: os.Path,
plugins: ArraySeq[Plugin]
): (View[(Plugin, Error)], View[(Plugin, String)]) =
): UIO[(View[(Plugin, Error)], View[(Plugin, String)])] =
UIO(
plugins
.sortBy(_.priority)
.view
.map { p =>
val path = dir / os.RelPath(p.name + ".plugin.json")
p ->
Either
.catchNonFatal(os.read(path))
.leftMap {
case _: FileNotFoundException => FileNotFound(path)
case _: NoSuchFileException => FileNotFound(path)
}
}
.partitionMap {
case (p, either) =>
either match {
case Left(value) => Left(p -> value)
case Right(value) => Right(p -> value)
}
}
)
// : (View[(Plugin, Error)], View[(Plugin, String)])
def findAndReadPluginFiles2(
dir: os.Path,
plugins: ArraySeq[Plugin]
) =
// IO.parTraverse(plugins.sortBy(_.priority))(p =>
// IO {
// val path = dir / os.RelPath(p.name + ".plugin.json")
// os.read(path)
// }
// .onErrorHandleWith {
// case _: FileNotFoundException =>
// IO.raiseError(FileNotFound(dir.toString))
// case _: NoSuchFileException =>
// IO.raiseError(FileNotFound(dir.toString))
// }
// .flatMap(r => UIO(p -> r))
// ).map {
// _.partitionMap {
// case (p, either) =>
// either match {
// case Left(value) => Left(p -> value)
// case Right(value) => Right(p -> value)
// }
// }
// }
plugins
.sortBy(_.priority)
.view
.map(p =>
p ->
Either
.catchNonFatal {
val path = dir / os.RelPath(p.name + ".plugin.json")
os.read(path)
}
.leftMap {
case _: FileNotFoundException =>
FileNotFound(p.name)
case _: NoSuchFileException => FileNotFound(p.name)
case e => GenericError
}
)
.partitionMap {
case (p, either) =>
either match {
.map { p =>
val path = dir / os.RelPath(p.name + ".plugin.json")
p -> IO(os.read(path))
.onErrorHandleWith {
case _: FileNotFoundException => IO.raiseError(FileNotFound(path))
case _: NoSuchFileException => IO.raiseError(FileNotFound(path))
}
// .map(r => p -> r)
}
.map {
case (p, io) =>
io.attempt.map {
case Left(value) => Left(p -> value)
case Right(value) => Right(p -> value)
}
}
def readPluginFiles(filePaths: View[os.Path]) =
filePaths.map(path => os.read(path))
.to(List)
.parSequence
// .partitionMap {
// _.map {
// case l @ Left(value) => l
// case r @ Right(value) => r
// }
// }
// .sequence
// def readPluginFiles(filePaths: View[os.Path]) =
// filePaths.map(path => os.read(path))
// def readPluginFiles2(filePaths: View[os.Path]) =
// filePaths
// .map(path =>
// IO(os.read(path)).onErrorHandleWith {
// case _: FileNotFoundException =>
// IO.raiseError(FileNotFound(path))
// case _: NoSuchFileException =>
// IO.raiseError(FileNotFound(path))
// }
// )
// .to(List)
// .parSequence
def parsePluginFiles(files: View[(Plugin, String)]) =
files
.map {
@ -92,73 +172,59 @@ object ModdingSystem {
case (p, Right(value)) => Right(p -> value)
}
def foldMerge(iterable: Iterable[Json]) =
iterable.foldLeft(Json.fromString("empty")) {
case (json, io.circe.Json.Null) => json //ignore null values
case (json, value) => json.deepMerge(value)
}
val emptyJson = Json.fromString("empty")
def mergePluginData(plugins: View[(Plugin, Json)]) =
foldMerge(plugins.map {
case (p, json) => json
})
val foldFn: (Json, Json) => Json = {
case (json, Json.Null) => json //ignore null values
case (json, value) => json.deepMerge(value)
}
def mergePluginDataConsumer =
Consumer.foldLeft[Json, Json](Json.fromString("empty")) {
case (json, io.circe.Json.Null) => json
case (json, that) => json.deepMerge(that)
}
Consumer.foldLeft[Json, Json](emptyJson)(foldFn)
def loadBalancedPluginDataMerger =
Consumer
.loadBalance(parallelism = 2, mergePluginDataConsumer)
.map(foldMerge)
// def test =
// for {
// filePaths <- Task(findPluginFiles(os.pwd))
// files <- Task(readPluginFiles(filePaths))
// (failures, successes) <- Task(parsePluginFiles(files))
// merged <- Task(mergePluginData(successes))
// _ <- Task {
// println(s"Successes = ${successes.to(Seq)}")