This commit is contained in:
Rohan Sircar 2021-01-16 18:50:32 +05:30
parent 2e05cb35fe
commit 89fad19d99
34 changed files with 725 additions and 435 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 monix.execution.Cancelable
import wow.doge.mygame.utils.AkkaUtils
import wow.doge.mygame.implicits._
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 monix.execution.cancelables.SingleAssignCancelable
import monix.reactive.Observable
import monix.reactive.OverflowStrategy
import wow.doge.mygame.implicits._
import wow.doge.mygame.subsystems.events.EventBus.ObservableSubscription
import wow.doge.mygame.utils.AkkaUtils
/**
* A (typed) event bus
@ -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))
}

View File

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

View File

@ -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
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): 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
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)
}
}
.to(List)
.parSequence
def readPluginFiles(filePaths: View[os.Path]) =
filePaths.map(path => os.read(path))
// .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)
.map(_.foldLeft(emptyJson)(foldFn))
// 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)}")
// println(s"Failure = ${failures.to(Seq)}")
// println(s"Merged = $merged")
// }
// } yield ()
def test(wd: os.Path = os.pwd) =
def run(wd: os.Path = os.pwd) =
for {
plugins <- IO.fromTryEither(readPluginsList(wd))
(readFailures, readSuccesses) <- UIO(findAndReadPluginFiles(wd, plugins))
plugins <- readPluginsList(wd)
(readFailures, readSuccesses) <- findAndReadPluginFiles(wd, plugins)
(parseFailures, parseSuccesses) <- UIO(parsePluginFiles(readSuccesses))
// res <- UIO(mergePluginData(parseSuccesses))
res <-
IOUtils
.toIO(
Observable
.fromIterable(parseSuccesses)
.map { case (p, json) => json }
.consumeWith(loadBalancedPluginDataMerger)
)
.hideErrors
_ <- UIO {
println(s"Read Successes = ${readSuccesses.to(Seq)}")
println(s"Read Failures = ${readFailures.to(Seq)}")
println(s"Parse Successes = ${parseSuccesses.to(Seq)}")
println(s"Parse Failures = ${parseFailures.to(Seq)}")
println(show"Merged = $res")
}
} yield ()
res <- UIO.parMap5(
UIO(readFailures.to(List)),
UIO(readSuccesses.to(List)),
UIO(parseFailures.to(List)),
UIO(parseSuccesses.to(List)),
toIO(
Observable
.fromIterable(parseSuccesses)
.map { case (p, json) => json }
.consumeWith(loadBalancedPluginDataMerger)
).hideErrors
)(Result.apply)
} yield res
// monix.eval.Task.deferAction(implicit s =>
// ModdingSystem
// .test()
// .leftMap(e => new Throwable(e.toString()))
// .to[monix.eval.Task]
// )
def log(res: Result) =
UIO {
pprint.log(show"Read Successes = ${res.readSuccesses}")
pprint.log(show"Read Failures = ${res.readFailures}")
pprint.log(show"Parse Successes = ${res.parseSuccesses}")
pprint.log(show"Parse Failures = ${res.parseFailures}")
pprint.log(show"Merged = ${res.pluginJson}")
}
case class Result(
readFailures: List[(Plugin, Error)],
readSuccesses: List[(Plugin, String)],
parseFailures: List[(Plugin, ParsingFailure)],
parseSuccesses: List[(Plugin, Json)],
pluginJson: Json
)
object Result {
implicit val show = Show.fromToString[Result]
// implicit val eq = Eq.fromUniversalEquals[Error]
}
// def test3(wd: os.Path = os.pwd) = {
// (readPluginsList(os.pwd).toValidatedNec)
// }
}

View File

@ -27,12 +27,14 @@ class ScriptSystemResource(
) {
val init = for {
scriptFiles <- Task(findScriptFiles(os.pwd / "assets" / "scripts"))
scriptFiles <- Task(
findScriptFiles(os.pwd / "assets" / "scripts")
).hideErrors
scriptCacheActor <- AkkaUtils.spawnActorL(
ScriptCachingActor(),
"scriptCachingActor"
)
} yield (scriptCacheActor)
} yield scriptCacheActor
def findScriptFiles(wd: os.Path) =
os.walk

View File

@ -6,10 +6,8 @@ import akka.actor.typed.Props
import akka.actor.typed.Scheduler
import akka.actor.typed.SpawnProtocol
import akka.util.Timeout
import wow.doge.mygame.implicits._
import java.util.concurrent.TimeoutException
import monix.bio.IO
import wow.doge.mygame.AppError.TimeoutError
import wow.doge.mygame.implicits._
object AkkaUtils {
@ -44,7 +42,5 @@ object AkkaUtils {
_
)
)
// .onErrorHandleWith {
// case ex: TimeoutException => IO.raiseError(TimeoutError(ex.getMessage))
// }
.onErrorHandleWith(TimeoutError.from)
}

View File

@ -1,12 +1,13 @@
package wow.doge.mygame.utils
import akka.actor.typed.scaladsl.Behaviors
import scala.concurrent.duration.FiniteDuration
import scala.util.Random
import akka.actor.typed.ActorRef
import akka.actor.typed.Behavior
import akka.actor.typed.scaladsl.TimerScheduler
import scala.util.Random
import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.scaladsl.Behaviors
import akka.actor.typed.scaladsl.TimerScheduler
import wow.doge.mygame.implicits._
object GenericTimerActor {

View File

@ -0,0 +1,31 @@
package wow.doge.mygame.utils
import cats.data.Reader
import cats.data.ReaderT
import monix.bio.UIO
object ReaderDemo {
type IoReaderT[S, E, A] = ReaderT[UIO, S, Either[E, A]]
val IoReaderT = ReaderT
val t =
ReaderT[UIO, String, Either[Error, Unit]](s => UIO.unit.attempt)
.run("s")
.rethrow
val r: IoReaderT[String, Error, Unit] = IoReaderT(s => UIO.unit.attempt)
val t2 = r.run("s").rethrow
// Kleisli[IO, String, Unit](s => IO.unit)
case class Environment(str: String, num: Int)
def fun1: Reader[String, UIO[Unit]] = Reader(str => UIO(println(str)))
def fun2: Reader[Int, UIO[Unit]] = Reader(num => UIO(println(num)))
def total: Reader[Environment, UIO[Unit]] =
for {
x <- fun1.local[Environment](_.str)
y <- fun2.local[Environment](_.num)
} yield UIO.parSequence(List(x, y)).void
val io: UIO[Unit] = total.run(Environment("hello", 50))
}

View File

@ -1,6 +1,8 @@
package wow.doge.mygame.utils.wrappers.jme
import scala.reflect.ClassTag
import cats.Show
import cats.kernel.Eq
import com.jme3.asset.AssetLoadException
import com.jme3.asset.AssetLocator
import com.jme3.asset.AssetNotFoundException
@ -48,14 +50,8 @@ object AssetManager {
case class AssetNotFound(message: String) extends Error
case class AssetLoadError(message: String) extends Error
case object CouldNotCastError extends Error
import cats.data.ReaderT
type IoReaderT[S, E, A] = ReaderT[UIO, S, Either[E, A]]
val IoReaderT = ReaderT
val t =
ReaderT[UIO, String, Either[Error, Unit]](s => UIO.unit.attempt)
.run("s")
.rethrow
val r: IoReaderT[String, Error, Unit] = IoReaderT(s => UIO.unit.attempt)
val t2 = r.run("s").rethrow
// Kleisli[IO, String, Unit](s => IO.unit)
object Error {
implicit val show = Show.fromToString[Error]
implicit val eq = Eq.fromUniversalEquals[Error]
}
}

View File

@ -1,5 +1,7 @@
package wow.doge.mygame.utils.wrappers.jme
import cats.Show
import cats.kernel.Eq
import com.jme3.bullet.collision.shapes.CollisionShape
import com.jme3.bullet.{util => jmebu}
import com.jme3.scene.Spatial
@ -9,10 +11,17 @@ object CollisionShapeFactory {
sealed trait Error
case class WrongArgumentError(reason: String) extends Error
object Error {
implicit val show = Show.fromToString[Error]
implicit val eq = Eq.fromUniversalEquals[Error]
}
def createMeshShape(subtree: Spatial): IO[Error, CollisionShape] =
IO(jmebu.CollisionShapeFactory.createMeshShape(subtree)).onErrorHandleWith {
case ex: IllegalArgumentException
if (ex.getMessage.startsWith("The spatial must either be a Node")) =>
IO.raiseError(WrongArgumentError(ex.getMessage))
}
IO(jmebu.CollisionShapeFactory.createMeshShape(subtree))
.onErrorHandleWith {
case ex: IllegalArgumentException
if (ex.getMessage
.startsWith("The spatial must either be a Node")) =>
IO.raiseError(WrongArgumentError(ex.getMessage))
}
}

View File

@ -36,13 +36,11 @@ object NodeWrapper {
def +=(n: Node[F]) = nw.add(n)
def -=(n: jmes.Spatial) = nw.remove(n)
def -=(wn: Node[F]) = nw.remove(wn)
def +=(light: Light) = {
def +=(light: Light) =
nw.addLight(light)
}
def -=(light: Light) = {
def -=(light: Light) =
nw.removeLight(light)
}
}
}
@ -108,13 +106,11 @@ object NodeWrapper2 {
def +=(n: Node2) = nw.add(n)
def -=(n: jmes.Spatial) = nw.remove(n)
def -=(wn: Node2) = nw.remove(wn)
def +=(light: Light) = {
def +=(light: Light) =
nw.addLight(light)
}
def -=(light: Light) = {
def -=(light: Light) =
nw.removeLight(light)
}
}
}
@ -133,7 +129,7 @@ object Node2 {
final class AppNode2 private (node: jmes.Node) extends NodeWrapper2(node)
object AppNode2 {
// sealed trait Error extends NodeWrapper2.Error
def apply(name: String) = new AppNode2(new jmes.Node(name))
def apply(n: jmes.Node) = new AppNode2(n)

View File

@ -1,22 +1,22 @@
package wow.doge.mygame.utils.wrappers.jme
import cats.effect.Sync
import com.jme3.{bullet => jmeb}
import com.jme3.{scene => jmes}
import monix.bio.UIO
import wow.doge.mygame.implicits._
final class PhysicsSpace[F[_]: Sync](space: jmeb.PhysicsSpace) {
def add(anyObject: Any) = Sync[F].delay(space.add(anyObject))
final class PhysicsSpace(space: jmeb.PhysicsSpace) {
def add(anyObject: Any) = UIO(space.add(anyObject))
def remove(anyObject: Any) =
Sync[F].delay {
UIO {
space.remove(anyObject)
space
}
def addAll(spatial: jmes.Spatial) = Sync[F].delay(space.addAll(spatial))
def addAll(spatial: jmes.Spatial) = UIO(space.addAll(spatial))
def removeAll(spatial: jmes.Spatial) =
Sync[F].delay {
UIO {
space.removeAll(spatial)
space
}
@ -25,7 +25,7 @@ final class PhysicsSpace[F[_]: Sync](space: jmeb.PhysicsSpace) {
def physicsTickObservable = space.physicsTickObservable()
}
object PhysicsSpace {
implicit final class PhysicsSpaceOps[F[_]](private val space: PhysicsSpace[F])
implicit final class PhysicsSpaceOps(private val space: PhysicsSpace)
extends AnyVal {
def +=(anyObject: Any) = space.add(anyObject)

View File

@ -12,7 +12,11 @@ import wow.doge.mygame.utils.wrappers.jme.AssetManager.CouldNotCastError
import com.jme3.scene.Node
import com.jme3.material.MaterialDef
import com.jme3.material.Material
import scala.annotation.nowarn
@nowarn("msg=method get in class LeftProjection is deprecated")
@nowarn("msg=method right in class Either is deprecated")
@nowarn("msg=method get in class RightProjection is deprecated")
class AssetManagerTest extends AnyFunSuite {
val _assetManager: jmea.AssetManager = new DesktopAssetManager(true)
@ -21,7 +25,8 @@ class AssetManagerTest extends AnyFunSuite {
test("Test for AssetNotFound error") {
val res =
assetManager.loadModel(os.rel / "doesnotexist").attempt.runSyncUnsafe()
assert(res === Left(AssetNotFound("doesnotexist")))
assert(res.isLeft)
assert(res.left.get eqv AssetNotFound("doesnotexist"))
}
test("Test for Model CouldNotCastError") {
@ -30,14 +35,15 @@ class AssetManagerTest extends AnyFunSuite {
.loadModelAs[Geometry](modelPath)
.attempt
.runSyncUnsafe()
assert(res1 === Left(CouldNotCastError))
assert(res1.isLeft)
assert(res1.left.get eqv CouldNotCastError)
val res2 = assetManager
.loadModelAs[Node](modelPath)
.attempt
.runSyncUnsafe()
assert(res2.map(_.getName) === Right("JaimeGeom-ogremesh"))
assert(res2.isRight)
assert(res2.map(_.getName).right.get eqv "JaimeGeom-ogremesh")
}
test("Test for Asset CouldNotCastError") {
@ -47,13 +53,14 @@ class AssetManagerTest extends AnyFunSuite {
.loadAssetAs[Material](assetPath)
.attempt
.runSyncUnsafe()
assert(res1 === Left(CouldNotCastError))
assert(res1.isLeft)
assert(res1.left.get eqv CouldNotCastError)
val res2 = assetManager
.loadAssetAs[MaterialDef](assetPath)
.attempt
.runSyncUnsafe()
assert(res2.map(_.getName) === Right("Unshaded"))
assert(res2.isRight)
assert(res2.map(_.getName).right.get eqv "Unshaded")
}
}

View File

@ -18,12 +18,13 @@ class CollisionShapeFactoryTest extends AnyFunSuite {
.attempt
.runSyncUnsafe()
assert(res.isLeft)
assert(
res === Left(
res.left.get eqv
CollisionShapeFactory.WrongArgumentError(
"The spatial must either be a Node or a Geometry!"
)
)
)
}
}

View File

@ -0,0 +1,51 @@
package wow.doge.mygame
import org.scalatest.funsuite.AnyFunSuite
import wow.doge.mygame.math.ImVector3f
import com.typesafe.scalalogging.LazyLogging
import cats.syntax.eq._
import cats.syntax.show._
class ImVector3fTest extends AnyFunSuite with LazyLogging {
test("maxvalue") {
val v1 = ImVector3f.Max
val v2 = ImVector3f.Max
logger.info(ImVector3f.dst(v1, v2).show)
}
test("minvalue") {
val v1 = ImVector3f.Min
val v2 = ImVector3f.Min
logger.info(ImVector3f.dst(v1, v2).show)
}
test("maxvalue and unit") {
val v1 = ImVector3f.Max
val v2 = ImVector3f(1, 1, 1)
assert(ImVector3f.dst(v1, v2) eqv 5.8938631329669654e38)
assert(ImVector3f.dst(v1, v2) eqv ImVector3f.dst(v2, v1))
}
test("minvalue and unit") {
val v1 = ImVector3f.Min
val v2 = ImVector3f(1, 1, 1)
assert(ImVector3f.dst(v1, v2) eqv 5.8938631329669654e38)
assert(ImVector3f.dst(v1, v2) eqv ImVector3f.dst(v2, v1))
}
test("another") {
{
val v1 = ImVector3f(1, 0, 0)
val v2 = ImVector3f(1, 1, 1)
logger.info(ImVector3f.dst(v1, v2).show)
assert(ImVector3f.dst(v1, v2) eqv ImVector3f.dst(v2, v1))
}
{
val v1 = ImVector3f(1, 1, 0)
val v2 = ImVector3f(1, 1, 1)
logger.info(ImVector3f.dst(v1, v2).show)
assert(ImVector3f.dst(v1, v2) eqv ImVector3f.dst(v2, v1))
}
}
}

View File

@ -0,0 +1,27 @@
package wow.doge.mygame
import org.scalatest.funsuite.AnyFunSuite
import wow.doge.mygame.subsystems.moddingsystem.ModdingSystem
import monix.execution.Scheduler.Implicits.global
import io.circe.Printer
import monix.bio.UIO
import cats.syntax.eq._
class ModdingSystemTest extends AnyFunSuite {
val printer = Printer.spaces2
test("main") {
val io = for {
res <- ModdingSystem.run()
_ <- UIO(
assert(
(res.parseSuccesses.length + res.parseFailures.length) eqv res.readSuccesses.length
)
)
_ <- ModdingSystem.log(res)
} yield res
io.attempt.runSyncUnsafe() match {
case Left(value) => pprint.log(value); ()
case Right(value) => ()
}
}
}

View File

@ -0,0 +1,67 @@
package wow.doge.mygame
import cats.data.ReaderT
import monix.bio.UIO
import org.scalatest.funsuite.AnyFunSuite
import monix.execution.Scheduler.Implicits.global
import monix.bio.IO
class ReaderT_Test extends AnyFunSuite {
// type IoReaderT[S, E, A] = ReaderT[UIO, S, Either[E, A]]
// val IoReaderT = ReaderT
// val t =
// ReaderT[UIO, String, Either[Error, Unit]](s => UIO.unit.attempt)
// .run("s")
// .rethrow
// val r: IoReaderT[String, Error, Unit] = IoReaderT(s => UIO.unit.attempt)
// val t2 = r.run("s").rethrow
// Kleisli[IO, String, Unit](s => IO.unit)
case class Environment(str: String, num: Int)
// test("runReaderT_Test") {
// def fun1: ReaderT[UIO, String, Unit] = ReaderT(str => UIO(println(str)))
// def fun2: ReaderT[UIO, Int, Unit] = ReaderT(num => UIO(println(num)))
// def total: ReaderT[UIO, Environment, Unit] =
// for {
// _ <- fun1.local[Environment](_.str)
// _ <- fun2.local[Environment](_.num)
// } yield ()
// val uio: UIO[Unit] = total.run(Environment("hello", 50))
// uio.runSyncUnsafe()
// }
test("2") {
def fun1: ReaderT[IO[String, ?], String, Unit] =
ReaderT(s => IO(println(s)).onErrorHandleWith(_ => IO.raiseError("wow")))
def fun2: ReaderT[IO[String, ?], Int, Unit] =
ReaderT(num =>
IO(println(num)).onErrorHandleWith(_ => IO.raiseError("whew"))
)
def fun3: ReaderT[IO[String, ?], Environment, Unit] =
for {
env <- ReaderT.ask[IO[String, ?], Environment]
} yield ()
def fun4: ReaderT[IO[String, ?], Environment, Unit] =
for {
env <- ReaderT.ask[IO[String, ?], Environment]
_ <- ReaderT.liftF(
IO(println(env)).onErrorHandleWith(_ => IO.raiseError("wow"))
)
_ <- fun3
} yield ()
def app: ReaderT[IO[String, ?], Environment, Unit] =
for {
_ <- fun1.local[Environment](_.str)
_ <- fun2.local[Environment](_.num)
_ <- fun3
_ <- fun4
} yield ()
val io: IO[String, Unit] = app.run(Environment("hello", 50))
io.attempt.runSyncUnsafe()
}
}

View File

@ -0,0 +1,37 @@
package wow.doge.mygame
import cats.data.Reader
import monix.bio.UIO
import org.scalatest.funsuite.AnyFunSuite
import monix.execution.Scheduler.Implicits.global
class ReaderTest extends AnyFunSuite {
// type IoReaderT[S, E, A] = ReaderT[UIO, S, Either[E, A]]
// val IoReaderT = ReaderT
// val t =
// ReaderT[UIO, String, Either[Error, Unit]](s => UIO.unit.attempt)
// .run("s")
// .rethrow
// val r: IoReaderT[String, Error, Unit] = IoReaderT(s => UIO.unit.attempt)
// val t2 = r.run("s").rethrow
// Kleisli[IO, String, Unit](s => IO.unit)
case class Environment(str: String, num: Int)
def fun1: Reader[String, UIO[Unit]] = Reader(str => UIO(println(str)))
def fun2: Reader[Int, UIO[Unit]] = Reader(num => UIO(println(num)))
def total: Reader[Environment, UIO[Unit]] =
for {
x <- fun1.local[Environment](_.str)
y <- fun2.local[Environment](_.num)
} yield UIO.parSequence(List(x, y)).void
val io: UIO[Unit] = total.run(Environment("hello", 50))
test("runTest") {
io.runSyncUnsafe()
}
}