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.
263 lines
8.2 KiB
263 lines
8.2 KiB
package wow.doge.mygame.scriptsystem
|
|
|
|
import scala.util.Failure
|
|
import scala.util.Success
|
|
|
|
import akka.actor.typed.ActorRef
|
|
import akka.actor.typed.Behavior
|
|
import akka.actor.typed.LogOptions
|
|
import akka.actor.typed.SupervisorStrategy
|
|
import akka.actor.typed.scaladsl.ActorContext
|
|
import akka.actor.typed.scaladsl.Behaviors
|
|
import akka.actor.typed.scaladsl.PoolRouter
|
|
import akka.actor.typed.scaladsl.Routers
|
|
import akka.util.Timeout
|
|
import com.typesafe.scalalogging.Logger
|
|
import org.slf4j.event.Level
|
|
import wow.doge.mygame.state.ScriptActor
|
|
import scala.concurrent.duration._
|
|
import ScriptActor.ScriptObject
|
|
|
|
object ScriptCachingActor {
|
|
|
|
type ScriptsMap = Map[os.Path, ScriptObject]
|
|
type ScriptResult = Either[ScriptActor.Error, ScriptObject]
|
|
|
|
sealed trait Command
|
|
|
|
/**
|
|
* @param scriptPath path of the script to compile
|
|
* @param requester return address of the asking actor
|
|
* @param force if true, forces script compilation even if a previous cached version exists
|
|
*/
|
|
final case class Get(
|
|
scriptPath: os.Path,
|
|
requester: ActorRef[ScriptResult],
|
|
force: Boolean = false
|
|
) extends Command
|
|
// final case class GetAll(
|
|
// scriptPaths: Seq[os.Path],
|
|
// requester: ActorRef[Map[os.Path, ScriptResult]],
|
|
// force: Boolean = false
|
|
// ) 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
|
|
// private[scriptsystem] final case class DelegateAllToChild(
|
|
// scriptPaths: Seq[os.Path],
|
|
// requester: ActorRef[Map[os.Path, ScriptResult]]
|
|
// ) extends Command
|
|
|
|
// private[scriptsystem] final case class ReplyWithPrecompiled(
|
|
// precompiled: Map[os.Path, ScriptResult],
|
|
// requester: ActorRef[Map[os.Path, ScriptResult]]
|
|
// ) extends Command
|
|
|
|
// final case class Props(
|
|
// ctx: ActorContext[Command],
|
|
// scriptActor: ActorRef[ScriptActor.Command]
|
|
// ) {
|
|
// def create(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(this)
|
|
// .receiveMessage(state)
|
|
// }
|
|
// }
|
|
// }
|
|
final case class State(scriptsMap: ScriptsMap)
|
|
|
|
def apply(state: State = State(Map.empty)): Behavior[Command] =
|
|
Behaviors.logMessages(
|
|
LogOptions()
|
|
.withLevel(Level.TRACE)
|
|
.withLogger(
|
|
Logger[ScriptCachingActor].underlying
|
|
),
|
|
Behaviors.setup { ctx =>
|
|
val pool = ScriptActorPool(4)
|
|
val scriptsRouter = ctx.spawn(pool, "script-actors-pool")
|
|
new ScriptCachingActor(ctx, scriptsRouter).receiveMessage(state)
|
|
}
|
|
)
|
|
}
|
|
|
|
class ScriptCachingActor(
|
|
ctx: ActorContext[ScriptCachingActor.Command],
|
|
scriptActor: ActorRef[ScriptActor.Command]
|
|
) {
|
|
import com.softwaremill.quicklens._
|
|
import ScriptCachingActor._
|
|
import Methods._
|
|
def receiveMessage(state: State): Behavior[Command] =
|
|
Behaviors.receiveMessage { msg =>
|
|
msg match {
|
|
case Get(scriptPath, requester, force) =>
|
|
if (force)
|
|
ctx.self ! DelegateToChild(scriptActor, scriptPath, requester)
|
|
else
|
|
getOrCompileScript(
|
|
ctx,
|
|
scriptPath,
|
|
state.scriptsMap,
|
|
scriptActor,
|
|
requester
|
|
)
|
|
Behaviors.same
|
|
|
|
// case GetAll(scriptPaths, requester, force) =>
|
|
// import scala.concurrent.duration._
|
|
// implicit val timeout = Timeout(15.seconds)
|
|
|
|
// /**
|
|
// * Holy complexity batman this is getting too complex
|
|
// */
|
|
// if (force) {
|
|
// scriptPaths
|
|
// .sliding(
|
|
// if (scriptPaths.length > 3 && scriptPaths.length % 2 == 0) 2
|
|
// else 3
|
|
// )
|
|
// .foreach(lst =>
|
|
// ctx.self ! DelegateAllToChild(scriptPaths, requester)
|
|
// )
|
|
// } else {
|
|
// val (failures, successes) = scriptPaths
|
|
// .partitionMap(path =>
|
|
// state.scriptsMap.get(path) match {
|
|
// case Some(value) => Right(path -> value)
|
|
// case None => Left(path)
|
|
// }
|
|
// ) match {
|
|
// case (failures, successes) =>
|
|
// import cats.syntax.either._
|
|
// failures -> Map.from(successes.map {
|
|
// case (p, obj) => p -> obj.asRight[ScriptActor.Error]
|
|
// })
|
|
// }
|
|
// ctx.ask(scriptActor, ScriptActor.CompileAll(failures, _)) {
|
|
// case Success(value) =>
|
|
// val total = successes ++ value
|
|
// requester ! total
|
|
// value.foreach {
|
|
// case (path, res) => res.foreach(r => ctx.self ! Put(path, r))
|
|
// }
|
|
// NoOp
|
|
// case Failure(exception) => NoOp
|
|
// }
|
|
// }
|
|
// // scriptPaths.foreach(p => ctx.self ! Get(p))
|
|
|
|
// Behaviors.same
|
|
|
|
case DelegateToChild(scriptActor, scriptPath, requester) =>
|
|
implicit val timeout = Timeout(15.seconds)
|
|
// child ! ScriptActor.CompileAny(scriptPath, requester)
|
|
askChildForScriptCompilation(
|
|
ctx,
|
|
scriptActor,
|
|
scriptPath,
|
|
requester
|
|
)
|
|
Behaviors.same
|
|
|
|
// case DelegateAllToChild(scriptPaths, requester) =>
|
|
// import scala.concurrent.duration._
|
|
// implicit val timeout = Timeout(15.seconds)
|
|
// ctx.ask(scriptActor, ScriptActor.CompileAll(scriptPaths, _)) {
|
|
// case Success(value) =>
|
|
// value.foreach {
|
|
// case (path, res) => res.foreach(r => ctx.self ! Put(path, r))
|
|
// }
|
|
// NoOp
|
|
// case Failure(exception) => NoOp
|
|
// }
|
|
// Behaviors.same
|
|
|
|
case GetMap(requester) =>
|
|
requester ! state.scriptsMap
|
|
Behaviors.same
|
|
|
|
case Put(scriptPath, script) =>
|
|
ctx.log.debug(s"Putting $script at path $scriptPath")
|
|
val newState =
|
|
state.modify(_.scriptsMap).using(_ + (scriptPath -> script))
|
|
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
|
|
}
|
|
}
|
|
}
|
|
}
|