Testing out JmonkeyEngine to make a game in Scala with Akka Actors within a pure FP layer
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

189 lines
5.3 KiB

package wow.doge.mygame.state
import javax.script.ScriptEngine
import javax.script.ScriptEngineManager
import akka.actor.typed.ActorRef
import akka.actor.typed.Behavior
import akka.actor.typed.LogOptions
import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.scaladsl.Behaviors
import ammonite.Main
import ammonite.main.Defaults
import ammonite.runtime.Storage.Folder
import ammonite.util.Res.Success
import cats.implicits._
import com.softwaremill.tagging._
import com.typesafe.scalalogging.Logger
import groovy.util.GroovyScriptEngine
import org.slf4j.event.Level
object ScriptActor {
/**
* aka script representation
*/
sealed trait ScriptTag
type ScriptObject = Any @@ ScriptTag
trait Kotlin
type KotlinScriptEngine = ScriptEngine @@ Kotlin
final case class Error(reason: String)
sealed trait Command
final case class CompileAny(
path: os.Path,
result: ActorRef[Either[Error, ScriptObject]]
) extends Command
final case class CompileAll(
paths: Seq[os.Path],
result: ActorRef[Map[os.Path, Either[Error, Any]]]
) extends Command
val defaultScalaRunner =
ammonite
.Main(
storageBackend = new Folder(
// os.pwd / "target"
Defaults.ammoniteHome,
isRepl = false
)
)
val defaultKotlinRunner: KotlinScriptEngine = {
val manager = new ScriptEngineManager()
val engine = manager.getEngineByExtension("main.kts")
engine.taggedWith[Kotlin]
}
val defaultGroovyRunner: GroovyScriptEngine =
new GroovyScriptEngine(os.pwd.toString)
def apply(
scalaRunner: Main = defaultScalaRunner,
kotlinRunner: KotlinScriptEngine = defaultKotlinRunner,
groovyRunner: GroovyScriptEngine = defaultGroovyRunner
): Behavior[ScriptActor.Command] =
Behaviors.logMessages(
LogOptions()
.withLevel(Level.TRACE)
.withLogger(
Logger[ScriptActor].underlying
),
Behaviors.setup(ctx =>
new ScriptActor(
scalaRunner,
kotlinRunner,
groovyRunner,
ctx
).receiveMessage
)
)
sealed trait ScriptType
case object ScalaType extends ScriptType
case object KotlinType extends ScriptType
case object GroovyType extends ScriptType
def determineScriptType(path: os.Path): Either[Error, ScriptType] =
path.toString match {
case s if s.endsWith(".sc") => Right(ScalaType)
case s if s.endsWith(".main.kts") => Right(KotlinType)
case s if s.endsWith(".groovy") => Right(GroovyType)
case _ => Left(Error("Unknown script type"))
}
def runScala(path: os.Path, scalaRunner: ammonite.Main): Either[Error, Any] =
scalaRunner
.runScript(path, Seq.empty)
._1 match {
case ammonite.util.Res.Exception(t, msg) => Left(Error(msg))
case Success(obj) => Right(obj)
case _ => Left(Error("Failed to run script"))
}
def runKotlin(
path: os.Path,
kotlinRunner: KotlinScriptEngine
): Either[Error, Any] =
Either
.catchNonFatal(kotlinRunner.eval(os.read(path)))
.leftMap(t => Error(t.getMessage()))
def runGroovy(
path: os.Path,
groovyRunner: GroovyScriptEngine
): Either[Error, Any] =
Either
.catchNonFatal(groovyRunner.run(path.relativeTo(os.pwd).toString(), ""))
.leftMap(t => Error(t.getMessage()))
def ensureReturnedObjectNotNull(scriptObject: Any): Either[Error, Any] =
Either.fromOption(Option(scriptObject), Error("unknown object"))
}
class ScriptActor(
val scalaRunner: ammonite.Main,
val kotlinRunner: ScriptActor.KotlinScriptEngine,
val groovyRunner: GroovyScriptEngine,
context: ActorContext[ScriptActor.Command]
) {
import ScriptActor._
def receiveMessage: Behavior[Command] =
Behaviors.receiveMessage {
case CompileAny(path, requester) =>
context.log.debug(s"Received $path")
val res = getScript(path)
context.log.debug(s"result = $res")
requester ! res
Behaviors.same
case CompileAll(paths, requester) =>
context.log.debug(s"Received $paths")
requester ! compileAll(paths)
Behaviors.same
}
def getScript(path: os.Path): Either[Error, ScriptObject] =
determineScriptType(path) match {
case Right(ScalaType) =>
runScala(path, scalaRunner)
.flatMap(ensureReturnedObjectNotNull)
.map(_.taggedWith[ScriptTag])
case Right(KotlinType) =>
runKotlin(path, kotlinRunner)
.flatMap(ensureReturnedObjectNotNull)
.map(_.taggedWith[ScriptTag])
case Right(GroovyType) =>
runGroovy(path, groovyRunner)
.flatMap(ensureReturnedObjectNotNull)
.map(_.taggedWith[ScriptTag])
case l @ Left(err) => l.map(_.taggedWith[ScriptTag])
}
type LOL = Map[os.Path, Either[wow.doge.mygame.state.ScriptActor.Error, Any]]
def compileAll(
paths: Seq[os.Path]
): LOL = {
@annotation.tailrec
def loop(
paths: Seq[os.Path],
scriptsMap: Map[
os.Path,
Either[wow.doge.mygame.state.ScriptActor.Error, Any]
]
): LOL =
paths match {
case head :: next => loop(next, scriptsMap + (head -> getScript(head)))
case Nil => scriptsMap
}
loop(paths, Map.empty)
}
}