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.

161 lines
4.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
  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. object ScriptCachingActor {
  14. /**
  15. * aka script representation
  16. */
  17. type ScriptObject = Any
  18. type ScriptsMap = Map[os.Path, ScriptObject]
  19. type ScriptResult = Either[ScriptActor.Error, ScriptObject]
  20. sealed trait Command
  21. final case class Get(
  22. scriptPath: os.Path,
  23. requester: ActorRef[ScriptResult]
  24. ) extends Command
  25. final case class GetMap(requester: ActorRef[ScriptsMap]) extends Command
  26. final case class Put(scriptPath: os.Path, script: ScriptObject)
  27. extends Command
  28. private[scriptsystem] final case object NoOp extends Command
  29. private[scriptsystem] final case class DelegateToChild(
  30. scriptActor: ActorRef[ScriptActor.Command],
  31. scriptPath: os.Path,
  32. requester: ActorRef[ScriptResult]
  33. ) extends Command
  34. final case class Props(
  35. ctx: ActorContext[Command],
  36. scriptActor: ActorRef[ScriptActor.Command]
  37. )
  38. final case class State(scriptsMap: ScriptsMap)
  39. def apply(state: State = State(Map.empty)): Behavior[Command] =
  40. Behaviors.logMessages {
  41. Behaviors.setup { ctx =>
  42. val pool = ScriptActorPool(4)
  43. val scriptsRouter = ctx.spawn(pool, "script-actors-pool")
  44. new ScriptCachingActor(Props(ctx, scriptsRouter)).receiveMessage(state)
  45. }
  46. }
  47. }
  48. class ScriptCachingActor(props: ScriptCachingActor.Props) {
  49. import com.softwaremill.quicklens._
  50. import ScriptCachingActor._
  51. import Methods._
  52. def receiveMessage(state: State): Behavior[Command] =
  53. Behaviors.receiveMessage { msg =>
  54. msg match {
  55. case Get(scriptPath, requester) =>
  56. getOrCompileScript(
  57. props.ctx,
  58. scriptPath,
  59. state.scriptsMap,
  60. props.scriptActor,
  61. requester
  62. )
  63. Behaviors.same
  64. case DelegateToChild(scriptActor, scriptPath, requester) =>
  65. import scala.concurrent.duration._
  66. implicit val timeout = Timeout(15.seconds)
  67. // child ! ScriptActor.CompileAny(scriptPath, requester)
  68. askChildForScriptCompilation(
  69. props.ctx,
  70. scriptActor,
  71. scriptPath,
  72. requester
  73. )
  74. Behaviors.same
  75. case GetMap(requester) =>
  76. requester ! state.scriptsMap
  77. Behaviors.same
  78. case Put(scriptPath, script) =>
  79. props.ctx.log.debug(s"Putting $script at path $scriptPath")
  80. val newState =
  81. state.modify(_.scriptsMap).using(_ + (scriptPath -> script))
  82. props.ctx.log.trace(newState.toString())
  83. receiveMessage(state = newState)
  84. case NoOp => Behaviors.same
  85. }
  86. }
  87. }
  88. object ScriptActorPool {
  89. def apply(
  90. poolSize: Int
  91. ): PoolRouter[ScriptActor.Command] =
  92. Routers.pool(poolSize = poolSize)(
  93. // make sure the workers are restarted if they fail
  94. Behaviors
  95. .supervise(ScriptActor())
  96. .onFailure[Exception](SupervisorStrategy.restart)
  97. )
  98. }
  99. private[scriptsystem] object Methods {
  100. import ScriptCachingActor._
  101. def getOrCompileScript(
  102. ctx: ActorContext[Command],
  103. scriptPath: os.Path,
  104. scriptsMap: ScriptsMap,
  105. scriptActor: ActorRef[ScriptActor.Command],
  106. requester: ActorRef[ScriptResult]
  107. ) = {
  108. scriptsMap
  109. .get(scriptPath)
  110. .fold {
  111. ctx.log.debug("Delegating to child")
  112. ctx.self ! DelegateToChild(
  113. scriptActor,
  114. scriptPath,
  115. requester
  116. )
  117. } { s =>
  118. ctx.log.debug("Getting script from cache")
  119. requester ! Right(s)
  120. }
  121. }
  122. def askChildForScriptCompilation(
  123. ctx: ActorContext[Command],
  124. scriptActor: ActorRef[ScriptActor.Command],
  125. scriptPath: os.Path,
  126. requester: ActorRef[ScriptResult]
  127. )(implicit timeout: Timeout) = {
  128. ctx.ask(scriptActor, ScriptActor.CompileAny(scriptPath, _)) {
  129. case Success(value) =>
  130. requester ! value
  131. value.fold(
  132. err => {
  133. ctx.log.error(err.reason)
  134. NoOp
  135. },
  136. res => {
  137. Put(scriptPath, res)
  138. }
  139. )
  140. case Failure(exception) => {
  141. ctx.log.error(exception.getMessage())
  142. NoOp
  143. }
  144. }
  145. }
  146. }