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.

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