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

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
import akka.actor.typed.LogOptions
import org.slf4j.event.Level
import com.typesafe.scalalogging.Logger
object ScriptCachingActor {
/**
* aka script representation
*/
type ScriptObject = Any
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 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]
// ) {
// 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 DelegateToChild(scriptActor, scriptPath, requester) =>
import scala.concurrent.duration._
implicit val timeout = Timeout(15.seconds)
// child ! ScriptActor.CompileAny(scriptPath, requester)
askChildForScriptCompilation(
ctx,
scriptActor,
scriptPath,
requester
)
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
}
}
}
}