Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions arc-scripting/src/main/kotlin/ScriptHotReload.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import java.io.IOException
import java.nio.file.FileSystems
import java.nio.file.StandardWatchEventKinds.*
import java.nio.file.WatchService
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executors.newCachedThreadPool
import java.util.concurrent.Executors.newFixedThreadPool
import java.util.concurrent.atomic.AtomicBoolean
Expand All @@ -35,6 +36,8 @@ class ScriptHotReload(
) : Closeable {
private val log = LoggerFactory.getLogger(this.javaClass)

private val agentToFileMappings = ConcurrentHashMap<File, Set<String>>()

private val fileWatcher = lazy {
try {
val watchService = FileSystems.getDefault().newWatchService()
Expand All @@ -52,24 +55,42 @@ class ScriptHotReload(
when (event) {
is FileEvent.Created -> {
if (event.file.isDirectory) return@watch
scriptAgentLoader.loadAgents(event.file)
val loadedAgents = scriptAgentLoader.loadAgents(event.file)
scriptFunctionLoader.loadFunctions(event.file)
removeRemovedAgents(loadedAgents)
}

is FileEvent.Modified -> {
if (event.file.isDirectory) return@watch
scriptAgentLoader.loadAgents(event.file)
val loadedAgents = scriptAgentLoader.loadAgents(event.file)
scriptFunctionLoader.loadFunctions(event.file)
removeRemovedAgents(loadedAgents)
}

is FileEvent.Deleted -> {
// TODO
if (event.file.isDirectory) return@watch
removeRemovedAgents(mapOf(event.file to emptySet()))
}
}
}.onFailure { logError(it) }
}
}

/**
* Removes agents that were removed from the script files.
*/
private fun removeRemovedAgents(loadedAgents: Map<File, Set<String>>) {
loadedAgents.forEach { (file, agents) ->
val previousAgents = agentToFileMappings[file] ?: emptySet()
val removedAgents = previousAgents - agents
if (removedAgents.isNotEmpty()) {
log.info("Removing agents ${removedAgents.joinToString()} from file ${file.name}")
removedAgents.forEach { scriptAgentLoader.removeAgent(it) }
}
agentToFileMappings[file] = agents
}
}

private fun logError(ex: Exception) {
log.error("Unexpected Error while hot-loading Agent scripts!", ex)
}
Expand Down
29 changes: 23 additions & 6 deletions arc-scripting/src/main/kotlin/agents/ScriptingAgentLoader.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import org.eclipse.lmos.arc.agents.events.EventPublisher
import org.eclipse.lmos.arc.core.Failure
import org.eclipse.lmos.arc.core.Result
import org.eclipse.lmos.arc.core.Success
import org.eclipse.lmos.arc.core.map
import org.eclipse.lmos.arc.core.onFailure
import org.eclipse.lmos.arc.scripting.ScriptFailedException
import org.slf4j.LoggerFactory
Expand All @@ -38,15 +39,17 @@ class ScriptingAgentLoader(

/**
* Loads the agents defined in an Agent DSL script.
* @return a Result containing the names of the loaded agents or an error if the script failed.
*/
fun loadAgent(agentScript: String): Result<ResultValue?, ScriptFailedException> {
fun loadAgent(agentScript: String): Result<Set<String>, ScriptFailedException> {
val context = BasicAgentDefinitionContext(agentFactory)
val result = agentScriptEngine.eval(agentScript, context)
if (result is Success && context.agents.isNotEmpty()) {
log.info("Discovered the following agents (scripting): ${context.agents.joinToString { it.name }}")
agents.putAll(context.agents.associateBy { it.name })
return result.map { context.agents.map { it.name }.toSet() }
}
return result
return result.map { emptySet<String>() }
}

fun loadCompiledAgent(compiledAgentScript: CompiledAgentLoader) {
Expand All @@ -60,18 +63,24 @@ class ScriptingAgentLoader(

/**
* Loads the agents defined in a list of Agent DSL script files.
* @return a Map containing the file and the names of the loaded agents.
*/
fun loadAgents(vararg files: File) {
fun loadAgents(vararg files: File): Map<File, Set<String>> = buildMap {
files
.asSequence()
.filter { it.name.endsWith(".agent.kts") }
.map { it.name to it.readText() }
.forEach { (name, script) ->
.map { it to it.readText() }
.forEach { (file, script) ->
val name = file.name
val result = loadAgent(script).onFailure {
log.warn("Failed to load agents from script: $name!", it)
}
when (result) {
is Success -> eventPublisher?.publish(AgentLoadedEvent(name))
is Success -> {
put(file, result.value)
eventPublisher?.publish(AgentLoadedEvent(name))
}

is Failure -> eventPublisher?.publish(
AgentLoadedEvent(
name,
Expand All @@ -88,4 +97,12 @@ class ScriptingAgentLoader(
fun loadAgentsFromFolder(folder: File) {
folder.walk().filter { it.isFile }.forEach { loadAgents(it) }
}

/**
* Removes the agent with the given name.
*/
fun removeAgent(name: String): Boolean {
val agent = agents.remove(name)
return agent != null
}
}
Loading