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.

254 lines
8.0 KiB

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