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.

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