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.

180 lines
5.0 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. package wow.doge.mygame.state
  2. import javax.script.ScriptEngine
  3. import javax.script.ScriptEngineManager
  4. import akka.actor.typed.ActorRef
  5. import akka.actor.typed.Behavior
  6. import akka.actor.typed.LogOptions
  7. import akka.actor.typed.scaladsl.ActorContext
  8. import akka.actor.typed.scaladsl.Behaviors
  9. import ammonite.Main
  10. import ammonite.main.Defaults
  11. import ammonite.runtime.Storage.Folder
  12. import ammonite.util.Res.Success
  13. import cats.implicits._
  14. import com.softwaremill.tagging._
  15. import com.typesafe.scalalogging.Logger
  16. import groovy.util.GroovyScriptEngine
  17. import org.slf4j.event.Level
  18. object ScriptActor {
  19. trait Kotlin
  20. type KotlinScriptEngine = ScriptEngine @@ Kotlin
  21. final case class Error(reason: String)
  22. sealed trait Command
  23. final case class CompileAny(
  24. path: os.Path,
  25. result: ActorRef[Either[Error, Any]]
  26. ) extends Command
  27. final case class CompileAll(
  28. paths: Seq[os.Path],
  29. result: ActorRef[Map[os.Path, Either[Error, Any]]]
  30. ) extends Command
  31. lazy val defaultScalaRunner =
  32. ammonite
  33. .Main(
  34. storageBackend = new Folder(
  35. // os.pwd / "target"
  36. Defaults.ammoniteHome,
  37. isRepl = false
  38. )
  39. )
  40. lazy val defaultKotlinRunner: KotlinScriptEngine = {
  41. val manager = new ScriptEngineManager()
  42. val engine = manager.getEngineByExtension("main.kts")
  43. engine.taggedWith[Kotlin]
  44. }
  45. lazy val defaultGroovyRunner: GroovyScriptEngine =
  46. new GroovyScriptEngine(os.pwd.toString)
  47. def apply(
  48. scalaRunner: Main = defaultScalaRunner,
  49. kotlinRunner: KotlinScriptEngine = defaultKotlinRunner,
  50. groovyRunner: GroovyScriptEngine = defaultGroovyRunner
  51. ): Behavior[ScriptActor.Command] =
  52. Behaviors.logMessages(
  53. LogOptions()
  54. .withLevel(Level.TRACE)
  55. .withLogger(
  56. Logger[ScriptActor].underlying
  57. ),
  58. Behaviors.setup(ctx =>
  59. new ScriptActor(
  60. scalaRunner,
  61. kotlinRunner,
  62. groovyRunner,
  63. ctx
  64. ).receiveMessage
  65. )
  66. )
  67. sealed trait ScriptType
  68. case object ScalaType extends ScriptType
  69. case object KotlinType extends ScriptType
  70. case object GroovyType extends ScriptType
  71. def determineScriptType(path: os.Path): Either[Error, ScriptType] =
  72. path.toString match {
  73. case s if s.endsWith(".sc") => Right(ScalaType)
  74. case s if s.endsWith(".main.kts") => Right(KotlinType)
  75. case s if s.endsWith(".groovy") => Right(GroovyType)
  76. case _ => Left(Error("Unknown script type"))
  77. }
  78. def runScala(path: os.Path, scalaRunner: ammonite.Main): Either[Error, Any] =
  79. scalaRunner
  80. .runScript(
  81. path,
  82. Seq.empty
  83. )
  84. ._1 match {
  85. case ammonite.util.Res.Exception(t, msg) => Left(Error(msg))
  86. case Success(obj) => Right(obj)
  87. case _ => Left(Error("Failed to run script"))
  88. }
  89. def runKotlin(
  90. path: os.Path,
  91. kotlinRunner: KotlinScriptEngine
  92. ): Either[Error, Any] =
  93. Either
  94. .catchNonFatal(kotlinRunner.eval(os.read(path)))
  95. .leftMap(t => Error(t.getMessage()))
  96. def runGroovy(
  97. path: os.Path,
  98. groovyRunner: GroovyScriptEngine
  99. ): Either[Error, Any] =
  100. Either
  101. .catchNonFatal(groovyRunner.run(path.relativeTo(os.pwd).toString(), ""))
  102. .leftMap(t => Error(t.getMessage()))
  103. def ensureReturnedObjectNotNull(scriptObject: Any): Either[Error, Any] =
  104. Either.fromOption(Option(scriptObject), Error("unknown object"))
  105. }
  106. class ScriptActor(
  107. val scalaRunner: ammonite.Main,
  108. val kotlinRunner: ScriptActor.KotlinScriptEngine,
  109. val groovyRunner: GroovyScriptEngine,
  110. context: ActorContext[ScriptActor.Command]
  111. ) {
  112. import ScriptActor._
  113. def receiveMessage: Behavior[Command] =
  114. Behaviors.receiveMessage {
  115. case CompileAny(path, requester) =>
  116. context.log.debug(s"Received $path")
  117. val res = getScript(path)
  118. context.log.debug(s"result = $res")
  119. requester ! res
  120. Behaviors.same
  121. case CompileAll(paths, requester) =>
  122. context.log.debug(s"Received $paths")
  123. requester ! compileAll(paths)
  124. Behaviors.same
  125. }
  126. def getScript(path: os.Path): Either[Error, Any] =
  127. determineScriptType(path) match {
  128. case Right(ScalaType) =>
  129. runScala(path, scalaRunner).flatMap(ensureReturnedObjectNotNull)
  130. case Right(KotlinType) =>
  131. runKotlin(path, kotlinRunner).flatMap(ensureReturnedObjectNotNull)
  132. case Right(GroovyType) =>
  133. runGroovy(path, groovyRunner).flatMap(ensureReturnedObjectNotNull)
  134. case l @ Left(err) => l
  135. }
  136. type LOL = Map[os.Path, Either[wow.doge.mygame.state.ScriptActor.Error, Any]]
  137. def compileAll(
  138. paths: Seq[os.Path]
  139. ): LOL = {
  140. @annotation.tailrec
  141. def loop(
  142. paths: Seq[os.Path],
  143. scriptsMap: Map[
  144. os.Path,
  145. Either[wow.doge.mygame.state.ScriptActor.Error, Any]
  146. ]
  147. ): LOL = {
  148. paths match {
  149. case head :: next => loop(next, scriptsMap + (head -> getScript(head)))
  150. case Nil => scriptsMap
  151. }
  152. }
  153. loop(paths, Map.empty)
  154. }
  155. }