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.5 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
  1. package wow.doge.mygame.scriptsystem
  2. import akka.actor.typed.scaladsl.Behaviors
  3. import akka.actor.typed.scaladsl.PoolRouter
  4. import akka.actor.typed.scaladsl.Routers
  5. import wow.doge.mygame.state.ScriptActor
  6. import akka.actor.typed.ActorRef
  7. import akka.actor.typed.scaladsl.ActorContext
  8. import akka.actor.typed.Behavior
  9. import akka.util.Timeout
  10. import scala.util.Success
  11. import scala.util.Failure
  12. import akka.actor.typed.SupervisorStrategy
  13. import akka.actor.typed.LogOptions
  14. import org.slf4j.event.Level
  15. import com.typesafe.scalalogging.Logger
  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 GetMap(requester: ActorRef[ScriptsMap]) extends Command
  35. final case class Put(scriptPath: os.Path, script: ScriptObject)
  36. extends Command
  37. private[scriptsystem] final case object NoOp extends Command
  38. private[scriptsystem] final case class DelegateToChild(
  39. scriptActor: ActorRef[ScriptActor.Command],
  40. scriptPath: os.Path,
  41. requester: ActorRef[ScriptResult]
  42. ) extends Command
  43. // final case class Props(
  44. // ctx: ActorContext[Command],
  45. // scriptActor: ActorRef[ScriptActor.Command]
  46. // ) {
  47. // def create(state: State = State(Map.empty)): Behavior[Command] =
  48. // Behaviors.logMessages {
  49. // Behaviors.setup { ctx =>
  50. // val pool = ScriptActorPool(4)
  51. // val scriptsRouter = ctx.spawn(pool, "script-actors-pool")
  52. // new ScriptCachingActor(this)
  53. // .receiveMessage(state)
  54. // }
  55. // }
  56. // }
  57. final case class State(scriptsMap: ScriptsMap)
  58. def apply(state: State = State(Map.empty)): Behavior[Command] =
  59. Behaviors.logMessages(
  60. LogOptions()
  61. .withLevel(Level.TRACE)
  62. .withLogger(
  63. Logger[ScriptCachingActor].underlying
  64. ),
  65. Behaviors.setup { ctx =>
  66. val pool = ScriptActorPool(4)
  67. val scriptsRouter = ctx.spawn(pool, "script-actors-pool")
  68. new ScriptCachingActor(ctx, scriptsRouter).receiveMessage(state)
  69. }
  70. )
  71. }
  72. class ScriptCachingActor(
  73. ctx: ActorContext[ScriptCachingActor.Command],
  74. scriptActor: ActorRef[ScriptActor.Command]
  75. ) {
  76. import com.softwaremill.quicklens._
  77. import ScriptCachingActor._
  78. import Methods._
  79. def receiveMessage(state: State): Behavior[Command] =
  80. Behaviors.receiveMessage { msg =>
  81. msg match {
  82. case Get(scriptPath, requester, force) =>
  83. if (force)
  84. ctx.self ! DelegateToChild(scriptActor, scriptPath, requester)
  85. else
  86. getOrCompileScript(
  87. ctx,
  88. scriptPath,
  89. state.scriptsMap,
  90. scriptActor,
  91. requester
  92. )
  93. Behaviors.same
  94. case DelegateToChild(scriptActor, scriptPath, requester) =>
  95. import scala.concurrent.duration._
  96. implicit val timeout = Timeout(15.seconds)
  97. // child ! ScriptActor.CompileAny(scriptPath, requester)
  98. askChildForScriptCompilation(
  99. ctx,
  100. scriptActor,
  101. scriptPath,
  102. requester
  103. )
  104. Behaviors.same
  105. case GetMap(requester) =>
  106. requester ! state.scriptsMap
  107. Behaviors.same
  108. case Put(scriptPath, script) =>
  109. ctx.log.debug(s"Putting $script at path $scriptPath")
  110. val newState =
  111. state.modify(_.scriptsMap).using(_ + (scriptPath -> script))
  112. ctx.log.trace(newState.toString())
  113. receiveMessage(state = newState)
  114. case NoOp => Behaviors.same
  115. }
  116. }
  117. }
  118. object ScriptActorPool {
  119. def apply(
  120. poolSize: Int
  121. ): PoolRouter[ScriptActor.Command] =
  122. Routers.pool(poolSize = poolSize)(
  123. // make sure the workers are restarted if they fail
  124. Behaviors
  125. .supervise(ScriptActor())
  126. .onFailure[Exception](SupervisorStrategy.restart)
  127. )
  128. }
  129. private[scriptsystem] object Methods {
  130. import ScriptCachingActor._
  131. def getOrCompileScript(
  132. ctx: ActorContext[Command],
  133. scriptPath: os.Path,
  134. scriptsMap: ScriptsMap,
  135. scriptActor: ActorRef[ScriptActor.Command],
  136. requester: ActorRef[ScriptResult]
  137. ) = {
  138. scriptsMap
  139. .get(scriptPath)
  140. .fold {
  141. ctx.log.debug("Delegating to child")
  142. ctx.self ! DelegateToChild(
  143. scriptActor,
  144. scriptPath,
  145. requester
  146. )
  147. } { s =>
  148. ctx.log.debug("Getting script from cache")
  149. requester ! Right(s)
  150. }
  151. }
  152. def askChildForScriptCompilation(
  153. ctx: ActorContext[Command],
  154. scriptActor: ActorRef[ScriptActor.Command],
  155. scriptPath: os.Path,
  156. requester: ActorRef[ScriptResult]
  157. )(implicit timeout: Timeout) = {
  158. ctx.ask(scriptActor, ScriptActor.CompileAny(scriptPath, _)) {
  159. case Success(value) =>
  160. requester ! value
  161. value.fold(
  162. err => {
  163. ctx.log.error(err.reason)
  164. NoOp
  165. },
  166. res => {
  167. Put(scriptPath, res)
  168. }
  169. )
  170. case Failure(exception) => {
  171. ctx.log.error(exception.getMessage())
  172. NoOp
  173. }
  174. }
  175. }
  176. }