Skip to content

Commit

Permalink
Starting layout/prototyping of mod script handler system. (A separate…
Browse files Browse the repository at this point in the history
… feature, but putting in the same branch for now.)
  • Loading branch information
will-ca committed Dec 11, 2021
1 parent 506858b commit d20d718
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 0 deletions.
58 changes: 58 additions & 0 deletions core/src/com/unciv/models/modscripting/ModScriptingHandlerTypes.kt
Original file line number Diff line number Diff line change
@@ -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<String, Any?>?
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<Handler>
}
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<Context>( // 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
}
}
}
Original file line number Diff line number Diff line change
@@ -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
)
}
67 changes: 67 additions & 0 deletions core/src/com/unciv/models/modscripting/ModScriptingRunManager.kt
Original file line number Diff line number Diff line change
@@ -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<Handler, Collection<RegisteredHandler>> =
ModScriptingHandlerTypes.all.asSequence()
.map { it.handlers }
.flatten()
.associateWith { HashSet<RegisteredHandler>() }

fun getRegisteredForHandler(handler: Handler) = registeredHandlers[handler]!!

fun lambdaRegisteredHandlerRunner(registeredHandler: RegisteredHandler, withParams: Params): () -> Unit {
// Looking at the source code, some .to<Collection>() 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() {}
}


3 changes: 3 additions & 0 deletions core/src/com/unciv/models/modscripting/ScriptedMod.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.unciv.models.modscripting

class ScriptedMod(val name: String) // See, try to follow conventions of, TilesetAndMod maybe?

0 comments on commit d20d718

Please sign in to comment.