From d20d71894dc3357332061b2fff9c6e38ec0fd0eb Mon Sep 17 00:00:00 2001 From: will-ca Date: Sat, 11 Dec 2021 19:26:49 +0000 Subject: [PATCH] Starting layout/prototyping of mod script handler system. (A separate feature, but putting in the same branch for now.) --- .../modscripting/ModScriptingHandlerTypes.kt | 58 ++++++++++++++++ .../ModScriptingRegistrationManager.kt | 31 +++++++++ .../modscripting/ModScriptingRunManager.kt | 67 +++++++++++++++++++ .../unciv/models/modscripting/ScriptedMod.kt | 3 + 4 files changed, 159 insertions(+) create mode 100644 core/src/com/unciv/models/modscripting/ModScriptingHandlerTypes.kt create mode 100644 core/src/com/unciv/models/modscripting/ModScriptingRegistrationManager.kt create mode 100644 core/src/com/unciv/models/modscripting/ModScriptingRunManager.kt create mode 100644 core/src/com/unciv/models/modscripting/ScriptedMod.kt diff --git a/core/src/com/unciv/models/modscripting/ModScriptingHandlerTypes.kt b/core/src/com/unciv/models/modscripting/ModScriptingHandlerTypes.kt new file mode 100644 index 0000000000000..2fe1672111d1c --- /dev/null +++ b/core/src/com/unciv/models/modscripting/ModScriptingHandlerTypes.kt @@ -0,0 +1,58 @@ +package com.unciv.models.modscripting + + +// Dependency/feature stack: +// ModScriptingHandlerTypes —> ModScriptingRunManager —> ModScriptingRegistrationHandler +// com.unciv.scripting —> com.unciv.models.modscripting +// Later namespaces in each stack may use members and features from earlier namespaces, but earlier namespaces should have behaviour that is completely independent of any later items. +// The scripting execution model (com.unciv.scripting) only *runs* scripts— Anything to do with loading them should go in here (com.unciv.models.modscripting) instead. +// Likewise, ModScriptingHandlerTypes is only for defining handlers, ModScriptingRunManager is only for using that during gameplay, and anything to do with parsing mod structures should go into the level of ModScriptingRegistrationHandler. + + +typealias Params = Map? +typealias ParamGetter = (Params?) -> Params +// Some handlers may have parameters that should be set universally; Others may use or simply pass on parameters from where they're called. + +interface Context { + val name: String + val handlers: Collection +} +interface Handler { + val name: String + val paramGetter: ParamGetter +} + +// For defining the types and behaviours of available handlers. +object ModScriptingHandlerTypes { + + private val defaultParamGetter: ParamGetter = { it } + + object All { // Not a great name for imports. + enum class UncivGame(override val paramGetter: ParamGetter = defaultParamGetter): Handler { + after_enter(), + before_exit() + ; + companion object: Context { + override val name = "UncivGame" + override val handlers = values().toList() + } + } + + enum class GameInfo(override val paramGetter: ParamGetter = defaultParamGetter): Handler { + ; + companion object: Context { + override val name = "GameInfo" + override val handlers = values().toList() + } + } + } + val all = listOf( // Explicitly type so anything missing companion raises compile/IDE error. + All.UncivGame, + All.GameInfo + ) + val byContextAndName = all.associate { context -> + context.name to context.handlers.associateWith { handler -> + handler.name + } + } +} diff --git a/core/src/com/unciv/models/modscripting/ModScriptingRegistrationManager.kt b/core/src/com/unciv/models/modscripting/ModScriptingRegistrationManager.kt new file mode 100644 index 0000000000000..b594dd6a623b4 --- /dev/null +++ b/core/src/com/unciv/models/modscripting/ModScriptingRegistrationManager.kt @@ -0,0 +1,31 @@ +package com.unciv.models.modscripting + +import com.unciv.scripting.ScriptingBackend +import com.unciv.scripting.ScriptingBackendType + +// For organizing and associating script handlers with specific mods. +// Uses ModScriptingHandlerTypes and ModScriptingRunManager. +object ModScriptingRegistrationManager { + private fun register(mod: ScriptedMod, backend: ScriptingBackend, handler: Handler, code: String) { + + } + + fun registerMod(mod: ScriptedMod) { + } + + private fun unregister(registeredHandler: RegisteredHandler) { + } + + fun unregisterMod(mod: ScriptedMod) { + } + + val languagesToBackends = mapOf( + "py3" to ScriptingBackendType.SystemPython, + "js" to ScriptingBackendType.SystemQuickJS + ) // …Yeah. Keep language awareness only in the mod loader. The actual backend interfaces and classes don't have to understand anything other than "text in, result out". (But then again, I'm already keeping .engine in the companions of the EnvironmentedScriptingBackends, and use those to instantiate their libraries…) + + class ModScriptingBackends( + var py3: ScriptingBackend? = null, + var js: ScriptingBackend? = null + ) +} diff --git a/core/src/com/unciv/models/modscripting/ModScriptingRunManager.kt b/core/src/com/unciv/models/modscripting/ModScriptingRunManager.kt new file mode 100644 index 0000000000000..1a407b0e463c0 --- /dev/null +++ b/core/src/com/unciv/models/modscripting/ModScriptingRunManager.kt @@ -0,0 +1,67 @@ +package com.unciv.models.modscripting + +import com.badlogic.gdx.Gdx +import com.unciv.scripting.ExecResult +import com.unciv.scripting.ScriptingBackend +import com.unciv.scripting.ScriptingState +import com.unciv.scripting.utils.ScriptingErrorHandling +import com.unciv.scripting.sync.ScriptingRunThreader +import com.unciv.scripting.sync.makeScriptingRunName + + +data class RegisteredHandler(val backend: ScriptingBackend, val code: String, val mod: ScriptedMod?) + +// For organizing and running script handlers during gameplay. +// Uses ModScriptingHandlerTypes. +object ModScriptingRunManager { + + private val registeredHandlers: Map> = + ModScriptingHandlerTypes.all.asSequence() + .map { it.handlers } + .flatten() + .associateWith { HashSet() } + + fun getRegisteredForHandler(handler: Handler) = registeredHandlers[handler]!! + + fun lambdaRegisteredHandlerRunner(registeredHandler: RegisteredHandler, withParams: Params): () -> Unit { + // Looking at the source code, some .to() extensions actually return mutable instances, and just type the return. + // That means that scripts, and the heavy casting in Reflection.kt, might actually be able to modify them. So make a copy before every script run. + val params = HashMap(withParams) + val name = makeScriptingRunName(registeredHandler.mod?.name, registeredHandler.backend) + return fun() { + val execResult: ExecResult? + try{ + execResult = ScriptingState.exec( + command = registeredHandler.code, + asName = name, + withParams = params, + withBackend = registeredHandler.backend + ) + } catch(e: Throwable) { + ScriptingErrorHandling.notifyPlayerScriptFailure(exception = e, asName = name) + return + } + if (execResult.isException) { + ScriptingErrorHandling.notifyPlayerScriptFailure(text = execResult.resultPrint, asName = name) + } + } + } + + fun runHandler(handler: Handler, baseParams: Params?) { // Not sure how to keep caller from advancing… (I wonder if qualified return syntax + val registeredHandlers = getRegisteredForHandler(handler) + if (registeredHandlers.isNotEmpty()) { + val params = handler.paramGetter(baseParams) + ScriptingRunThreader.queueRuns( + registeredHandlers.asSequence().map { lambdaRegisteredHandlerRunner(it, params) } + ) + lockGame() + ScriptingRunThreader.queueRun { Gdx.app.postRunnable(::unlockGame) } + ScriptingRunThreader.doRuns() + } + } + + private fun lockGame() {} + private fun unlockGame() {} +} + + diff --git a/core/src/com/unciv/models/modscripting/ScriptedMod.kt b/core/src/com/unciv/models/modscripting/ScriptedMod.kt new file mode 100644 index 0000000000000..b65aff0399032 --- /dev/null +++ b/core/src/com/unciv/models/modscripting/ScriptedMod.kt @@ -0,0 +1,3 @@ +package com.unciv.models.modscripting + +class ScriptedMod(val name: String) // See, try to follow conventions of, TilesetAndMod maybe?