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.

266 lines
8.2 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
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. package wow.doge.mygame.scriptsystem
  2. import scala.util.Failure
  3. import scala.util.Success
  4. import akka.actor.typed.ActorRef
  5. import akka.actor.typed.Behavior
  6. import akka.actor.typed.LogOptions
  7. import akka.actor.typed.SupervisorStrategy
  8. import akka.actor.typed.scaladsl.ActorContext
  9. import akka.actor.typed.scaladsl.Behaviors
  10. import akka.actor.typed.scaladsl.PoolRouter
  11. import akka.actor.typed.scaladsl.Routers
  12. import akka.util.Timeout
  13. import com.typesafe.scalalogging.Logger
  14. import org.slf4j.event.Level
  15. import wow.doge.mygame.state.ScriptActor
  16. object ScriptCachingActor {
  17. /**
  18. * aka script representation
  19. */
  20. type ScriptObject = Any
  21. type ScriptsMap = Map[os.Path, ScriptObject]
  22. type ScriptResult = Either[ScriptActor.Error, ScriptObject]
  23. sealed trait Command
  24. /**
  25. * @param scriptPath path of the script to compile
  26. * @param requester return address of the asking actor
  27. * @param force if true, forces script compilation even if a previous cached version exists
  28. */
  29. final case class Get(
  30. scriptPath: os.Path,
  31. requester: ActorRef[ScriptResult],
  32. force: Boolean = false
  33. ) extends Command
  34. // final case class GetAll(
  35. // scriptPaths: Seq[os.Path],
  36. // requester: ActorRef[Map[os.Path, ScriptResult]],
  37. // force: Boolean = false
  38. // ) extends Command
  39. final case class GetMap(requester: ActorRef[ScriptsMap]) extends Command
  40. final case class Put(scriptPath: os.Path, script: ScriptObject)
  41. extends Command
  42. private[scriptsystem] final case object NoOp extends Command
  43. private[scriptsystem] final case class DelegateToChild(
  44. scriptActor: ActorRef[ScriptActor.Command],
  45. scriptPath: os.Path,
  46. requester: ActorRef[ScriptResult]
  47. ) extends Command
  48. // private[scriptsystem] final case class DelegateAllToChild(
  49. // scriptPaths: Seq[os.Path],
  50. // requester: ActorRef[Map[os.Path, ScriptResult]]
  51. // ) extends Command
  52. // private[scriptsystem] final case class ReplyWithPrecompiled(
  53. // precompiled: Map[os.Path, ScriptResult],
  54. // requester: ActorRef[Map[os.Path, ScriptResult]]
  55. // ) extends Command
  56. // final case class Props(
  57. // ctx: ActorContext[Command],
  58. // scriptActor: ActorRef[ScriptActor.Command]
  59. // ) {
  60. // def create(state: State = State(Map.empty)): Behavior[Command] =
  61. // Behaviors.logMessages {
  62. // Behaviors.setup { ctx =>
  63. // val pool = ScriptActorPool(4)
  64. // val scriptsRouter = ctx.spawn(pool, "script-actors-pool")
  65. // new ScriptCachingActor(this)
  66. // .receiveMessage(state)
  67. // }
  68. // }
  69. // }
  70. final case class State(scriptsMap: ScriptsMap)
  71. def apply(state: State = State(Map.empty)): Behavior[Command] =
  72. Behaviors.logMessages(
  73. LogOptions()
  74. .withLevel(Level.TRACE)
  75. .withLogger(
  76. Logger[ScriptCachingActor].underlying
  77. ),
  78. Behaviors.setup { ctx =>
  79. val pool = ScriptActorPool(4)
  80. val scriptsRouter = ctx.spawn(pool, "script-actors-pool")
  81. new ScriptCachingActor(ctx, scriptsRouter).receiveMessage(state)
  82. }
  83. )
  84. }
  85. class ScriptCachingActor(
  86. ctx: ActorContext[ScriptCachingActor.Command],
  87. scriptActor: ActorRef[ScriptActor.Command]
  88. ) {
  89. import com.softwaremill.quicklens._
  90. import ScriptCachingActor._
  91. import Methods._
  92. def receiveMessage(state: State): Behavior[Command] =
  93. Behaviors.receiveMessage { msg =>
  94. msg match {
  95. case Get(scriptPath, requester, force) =>
  96. if (force)
  97. ctx.self ! DelegateToChild(scriptActor, scriptPath, requester)
  98. else
  99. getOrCompileScript(
  100. ctx,
  101. scriptPath,
  102. state.scriptsMap,
  103. scriptActor,
  104. requester
  105. )
  106. Behaviors.same
  107. // case GetAll(scriptPaths, requester, force) =>
  108. // import scala.concurrent.duration._
  109. // implicit val timeout = Timeout(15.seconds)
  110. // /**
  111. // * Holy complexity batman this is getting too complex
  112. // */
  113. // if (force) {
  114. // scriptPaths
  115. // .sliding(
  116. // if (scriptPaths.length > 3 && scriptPaths.length % 2 == 0) 2
  117. // else 3
  118. // )
  119. // .foreach(lst =>
  120. // ctx.self ! DelegateAllToChild(scriptPaths, requester)
  121. // )
  122. // } else {
  123. // val (failures, successes) = scriptPaths
  124. // .partitionMap(path =>
  125. // state.scriptsMap.get(path) match {
  126. // case Some(value) => Right(path -> value)
  127. // case None => Left(path)
  128. // }
  129. // ) match {
  130. // case (failures, successes) =>
  131. // import cats.syntax.either._
  132. // failures -> Map.from(successes.map {
  133. // case (p, obj) => p -> obj.asRight[ScriptActor.Error]
  134. // })
  135. // }
  136. // ctx.ask(scriptActor, ScriptActor.CompileAll(failures, _)) {
  137. // case Success(value) =>
  138. // val total = successes ++ value
  139. // requester ! total
  140. // value.foreach {
  141. // case (path, res) => res.foreach(r => ctx.self ! Put(path, r))
  142. // }
  143. // NoOp
  144. // case Failure(exception) => NoOp
  145. // }
  146. // }
  147. // // scriptPaths.foreach(p => ctx.self ! Get(p))
  148. // Behaviors.same
  149. case DelegateToChild(scriptActor, scriptPath, requester) =>
  150. import scala.concurrent.duration._
  151. implicit val timeout = Timeout(15.seconds)
  152. // child ! ScriptActor.CompileAny(scriptPath, requester)
  153. askChildForScriptCompilation(
  154. ctx,
  155. scriptActor,
  156. scriptPath,
  157. requester
  158. )
  159. Behaviors.same
  160. // case DelegateAllToChild(scriptPaths, requester) =>
  161. // import scala.concurrent.duration._
  162. // implicit val timeout = Timeout(15.seconds)
  163. // ctx.ask(scriptActor, ScriptActor.CompileAll(scriptPaths, _)) {
  164. // case Success(value) =>
  165. // value.foreach {
  166. // case (path, res) => res.foreach(r => ctx.self ! Put(path, r))
  167. // }
  168. // NoOp
  169. // case Failure(exception) => NoOp
  170. // }
  171. // Behaviors.same
  172. case GetMap(requester) =>
  173. requester ! state.scriptsMap
  174. Behaviors.same
  175. case Put(scriptPath, script) =>
  176. ctx.log.debug(s"Putting $script at path $scriptPath")
  177. val newState =
  178. state.modify(_.scriptsMap).using(_ + (scriptPath -> script))
  179. ctx.log.trace(newState.toString())
  180. receiveMessage(state = newState)
  181. case NoOp => Behaviors.same
  182. }
  183. }
  184. }
  185. object ScriptActorPool {
  186. def apply(
  187. poolSize: Int
  188. ): PoolRouter[ScriptActor.Command] =
  189. Routers.pool(poolSize = poolSize)(
  190. // make sure the workers are restarted if they fail
  191. Behaviors
  192. .supervise(ScriptActor())
  193. .onFailure[Exception](SupervisorStrategy.restart)
  194. )
  195. }
  196. private[scriptsystem] object Methods {
  197. import ScriptCachingActor._
  198. def getOrCompileScript(
  199. ctx: ActorContext[Command],
  200. scriptPath: os.Path,
  201. scriptsMap: ScriptsMap,
  202. scriptActor: ActorRef[ScriptActor.Command],
  203. requester: ActorRef[ScriptResult]
  204. ) = {
  205. scriptsMap
  206. .get(scriptPath)
  207. .fold {
  208. ctx.log.debug("Delegating to child")
  209. ctx.self ! DelegateToChild(
  210. scriptActor,
  211. scriptPath,
  212. requester
  213. )
  214. } { s =>
  215. ctx.log.debug("Getting script from cache")
  216. requester ! Right(s)
  217. }
  218. }
  219. def askChildForScriptCompilation(
  220. ctx: ActorContext[Command],
  221. scriptActor: ActorRef[ScriptActor.Command],
  222. scriptPath: os.Path,
  223. requester: ActorRef[ScriptResult]
  224. )(implicit timeout: Timeout) = {
  225. ctx.ask(scriptActor, ScriptActor.CompileAny(scriptPath, _)) {
  226. case Success(value) =>
  227. requester ! value
  228. value.fold(
  229. err => {
  230. ctx.log.error(err.reason)
  231. NoOp
  232. },
  233. res => {
  234. Put(scriptPath, res)
  235. }
  236. )
  237. case Failure(exception) => {
  238. ctx.log.error(exception.getMessage())
  239. NoOp
  240. }
  241. }
  242. }
  243. }