diff --git a/build.sbt b/build.sbt index 5715a98..ad79381 100644 --- a/build.sbt +++ b/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 diff --git a/src/main/scala/wow/doge/mygame/AppError.scala b/src/main/scala/wow/doge/mygame/AppError.scala index 9ba2fd9..90b49b9 100644 --- a/src/main/scala/wow/doge/mygame/AppError.scala +++ b/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)) + + } } diff --git a/src/main/scala/wow/doge/mygame/Main.scala b/src/main/scala/wow/doge/mygame/Main.scala index bba8895..6e43fb4 100644 --- a/src/main/scala/wow/doge/mygame/Main.scala +++ b/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( diff --git a/src/main/scala/wow/doge/mygame/MainApp.scala b/src/main/scala/wow/doge/mygame/MainApp.scala index 03f6995..cebfe23 100644 --- a/src/main/scala/wow/doge/mygame/MainApp.scala +++ b/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) } diff --git a/src/main/scala/wow/doge/mygame/MainModule.scala b/src/main/scala/wow/doge/mygame/MainModule.scala index 6b65566..ce77f93 100644 --- a/src/main/scala/wow/doge/mygame/MainModule.scala +++ b/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 { diff --git a/src/main/scala/wow/doge/mygame/game/GameApp.scala b/src/main/scala/wow/doge/mygame/game/GameApp.scala index 6863ae5..cef597a 100644 --- a/src/main/scala/wow/doge/mygame/game/GameApp.scala +++ b/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)) } } diff --git a/src/main/scala/wow/doge/mygame/game/GameAppActor.scala b/src/main/scala/wow/doge/mygame/game/GameAppActor.scala index 2b5d83a..c33f04e 100644 --- a/src/main/scala/wow/doge/mygame/game/GameAppActor.scala +++ b/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 { diff --git a/src/main/scala/wow/doge/mygame/game/entities/player/PlayerActorSupervisor.scala b/src/main/scala/wow/doge/mygame/game/entities/player/PlayerActorSupervisor.scala index 63dbfff..78746b7 100644 --- a/src/main/scala/wow/doge/mygame/game/entities/player/PlayerActorSupervisor.scala +++ b/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( diff --git a/src/main/scala/wow/doge/mygame/game/entities/player/PlayerController.scala b/src/main/scala/wow/doge/mygame/game/entities/player/PlayerController.scala index b4e2387..c941a1a 100644 --- a/src/main/scala/wow/doge/mygame/game/entities/player/PlayerController.scala +++ b/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, diff --git a/src/main/scala/wow/doge/mygame/game/subsystems/input/GameInputHandler.scala b/src/main/scala/wow/doge/mygame/game/subsystems/input/GameInputHandler.scala index 118b6ad..4e9f544 100644 --- a/src/main/scala/wow/doge/mygame/game/subsystems/input/GameInputHandler.scala +++ b/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 { diff --git a/src/main/scala/wow/doge/mygame/game/subsystems/level/DefaultGameLevel.scala b/src/main/scala/wow/doge/mygame/game/subsystems/level/DefaultGameLevel.scala index 69a7901..ef3c145 100644 --- a/src/main/scala/wow/doge/mygame/game/subsystems/level/DefaultGameLevel.scala +++ b/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)) diff --git a/src/main/scala/wow/doge/mygame/game/subsystems/level/GameLevel.scala b/src/main/scala/wow/doge/mygame/game/subsystems/level/GameLevel.scala index d2f7925..9176f4c 100644 --- a/src/main/scala/wow/doge/mygame/game/subsystems/level/GameLevel.scala +++ b/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( diff --git a/src/main/scala/wow/doge/mygame/game/subsystems/movement/CanMove2.scala b/src/main/scala/wow/doge/mygame/game/subsystems/movement/CanMove2.scala index 8a888f6..42d596f 100644 --- a/src/main/scala/wow/doge/mygame/game/subsystems/movement/CanMove2.scala +++ b/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] = ??? diff --git a/src/main/scala/wow/doge/mygame/game/subsystems/movement/MovementActor.scala b/src/main/scala/wow/doge/mygame/game/subsystems/movement/MovementActor.scala index 294f433..faa68a7 100644 --- a/src/main/scala/wow/doge/mygame/game/subsystems/movement/MovementActor.scala +++ b/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() diff --git a/src/main/scala/wow/doge/mygame/implicits/CatsImplicits.scala b/src/main/scala/wow/doge/mygame/implicits/CatsImplicits.scala index 97fe9af..cc201ea 100644 --- a/src/main/scala/wow/doge/mygame/implicits/CatsImplicits.scala +++ b/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) { diff --git a/src/main/scala/wow/doge/mygame/launcher/Launcher.scala b/src/main/scala/wow/doge/mygame/launcher/Launcher.scala index 9da2904..707d60d 100644 --- a/src/main/scala/wow/doge/mygame/launcher/Launcher.scala +++ b/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) { diff --git a/src/main/scala/wow/doge/mygame/math/ImVector3f.scala b/src/main/scala/wow/doge/mygame/math/ImVector3f.scala index e1b3750..ccf3729 100644 --- a/src/main/scala/wow/doge/mygame/math/ImVector3f.scala +++ b/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) + } diff --git a/src/main/scala/wow/doge/mygame/subsystems/events/EventBus.scala b/src/main/scala/wow/doge/mygame/subsystems/events/EventBus.scala index 7d12d31..3d720c1 100644 --- a/src/main/scala/wow/doge/mygame/subsystems/events/EventBus.scala +++ b/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)) } diff --git a/src/main/scala/wow/doge/mygame/subsystems/events/EventsModule.scala b/src/main/scala/wow/doge/mygame/subsystems/events/EventsModule.scala index 200b18d..6fdbbf1 100644 --- a/src/main/scala/wow/doge/mygame/subsystems/events/EventsModule.scala +++ b/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 { diff --git a/src/main/scala/wow/doge/mygame/subsystems/moddingsystem/ModdingSystem.scala b/src/main/scala/wow/doge/mygame/subsystems/moddingsystem/ModdingSystem.scala index b82e87f..bdc7a62 100644 --- a/src/main/scala/wow/doge/mygame/subsystems/moddingsystem/ModdingSystem.scala +++ b/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)}") -// println(s"Failure = ${failures.to(Seq)}") -// println(s"Merged = $merged") -// } -// } yield () - - def test(wd: os.Path = os.pwd) = + .map(_.foldLeft(emptyJson)(foldFn)) + + 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 + + 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}") + } - // monix.eval.Task.deferAction(implicit s => - // ModdingSystem - // .test() - // .leftMap(e => new Throwable(e.toString())) - // .to[monix.eval.Task] - // ) + 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) - // } } diff --git a/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptSystemModule.scala b/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptSystemModule.scala index 08e1737..a48f668 100644 --- a/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptSystemModule.scala +++ b/src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptSystemModule.scala @@ -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 diff --git a/src/main/scala/wow/doge/mygame/utils/AkkaUtils.scala b/src/main/scala/wow/doge/mygame/utils/AkkaUtils.scala index 38f71c1..b7d9997 100644 --- a/src/main/scala/wow/doge/mygame/utils/AkkaUtils.scala +++ b/src/main/scala/wow/doge/mygame/utils/AkkaUtils.scala @@ -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) } diff --git a/src/main/scala/wow/doge/mygame/utils/GenericTimerActor.scala b/src/main/scala/wow/doge/mygame/utils/GenericTimerActor.scala index 96a66e0..10e6459 100644 --- a/src/main/scala/wow/doge/mygame/utils/GenericTimerActor.scala +++ b/src/main/scala/wow/doge/mygame/utils/GenericTimerActor.scala @@ -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 { diff --git a/src/main/scala/wow/doge/mygame/utils/ReaderDemo.scala b/src/main/scala/wow/doge/mygame/utils/ReaderDemo.scala new file mode 100644 index 0000000..45e9976 --- /dev/null +++ b/src/main/scala/wow/doge/mygame/utils/ReaderDemo.scala @@ -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)) + +} diff --git a/src/main/scala/wow/doge/mygame/utils/wrappers/jme/AssetManager.scala b/src/main/scala/wow/doge/mygame/utils/wrappers/jme/AssetManager.scala index 9f63fc1..f9c6bb9 100644 --- a/src/main/scala/wow/doge/mygame/utils/wrappers/jme/AssetManager.scala +++ b/src/main/scala/wow/doge/mygame/utils/wrappers/jme/AssetManager.scala @@ -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] + } } diff --git a/src/main/scala/wow/doge/mygame/utils/wrappers/jme/CollisionShapeFactory.scala b/src/main/scala/wow/doge/mygame/utils/wrappers/jme/CollisionShapeFactory.scala index 178a201..e8b88a5 100644 --- a/src/main/scala/wow/doge/mygame/utils/wrappers/jme/CollisionShapeFactory.scala +++ b/src/main/scala/wow/doge/mygame/utils/wrappers/jme/CollisionShapeFactory.scala @@ -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)) + } } diff --git a/src/main/scala/wow/doge/mygame/utils/wrappers/jme/Node.scala b/src/main/scala/wow/doge/mygame/utils/wrappers/jme/Node.scala index 4f124cc..4aa7430 100644 --- a/src/main/scala/wow/doge/mygame/utils/wrappers/jme/Node.scala +++ b/src/main/scala/wow/doge/mygame/utils/wrappers/jme/Node.scala @@ -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) diff --git a/src/main/scala/wow/doge/mygame/utils/wrappers/jme/PhysicsSpace.scala b/src/main/scala/wow/doge/mygame/utils/wrappers/jme/PhysicsSpace.scala index 671fa5a..b689eee 100644 --- a/src/main/scala/wow/doge/mygame/utils/wrappers/jme/PhysicsSpace.scala +++ b/src/main/scala/wow/doge/mygame/utils/wrappers/jme/PhysicsSpace.scala @@ -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) diff --git a/src/test/scala/wow/doge/mygame/AssetManagerTest.scala b/src/test/scala/wow/doge/mygame/AssetManagerTest.scala index a1beef3..988581b 100644 --- a/src/test/scala/wow/doge/mygame/AssetManagerTest.scala +++ b/src/test/scala/wow/doge/mygame/AssetManagerTest.scala @@ -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") } } diff --git a/src/test/scala/wow/doge/mygame/CollisionShapeFactoryTest.scala b/src/test/scala/wow/doge/mygame/CollisionShapeFactoryTest.scala index 76e4203..8a7722f 100644 --- a/src/test/scala/wow/doge/mygame/CollisionShapeFactoryTest.scala +++ b/src/test/scala/wow/doge/mygame/CollisionShapeFactoryTest.scala @@ -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!" ) - ) ) } } diff --git a/src/test/scala/wow/doge/mygame/ImVector3fTest.scala b/src/test/scala/wow/doge/mygame/ImVector3fTest.scala new file mode 100644 index 0000000..46853d6 --- /dev/null +++ b/src/test/scala/wow/doge/mygame/ImVector3fTest.scala @@ -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)) + } + + } +} diff --git a/src/test/scala/wow/doge/mygame/ModdingSystemTest.scala b/src/test/scala/wow/doge/mygame/ModdingSystemTest.scala new file mode 100644 index 0000000..b3d19d2 --- /dev/null +++ b/src/test/scala/wow/doge/mygame/ModdingSystemTest.scala @@ -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) => () + } + } +} diff --git a/src/test/scala/wow/doge/mygame/ReaderT_Test.scala b/src/test/scala/wow/doge/mygame/ReaderT_Test.scala new file mode 100644 index 0000000..9b8d29a --- /dev/null +++ b/src/test/scala/wow/doge/mygame/ReaderT_Test.scala @@ -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() + } + +} diff --git a/src/test/scala/wow/doge/mygame/ReaderTest.scala b/src/test/scala/wow/doge/mygame/ReaderTest.scala new file mode 100644 index 0000000..2230c6f --- /dev/null +++ b/src/test/scala/wow/doge/mygame/ReaderTest.scala @@ -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() + } + +}