forked from nova/jmonkey-test
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
161 lines
4.5 KiB
package wow.doge.mygame.scriptsystem
|
|
|
|
import akka.actor.typed.scaladsl.Behaviors
|
|
import akka.actor.typed.scaladsl.PoolRouter
|
|
import akka.actor.typed.scaladsl.Routers
|
|
import wow.doge.mygame.state.ScriptActor
|
|
import akka.actor.typed.ActorRef
|
|
import akka.actor.typed.scaladsl.ActorContext
|
|
import akka.actor.typed.Behavior
|
|
import akka.util.Timeout
|
|
import scala.util.Success
|
|
import scala.util.Failure
|
|
import akka.actor.typed.SupervisorStrategy
|
|
|
|
object ScriptCachingActor {
|
|
|
|
/**
|
|
* aka script representation
|
|
*/
|
|
type ScriptObject = Any
|
|
type ScriptsMap = Map[os.Path, ScriptObject]
|
|
type ScriptResult = Either[ScriptActor.Error, ScriptObject]
|
|
|
|
sealed trait Command
|
|
final case class Get(
|
|
scriptPath: os.Path,
|
|
requester: ActorRef[ScriptResult]
|
|
) extends Command
|
|
final case class GetMap(requester: ActorRef[ScriptsMap]) extends Command
|
|
final case class Put(scriptPath: os.Path, script: ScriptObject)
|
|
extends Command
|
|
private[scriptsystem] final case object NoOp extends Command
|
|
|
|
private[scriptsystem] final case class DelegateToChild(
|
|
scriptActor: ActorRef[ScriptActor.Command],
|
|
scriptPath: os.Path,
|
|
requester: ActorRef[ScriptResult]
|
|
) extends Command
|
|
|
|
final case class Props(
|
|
ctx: ActorContext[Command],
|
|
scriptActor: ActorRef[ScriptActor.Command]
|
|
)
|
|
final case class State(scriptsMap: ScriptsMap)
|
|
|
|
def apply(state: State = State(Map.empty)): Behavior[Command] =
|
|
Behaviors.logMessages {
|
|
Behaviors.setup { ctx =>
|
|
val pool = ScriptActorPool(4)
|
|
val scriptsRouter = ctx.spawn(pool, "script-actors-pool")
|
|
new ScriptCachingActor(Props(ctx, scriptsRouter)).receiveMessage(state)
|
|
}
|
|
}
|
|
}
|
|
|
|
class ScriptCachingActor(props: ScriptCachingActor.Props) {
|
|
import com.softwaremill.quicklens._
|
|
import ScriptCachingActor._
|
|
import Methods._
|
|
def receiveMessage(state: State): Behavior[Command] =
|
|
Behaviors.receiveMessage { msg =>
|
|
msg match {
|
|
case Get(scriptPath, requester) =>
|
|
getOrCompileScript(
|
|
props.ctx,
|
|
scriptPath,
|
|
state.scriptsMap,
|
|
props.scriptActor,
|
|
requester
|
|
)
|
|
Behaviors.same
|
|
|
|
case DelegateToChild(scriptActor, scriptPath, requester) =>
|
|
import scala.concurrent.duration._
|
|
implicit val timeout = Timeout(15.seconds)
|
|
// child ! ScriptActor.CompileAny(scriptPath, requester)
|
|
askChildForScriptCompilation(
|
|
props.ctx,
|
|
scriptActor,
|
|
scriptPath,
|
|
requester
|
|
)
|
|
Behaviors.same
|
|
|
|
case GetMap(requester) =>
|
|
requester ! state.scriptsMap
|
|
Behaviors.same
|
|
|
|
case Put(scriptPath, script) =>
|
|
props.ctx.log.debug(s"Putting $script at path $scriptPath")
|
|
val newState =
|
|
state.modify(_.scriptsMap).using(_ + (scriptPath -> script))
|
|
props.ctx.log.trace(newState.toString())
|
|
receiveMessage(state = newState)
|
|
|
|
case NoOp => Behaviors.same
|
|
}
|
|
}
|
|
}
|
|
|
|
object ScriptActorPool {
|
|
def apply(
|
|
poolSize: Int
|
|
): PoolRouter[ScriptActor.Command] =
|
|
Routers.pool(poolSize = poolSize)(
|
|
// make sure the workers are restarted if they fail
|
|
Behaviors
|
|
.supervise(ScriptActor())
|
|
.onFailure[Exception](SupervisorStrategy.restart)
|
|
)
|
|
}
|
|
|
|
private[scriptsystem] object Methods {
|
|
import ScriptCachingActor._
|
|
def getOrCompileScript(
|
|
ctx: ActorContext[Command],
|
|
scriptPath: os.Path,
|
|
scriptsMap: ScriptsMap,
|
|
scriptActor: ActorRef[ScriptActor.Command],
|
|
requester: ActorRef[ScriptResult]
|
|
) = {
|
|
scriptsMap
|
|
.get(scriptPath)
|
|
.fold {
|
|
ctx.log.debug("Delegating to child")
|
|
ctx.self ! DelegateToChild(
|
|
scriptActor,
|
|
scriptPath,
|
|
requester
|
|
)
|
|
} { s =>
|
|
ctx.log.debug("Getting script from cache")
|
|
requester ! Right(s)
|
|
}
|
|
}
|
|
|
|
def askChildForScriptCompilation(
|
|
ctx: ActorContext[Command],
|
|
scriptActor: ActorRef[ScriptActor.Command],
|
|
scriptPath: os.Path,
|
|
requester: ActorRef[ScriptResult]
|
|
)(implicit timeout: Timeout) = {
|
|
ctx.ask(scriptActor, ScriptActor.CompileAny(scriptPath, _)) {
|
|
case Success(value) =>
|
|
requester ! value
|
|
value.fold(
|
|
err => {
|
|
ctx.log.error(err.reason)
|
|
NoOp
|
|
},
|
|
res => {
|
|
Put(scriptPath, res)
|
|
}
|
|
)
|
|
case Failure(exception) => {
|
|
ctx.log.error(exception.getMessage())
|
|
NoOp
|
|
}
|
|
}
|
|
}
|
|
}
|