From 281738c70aed3ed705135008b12ba973dc494666 Mon Sep 17 00:00:00 2001 From: will-ca Date: Sat, 30 Oct 2021 07:59:05 +0000 Subject: [PATCH 01/93] First bits of framework for CLI console. --- core/src/com/unciv/console/ConsoleBackend.kt | 20 +++++ core/src/com/unciv/console/ConsoleScope.kt | 7 ++ core/src/com/unciv/console/ConsoleState.kt | 48 ++++++++++++ .../src/com/unciv/ui/console/ConsoleScreen.kt | 77 +++++++++++++++++++ .../com/unciv/ui/worldscreen/WorldScreen.kt | 8 +- 5 files changed, 158 insertions(+), 2 deletions(-) create mode 100644 core/src/com/unciv/console/ConsoleBackend.kt create mode 100644 core/src/com/unciv/console/ConsoleScope.kt create mode 100644 core/src/com/unciv/console/ConsoleState.kt create mode 100644 core/src/com/unciv/ui/console/ConsoleScreen.kt diff --git a/core/src/com/unciv/console/ConsoleBackend.kt b/core/src/com/unciv/console/ConsoleBackend.kt new file mode 100644 index 0000000000000..3144bf9c3f9fb --- /dev/null +++ b/core/src/com/unciv/console/ConsoleBackend.kt @@ -0,0 +1,20 @@ +package com.unciv.console + +import com.unciv.console.ConsoleScope +import kotlin.collections.ArrayList + +class ConsoleBackend(val consoleScope:ConsoleScope) { + + //val history: ArrayList = ArrayList() + + init { + } + + fun motd (): String { + return "Welcome to the Unciv CLI!\nYou are currently running the dummy backend, which will echo all commands but never do anything." + } + + fun exec (command: String): String { + return command + } +} diff --git a/core/src/com/unciv/console/ConsoleScope.kt b/core/src/com/unciv/console/ConsoleScope.kt new file mode 100644 index 0000000000000..6acda0e52d6fb --- /dev/null +++ b/core/src/com/unciv/console/ConsoleScope.kt @@ -0,0 +1,7 @@ +package com.unciv.console + +import com.unciv.logic.civilization.CivilizationInfo + +class ConsoleScope(val civInfo: CivilizationInfo) { + // Holds references to all internal game data that the console has access to. +} diff --git a/core/src/com/unciv/console/ConsoleState.kt b/core/src/com/unciv/console/ConsoleState.kt new file mode 100644 index 0000000000000..b061404334428 --- /dev/null +++ b/core/src/com/unciv/console/ConsoleState.kt @@ -0,0 +1,48 @@ +package com.unciv.console + +import com.unciv.console.ConsoleBackend +import com.unciv.console.ConsoleScope +import kotlin.collections.ArrayList + +class ConsoleState(val consoleScope: ConsoleScope){ + + val consoleBackends:ArrayList = ArrayList() + + val outputHistory:ArrayList = ArrayList() + val commandHistory:ArrayList = ArrayList() + + var activeBackend:Int = 0 + + var maxOutputHistory:Int = 50 + var maxCommandHistory:Int = 50 + + init { + echo(spawnBackend()) + } + + fun spawnBackend(): String { + var backend = ConsoleBackend(consoleScope) + consoleBackends.add(backend) + activeBackend = consoleBackends.size - 1 + return backend.motd() + } + + fun getActiveBackend(): ConsoleBackend { + return consoleBackends[activeBackend] + } + + //fun getOutputHistory(): ArrayList { + // return outputHistory + //} + + fun echo(text: String) { + outputHistory.add(text) + } + + fun exec(command: String): String { + commandHistory.add(command) + var out = getActiveBackend().exec(command) + echo(out) + return out + } +} diff --git a/core/src/com/unciv/ui/console/ConsoleScreen.kt b/core/src/com/unciv/ui/console/ConsoleScreen.kt new file mode 100644 index 0000000000000..47354ceb783db --- /dev/null +++ b/core/src/com/unciv/ui/console/ConsoleScreen.kt @@ -0,0 +1,77 @@ +package com.unciv.ui.console + +import com.badlogic.gdx.Input +import com.badlogic.gdx.scenes.scene2d.ui.Label +import com.badlogic.gdx.scenes.scene2d.ui.Table +import com.badlogic.gdx.scenes.scene2d.ui.TextButton +import com.badlogic.gdx.scenes.scene2d.ui.TextField +import com.unciv.Constants +import com.unciv.console.ConsoleState +import com.unciv.logic.civilization.CivilizationInfo +import com.unciv.ui.utils.* +import com.unciv.ui.utils.AutoScrollPane as ScrollPane + +class ConsoleScreen(val consoleState:ConsoleState, closeAction: ()->Unit): CameraStageBaseScreen() { + + private val lineHeight = 30f + + private val layoutTable: Table = Table() + private val topBar: Table = Table() + private val printHistory: Table = Table()//ScrollPane = ScrollPane() + private var printScroll: ScrollPane + private val inputBar: Table = Table() + private val inputField: TextField = TextField("", skin) + private val runButton: TextButton = "Enter".toTextButton() + private val closeButton: TextButton = Constants.close.toTextButton() + + init { + onBackButtonClicked(closeAction) + closeButton.onClick(closeAction) + + topBar.add(closeButton) + + printHistory.left() + printHistory.bottom() + printScroll = ScrollPane(printHistory) + + inputBar.add(inputField).minWidth(stage.width - runButton.getPrefWidth()) + inputBar.add(runButton) + + layoutTable.setSize(stage.width, stage.height) + + layoutTable.add(topBar).minWidth(stage.width).row() + + layoutTable.add(printScroll).minWidth(stage.width).minHeight(stage.height - topBar.getPrefHeight() - inputBar.getPrefHeight()).row() + + layoutTable.add(inputBar) + + runButton.onClick({ this.run() }) + keyPressDispatcher[Input.Keys.ENTER] = { this.run() } + keyPressDispatcher[Input.Keys.NUMPAD_ENTER] = { this.run() } + + stage.addActor(layoutTable) + + echoHistory() + } + + private fun clear() { + printHistory.clearChildren() + } + + private fun echoHistory() { + for (hist in consoleState.outputHistory) { + echo(hist) + } + } + + private fun echo(text: String) { + printHistory.add(text.toLabel()).left().bottom().padLeft(15f).row() + } + + private fun run() { + echo(consoleState.exec(inputField.text)) + inputField.setText("") + } +} + +// Screen, or widget? Screen would create whole new context for hotkeys and such, I think? Widget would be nice for diff --git a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt index f52915d69e142..f6d411d5d01d8 100644 --- a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt +++ b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt @@ -13,6 +13,8 @@ import com.badlogic.gdx.scenes.scene2d.ui.Button import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.TextButton import com.badlogic.gdx.utils.Align +import com.unciv.console.ConsoleScope +import com.unciv.console.ConsoleState import com.unciv.UncivGame import com.unciv.logic.GameInfo import com.unciv.logic.GameSaver @@ -27,6 +29,7 @@ import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.translations.tr import com.unciv.ui.cityscreen.CityScreen import com.unciv.ui.civilopedia.CivilopediaScreen +import com.unciv.ui.console.ConsoleScreen import com.unciv.ui.overviewscreen.EmpireOverviewScreen import com.unciv.ui.pickerscreens.* import com.unciv.ui.saves.LoadGameScreen @@ -239,6 +242,7 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas } // Space and N are assigned in createNextTurnButton + keyPressDispatcher[Input.Keys.GRAVE] = { game.setScreen(ConsoleScreen(ConsoleState(ConsoleScope(selectedCiv)), { game.setWorldScreen() })) } // CLI console keyPressDispatcher[Input.Keys.F1] = { game.setScreen(CivilopediaScreen(gameInfo.ruleSet, this)) } keyPressDispatcher['E'] = { game.setScreen(EmpireOverviewScreen(selectedCiv)) } // Empire overview last used page /* @@ -339,8 +343,8 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas // if we find the current player didn't change, don't update // Additionally, check if we are the current player, and in that case always stop // This fixes a bug where for some reason players were waiting for themselves. - if (gameInfo.currentPlayer == latestGame.currentPlayer - && gameInfo.turns == latestGame.turns + if (gameInfo.currentPlayer == latestGame.currentPlayer + && gameInfo.turns == latestGame.turns && latestGame.currentPlayer != gameInfo.getPlayerToViewAs().civName ) { Gdx.app.postRunnable { loadingGamePopup.close() } From 019f6bbc5f10904401efc8b017c2436e67b01549 Mon Sep 17 00:00:00 2001 From: will-ca Date: Sat, 30 Oct 2021 10:12:07 +0000 Subject: [PATCH 02/93] More fleshed out backend API, autocomplete API, command history, persistent state. --- core/src/com/unciv/console/ConsoleBackend.kt | 45 ++++++++++++++++--- core/src/com/unciv/console/ConsoleState.kt | 24 ++++++++-- .../src/com/unciv/ui/console/ConsoleScreen.kt | 33 +++++++++++++- .../com/unciv/ui/worldscreen/WorldScreen.kt | 6 ++- 4 files changed, 97 insertions(+), 11 deletions(-) diff --git a/core/src/com/unciv/console/ConsoleBackend.kt b/core/src/com/unciv/console/ConsoleBackend.kt index 3144bf9c3f9fb..5f3315aaf0428 100644 --- a/core/src/com/unciv/console/ConsoleBackend.kt +++ b/core/src/com/unciv/console/ConsoleBackend.kt @@ -3,18 +3,51 @@ package com.unciv.console import com.unciv.console.ConsoleScope import kotlin.collections.ArrayList -class ConsoleBackend(val consoleScope:ConsoleScope) { - //val history: ArrayList = ArrayList() +data class AutoCompleteResults(val isHelpText:Boolean, val matches:List, val helpText:String) - init { +open class ConsoleBackend(consoleScope:ConsoleScope) { + + open val displayname:String = "Dummy" + + open fun motd(): String { + // Message to print on launch. + return "Welcome to the Unciv CLI!\nYou are currently running the dummy backend, which will echo all commands but never do anything." } - fun motd (): String { - return "Welcome to the Unciv CLI!\nYou are currently running the dummy backend, which will echo all commands but never do anything." + open fun getAutocomplete(command: String): AutoCompleteResults { + // Return either a `List` of autocomplete matches, or a + return AutoCompleteResults(false, listOf(command+"_autocomplete"), "") } - fun exec (command: String): String { + open fun exec(command: String): String { + // Execute code and return output. return command } + + open fun terminate(): Boolean { + // Return `true` on successful termination, `false` otherwise. + return true + } +} + + +class HardcodedConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(consoleScope) { + + override val displayname:String = "Hardcoded" +} + + +enum class ConsoleBackendType(val displayname:String) { + Dummy("Dummy"), + Hardcoded("Hardcoded") +} + + +fun GetNamedConsoleBackend(backendtype:ConsoleBackendType, consoleScope:ConsoleScope): ConsoleBackend { + if (backendtype == ConsoleBackendType.Dummy) { + return ConsoleBackend(consoleScope) + } else { + throw IllegalArgumentException("Unexpected backend requsted: ${backendtype.displayname}") + } } diff --git a/core/src/com/unciv/console/ConsoleState.kt b/core/src/com/unciv/console/ConsoleState.kt index b061404334428..03f008c896873 100644 --- a/core/src/com/unciv/console/ConsoleState.kt +++ b/core/src/com/unciv/console/ConsoleState.kt @@ -3,6 +3,8 @@ package com.unciv.console import com.unciv.console.ConsoleBackend import com.unciv.console.ConsoleScope import kotlin.collections.ArrayList +import kotlin.math.max +import kotlin.math.min class ConsoleState(val consoleScope: ConsoleScope){ @@ -16,12 +18,14 @@ class ConsoleState(val consoleScope: ConsoleScope){ var maxOutputHistory:Int = 50 var maxCommandHistory:Int = 50 + var activeCommandHistory:Int = 0 + init { - echo(spawnBackend()) + echo(spawnBackend(ConsoleBackendType.Dummy)) } - fun spawnBackend(): String { - var backend = ConsoleBackend(consoleScope) + fun spawnBackend(backendtype: ConsoleBackendType): String { + var backend:ConsoleBackend = GetNamedConsoleBackend(backendtype, consoleScope) consoleBackends.add(backend) activeBackend = consoleBackends.size - 1 return backend.motd() @@ -39,10 +43,24 @@ class ConsoleState(val consoleScope: ConsoleScope){ outputHistory.add(text) } + fun getAutocomplete(command: String): AutoCompleteResults { + return getActiveBackend().getAutocomplete(command) + } + + fun navigateHistory(increment: Int): String { + activeCommandHistory = max(0, min(commandHistory.size, activeCommandHistory + increment)) + if (activeCommandHistory <= 0) { + return "" + } else { + return commandHistory[commandHistory.size - activeCommandHistory] + } + } + fun exec(command: String): String { commandHistory.add(command) var out = getActiveBackend().exec(command) echo(out) + activeCommandHistory = 0 return out } } diff --git a/core/src/com/unciv/ui/console/ConsoleScreen.kt b/core/src/com/unciv/ui/console/ConsoleScreen.kt index 47354ceb783db..d12179a930aca 100644 --- a/core/src/com/unciv/ui/console/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/console/ConsoleScreen.kt @@ -49,6 +49,11 @@ class ConsoleScreen(val consoleState:ConsoleState, closeAction: ()->Unit): Camer keyPressDispatcher[Input.Keys.ENTER] = { this.run() } keyPressDispatcher[Input.Keys.NUMPAD_ENTER] = { this.run() } + keyPressDispatcher[Input.Keys.TAB] = { this.autocomplete() } + + keyPressDispatcher[Input.Keys.UP] = { this.navigateHistory(1) } + keyPressDispatcher[Input.Keys.DOWN] = { this.navigateHistory(-1) } + stage.addActor(layoutTable) echoHistory() @@ -58,19 +63,45 @@ class ConsoleScreen(val consoleState:ConsoleState, closeAction: ()->Unit): Camer printHistory.clearChildren() } + private fun setText(text:String) { + inputField.setText(text) + inputField.setCursorPosition(inputField.text.length) + } + private fun echoHistory() { for (hist in consoleState.outputHistory) { echo(hist) } } + private fun autocomplete() { + var results = consoleState.getAutocomplete(inputField.text) + if (results.isHelpText) { + echo(results.helpText) + return + } + if (results.matches.size < 1) { + return + } else if (results.matches.size == 1) { + setText(results.matches[0]) + } else { + for (m in results.matches) { + echo(m) + } + } + } + + private fun navigateHistory(increment:Int) { + setText(consoleState.navigateHistory(increment)) + } + private fun echo(text: String) { printHistory.add(text.toLabel()).left().bottom().padLeft(15f).row() } private fun run() { echo(consoleState.exec(inputField.text)) - inputField.setText("") + setText("") } } diff --git a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt index f6d411d5d01d8..af3eb5a0c2db7 100644 --- a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt +++ b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt @@ -89,6 +89,8 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas private val notificationsScroll: NotificationsScroll var shouldUpdate = false + private var consoleScreen: ConsoleScreen + companion object { /** Switch for console logging of next turn duration */ private const val consoleLog = false @@ -194,6 +196,8 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas // don't run update() directly, because the UncivGame.worldScreen should be set so that the city buttons and tile groups // know what the viewing civ is. shouldUpdate = true + + consoleScreen = ConsoleScreen(ConsoleState(ConsoleScope(selectedCiv)), { game.setWorldScreen() }) } private fun stopMultiPlayerRefresher() { @@ -242,7 +246,7 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas } // Space and N are assigned in createNextTurnButton - keyPressDispatcher[Input.Keys.GRAVE] = { game.setScreen(ConsoleScreen(ConsoleState(ConsoleScope(selectedCiv)), { game.setWorldScreen() })) } // CLI console + keyPressDispatcher[Input.Keys.GRAVE] = { game.setScreen(consoleScreen) } // CLI console keyPressDispatcher[Input.Keys.F1] = { game.setScreen(CivilopediaScreen(gameInfo.ruleSet, this)) } keyPressDispatcher['E'] = { game.setScreen(EmpireOverviewScreen(selectedCiv)) } // Empire overview last used page /* From c617486ea0066fb44840b98ca0e910ab2979d455 Mon Sep 17 00:00:00 2001 From: will-ca Date: Sat, 30 Oct 2021 13:35:06 +0000 Subject: [PATCH 03/93] Add management UI for simultaneous running CLI backends, and example backends. --- core/src/com/unciv/console/ConsoleBackend.kt | 120 +++++++++++++++++- core/src/com/unciv/console/ConsoleScope.kt | 1 + core/src/com/unciv/console/ConsoleState.kt | 41 ++++-- .../src/com/unciv/ui/console/ConsoleScreen.kt | 87 ++++++++++++- 4 files changed, 226 insertions(+), 23 deletions(-) diff --git a/core/src/com/unciv/console/ConsoleBackend.kt b/core/src/com/unciv/console/ConsoleBackend.kt index 5f3315aaf0428..da9a16c31f114 100644 --- a/core/src/com/unciv/console/ConsoleBackend.kt +++ b/core/src/com/unciv/console/ConsoleBackend.kt @@ -1,10 +1,10 @@ package com.unciv.console -import com.unciv.console.ConsoleScope import kotlin.collections.ArrayList -data class AutoCompleteResults(val isHelpText:Boolean, val matches:List, val helpText:String) +data class AutocompleteResults(val isHelpText:Boolean, val matches:List, val helpText:String) + open class ConsoleBackend(consoleScope:ConsoleScope) { @@ -12,12 +12,12 @@ open class ConsoleBackend(consoleScope:ConsoleScope) { open fun motd(): String { // Message to print on launch. - return "Welcome to the Unciv CLI!\nYou are currently running the dummy backend, which will echo all commands but never do anything." + return "\n\nWelcome to the Unciv CLI!\nYou are currently running the dummy backend, which will echo all commands but never do anything." } - open fun getAutocomplete(command: String): AutoCompleteResults { + open fun getAutocomplete(command: String): AutocompleteResults { // Return either a `List` of autocomplete matches, or a - return AutoCompleteResults(false, listOf(command+"_autocomplete"), "") + return AutocompleteResults(false, listOf(command+"_autocomplete"), "") } open fun exec(command: String): String { @@ -35,18 +35,126 @@ open class ConsoleBackend(consoleScope:ConsoleScope) { class HardcodedConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(consoleScope) { override val displayname:String = "Hardcoded" + + val commands:List = listOf("help", "countcities", "locatebuildings", "listcities", "cheatson", "cheatsoff", "entergodmode", "exitgodmode") + val commandshelp:Map = mapOf( + "help" to "help - Display all commands\nhelp - Display information on a specific command.", + "countcities" to "countcities - Print out a numerical count of all cities in the current empire.", + "locatebuildings" to "locatebuildings - Print out a list of all cities that have a given building.\nlocatebuildings - Print out a list of all cities that are using a given resource.", + "listcities" to "listcities - Print the names of all cities in the current empire.", + "cheatson" to "cheatson - Enable commands that break game rules.", + "cheatsoff" to "cheatsoff - Disable commands that break game rules.", + "entergodmode" to "entergodmode - Massively boost all empire growth stats. (Requires cheats.)", + "exitgodemode" to "exitgodmode - Clear any previous boosts on all empire growth stats. (Requires cheats.)" + ) + + var cheats:Boolean = false + + override fun motd(): String { + return "\n\nWelcome to the hardcoded demo backend.\n\nPlease run \"help\" or press [TAB] to see a list of available commands.\nPress [TAB] at any time to see help for currently typed command.\n\nPlease note that the available commands are meant as a DEMO for the CLI." + } + + fun getCommandHelpText(command: String): String { + if (command in commandshelp) { + return "\n${commandshelp[command]}" + } else { + return "\nNo help entry found for command '${command}'" + } + } + + override fun getAutocomplete(command: String): AutocompleteResults{ + if (' ' in command) { + return AutocompleteResults(true, listOf(), getCommandHelpText(command.split(' ')[0])) + } else { + return AutocompleteResults(false, commands.filter({ c -> c.startsWith(command) }).map({ c -> c + " " }), "") + } + } + + override fun exec(command: String): String { + var args = command.split(' ') + var out:String + if (args[0] == "help") { + if (args.size > 1) { + out = getCommandHelpText(args[1]) + } else { + out = commands.joinToString(", ") + } + } else if (args[0] == "countcities") { + out = "Not implemented." + } else if (args[0] == "locatebuildings") { + out = "Not implemented." + } else if (args[0] == "listcities") { + out = "Not implemented." + } else if (args[0] == "cheatson") { + cheats = true + out = "Cheats enabled." + } else if (args[0] == "cheatsoff") { + cheats = false + out = "Cheats disabled." + } else if (args[0] == "entergodmode") { + if (cheats) { + out = "Not implemented." + } else { + out = "Cheats must be enabled to use this command!" + } + } else if (args[0] == "exitgodmode") { + if (cheats) { + out = "Not implemented." + } else { + out = "Cheats must be enabled to use this command!" + } + } else { + out = "The command ${args[0]} is either not known or not implemented." + } + return "\n> ${command}\n${out}" + } +} + + +class QjsConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(consoleScope) { + override val displayname:String = "QuickJS" + override fun motd(): String { + return "\n\nWelcome to the QuickJS Unciv CLI, which doesn't currently run QuickJS but might one day!" + } +} + + +class LuaConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(consoleScope) { + override val displayname:String = "Lua" + override fun motd(): String { + return "\n\nWelcome to the Lua Unciv CLI, which doesn't currently run Lua but might one day!" + } +} + + +class UpyConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(consoleScope) { + override val displayname:String = "MicroPython" + override fun motd(): String { + return "\n\nWelcome to the MicroPython Unciv CLI, which doesn't currently run MicroPython but might one day!" + } } enum class ConsoleBackendType(val displayname:String) { Dummy("Dummy"), - Hardcoded("Hardcoded") + Hardcoded("Hardcoded"), + QuickJS("QuickJS"), + Lua("Lua"), + MicroPython("MicroPython") } fun GetNamedConsoleBackend(backendtype:ConsoleBackendType, consoleScope:ConsoleScope): ConsoleBackend { if (backendtype == ConsoleBackendType.Dummy) { return ConsoleBackend(consoleScope) + } else if (backendtype == ConsoleBackendType.Hardcoded) { + return HardcodedConsoleBackend(consoleScope) + } else if (backendtype == ConsoleBackendType.QuickJS) { + return QjsConsoleBackend(consoleScope) + } else if (backendtype == ConsoleBackendType.Lua) { + return LuaConsoleBackend(consoleScope) + } else if (backendtype == ConsoleBackendType.MicroPython) { + return UpyConsoleBackend(consoleScope) } else { throw IllegalArgumentException("Unexpected backend requsted: ${backendtype.displayname}") } diff --git a/core/src/com/unciv/console/ConsoleScope.kt b/core/src/com/unciv/console/ConsoleScope.kt index 6acda0e52d6fb..25385241e1c79 100644 --- a/core/src/com/unciv/console/ConsoleScope.kt +++ b/core/src/com/unciv/console/ConsoleScope.kt @@ -4,4 +4,5 @@ import com.unciv.logic.civilization.CivilizationInfo class ConsoleScope(val civInfo: CivilizationInfo) { // Holds references to all internal game data that the console has access to. + // Currently just `.civInfo`, but could be cool to E.G. allow loading and making saves through CLI/API too. } diff --git a/core/src/com/unciv/console/ConsoleState.kt b/core/src/com/unciv/console/ConsoleState.kt index 03f008c896873..88a8206190b2e 100644 --- a/core/src/com/unciv/console/ConsoleState.kt +++ b/core/src/com/unciv/console/ConsoleState.kt @@ -1,7 +1,5 @@ package com.unciv.console -import com.unciv.console.ConsoleBackend -import com.unciv.console.ConsoleScope import kotlin.collections.ArrayList import kotlin.math.max import kotlin.math.min @@ -10,8 +8,8 @@ class ConsoleState(val consoleScope: ConsoleScope){ val consoleBackends:ArrayList = ArrayList() - val outputHistory:ArrayList = ArrayList() - val commandHistory:ArrayList = ArrayList() + val outputHistory:ArrayList = ArrayList() // Not implemented + val commandHistory:ArrayList = ArrayList() // Not implemented var activeBackend:Int = 0 @@ -31,19 +29,37 @@ class ConsoleState(val consoleScope: ConsoleScope){ return backend.motd() } + fun switchToBackend(index: Int) { + activeBackend = max(0, min(consoleBackends.size - 1, index)) + } + + fun termBackend(index: Int) { + if (!(0 <= index && index < consoleBackends.size)) { + return // Maybe checking should be better done and unified, and raise a warning when out of bounds. + } + val result = consoleBackends[index].terminate() + if (result) { + consoleBackends.removeAt(index) + activeBackend = min(activeBackend, consoleBackends.size - 1) + } + } + + fun hasBackend(): Boolean { + return consoleBackends.size > 0 + } + fun getActiveBackend(): ConsoleBackend { return consoleBackends[activeBackend] } - //fun getOutputHistory(): ArrayList { - // return outputHistory - //} - fun echo(text: String) { outputHistory.add(text) } - fun getAutocomplete(command: String): AutoCompleteResults { + fun getAutocomplete(command: String): AutocompleteResults { + if (!(hasBackend())) { + return AutocompleteResults(false, listOf(), "") + } return getActiveBackend().getAutocomplete(command) } @@ -58,7 +74,12 @@ class ConsoleState(val consoleScope: ConsoleScope){ fun exec(command: String): String { commandHistory.add(command) - var out = getActiveBackend().exec(command) + var out:String + if (hasBackend()) { + out = getActiveBackend().exec(command) + } else { + out = "" + } echo(out) activeCommandHistory = 0 return out diff --git a/core/src/com/unciv/ui/console/ConsoleScreen.kt b/core/src/com/unciv/ui/console/ConsoleScreen.kt index d12179a930aca..efcb8d3561036 100644 --- a/core/src/com/unciv/ui/console/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/console/ConsoleScreen.kt @@ -1,47 +1,90 @@ package com.unciv.ui.console import com.badlogic.gdx.Input +import com.badlogic.gdx.graphics.Color +import com.badlogic.gdx.scenes.scene2d.ui.Image import com.badlogic.gdx.scenes.scene2d.ui.Label +import com.badlogic.gdx.scenes.scene2d.ui.SplitPane import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.TextButton import com.badlogic.gdx.scenes.scene2d.ui.TextField import com.unciv.Constants +import com.unciv.console.ConsoleBackend +import com.unciv.console.ConsoleBackendType import com.unciv.console.ConsoleState -import com.unciv.logic.civilization.CivilizationInfo import com.unciv.ui.utils.* import com.unciv.ui.utils.AutoScrollPane as ScrollPane + class ConsoleScreen(val consoleState:ConsoleState, closeAction: ()->Unit): CameraStageBaseScreen() { private val lineHeight = 30f private val layoutTable: Table = Table() + private val topBar: Table = Table() - private val printHistory: Table = Table()//ScrollPane = ScrollPane() + private var backendsScroll: ScrollPane + private val backendsAdders: Table = Table() + private val closeButton: TextButton = Constants.close.toTextButton() + + private var middleSplit: SplitPane private var printScroll: ScrollPane + private val printHistory: Table = Table() + private val runningContainer: Table = Table() + private val runningList: Table = Table() + private val inputBar: Table = Table() private val inputField: TextField = TextField("", skin) - private val runButton: TextButton = "Enter".toTextButton() - private val closeButton: TextButton = Constants.close.toTextButton() + + private val inputControls: Table = Table() + private val tabButton: TextButton = "TAB".toTextButton() + private val upButton: Image = ImageGetter.getImage("OtherIcons/Up") + private val downButton: Image = ImageGetter.getImage("OtherIcons/Down") + private val runButton: TextButton = "ENTER".toTextButton() init { onBackButtonClicked(closeAction) closeButton.onClick(closeAction) + backendsAdders.add("Launch new backend:".toLabel()) + for (backendtype in ConsoleBackendType.values()) { + var backendadder = backendtype.displayname.toTextButton() + backendadder.onClick({ + echo(consoleState.spawnBackend(backendtype)) + updateRunning() + }) + backendsAdders.add(backendadder) + } + backendsScroll = ScrollPane(backendsAdders) + + backendsAdders.left() + + topBar.add(backendsScroll).minWidth(stage.width - closeButton.getPrefWidth()) topBar.add(closeButton) printHistory.left() printHistory.bottom() printScroll = ScrollPane(printHistory) - inputBar.add(inputField).minWidth(stage.width - runButton.getPrefWidth()) - inputBar.add(runButton) + runningContainer.add("Active Backends:".toLabel()).row() + runningContainer.add(runningList) + + middleSplit = SplitPane(printScroll, runningContainer, false, skin) + middleSplit.setSplitAmount(0.8f) + + inputControls.add(tabButton) + inputControls.add(upButton.surroundWithCircle(40f)) + inputControls.add(downButton.surroundWithCircle(40f)) + inputControls.add(runButton) + + inputBar.add(inputField).minWidth(stage.width - inputControls.getPrefWidth()) + inputBar.add(inputControls) layoutTable.setSize(stage.width, stage.height) layoutTable.add(topBar).minWidth(stage.width).row() - layoutTable.add(printScroll).minWidth(stage.width).minHeight(stage.height - topBar.getPrefHeight() - inputBar.getPrefHeight()).row() + layoutTable.add(middleSplit).minWidth(stage.width).minHeight(stage.height - topBar.getPrefHeight() - inputBar.getPrefHeight()).row() layoutTable.add(inputBar) @@ -49,14 +92,43 @@ class ConsoleScreen(val consoleState:ConsoleState, closeAction: ()->Unit): Camer keyPressDispatcher[Input.Keys.ENTER] = { this.run() } keyPressDispatcher[Input.Keys.NUMPAD_ENTER] = { this.run() } + tabButton.onClick({ this.autocomplete() }) keyPressDispatcher[Input.Keys.TAB] = { this.autocomplete() } + upButton.onClick({ this.navigateHistory(1) }) keyPressDispatcher[Input.Keys.UP] = { this.navigateHistory(1) } + downButton.onClick({ this.navigateHistory(-1) }) keyPressDispatcher[Input.Keys.DOWN] = { this.navigateHistory(-1) } stage.addActor(layoutTable) echoHistory() + + updateRunning() + } + + private fun updateRunning() { + runningList.clearChildren() + var i = 0 + for (backend in consoleState.consoleBackends) { + var button = backend.displayname.toTextButton() + val index = i + runningList.add(button) + if (i == consoleState.activeBackend) { + button.color = Color.GREEN + } + button.onClick({ + consoleState.switchToBackend(index) + updateRunning() + }) + var termbutton = ImageGetter.getImage("OtherIcons/Stop") + termbutton.onClick({ + consoleState.termBackend(index) + updateRunning() + }) + runningList.add(termbutton.surroundWithCircle(40f)).row() + i += 1 + } } private fun clear() { @@ -97,6 +169,7 @@ class ConsoleScreen(val consoleState:ConsoleState, closeAction: ()->Unit): Camer private fun echo(text: String) { printHistory.add(text.toLabel()).left().bottom().padLeft(15f).row() + printScroll.scrollTo(0f,0f,1f,1f) } private fun run() { From 4ed108370688e32d476ad2063fa09d4cc14a4eda Mon Sep 17 00:00:00 2001 From: will-ca Date: Sun, 31 Oct 2021 22:55:38 +0000 Subject: [PATCH 04/93] Fix translation/substitution in CLI printing. Make most demo CLI commands functional. --- core/src/com/unciv/UncivGame.kt | 2 +- core/src/com/unciv/console/ConsoleBackend.kt | 47 ++++++++++++++----- core/src/com/unciv/console/ConsoleScope.kt | 4 +- .../src/com/unciv/ui/console/ConsoleScreen.kt | 2 +- .../com/unciv/ui/worldscreen/WorldScreen.kt | 10 +++- 5 files changed, 48 insertions(+), 17 deletions(-) diff --git a/core/src/com/unciv/UncivGame.kt b/core/src/com/unciv/UncivGame.kt index 3630d13f8c80e..d2230cd1ef592 100644 --- a/core/src/com/unciv/UncivGame.kt +++ b/core/src/com/unciv/UncivGame.kt @@ -54,7 +54,7 @@ class UncivGame(parameters: UncivGameParameters) : Game() { * Does not update World View changes until finished. * Set to 0 to disable. */ - val simulateUntilTurnForDebug: Int = 0 + var simulateUntilTurnForDebug: Int = 0 /** Console log battles */ diff --git a/core/src/com/unciv/console/ConsoleBackend.kt b/core/src/com/unciv/console/ConsoleBackend.kt index da9a16c31f114..66c9ab51fb3a5 100644 --- a/core/src/com/unciv/console/ConsoleBackend.kt +++ b/core/src/com/unciv/console/ConsoleBackend.kt @@ -6,7 +6,7 @@ import kotlin.collections.ArrayList data class AutocompleteResults(val isHelpText:Boolean, val matches:List, val helpText:String) -open class ConsoleBackend(consoleScope:ConsoleScope) { +open class ConsoleBackend(val consoleScope:ConsoleScope) { open val displayname:String = "Dummy" @@ -36,7 +36,6 @@ class HardcodedConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(console override val displayname:String = "Hardcoded" - val commands:List = listOf("help", "countcities", "locatebuildings", "listcities", "cheatson", "cheatsoff", "entergodmode", "exitgodmode") val commandshelp:Map = mapOf( "help" to "help - Display all commands\nhelp - Display information on a specific command.", "countcities" to "countcities - Print out a numerical count of all cities in the current empire.", @@ -44,8 +43,9 @@ class HardcodedConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(console "listcities" to "listcities - Print the names of all cities in the current empire.", "cheatson" to "cheatson - Enable commands that break game rules.", "cheatsoff" to "cheatsoff - Disable commands that break game rules.", - "entergodmode" to "entergodmode - Massively boost all empire growth stats. (Requires cheats.)", - "exitgodemode" to "exitgodmode - Clear any previous boosts on all empire growth stats. (Requires cheats.)" + "supercharge" to "supercharge [true|false] - Massively boost all empire growth stats.\n\tRun with no arguments to toggle. (Requires cheats.)", + "godview" to "godview [true|false] - Make the entire map visible.\n\tRun with no arguments to toggle. (Requires cheats.)", + "simulatetoturn" to "simulatetoturn - After this turn, automatically play until the turn specified by `integer`.\n\tMap view will be frozen while simulating. (Requires cheats.)" ) var cheats:Boolean = false @@ -66,40 +66,61 @@ class HardcodedConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(console if (' ' in command) { return AutocompleteResults(true, listOf(), getCommandHelpText(command.split(' ')[0])) } else { - return AutocompleteResults(false, commands.filter({ c -> c.startsWith(command) }).map({ c -> c + " " }), "") + return AutocompleteResults(false, commandshelp.keys.filter({ c -> c.startsWith(command) }).map({ c -> c + " " }), "") } } override fun exec(command: String): String { var args = command.split(' ') - var out:String + var out = "" if (args[0] == "help") { if (args.size > 1) { out = getCommandHelpText(args[1]) } else { - out = commands.joinToString(", ") + out = commandshelp.keys.joinToString(", ") } } else if (args[0] == "countcities") { - out = "Not implemented." + out = consoleScope.civInfo.cities.size.toString() } else if (args[0] == "locatebuildings") { out = "Not implemented." } else if (args[0] == "listcities") { - out = "Not implemented." + out = consoleScope.civInfo.cities + .map { city -> city.name } + .joinToString(", ") } else if (args[0] == "cheatson") { cheats = true out = "Cheats enabled." } else if (args[0] == "cheatsoff") { cheats = false out = "Cheats disabled." - } else if (args[0] == "entergodmode") { + } else if (args[0] == "supercharge") { + if (cheats) { + var supercharge = if (args.size > 1) args[1].toBoolean() else !(consoleScope.uncivGame.superchargedForDebug) + consoleScope.uncivGame.superchargedForDebug = supercharge + out = "${if (supercharge) "Enabled" else "Disabled"} stats supercharge." + } else { + out = "Cheats must be enabled to use this command!" + } + } else if (args[0] == "godview") { if (cheats) { - out = "Not implemented." + var godview = if (args.size > 1) args[1].toBoolean() else !(consoleScope.uncivGame.viewEntireMapForDebug) + consoleScope.uncivGame.viewEntireMapForDebug = godview + out = "${if (godview) "Enabled" else "Disabled"} whole map visibility." } else { out = "Cheats must be enabled to use this command!" } - } else if (args[0] == "exitgodmode") { + } else if (args[0] == "simulatetoturn") { if (cheats) { - out = "Not implemented." + var numturn = 0 + if (args.size > 1) { + try { + numturn = args[1].toInt() + } catch (e: NumberFormatException) { + out += "Invalid number: ${args[1]}\n" + } + } + consoleScope.uncivGame.simulateUntilTurnForDebug = numturn + out += "Will automatically simulate game until turn ${numturn} after this turn.\nThe map will not update until completed." } else { out = "Cheats must be enabled to use this command!" } diff --git a/core/src/com/unciv/console/ConsoleScope.kt b/core/src/com/unciv/console/ConsoleScope.kt index 25385241e1c79..a39e3abc962fd 100644 --- a/core/src/com/unciv/console/ConsoleScope.kt +++ b/core/src/com/unciv/console/ConsoleScope.kt @@ -1,8 +1,10 @@ package com.unciv.console import com.unciv.logic.civilization.CivilizationInfo +import com.unciv.UncivGame -class ConsoleScope(val civInfo: CivilizationInfo) { +class ConsoleScope(val civInfo: CivilizationInfo, val uncivGame: UncivGame) { // Holds references to all internal game data that the console has access to. // Currently just `.civInfo`, but could be cool to E.G. allow loading and making saves through CLI/API too. + // Also where to put any `PlayerAPI`, `CheatAPI`, `ModAPI`, etc. } diff --git a/core/src/com/unciv/ui/console/ConsoleScreen.kt b/core/src/com/unciv/ui/console/ConsoleScreen.kt index efcb8d3561036..07f692faff1a7 100644 --- a/core/src/com/unciv/ui/console/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/console/ConsoleScreen.kt @@ -168,7 +168,7 @@ class ConsoleScreen(val consoleState:ConsoleState, closeAction: ()->Unit): Camer } private fun echo(text: String) { - printHistory.add(text.toLabel()).left().bottom().padLeft(15f).row() + printHistory.add(Label(text, skin)).left().bottom().padLeft(15f).row() printScroll.scrollTo(0f,0f,1f,1f) } diff --git a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt index af3eb5a0c2db7..03e55e0879cd0 100644 --- a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt +++ b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt @@ -197,7 +197,15 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas // know what the viewing civ is. shouldUpdate = true - consoleScreen = ConsoleScreen(ConsoleState(ConsoleScope(selectedCiv)), { game.setWorldScreen() }) + consoleScreen = ConsoleScreen( + ConsoleState( + ConsoleScope( + selectedCiv, + UncivGame.Current + ) + ), + { game.setWorldScreen() } + ) } private fun stopMultiPlayerRefresher() { From 03283e9bf1b9924767cb3249d75d0296dcc70461 Mon Sep 17 00:00:00 2001 From: will-ca Date: Sun, 31 Oct 2021 23:24:21 +0000 Subject: [PATCH 05/93] Try to fix CLI screen key binding persistence with SomeTroglodyte's suggestion. --- core/src/com/unciv/console/ConsoleBackend.kt | 8 ++++- .../src/com/unciv/ui/console/ConsoleScreen.kt | 35 ++++++++++++------- .../com/unciv/ui/worldscreen/WorldScreen.kt | 5 +++ 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/core/src/com/unciv/console/ConsoleBackend.kt b/core/src/com/unciv/console/ConsoleBackend.kt index 66c9ab51fb3a5..bda89bd198ea3 100644 --- a/core/src/com/unciv/console/ConsoleBackend.kt +++ b/core/src/com/unciv/console/ConsoleBackend.kt @@ -82,7 +82,13 @@ class HardcodedConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(console } else if (args[0] == "countcities") { out = consoleScope.civInfo.cities.size.toString() } else if (args[0] == "locatebuildings") { - out = "Not implemented." + var buildingcities:List = listOf() + if (args.size > 1) { + var buildingcities = consoleScope.civInfo.cities + .filter { args[1] in it.cityConstructions.builtBuildings } + .map { it.name } + } + out = buildingcities.joinToString(", ") } else if (args[0] == "listcities") { out = consoleScope.civInfo.cities .map { city -> city.name } diff --git a/core/src/com/unciv/ui/console/ConsoleScreen.kt b/core/src/com/unciv/ui/console/ConsoleScreen.kt index 07f692faff1a7..c9e8c9ef37465 100644 --- a/core/src/com/unciv/ui/console/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/console/ConsoleScreen.kt @@ -16,7 +16,7 @@ import com.unciv.ui.utils.* import com.unciv.ui.utils.AutoScrollPane as ScrollPane -class ConsoleScreen(val consoleState:ConsoleState, closeAction: ()->Unit): CameraStageBaseScreen() { +class ConsoleScreen(val consoleState:ConsoleState, val closeAction: ()->Unit): CameraStageBaseScreen() { private val lineHeight = 30f @@ -43,8 +43,6 @@ class ConsoleScreen(val consoleState:ConsoleState, closeAction: ()->Unit): Camer private val runButton: TextButton = "ENTER".toTextButton() init { - onBackButtonClicked(closeAction) - closeButton.onClick(closeAction) backendsAdders.add("Launch new backend:".toLabel()) for (backendtype in ConsoleBackendType.values()) { @@ -88,17 +86,20 @@ class ConsoleScreen(val consoleState:ConsoleState, closeAction: ()->Unit): Camer layoutTable.add(inputBar) - runButton.onClick({ this.run() }) - keyPressDispatcher[Input.Keys.ENTER] = { this.run() } - keyPressDispatcher[Input.Keys.NUMPAD_ENTER] = { this.run() } + runButton.onClick({ run() }) + keyPressDispatcher[Input.Keys.ENTER] = { run() } + keyPressDispatcher[Input.Keys.NUMPAD_ENTER] = { run() } - tabButton.onClick({ this.autocomplete() }) - keyPressDispatcher[Input.Keys.TAB] = { this.autocomplete() } + tabButton.onClick({ autocomplete() }) + keyPressDispatcher[Input.Keys.TAB] = { autocomplete() } - upButton.onClick({ this.navigateHistory(1) }) - keyPressDispatcher[Input.Keys.UP] = { this.navigateHistory(1) } - downButton.onClick({ this.navigateHistory(-1) }) - keyPressDispatcher[Input.Keys.DOWN] = { this.navigateHistory(-1) } + upButton.onClick({ navigateHistory(1) }) + keyPressDispatcher[Input.Keys.UP] = { navigateHistory(1) } + downButton.onClick({ navigateHistory(-1) }) + keyPressDispatcher[Input.Keys.DOWN] = { navigateHistory(-1) } + + onBackButtonClicked({ closeConsole() }) + closeButton.onClick({ closeConsole() }) stage.addActor(layoutTable) @@ -107,6 +108,16 @@ class ConsoleScreen(val consoleState:ConsoleState, closeAction: ()->Unit): Camer updateRunning() } + fun openConsole() { + game.setScreen(this) + keyPressDispatcher.install(stage) + } + + fun closeConsole() { + closeAction() + keyPressDispatcher.uninstall() + } + private fun updateRunning() { runningList.clearChildren() var i = 0 diff --git a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt index 03e55e0879cd0..808decd9c6d4f 100644 --- a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt +++ b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt @@ -254,8 +254,13 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas } // Space and N are assigned in createNextTurnButton +<<<<<<< HEAD keyPressDispatcher[Input.Keys.GRAVE] = { game.setScreen(consoleScreen) } // CLI console keyPressDispatcher[Input.Keys.F1] = { game.setScreen(CivilopediaScreen(gameInfo.ruleSet, this)) } +======= + keyPressDispatcher[Input.Keys.GRAVE] = { consoleScreen.openConsole() } // CLI console + keyPressDispatcher[Input.Keys.F1] = { game.setScreen(CivilopediaScreen(gameInfo.ruleSet)) } +>>>>>>> b18fdd2d1 (Try to fix CLI screen key binding persistence with SomeTroglodyte's suggestion.) keyPressDispatcher['E'] = { game.setScreen(EmpireOverviewScreen(selectedCiv)) } // Empire overview last used page /* * These try to be faithful to default Civ5 key bindings as found in several places online From 2e0ca173791a67b7d5e48fbfb2877f3061b7ea6f Mon Sep 17 00:00:00 2001 From: will-ca Date: Mon, 1 Nov 2021 00:31:05 +0000 Subject: [PATCH 06/93] Make last demo command functional. --- core/src/com/unciv/console/ConsoleBackend.kt | 20 ++++++++++++++++--- core/src/com/unciv/console/ConsoleScope.kt | 6 ++++-- .../src/com/unciv/ui/console/ConsoleScreen.kt | 5 ++++- .../com/unciv/ui/worldscreen/WorldScreen.kt | 1 + 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/core/src/com/unciv/console/ConsoleBackend.kt b/core/src/com/unciv/console/ConsoleBackend.kt index bda89bd198ea3..9f95b11a40e1c 100644 --- a/core/src/com/unciv/console/ConsoleBackend.kt +++ b/core/src/com/unciv/console/ConsoleBackend.kt @@ -39,8 +39,9 @@ class HardcodedConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(console val commandshelp:Map = mapOf( "help" to "help - Display all commands\nhelp - Display information on a specific command.", "countcities" to "countcities - Print out a numerical count of all cities in the current empire.", - "locatebuildings" to "locatebuildings - Print out a list of all cities that have a given building.\nlocatebuildings - Print out a list of all cities that are using a given resource.", "listcities" to "listcities - Print the names of all cities in the current empire.", + "locatebuildings" to "locatebuildings - Print out a list of all cities that have a given building.\nlocatebuildings - Print out a list of all cities that are using a given resource.", + "missingbuildings" to "missingbuildings - Print out a list of all cities that do not have a given building.", "cheatson" to "cheatson - Enable commands that break game rules.", "cheatsoff" to "cheatsoff - Disable commands that break game rules.", "supercharge" to "supercharge [true|false] - Massively boost all empire growth stats.\n\tRun with no arguments to toggle. (Requires cheats.)", @@ -84,8 +85,21 @@ class HardcodedConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(console } else if (args[0] == "locatebuildings") { var buildingcities:List = listOf() if (args.size > 1) { - var buildingcities = consoleScope.civInfo.cities - .filter { args[1] in it.cityConstructions.builtBuildings } + buildingcities = consoleScope.civInfo.cities + .filter { + args[1] in it.cityConstructions.builtBuildings || + it.cityConstructions.builtBuildings.any({ building -> + consoleScope.gameInfo.ruleSet.buildings[building]!!.requiresResource(args[1]) + }) + } + .map { it.name } + } + out = buildingcities.joinToString(", ") + } else if (args[0] == "missingbuildings") { + var buildingcities:List = listOf() + if (args.size > 1) { + buildingcities = consoleScope.civInfo.cities + .filter { !(args[1] in it.cityConstructions.builtBuildings) } .map { it.name } } out = buildingcities.joinToString(", ") diff --git a/core/src/com/unciv/console/ConsoleScope.kt b/core/src/com/unciv/console/ConsoleScope.kt index a39e3abc962fd..0111e35e96977 100644 --- a/core/src/com/unciv/console/ConsoleScope.kt +++ b/core/src/com/unciv/console/ConsoleScope.kt @@ -1,10 +1,12 @@ package com.unciv.console +import com.unciv.logic.GameInfo import com.unciv.logic.civilization.CivilizationInfo import com.unciv.UncivGame -class ConsoleScope(val civInfo: CivilizationInfo, val uncivGame: UncivGame) { +class ConsoleScope(val civInfo: CivilizationInfo, val gameInfo: GameInfo, val uncivGame: UncivGame) { // Holds references to all internal game data that the console has access to. - // Currently just `.civInfo`, but could be cool to E.G. allow loading and making saves through CLI/API too. + // Mostly `.civInfo`/.`gameInfo`, but could be cool to E.G. allow loading and making saves through CLI/API too. // Also where to put any `PlayerAPI`, `CheatAPI`, `ModAPI`, etc. + // For `LuaConsoleBackend`, `UpyConsoleBackend`, `QjsConsoleBackend`, etc, this should probably directly mirror the wrappers exposed to the scripting language. } diff --git a/core/src/com/unciv/ui/console/ConsoleScreen.kt b/core/src/com/unciv/ui/console/ConsoleScreen.kt index c9e8c9ef37465..080c4a3d2101d 100644 --- a/core/src/com/unciv/ui/console/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/console/ConsoleScreen.kt @@ -168,6 +168,7 @@ class ConsoleScreen(val consoleState:ConsoleState, val closeAction: ()->Unit): C } else if (results.matches.size == 1) { setText(results.matches[0]) } else { + echo("") for (m in results.matches) { echo(m) } @@ -179,7 +180,9 @@ class ConsoleScreen(val consoleState:ConsoleState, val closeAction: ()->Unit): C } private fun echo(text: String) { - printHistory.add(Label(text, skin)).left().bottom().padLeft(15f).row() + var label = Label(text, skin) + label.setWrap(true) + printHistory.add(label).left().bottom().width(stage.width*0.75f).padLeft(15f).row() printScroll.scrollTo(0f,0f,1f,1f) } diff --git a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt index 808decd9c6d4f..65ff5b9297e50 100644 --- a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt +++ b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt @@ -201,6 +201,7 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas ConsoleState( ConsoleScope( selectedCiv, + gameInfo, UncivGame.Current ) ), From d045c65ad4c2740cda31f372549e3257139dcdf3 Mon Sep 17 00:00:00 2001 From: will-ca Date: Mon, 1 Nov 2021 06:46:30 +0000 Subject: [PATCH 07/93] Property path parsing/introspecting from strings. --- build.gradle.kts | 5 +- core/src/com/unciv/console/ConsoleBackend.kt | 173 +++++++++++------- .../com/unciv/console/ConsoleProtocolUtils.kt | 140 ++++++++++++++ core/src/com/unciv/console/ConsoleScope.kt | 2 +- .../src/com/unciv/ui/console/ConsoleScreen.kt | 4 +- .../com/unciv/ui/worldscreen/WorldScreen.kt | 7 +- 6 files changed, 256 insertions(+), 75 deletions(-) create mode 100644 core/src/com/unciv/console/ConsoleProtocolUtils.kt diff --git a/build.gradle.kts b/build.gradle.kts index d14e8f9371c2f..39c763de734ac 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -33,7 +33,7 @@ buildscript { classpath("com.github.anuken:packr:-SNAPSHOT") } } - + allprojects { apply(plugin = "eclipse") apply(plugin = "idea") @@ -62,6 +62,7 @@ project(":desktop") { dependencies { "implementation"(project(":core")) + "implementation"("org.jetbrains.kotlin:kotlin-reflect:${com.unciv.build.BuildConfig.kotlinVersion}") "implementation"("com.badlogicgames.gdx:gdx-backend-lwjgl3:${gdxVersion}") "implementation"("com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop") @@ -82,6 +83,7 @@ project(":android") { dependencies { "implementation"(project(":core")) + "implementation"("org.jetbrains.kotlin:kotlin-reflect:${com.unciv.build.BuildConfig.kotlinVersion}") "implementation"("com.badlogicgames.gdx:gdx-backend-android:$gdxVersion") natives("com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-armeabi-v7a") natives("com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-arm64-v8a") @@ -96,6 +98,7 @@ project(":ios") { dependencies { "implementation"(project(":core")) + "implementation"("org.jetbrains.kotlin:kotlin-reflect:${com.unciv.build.BuildConfig.kotlinVersion}") "implementation"("com.mobidevelop.robovm:robovm-rt:$roboVMVersion") "implementation"("com.mobidevelop.robovm:robovm-cocoatouch:$roboVMVersion") "implementation"("com.badlogicgames.gdx:gdx-backend-robovm:$gdxVersion") diff --git a/core/src/com/unciv/console/ConsoleBackend.kt b/core/src/com/unciv/console/ConsoleBackend.kt index 9f95b11a40e1c..c3e49a34f7522 100644 --- a/core/src/com/unciv/console/ConsoleBackend.kt +++ b/core/src/com/unciv/console/ConsoleBackend.kt @@ -1,6 +1,6 @@ package com.unciv.console -import kotlin.collections.ArrayList +import java.util.* data class AutocompleteResults(val isHelpText:Boolean, val matches:List, val helpText:String) @@ -10,6 +10,9 @@ open class ConsoleBackend(val consoleScope:ConsoleScope) { open val displayname:String = "Dummy" + /* val consoleScope: ConsoleScope + get() = consoleState.consoleScope */ + open fun motd(): String { // Message to print on launch. return "\n\nWelcome to the Unciv CLI!\nYou are currently running the dummy backend, which will echo all commands but never do anything." @@ -44,9 +47,13 @@ class HardcodedConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(console "missingbuildings" to "missingbuildings - Print out a list of all cities that do not have a given building.", "cheatson" to "cheatson - Enable commands that break game rules.", "cheatsoff" to "cheatsoff - Disable commands that break game rules.", - "supercharge" to "supercharge [true|false] - Massively boost all empire growth stats.\n\tRun with no arguments to toggle. (Requires cheats.)", + "godmode" to "godmode [true|false] - Ignore many game rule restrictions. Allows instant purchase of tech, policies, buildings, tiles, and more.\n\tRun with no arguments to toggle. (Requires cheats.)", "godview" to "godview [true|false] - Make the entire map visible.\n\tRun with no arguments to toggle. (Requires cheats.)", - "simulatetoturn" to "simulatetoturn - After this turn, automatically play until the turn specified by `integer`.\n\tMap view will be frozen while simulating. (Requires cheats.)" + "inspectpath" to "", + "simulatetoturn" to "simulatetoturn - After this turn, automatically play until the turn specified by `integer`.\n\tMap view will be frozen while simulating. (Requires cheats.)", + "spawnbuilding" to "", + "spawnunit" to "", + "supercharge" to "supercharge [true|false] - Massively boost all empire growth stats.\n\tRun with no arguments to toggle. (Requires cheats.)" ) var cheats:Boolean = false @@ -74,78 +81,112 @@ class HardcodedConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(console override fun exec(command: String): String { var args = command.split(' ') var out = "" - if (args[0] == "help") { - if (args.size > 1) { - out = getCommandHelpText(args[1]) - } else { - out = commandshelp.keys.joinToString(", ") + when (args[0]) { + "help" -> { + if (args.size > 1) { + out = getCommandHelpText(args[1]) + } else { + out = commandshelp.keys.joinToString(", ") + } } - } else if (args[0] == "countcities") { - out = consoleScope.civInfo.cities.size.toString() - } else if (args[0] == "locatebuildings") { - var buildingcities:List = listOf() - if (args.size > 1) { - buildingcities = consoleScope.civInfo.cities - .filter { - args[1] in it.cityConstructions.builtBuildings || - it.cityConstructions.builtBuildings.any({ building -> - consoleScope.gameInfo.ruleSet.buildings[building]!!.requiresResource(args[1]) - }) - } - .map { it.name } + "countcities" -> { + out = consoleScope.civInfo.cities.size.toString() } - out = buildingcities.joinToString(", ") - } else if (args[0] == "missingbuildings") { - var buildingcities:List = listOf() - if (args.size > 1) { - buildingcities = consoleScope.civInfo.cities - .filter { !(args[1] in it.cityConstructions.builtBuildings) } - .map { it.name } + "locatebuildings" -> { + var buildingcities:List = listOf() + if (args.size > 1) { + var searchfor = args.slice(1..args.size-1).joinToString(" ").trim(' ') + buildingcities = consoleScope.civInfo.cities + .filter { + searchfor in it.cityConstructions.builtBuildings || + it.cityConstructions.builtBuildings.any({ building -> + consoleScope.gameInfo.ruleSet.buildings[building]!!.requiresResource(searchfor) + }) + } + .map { it.name } + } + out = buildingcities.joinToString(", ") } - out = buildingcities.joinToString(", ") - } else if (args[0] == "listcities") { - out = consoleScope.civInfo.cities - .map { city -> city.name } - .joinToString(", ") - } else if (args[0] == "cheatson") { - cheats = true - out = "Cheats enabled." - } else if (args[0] == "cheatsoff") { - cheats = false - out = "Cheats disabled." - } else if (args[0] == "supercharge") { - if (cheats) { - var supercharge = if (args.size > 1) args[1].toBoolean() else !(consoleScope.uncivGame.superchargedForDebug) - consoleScope.uncivGame.superchargedForDebug = supercharge - out = "${if (supercharge) "Enabled" else "Disabled"} stats supercharge." - } else { - out = "Cheats must be enabled to use this command!" + "missingbuildings" -> { + var buildingcities:List = listOf() + if (args.size > 1) { + var searchfor = args.slice(1..args.size-1).joinToString(" ").trim(' ') + buildingcities = consoleScope.civInfo.cities + .filter { !(searchfor in it.cityConstructions.builtBuildings) } + .map { it.name } + } + out = buildingcities.joinToString(", ") } - } else if (args[0] == "godview") { - if (cheats) { - var godview = if (args.size > 1) args[1].toBoolean() else !(consoleScope.uncivGame.viewEntireMapForDebug) - consoleScope.uncivGame.viewEntireMapForDebug = godview - out = "${if (godview) "Enabled" else "Disabled"} whole map visibility." - } else { - out = "Cheats must be enabled to use this command!" + "listcities" -> { + out = consoleScope.civInfo.cities + .map { city -> city.name } + .joinToString(", ") } - } else if (args[0] == "simulatetoturn") { - if (cheats) { - var numturn = 0 - if (args.size > 1) { + "cheatson" -> { + cheats = true + out = "Cheats enabled." + } + "cheatsoff" -> { + cheats = false + out = "Cheats disabled." + } + "godmode" -> { + if (cheats) { + var godmode = if (args.size > 1) args[1].toBoolean() else !(consoleScope.gameInfo.gameParameters.godMode) + consoleScope.gameInfo.gameParameters.godMode = godmode + out = "${if (godmode) "Enabled" else "Disabled"} godmode." + } else { + out = "Cheats must be enabled to use this command!" + } + } + "godview" -> { + if (cheats) { + var godview = if (args.size > 1) args[1].toBoolean() else !(consoleScope.uncivGame.viewEntireMapForDebug) + consoleScope.uncivGame.viewEntireMapForDebug = godview + out = "${if (godview) "Enabled" else "Disabled"} whole map visibility." + } else { + out = "Cheats must be enabled to use this command!" + } + } + "inspectpath" -> { + if (cheats) { try { - numturn = args[1].toInt() - } catch (e: NumberFormatException) { - out += "Invalid number: ${args[1]}\n" + out = "${resolveInstancePath(consoleScope, parseKotlinPath(args[1]))}" + //out = "${evalKotlinPath(consoleScope, args[1])}" + } catch (e: RuntimeException) { + out = "Error accessing: ${e}" } + } else { + out = "Cheats must be enabled to use this command!" } - consoleScope.uncivGame.simulateUntilTurnForDebug = numturn - out += "Will automatically simulate game until turn ${numturn} after this turn.\nThe map will not update until completed." - } else { - out = "Cheats must be enabled to use this command!" } - } else { - out = "The command ${args[0]} is either not known or not implemented." + "simulatetoturn" -> { + if (cheats) { + var numturn = 0 + if (args.size > 1) { + try { + numturn = args[1].toInt() + } catch (e: NumberFormatException) { + out += "Invalid number: ${args[1]}\n" + } + } + consoleScope.uncivGame.simulateUntilTurnForDebug = numturn + out += "Will automatically simulate game until turn ${numturn} after this turn.\nThe map will not update until completed." + } else { + out = "Cheats must be enabled to use this command!" + } + } + "supercharge" -> { + if (cheats) { + var supercharge = if (args.size > 1) args[1].toBoolean() else !(consoleScope.uncivGame.superchargedForDebug) + consoleScope.uncivGame.superchargedForDebug = supercharge + out = "${if (supercharge) "Enabled" else "Disabled"} stats supercharge." + } else { + out = "Cheats must be enabled to use this command!" + } + } else -> { + out = "The command ${args[0]} is either not known or not implemented." + } } return "\n> ${command}\n${out}" } diff --git a/core/src/com/unciv/console/ConsoleProtocolUtils.kt b/core/src/com/unciv/console/ConsoleProtocolUtils.kt new file mode 100644 index 0000000000000..98b3f279376bb --- /dev/null +++ b/core/src/com/unciv/console/ConsoleProtocolUtils.kt @@ -0,0 +1,140 @@ +package com.unciv.console + +import kotlin.collections.ArrayList +import kotlin.reflect.KProperty1 +import java.util.* + + +@Suppress("UNCHECKED_CAST") +fun readInstanceProperty(instance: Any, propertyName: String): R { + // From https://stackoverflow.com/a/35539628/12260302 + val property = instance::class.members + .first { it.name == propertyName } as KProperty1 + return property.get(instance) as R +} + + +interface PathElementArg { + val value: Any +} + +data class PathElementArgString(override val value: String): PathElementArg +data class PathElementArgInt(override val value: Int): PathElementArg +data class PathElementArgFloat(override val value: Float): PathElementArg +data class PathElementArgBoolean(override val value: Boolean): PathElementArg + + +enum class PathElementType() { + Property(), + Key(), + //Index(), + Call() +} + +data class PathElement( + val type: PathElementType, + val name: String, + //val args: Collection, + val doEval: Boolean = false +) + +private val brackettypes: Map = mapOf( + '[' to "[]", + '(' to "()" +) + +private val bracketmeanings: Map = mapOf( + "[]" to PathElementType.Key, + "()" to PathElementType.Call +) + +fun parseKotlinPath(text: String): Collection { + var path:MutableList = ArrayList() + var curr_type = PathElementType.Property + var curr_name = ArrayList() + var curr_brackets = "" + var curr_bracketdepth = 0 + var just_closed_brackets = false + for (char in text) { + if (curr_bracketdepth == 0) { + if (char == '.') { + if (!just_closed_brackets) { + path.add(PathElement(PathElementType.Property, curr_name.joinToString(""))) + } + curr_name.clear() + continue + } + if (char in brackettypes) { + if (!just_closed_brackets) { + path.add(PathElement(PathElementType.Property, curr_name.joinToString(""))) + } + curr_name.clear() + curr_brackets = brackettypes[char]!! + curr_bracketdepth += 1 + continue + } + curr_name.add(char) + } + if (just_closed_brackets) { + just_closed_brackets = false + } + if (curr_bracketdepth > 0) { + if (char == curr_brackets[1]) { + curr_bracketdepth -= 1 + if (curr_bracketdepth == 0) { + path.add(PathElement( + bracketmeanings[curr_brackets]!!, + curr_name.joinToString(""), + true + )) + curr_brackets = "" + curr_name.clear() + just_closed_brackets = true + continue + } + } else if (char == curr_brackets[0]) { + curr_bracketdepth += 1 + } + curr_name.add(char) + } + } + if (!just_closed_brackets && curr_bracketdepth == 0) { + path.add(PathElement(PathElementType.Property, curr_name.joinToString(""))) + curr_name.clear() + } + if (curr_bracketdepth > 0) { + throw IllegalArgumentException("Unclosed parentheses.") + } + return path +} + + +fun resolveInstancePath(instance: Any, path: Collection): Any { + var obj = instance + print("\n") + path.map({print(it);print("\n")}) + for (element in path) { + when (element.type) { + PathElementType.Property -> { obj = readInstanceProperty(obj, element.name) } + } + } + return obj +} + + + +fun evalKotlinPath(scope: Any, path: String): Any{ + if (path.length > 1 && path.startsWith('"') && path.endsWith('"')) { + return path.slice(1..path.length-2) + } + val asint = path.toIntOrNull() + if (asint != null) { + return asint + } + val asfloat = path.toFloatOrNull() + if (asfloat != null) { + return asfloat + } + return 5 +} + diff --git a/core/src/com/unciv/console/ConsoleScope.kt b/core/src/com/unciv/console/ConsoleScope.kt index 0111e35e96977..66bbffb0dd243 100644 --- a/core/src/com/unciv/console/ConsoleScope.kt +++ b/core/src/com/unciv/console/ConsoleScope.kt @@ -4,7 +4,7 @@ import com.unciv.logic.GameInfo import com.unciv.logic.civilization.CivilizationInfo import com.unciv.UncivGame -class ConsoleScope(val civInfo: CivilizationInfo, val gameInfo: GameInfo, val uncivGame: UncivGame) { +class ConsoleScope(var civInfo: CivilizationInfo, var gameInfo: GameInfo, var uncivGame: UncivGame) { // Holds references to all internal game data that the console has access to. // Mostly `.civInfo`/.`gameInfo`, but could be cool to E.G. allow loading and making saves through CLI/API too. // Also where to put any `PlayerAPI`, `CheatAPI`, `ModAPI`, etc. diff --git a/core/src/com/unciv/ui/console/ConsoleScreen.kt b/core/src/com/unciv/ui/console/ConsoleScreen.kt index 080c4a3d2101d..f6bbf75ca87fa 100644 --- a/core/src/com/unciv/ui/console/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/console/ConsoleScreen.kt @@ -181,8 +181,10 @@ class ConsoleScreen(val consoleState:ConsoleState, val closeAction: ()->Unit): C private fun echo(text: String) { var label = Label(text, skin) + var width = stage.width * 0.75f + label.setWidth(width) label.setWrap(true) - printHistory.add(label).left().bottom().width(stage.width*0.75f).padLeft(15f).row() + printHistory.add(label).left().bottom().width(width).padLeft(15f).row() printScroll.scrollTo(0f,0f,1f,1f) } diff --git a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt index 65ff5b9297e50..48dde7b887f83 100644 --- a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt +++ b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt @@ -255,13 +255,8 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas } // Space and N are assigned in createNextTurnButton -<<<<<<< HEAD - keyPressDispatcher[Input.Keys.GRAVE] = { game.setScreen(consoleScreen) } // CLI console - keyPressDispatcher[Input.Keys.F1] = { game.setScreen(CivilopediaScreen(gameInfo.ruleSet, this)) } -======= keyPressDispatcher[Input.Keys.GRAVE] = { consoleScreen.openConsole() } // CLI console - keyPressDispatcher[Input.Keys.F1] = { game.setScreen(CivilopediaScreen(gameInfo.ruleSet)) } ->>>>>>> b18fdd2d1 (Try to fix CLI screen key binding persistence with SomeTroglodyte's suggestion.) + keyPressDispatcher[Input.Keys.F1] = { game.setScreen(CivilopediaScreen(gameInfo.ruleSet, this)) } keyPressDispatcher['E'] = { game.setScreen(EmpireOverviewScreen(selectedCiv)) } // Empire overview last used page /* * These try to be faithful to default Civ5 key bindings as found in several places online From 8182a99088099d2296ce711fdc5c8b36f95ef176 Mon Sep 17 00:00:00 2001 From: will-ca Date: Mon, 1 Nov 2021 08:32:53 +0000 Subject: [PATCH 08/93] More on path resolution/inspection. Better autocomplete. --- core/src/com/unciv/console/ConsoleBackend.kt | 18 +++++--- .../com/unciv/console/ConsoleProtocolUtils.kt | 43 +++++++++++++++---- .../src/com/unciv/ui/console/ConsoleScreen.kt | 14 +++++- 3 files changed, 60 insertions(+), 15 deletions(-) diff --git a/core/src/com/unciv/console/ConsoleBackend.kt b/core/src/com/unciv/console/ConsoleBackend.kt index c3e49a34f7522..c745cc025af80 100644 --- a/core/src/com/unciv/console/ConsoleBackend.kt +++ b/core/src/com/unciv/console/ConsoleBackend.kt @@ -49,8 +49,8 @@ class HardcodedConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(console "cheatsoff" to "cheatsoff - Disable commands that break game rules.", "godmode" to "godmode [true|false] - Ignore many game rule restrictions. Allows instant purchase of tech, policies, buildings, tiles, and more.\n\tRun with no arguments to toggle. (Requires cheats.)", "godview" to "godview [true|false] - Make the entire map visible.\n\tRun with no arguments to toggle. (Requires cheats.)", - "inspectpath" to "", - "simulatetoturn" to "simulatetoturn - After this turn, automatically play until the turn specified by `integer`.\n\tMap view will be frozen while simulating. (Requires cheats.)", + "inspectpath" to "inspectpath - Read out the value of the Kotlin object at a given path.\n\tThe path can be a string representing any combination of property accesses, map keys, array indexes, and method calls.\ninspectpath detailed - Also print out the class name and members of the object at the given path.", + "simulatetoturn" to "simulatetoturn - After this turn, automatically play until the turn specified by .\n\tMap view will be frozen while simulating. (Requires cheats.)", "spawnbuilding" to "", "spawnunit" to "", "supercharge" to "supercharge [true|false] - Massively boost all empire growth stats.\n\tRun with no arguments to toggle. (Requires cheats.)" @@ -150,10 +150,18 @@ class HardcodedConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(console } "inspectpath" -> { if (cheats) { + val detailed = args.size > 1 && args[1] == "detailed" + val startindex = if (detailed) 2 else 1 + val path = (if (args.size > startindex) args.slice(startindex..args.size-1) else listOf()).joinToString(" ") try { - out = "${resolveInstancePath(consoleScope, parseKotlinPath(args[1]))}" - //out = "${evalKotlinPath(consoleScope, args[1])}" - } catch (e: RuntimeException) { + //var obj = resolveInstancePath(consoleScope, parseKotlinPath(path)) + var obj = evalKotlinString(consoleScope, path) + out = + if (detailed) + "Type: ${obj::class.qualifiedName}\n\nValue: ${obj}\n\nMembers: ${obj::class.members.map{it.name}}\n" + else + "${obj}" + } catch (e: Exception) { out = "Error accessing: ${e}" } } else { diff --git a/core/src/com/unciv/console/ConsoleProtocolUtils.kt b/core/src/com/unciv/console/ConsoleProtocolUtils.kt index 98b3f279376bb..de46b4b5d8004 100644 --- a/core/src/com/unciv/console/ConsoleProtocolUtils.kt +++ b/core/src/com/unciv/console/ConsoleProtocolUtils.kt @@ -13,6 +13,14 @@ fun readInstanceProperty(instance: Any, propertyName: String): R { return property.get(instance) as R } +fun readInstanceItem(instance: Any, keyOrIndex: Any): Any { + if (keyOrIndex is Int) { + return (instance as List)[keyOrIndex]!! + } else { + return (instance as Map)[keyOrIndex]!! + } +} + interface PathElementArg { val value: Any @@ -38,6 +46,7 @@ data class PathElement( val doEval: Boolean = false ) + private val brackettypes: Map = mapOf( '[' to "[]", '(' to "()" @@ -54,7 +63,7 @@ fun parseKotlinPath(text: String): Collection { var curr_name = ArrayList() var curr_brackets = "" var curr_bracketdepth = 0 - var just_closed_brackets = false + var just_closed_brackets = true for (char in text) { if (curr_bracketdepth == 0) { if (char == '.') { @@ -115,26 +124,42 @@ fun resolveInstancePath(instance: Any, path: Collection): Any { path.map({print(it);print("\n")}) for (element in path) { when (element.type) { - PathElementType.Property -> { obj = readInstanceProperty(obj, element.name) } + PathElementType.Property -> { + obj = readInstanceProperty(obj, element.name) + } + PathElementType.Key -> { + obj = readInstanceItem( + obj, + if (element.doEval) + evalKotlinString(instance, element.name) + else + element.name + ) + } + PathElementType.Call -> { + throw UnsupportedOperationException("Calls not implemented.") + } + else -> { + throw UnsupportedOperationException("Unknown path element type: ${element.type}") + } } } return obj } - -fun evalKotlinPath(scope: Any, path: String): Any{ - if (path.length > 1 && path.startsWith('"') && path.endsWith('"')) { - return path.slice(1..path.length-2) +fun evalKotlinString(scope: Any, string: String): Any{ + if (string.length > 1 && string.startsWith('"') && string.endsWith('"')) { + return string.slice(1..string.length-2) } - val asint = path.toIntOrNull() + val asint = string.toIntOrNull() if (asint != null) { return asint } - val asfloat = path.toFloatOrNull() + val asfloat = string.toFloatOrNull() if (asfloat != null) { return asfloat } - return 5 + return resolveInstancePath(scope, parseKotlinPath(string)) } diff --git a/core/src/com/unciv/ui/console/ConsoleScreen.kt b/core/src/com/unciv/ui/console/ConsoleScreen.kt index f6bbf75ca87fa..cca598302f133 100644 --- a/core/src/com/unciv/ui/console/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/console/ConsoleScreen.kt @@ -158,7 +158,8 @@ class ConsoleScreen(val consoleState:ConsoleState, val closeAction: ()->Unit): C } private fun autocomplete() { - var results = consoleState.getAutocomplete(inputField.text) + var input = inputField.text + var results = consoleState.getAutocomplete(input) if (results.isHelpText) { echo(results.helpText) return @@ -172,6 +173,17 @@ class ConsoleScreen(val consoleState:ConsoleState, val closeAction: ()->Unit): C for (m in results.matches) { echo(m) } + var minmatch = input + var chosenresult = results.matches.first({true}) + for (l in input.length..chosenresult.length) { + var longer = chosenresult.slice(0..l) + if (results.matches.all { it.startsWith(longer) }) { + minmatch = longer + } else { + break + } + } + setText(minmatch) } } From 37f618428400f72306d5f80b1a907c2dc26ea403 Mon Sep 17 00:00:00 2001 From: will-ca Date: Mon, 1 Nov 2021 09:42:15 +0000 Subject: [PATCH 09/93] Implement setting arbitrary properties by path and value. --- core/src/com/unciv/console/ConsoleBackend.kt | 22 ++++++++++- .../com/unciv/console/ConsoleProtocolUtils.kt | 39 ++++++++++++++++++- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/core/src/com/unciv/console/ConsoleBackend.kt b/core/src/com/unciv/console/ConsoleBackend.kt index c745cc025af80..603e9459c263c 100644 --- a/core/src/com/unciv/console/ConsoleBackend.kt +++ b/core/src/com/unciv/console/ConsoleBackend.kt @@ -49,7 +49,8 @@ class HardcodedConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(console "cheatsoff" to "cheatsoff - Disable commands that break game rules.", "godmode" to "godmode [true|false] - Ignore many game rule restrictions. Allows instant purchase of tech, policies, buildings, tiles, and more.\n\tRun with no arguments to toggle. (Requires cheats.)", "godview" to "godview [true|false] - Make the entire map visible.\n\tRun with no arguments to toggle. (Requires cheats.)", - "inspectpath" to "inspectpath - Read out the value of the Kotlin object at a given path.\n\tThe path can be a string representing any combination of property accesses, map keys, array indexes, and method calls.\ninspectpath detailed - Also print out the class name and members of the object at the given path.", + "inspectpath" to "inspectpath - Read out the value of the Kotlin object at a given .\n\tThe path can be a string representing any combination of property accesses, map keys, array indexes, and method calls.\ninspectpath detailed - Also print out the class name and members of the object at the given path.", + "setpath" to "setpath - Set the Kotlin property at a given to a given .\n\tThe can be a string representing any combination of property accesses, map keys, array indexes, and method calls.\n\tThe value will be resolved the same was as the path, but will be delineated by the first space character after its start.", "simulatetoturn" to "simulatetoturn - After this turn, automatically play until the turn specified by .\n\tMap view will be frozen while simulating. (Requires cheats.)", "spawnbuilding" to "", "spawnunit" to "", @@ -154,7 +155,6 @@ class HardcodedConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(console val startindex = if (detailed) 2 else 1 val path = (if (args.size > startindex) args.slice(startindex..args.size-1) else listOf()).joinToString(" ") try { - //var obj = resolveInstancePath(consoleScope, parseKotlinPath(path)) var obj = evalKotlinString(consoleScope, path) out = if (detailed) @@ -168,6 +168,24 @@ class HardcodedConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(console out = "Cheats must be enabled to use this command!" } } + "setpath" -> { + if (cheats) { + try { + val path = (if (args.size > 2) args.slice(2..args.size-1) else listOf()).joinToString(" ") + val value = evalKotlinString(consoleScope, args[1]) + var obj = setInstancePath( + consoleScope, + parseKotlinPath(path), + value + ) + out = "Set ${path} to ${value}." + } catch (e: Exception) { + out = "Error setting: ${e}" + } + } else { + out = "Cheats must be enabled to use this command!" + } + } "simulatetoturn" -> { if (cheats) { var numturn = 0 diff --git a/core/src/com/unciv/console/ConsoleProtocolUtils.kt b/core/src/com/unciv/console/ConsoleProtocolUtils.kt index de46b4b5d8004..8a232734ca44b 100644 --- a/core/src/com/unciv/console/ConsoleProtocolUtils.kt +++ b/core/src/com/unciv/console/ConsoleProtocolUtils.kt @@ -2,6 +2,7 @@ package com.unciv.console import kotlin.collections.ArrayList import kotlin.reflect.KProperty1 +import kotlin.reflect.KMutableProperty1 import java.util.* @@ -22,6 +23,13 @@ fun readInstanceItem(instance: Any, keyOrIndex: Any): Any { } +fun setInstanceProperty(instance: Any, propertyName: String, value: T): Unit { + val property = instance::class.members + .first { it.name == propertyName } as KMutableProperty1 + property.set(instance, value) +} + + interface PathElementArg { val value: Any } @@ -57,7 +65,7 @@ private val bracketmeanings: Map = mapOf( "()" to PathElementType.Call ) -fun parseKotlinPath(text: String): Collection { +fun parseKotlinPath(text: String): List { var path:MutableList = ArrayList() var curr_type = PathElementType.Property var curr_name = ArrayList() @@ -118,7 +126,7 @@ fun parseKotlinPath(text: String): Collection { } -fun resolveInstancePath(instance: Any, path: Collection): Any { +fun resolveInstancePath(instance: Any, path: List): Any { var obj = instance print("\n") path.map({print(it);print("\n")}) @@ -163,3 +171,30 @@ fun evalKotlinString(scope: Any, string: String): Any{ return resolveInstancePath(scope, parseKotlinPath(string)) } + +fun setInstancePath(instance: Any, path: List, value: Any): Unit { + val leafobj = resolveInstancePath(instance, path.slice(0..path.size-2)) + val leafelement = path[path.size - 1] + when (leafelement.type) { + PathElementType.Property -> { + setInstanceProperty(leafobj, leafelement.name, value) + } + PathElementType.Key -> { + throw UnsupportedOperationException("Keys not implemented.") + leafobj = readInstanceItem( + leafobj, + if (leafelement.doEval) + evalKotlinString(instance, leafelement.name) + else + leafelement.name + ) + } + PathElementType.Call -> { + throw UnsupportedOperationException("Cannot assign to function call.") + } + else -> { + throw UnsupportedOperationException("Unknown path element type: ${leafelement.type}") + } + } +} + From c8bb5ae7a324d527b2691c32ddb71224fbdb16be Mon Sep 17 00:00:00 2001 From: will-ca Date: Mon, 1 Nov 2021 18:49:14 +0000 Subject: [PATCH 10/93] Rename backend-y CLI stuff to "Scripting" instead of "Console". --- .../ScriptingBackend.kt} | 88 +++++++++---------- .../ScriptingProtocolUtils.kt} | 2 +- .../ScriptingScope.kt} | 6 +- .../ScriptingState.kt} | 60 ++++++------- .../src/com/unciv/ui/console/ConsoleScreen.kt | 30 +++---- .../com/unciv/ui/worldscreen/WorldScreen.kt | 8 +- 6 files changed, 97 insertions(+), 97 deletions(-) rename core/src/com/unciv/{console/ConsoleBackend.kt => scripting/ScriptingBackend.kt} (81%) rename core/src/com/unciv/{console/ConsoleProtocolUtils.kt => scripting/ScriptingProtocolUtils.kt} (99%) rename core/src/com/unciv/{console/ConsoleScope.kt => scripting/ScriptingScope.kt} (55%) rename core/src/com/unciv/{console/ConsoleState.kt => scripting/ScriptingState.kt} (63%) diff --git a/core/src/com/unciv/console/ConsoleBackend.kt b/core/src/com/unciv/scripting/ScriptingBackend.kt similarity index 81% rename from core/src/com/unciv/console/ConsoleBackend.kt rename to core/src/com/unciv/scripting/ScriptingBackend.kt index 603e9459c263c..841234ba49421 100644 --- a/core/src/com/unciv/console/ConsoleBackend.kt +++ b/core/src/com/unciv/scripting/ScriptingBackend.kt @@ -1,4 +1,4 @@ -package com.unciv.console +package com.unciv.scripting import java.util.* @@ -6,28 +6,28 @@ import java.util.* data class AutocompleteResults(val isHelpText:Boolean, val matches:List, val helpText:String) -open class ConsoleBackend(val consoleScope:ConsoleScope) { +open class ScriptingBackend(val scriptingScope:ScriptingScope) { open val displayname:String = "Dummy" - /* val consoleScope: ConsoleScope - get() = consoleState.consoleScope */ + /* val scriptingScope: ScriptingScope + get() = scriptingState.scriptingScope */ open fun motd(): String { // Message to print on launch. return "\n\nWelcome to the Unciv CLI!\nYou are currently running the dummy backend, which will echo all commands but never do anything." } - + open fun getAutocomplete(command: String): AutocompleteResults { // Return either a `List` of autocomplete matches, or a return AutocompleteResults(false, listOf(command+"_autocomplete"), "") } - + open fun exec(command: String): String { // Execute code and return output. return command } - + open fun terminate(): Boolean { // Return `true` on successful termination, `false` otherwise. return true @@ -35,10 +35,10 @@ open class ConsoleBackend(val consoleScope:ConsoleScope) { } -class HardcodedConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(consoleScope) { +class HardcodedScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend(scriptingScope) { override val displayname:String = "Hardcoded" - + val commandshelp:Map = mapOf( "help" to "help - Display all commands\nhelp - Display information on a specific command.", "countcities" to "countcities - Print out a numerical count of all cities in the current empire.", @@ -56,13 +56,13 @@ class HardcodedConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(console "spawnunit" to "", "supercharge" to "supercharge [true|false] - Massively boost all empire growth stats.\n\tRun with no arguments to toggle. (Requires cheats.)" ) - + var cheats:Boolean = false - + override fun motd(): String { return "\n\nWelcome to the hardcoded demo backend.\n\nPlease run \"help\" or press [TAB] to see a list of available commands.\nPress [TAB] at any time to see help for currently typed command.\n\nPlease note that the available commands are meant as a DEMO for the CLI." } - + fun getCommandHelpText(command: String): String { if (command in commandshelp) { return "\n${commandshelp[command]}" @@ -70,7 +70,7 @@ class HardcodedConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(console return "\nNo help entry found for command '${command}'" } } - + override fun getAutocomplete(command: String): AutocompleteResults{ if (' ' in command) { return AutocompleteResults(true, listOf(), getCommandHelpText(command.split(' ')[0])) @@ -78,7 +78,7 @@ class HardcodedConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(console return AutocompleteResults(false, commandshelp.keys.filter({ c -> c.startsWith(command) }).map({ c -> c + " " }), "") } } - + override fun exec(command: String): String { var args = command.split(' ') var out = "" @@ -91,17 +91,17 @@ class HardcodedConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(console } } "countcities" -> { - out = consoleScope.civInfo.cities.size.toString() + out = scriptingScope.civInfo.cities.size.toString() } "locatebuildings" -> { var buildingcities:List = listOf() if (args.size > 1) { var searchfor = args.slice(1..args.size-1).joinToString(" ").trim(' ') - buildingcities = consoleScope.civInfo.cities + buildingcities = scriptingScope.civInfo.cities .filter { searchfor in it.cityConstructions.builtBuildings || it.cityConstructions.builtBuildings.any({ building -> - consoleScope.gameInfo.ruleSet.buildings[building]!!.requiresResource(searchfor) + scriptingScope.gameInfo.ruleSet.buildings[building]!!.requiresResource(searchfor) }) } .map { it.name } @@ -112,14 +112,14 @@ class HardcodedConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(console var buildingcities:List = listOf() if (args.size > 1) { var searchfor = args.slice(1..args.size-1).joinToString(" ").trim(' ') - buildingcities = consoleScope.civInfo.cities + buildingcities = scriptingScope.civInfo.cities .filter { !(searchfor in it.cityConstructions.builtBuildings) } .map { it.name } } out = buildingcities.joinToString(", ") } "listcities" -> { - out = consoleScope.civInfo.cities + out = scriptingScope.civInfo.cities .map { city -> city.name } .joinToString(", ") } @@ -133,8 +133,8 @@ class HardcodedConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(console } "godmode" -> { if (cheats) { - var godmode = if (args.size > 1) args[1].toBoolean() else !(consoleScope.gameInfo.gameParameters.godMode) - consoleScope.gameInfo.gameParameters.godMode = godmode + var godmode = if (args.size > 1) args[1].toBoolean() else !(scriptingScope.gameInfo.gameParameters.godMode) + scriptingScope.gameInfo.gameParameters.godMode = godmode out = "${if (godmode) "Enabled" else "Disabled"} godmode." } else { out = "Cheats must be enabled to use this command!" @@ -142,8 +142,8 @@ class HardcodedConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(console } "godview" -> { if (cheats) { - var godview = if (args.size > 1) args[1].toBoolean() else !(consoleScope.uncivGame.viewEntireMapForDebug) - consoleScope.uncivGame.viewEntireMapForDebug = godview + var godview = if (args.size > 1) args[1].toBoolean() else !(scriptingScope.uncivGame.viewEntireMapForDebug) + scriptingScope.uncivGame.viewEntireMapForDebug = godview out = "${if (godview) "Enabled" else "Disabled"} whole map visibility." } else { out = "Cheats must be enabled to use this command!" @@ -155,7 +155,7 @@ class HardcodedConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(console val startindex = if (detailed) 2 else 1 val path = (if (args.size > startindex) args.slice(startindex..args.size-1) else listOf()).joinToString(" ") try { - var obj = evalKotlinString(consoleScope, path) + var obj = evalKotlinString(scriptingScope, path) out = if (detailed) "Type: ${obj::class.qualifiedName}\n\nValue: ${obj}\n\nMembers: ${obj::class.members.map{it.name}}\n" @@ -172,9 +172,9 @@ class HardcodedConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(console if (cheats) { try { val path = (if (args.size > 2) args.slice(2..args.size-1) else listOf()).joinToString(" ") - val value = evalKotlinString(consoleScope, args[1]) + val value = evalKotlinString(scriptingScope, args[1]) var obj = setInstancePath( - consoleScope, + scriptingScope, parseKotlinPath(path), value ) @@ -196,7 +196,7 @@ class HardcodedConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(console out += "Invalid number: ${args[1]}\n" } } - consoleScope.uncivGame.simulateUntilTurnForDebug = numturn + scriptingScope.uncivGame.simulateUntilTurnForDebug = numturn out += "Will automatically simulate game until turn ${numturn} after this turn.\nThe map will not update until completed." } else { out = "Cheats must be enabled to use this command!" @@ -204,8 +204,8 @@ class HardcodedConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(console } "supercharge" -> { if (cheats) { - var supercharge = if (args.size > 1) args[1].toBoolean() else !(consoleScope.uncivGame.superchargedForDebug) - consoleScope.uncivGame.superchargedForDebug = supercharge + var supercharge = if (args.size > 1) args[1].toBoolean() else !(scriptingScope.uncivGame.superchargedForDebug) + scriptingScope.uncivGame.superchargedForDebug = supercharge out = "${if (supercharge) "Enabled" else "Disabled"} stats supercharge." } else { out = "Cheats must be enabled to use this command!" @@ -219,7 +219,7 @@ class HardcodedConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(console } -class QjsConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(consoleScope) { +class QjsScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend(scriptingScope) { override val displayname:String = "QuickJS" override fun motd(): String { return "\n\nWelcome to the QuickJS Unciv CLI, which doesn't currently run QuickJS but might one day!" @@ -227,7 +227,7 @@ class QjsConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(consoleScope) } -class LuaConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(consoleScope) { +class LuaScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend(scriptingScope) { override val displayname:String = "Lua" override fun motd(): String { return "\n\nWelcome to the Lua Unciv CLI, which doesn't currently run Lua but might one day!" @@ -235,7 +235,7 @@ class LuaConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(consoleScope) } -class UpyConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(consoleScope) { +class UpyScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend(scriptingScope) { override val displayname:String = "MicroPython" override fun motd(): String { return "\n\nWelcome to the MicroPython Unciv CLI, which doesn't currently run MicroPython but might one day!" @@ -243,7 +243,7 @@ class UpyConsoleBackend(consoleScope:ConsoleScope): ConsoleBackend(consoleScope) } -enum class ConsoleBackendType(val displayname:String) { +enum class ScriptingBackendType(val displayname:String) { Dummy("Dummy"), Hardcoded("Hardcoded"), QuickJS("QuickJS"), @@ -252,17 +252,17 @@ enum class ConsoleBackendType(val displayname:String) { } -fun GetNamedConsoleBackend(backendtype:ConsoleBackendType, consoleScope:ConsoleScope): ConsoleBackend { - if (backendtype == ConsoleBackendType.Dummy) { - return ConsoleBackend(consoleScope) - } else if (backendtype == ConsoleBackendType.Hardcoded) { - return HardcodedConsoleBackend(consoleScope) - } else if (backendtype == ConsoleBackendType.QuickJS) { - return QjsConsoleBackend(consoleScope) - } else if (backendtype == ConsoleBackendType.Lua) { - return LuaConsoleBackend(consoleScope) - } else if (backendtype == ConsoleBackendType.MicroPython) { - return UpyConsoleBackend(consoleScope) +fun GetNamedScriptingBackend(backendtype:ScriptingBackendType, scriptingScope:ScriptingScope): ScriptingBackend { + if (backendtype == ScriptingBackendType.Dummy) { + return ScriptingBackend(scriptingScope) + } else if (backendtype == ScriptingBackendType.Hardcoded) { + return HardcodedScriptingBackend(scriptingScope) + } else if (backendtype == ScriptingBackendType.QuickJS) { + return QjsScriptingBackend(scriptingScope) + } else if (backendtype == ScriptingBackendType.Lua) { + return LuaScriptingBackend(scriptingScope) + } else if (backendtype == ScriptingBackendType.MicroPython) { + return UpyScriptingBackend(scriptingScope) } else { throw IllegalArgumentException("Unexpected backend requsted: ${backendtype.displayname}") } diff --git a/core/src/com/unciv/console/ConsoleProtocolUtils.kt b/core/src/com/unciv/scripting/ScriptingProtocolUtils.kt similarity index 99% rename from core/src/com/unciv/console/ConsoleProtocolUtils.kt rename to core/src/com/unciv/scripting/ScriptingProtocolUtils.kt index 8a232734ca44b..20bb56c6400fd 100644 --- a/core/src/com/unciv/console/ConsoleProtocolUtils.kt +++ b/core/src/com/unciv/scripting/ScriptingProtocolUtils.kt @@ -1,4 +1,4 @@ -package com.unciv.console +package com.unciv.scripting import kotlin.collections.ArrayList import kotlin.reflect.KProperty1 diff --git a/core/src/com/unciv/console/ConsoleScope.kt b/core/src/com/unciv/scripting/ScriptingScope.kt similarity index 55% rename from core/src/com/unciv/console/ConsoleScope.kt rename to core/src/com/unciv/scripting/ScriptingScope.kt index 66bbffb0dd243..9cf7c8b70dec4 100644 --- a/core/src/com/unciv/console/ConsoleScope.kt +++ b/core/src/com/unciv/scripting/ScriptingScope.kt @@ -1,12 +1,12 @@ -package com.unciv.console +package com.unciv.scripting import com.unciv.logic.GameInfo import com.unciv.logic.civilization.CivilizationInfo import com.unciv.UncivGame -class ConsoleScope(var civInfo: CivilizationInfo, var gameInfo: GameInfo, var uncivGame: UncivGame) { +class ScriptingScope(var civInfo: CivilizationInfo, var gameInfo: GameInfo, var uncivGame: UncivGame) { // Holds references to all internal game data that the console has access to. // Mostly `.civInfo`/.`gameInfo`, but could be cool to E.G. allow loading and making saves through CLI/API too. // Also where to put any `PlayerAPI`, `CheatAPI`, `ModAPI`, etc. - // For `LuaConsoleBackend`, `UpyConsoleBackend`, `QjsConsoleBackend`, etc, this should probably directly mirror the wrappers exposed to the scripting language. + // For `LuaScriptingBackend`, `UpyScriptingBackend`, `QjsScriptingBackend`, etc, this should probably directly mirror the wrappers exposed to the scripting language. } diff --git a/core/src/com/unciv/console/ConsoleState.kt b/core/src/com/unciv/scripting/ScriptingState.kt similarity index 63% rename from core/src/com/unciv/console/ConsoleState.kt rename to core/src/com/unciv/scripting/ScriptingState.kt index 88a8206190b2e..3115d555de6b3 100644 --- a/core/src/com/unciv/console/ConsoleState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -1,68 +1,68 @@ -package com.unciv.console +package com.unciv.scripting import kotlin.collections.ArrayList import kotlin.math.max import kotlin.math.min -class ConsoleState(val consoleScope: ConsoleScope){ +class ScriptingState(val scriptingScope: ScriptingScope){ + + val scriptingBackends:ArrayList = ArrayList() - val consoleBackends:ArrayList = ArrayList() - val outputHistory:ArrayList = ArrayList() // Not implemented val commandHistory:ArrayList = ArrayList() // Not implemented - + var activeBackend:Int = 0 - + var maxOutputHistory:Int = 50 var maxCommandHistory:Int = 50 - + var activeCommandHistory:Int = 0 - + init { - echo(spawnBackend(ConsoleBackendType.Dummy)) + echo(spawnBackend(ScriptingBackendType.Dummy)) } - - fun spawnBackend(backendtype: ConsoleBackendType): String { - var backend:ConsoleBackend = GetNamedConsoleBackend(backendtype, consoleScope) - consoleBackends.add(backend) - activeBackend = consoleBackends.size - 1 + + fun spawnBackend(backendtype: ScriptingBackendType): String { + var backend:ScriptingBackend = GetNamedScriptingBackend(backendtype, scriptingScope) + scriptingBackends.add(backend) + activeBackend = scriptingBackends.size - 1 return backend.motd() } - + fun switchToBackend(index: Int) { - activeBackend = max(0, min(consoleBackends.size - 1, index)) + activeBackend = max(0, min(scriptingBackends.size - 1, index)) } - + fun termBackend(index: Int) { - if (!(0 <= index && index < consoleBackends.size)) { + if (!(0 <= index && index < scriptingBackends.size)) { return // Maybe checking should be better done and unified, and raise a warning when out of bounds. } - val result = consoleBackends[index].terminate() + val result = scriptingBackends[index].terminate() if (result) { - consoleBackends.removeAt(index) - activeBackend = min(activeBackend, consoleBackends.size - 1) + scriptingBackends.removeAt(index) + activeBackend = min(activeBackend, scriptingBackends.size - 1) } } - + fun hasBackend(): Boolean { - return consoleBackends.size > 0 + return scriptingBackends.size > 0 } - - fun getActiveBackend(): ConsoleBackend { - return consoleBackends[activeBackend] + + fun getActiveBackend(): ScriptingBackend { + return scriptingBackends[activeBackend] } - + fun echo(text: String) { outputHistory.add(text) } - + fun getAutocomplete(command: String): AutocompleteResults { if (!(hasBackend())) { return AutocompleteResults(false, listOf(), "") } return getActiveBackend().getAutocomplete(command) } - + fun navigateHistory(increment: Int): String { activeCommandHistory = max(0, min(commandHistory.size, activeCommandHistory + increment)) if (activeCommandHistory <= 0) { @@ -71,7 +71,7 @@ class ConsoleState(val consoleScope: ConsoleScope){ return commandHistory[commandHistory.size - activeCommandHistory] } } - + fun exec(command: String): String { commandHistory.add(command) var out:String diff --git a/core/src/com/unciv/ui/console/ConsoleScreen.kt b/core/src/com/unciv/ui/console/ConsoleScreen.kt index cca598302f133..a983ebef6b10c 100644 --- a/core/src/com/unciv/ui/console/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/console/ConsoleScreen.kt @@ -9,14 +9,14 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.TextButton import com.badlogic.gdx.scenes.scene2d.ui.TextField import com.unciv.Constants -import com.unciv.console.ConsoleBackend -import com.unciv.console.ConsoleBackendType -import com.unciv.console.ConsoleState +import com.unciv.scripting.ScriptingBackend +import com.unciv.scripting.ScriptingBackendType +import com.unciv.scripting.ScriptingState import com.unciv.ui.utils.* import com.unciv.ui.utils.AutoScrollPane as ScrollPane -class ConsoleScreen(val consoleState:ConsoleState, val closeAction: ()->Unit): CameraStageBaseScreen() { +class ConsoleScreen(val scriptingState:ScriptingState, val closeAction: ()->Unit): CameraStageBaseScreen() { private val lineHeight = 30f @@ -44,11 +44,11 @@ class ConsoleScreen(val consoleState:ConsoleState, val closeAction: ()->Unit): C init { - backendsAdders.add("Launch new backend:".toLabel()) - for (backendtype in ConsoleBackendType.values()) { + backendsAdders.add("Launch new backend:".toLabel()).padRight(30f) + for (backendtype in ScriptingBackendType.values()) { var backendadder = backendtype.displayname.toTextButton() backendadder.onClick({ - echo(consoleState.spawnBackend(backendtype)) + echo(scriptingState.spawnBackend(backendtype)) updateRunning() }) backendsAdders.add(backendadder) @@ -121,20 +121,20 @@ class ConsoleScreen(val consoleState:ConsoleState, val closeAction: ()->Unit): C private fun updateRunning() { runningList.clearChildren() var i = 0 - for (backend in consoleState.consoleBackends) { + for (backend in scriptingState.scriptingBackends) { var button = backend.displayname.toTextButton() val index = i runningList.add(button) - if (i == consoleState.activeBackend) { + if (i == scriptingState.activeBackend) { button.color = Color.GREEN } button.onClick({ - consoleState.switchToBackend(index) + scriptingState.switchToBackend(index) updateRunning() }) var termbutton = ImageGetter.getImage("OtherIcons/Stop") termbutton.onClick({ - consoleState.termBackend(index) + scriptingState.termBackend(index) updateRunning() }) runningList.add(termbutton.surroundWithCircle(40f)).row() @@ -152,14 +152,14 @@ class ConsoleScreen(val consoleState:ConsoleState, val closeAction: ()->Unit): C } private fun echoHistory() { - for (hist in consoleState.outputHistory) { + for (hist in scriptingState.outputHistory) { echo(hist) } } private fun autocomplete() { var input = inputField.text - var results = consoleState.getAutocomplete(input) + var results = scriptingState.getAutocomplete(input) if (results.isHelpText) { echo(results.helpText) return @@ -188,7 +188,7 @@ class ConsoleScreen(val consoleState:ConsoleState, val closeAction: ()->Unit): C } private fun navigateHistory(increment:Int) { - setText(consoleState.navigateHistory(increment)) + setText(scriptingState.navigateHistory(increment)) } private fun echo(text: String) { @@ -201,7 +201,7 @@ class ConsoleScreen(val consoleState:ConsoleState, val closeAction: ()->Unit): C } private fun run() { - echo(consoleState.exec(inputField.text)) + echo(scriptingState.exec(inputField.text)) setText("") } } diff --git a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt index 48dde7b887f83..d8d508199206f 100644 --- a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt +++ b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt @@ -13,8 +13,8 @@ import com.badlogic.gdx.scenes.scene2d.ui.Button import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.TextButton import com.badlogic.gdx.utils.Align -import com.unciv.console.ConsoleScope -import com.unciv.console.ConsoleState +import com.unciv.scripting.ScriptingScope +import com.unciv.scripting.ScriptingState import com.unciv.UncivGame import com.unciv.logic.GameInfo import com.unciv.logic.GameSaver @@ -198,8 +198,8 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas shouldUpdate = true consoleScreen = ConsoleScreen( - ConsoleState( - ConsoleScope( + ScriptingState( + ScriptingScope( selectedCiv, gameInfo, UncivGame.Current From b49bec6d6e28d0133ea55b28b628a61575e96c44 Mon Sep 17 00:00:00 2001 From: will-ca Date: Tue, 2 Nov 2021 02:51:41 +0000 Subject: [PATCH 11/93] Depend on Enums more for organizing backend types. --- build.gradle.kts | 11 +-- .../com/unciv/scripting/ScriptingBackend.kt | 75 ++++++++++++------- .../src/com/unciv/scripting/ScriptingState.kt | 8 +- .../src/com/unciv/ui/console/ConsoleScreen.kt | 7 +- 4 files changed, 62 insertions(+), 39 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 39c763de734ac..b5f6c4f2ac9ac 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,10 +2,10 @@ import com.unciv.build.BuildConfig.gdxVersion import com.unciv.build.BuildConfig.roboVMVersion -// You'll still get kotlin-reflect-1.3.70.jar in your classpath, but will no longer be used -configurations.all { resolutionStrategy { - force("org.jetbrains.kotlin:kotlin-reflect:${com.unciv.build.BuildConfig.kotlinVersion}") -} } +//// You'll still get kotlin-reflect-1.3.70.jar in your classpath, but will no longer be used +//configurations.all { resolutionStrategy { +// force("org.jetbrains.kotlin:kotlin-reflect:${com.unciv.build.BuildConfig.kotlinVersion}") +//} } buildscript { @@ -62,7 +62,7 @@ project(":desktop") { dependencies { "implementation"(project(":core")) - "implementation"("org.jetbrains.kotlin:kotlin-reflect:${com.unciv.build.BuildConfig.kotlinVersion}") + "implementation"("org.jetbrains.kotlin:kotlin-reflect:${com.unciv.build.BuildConfig.kotlinVersion}") // Seem to need here as well as in `:core`, or else fails to find class def at run time. "implementation"("com.badlogicgames.gdx:gdx-backend-lwjgl3:${gdxVersion}") "implementation"("com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop") @@ -112,6 +112,7 @@ project(":core") { dependencies { "implementation"("com.badlogicgames.gdx:gdx:$gdxVersion") + "implementation"("org.jetbrains.kotlin:kotlin-reflect:${com.unciv.build.BuildConfig.kotlinVersion}") // Used for scripting API backends. } diff --git a/core/src/com/unciv/scripting/ScriptingBackend.kt b/core/src/com/unciv/scripting/ScriptingBackend.kt index 841234ba49421..f4061276b190e 100644 --- a/core/src/com/unciv/scripting/ScriptingBackend.kt +++ b/core/src/com/unciv/scripting/ScriptingBackend.kt @@ -1,17 +1,27 @@ package com.unciv.scripting +import kotlin.reflect.full.* import java.util.* data class AutocompleteResults(val isHelpText:Boolean, val matches:List, val helpText:String) +interface ScriptingBackend_metadata { + fun new(scriptingScope: ScriptingScope): ScriptingBackend + val displayname:String +} + + open class ScriptingBackend(val scriptingScope:ScriptingScope) { - open val displayname:String = "Dummy" + companion object Metadata: ScriptingBackend_metadata { + override fun new(scriptingScope: ScriptingScope) = ScriptingBackend(scriptingScope) + override val displayname:String = "Dummy" + } + val metadata: ScriptingBackend_metadata + get(): ScriptingBackend_metadata = this::class.companionObjectInstance as ScriptingBackend_metadata - /* val scriptingScope: ScriptingScope - get() = scriptingState.scriptingScope */ open fun motd(): String { // Message to print on launch. @@ -37,9 +47,12 @@ open class ScriptingBackend(val scriptingScope:ScriptingScope) { class HardcodedScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend(scriptingScope) { - override val displayname:String = "Hardcoded" + companion object Metadata: ScriptingBackend_metadata { + override fun new(scriptingScope:ScriptingScope) = HardcodedScriptingBackend(scriptingScope) + override val displayname:String = "Hardcoded" + } - val commandshelp:Map = mapOf( + val commandshelp = mapOf( "help" to "help - Display all commands\nhelp - Display information on a specific command.", "countcities" to "countcities - Print out a numerical count of all cities in the current empire.", "listcities" to "listcities - Print the names of all cities in the current empire.", @@ -173,7 +186,7 @@ class HardcodedScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend try { val path = (if (args.size > 2) args.slice(2..args.size-1) else listOf()).joinToString(" ") val value = evalKotlinString(scriptingScope, args[1]) - var obj = setInstancePath( + setInstancePath( scriptingScope, parseKotlinPath(path), value @@ -220,7 +233,12 @@ class HardcodedScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend class QjsScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend(scriptingScope) { - override val displayname:String = "QuickJS" + + companion object Metadata: ScriptingBackend_metadata { + override fun new(scriptingScope:ScriptingScope) = QjsScriptingBackend(scriptingScope) + override val displayname:String = "QuickJS" + } + override fun motd(): String { return "\n\nWelcome to the QuickJS Unciv CLI, which doesn't currently run QuickJS but might one day!" } @@ -228,7 +246,12 @@ class QjsScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend(scrip class LuaScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend(scriptingScope) { - override val displayname:String = "Lua" + + companion object Metadata: ScriptingBackend_metadata { + override fun new(scriptingScope:ScriptingScope) = LuaScriptingBackend(scriptingScope) + override val displayname:String = "Lua" + } + override fun motd(): String { return "\n\nWelcome to the Lua Unciv CLI, which doesn't currently run Lua but might one day!" } @@ -236,34 +259,28 @@ class LuaScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend(scrip class UpyScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend(scriptingScope) { - override val displayname:String = "MicroPython" + + companion object Metadata: ScriptingBackend_metadata { + override fun new(scriptingScope:ScriptingScope) = UpyScriptingBackend(scriptingScope) + override val displayname:String = "MicroPython" + } + override fun motd(): String { return "\n\nWelcome to the MicroPython Unciv CLI, which doesn't currently run MicroPython but might one day!" } } -enum class ScriptingBackendType(val displayname:String) { - Dummy("Dummy"), - Hardcoded("Hardcoded"), - QuickJS("QuickJS"), - Lua("Lua"), - MicroPython("MicroPython") +enum class ScriptingBackendType(val metadata:ScriptingBackend_metadata) { + Dummy(ScriptingBackend), + Hardcoded(HardcodedScriptingBackend), + //Reflective(), + QuickJS(QjsScriptingBackend), + Lua(LuaScriptingBackend), + MicroPython(UpyScriptingBackend) } -fun GetNamedScriptingBackend(backendtype:ScriptingBackendType, scriptingScope:ScriptingScope): ScriptingBackend { - if (backendtype == ScriptingBackendType.Dummy) { - return ScriptingBackend(scriptingScope) - } else if (backendtype == ScriptingBackendType.Hardcoded) { - return HardcodedScriptingBackend(scriptingScope) - } else if (backendtype == ScriptingBackendType.QuickJS) { - return QjsScriptingBackend(scriptingScope) - } else if (backendtype == ScriptingBackendType.Lua) { - return LuaScriptingBackend(scriptingScope) - } else if (backendtype == ScriptingBackendType.MicroPython) { - return UpyScriptingBackend(scriptingScope) - } else { - throw IllegalArgumentException("Unexpected backend requsted: ${backendtype.displayname}") - } +fun SpawnNamedScriptingBackend(backendtype:ScriptingBackendType, scriptingScope:ScriptingScope): ScriptingBackend { + return backendtype.metadata.new(scriptingScope) } diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index 3115d555de6b3..db4359f73ffe9 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -4,7 +4,7 @@ import kotlin.collections.ArrayList import kotlin.math.max import kotlin.math.min -class ScriptingState(val scriptingScope: ScriptingScope){ +class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: ScriptingBackendType? = null){ val scriptingBackends:ArrayList = ArrayList() @@ -19,11 +19,13 @@ class ScriptingState(val scriptingScope: ScriptingScope){ var activeCommandHistory:Int = 0 init { - echo(spawnBackend(ScriptingBackendType.Dummy)) + if (initialBackendType != null) { + echo(spawnBackend(initialBackendType)) + } } fun spawnBackend(backendtype: ScriptingBackendType): String { - var backend:ScriptingBackend = GetNamedScriptingBackend(backendtype, scriptingScope) + var backend:ScriptingBackend = SpawnNamedScriptingBackend(backendtype, scriptingScope) scriptingBackends.add(backend) activeBackend = scriptingBackends.size - 1 return backend.motd() diff --git a/core/src/com/unciv/ui/console/ConsoleScreen.kt b/core/src/com/unciv/ui/console/ConsoleScreen.kt index a983ebef6b10c..677a2085b9893 100644 --- a/core/src/com/unciv/ui/console/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/console/ConsoleScreen.kt @@ -46,7 +46,7 @@ class ConsoleScreen(val scriptingState:ScriptingState, val closeAction: ()->Unit backendsAdders.add("Launch new backend:".toLabel()).padRight(30f) for (backendtype in ScriptingBackendType.values()) { - var backendadder = backendtype.displayname.toTextButton() + var backendadder = backendtype.metadata.displayname.toTextButton() backendadder.onClick({ echo(scriptingState.spawnBackend(backendtype)) updateRunning() @@ -108,6 +108,9 @@ class ConsoleScreen(val scriptingState:ScriptingState, val closeAction: ()->Unit updateRunning() } + fun updateLayout() { + } + fun openConsole() { game.setScreen(this) keyPressDispatcher.install(stage) @@ -122,7 +125,7 @@ class ConsoleScreen(val scriptingState:ScriptingState, val closeAction: ()->Unit runningList.clearChildren() var i = 0 for (backend in scriptingState.scriptingBackends) { - var button = backend.displayname.toTextButton() + var button = backend.metadata.displayname.toTextButton() val index = i runningList.add(button) if (i == scriptingState.activeBackend) { From 02d490260c4a5647419381f1f105f5baa868cdd4 Mon Sep 17 00:00:00 2001 From: will-ca Date: Tue, 2 Nov 2021 03:14:06 +0000 Subject: [PATCH 12/93] Make `ScriptingScope` properties nullable. --- .../com/unciv/scripting/ScriptingBackend.kt | 80 +++++++++++-------- .../src/com/unciv/scripting/ScriptingScope.kt | 4 +- .../com/unciv/ui/worldscreen/WorldScreen.kt | 4 +- 3 files changed, 52 insertions(+), 36 deletions(-) diff --git a/core/src/com/unciv/scripting/ScriptingBackend.kt b/core/src/com/unciv/scripting/ScriptingBackend.kt index f4061276b190e..9e5bbc2a7aa02 100644 --- a/core/src/com/unciv/scripting/ScriptingBackend.kt +++ b/core/src/com/unciv/scripting/ScriptingBackend.kt @@ -94,75 +94,86 @@ class HardcodedScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend override fun exec(command: String): String { var args = command.split(' ') - var out = "" + var out = "\n> ${command}\n" + fun appendOut(text: String) { + out += text + "\n" + } when (args[0]) { "help" -> { if (args.size > 1) { - out = getCommandHelpText(args[1]) + appendOut(getCommandHelpText(args[1])) } else { - out = commandshelp.keys.joinToString(", ") + appendOut(commandshelp.keys.joinToString(", ")) } } "countcities" -> { - out = scriptingScope.civInfo.cities.size.toString() + if (!(scriptingScope.isInGame)) { appendOut("Must be in-game for this command!"); return out } + appendOut(scriptingScope.civInfo!!.cities.size.toString()) } "locatebuildings" -> { var buildingcities:List = listOf() + if (!(scriptingScope.isInGame)) { appendOut("Must be in-game for this command!"); return out } if (args.size > 1) { var searchfor = args.slice(1..args.size-1).joinToString(" ").trim(' ') - buildingcities = scriptingScope.civInfo.cities + buildingcities = scriptingScope.civInfo!!.cities .filter { searchfor in it.cityConstructions.builtBuildings || it.cityConstructions.builtBuildings.any({ building -> - scriptingScope.gameInfo.ruleSet.buildings[building]!!.requiresResource(searchfor) + scriptingScope.gameInfo!!.ruleSet.buildings[building]!!.requiresResource(searchfor) }) } .map { it.name } } - out = buildingcities.joinToString(", ") + appendOut(buildingcities.joinToString(", ")) } "missingbuildings" -> { var buildingcities:List = listOf() + if (!(scriptingScope.isInGame)) { appendOut("Must be in-game for this command!"); return out } if (args.size > 1) { var searchfor = args.slice(1..args.size-1).joinToString(" ").trim(' ') - buildingcities = scriptingScope.civInfo.cities + buildingcities = scriptingScope.civInfo!!.cities .filter { !(searchfor in it.cityConstructions.builtBuildings) } .map { it.name } } - out = buildingcities.joinToString(", ") + appendOut(buildingcities.joinToString(", ")) } "listcities" -> { - out = scriptingScope.civInfo.cities + if (!(scriptingScope.isInGame)) { appendOut("Must be in-game for this command!"); return out } + appendOut(scriptingScope.civInfo!!.cities .map { city -> city.name } .joinToString(", ") + ) } "cheatson" -> { cheats = true - out = "Cheats enabled." + appendOut("Cheats enabled.") } "cheatsoff" -> { cheats = false - out = "Cheats disabled." + appendOut("Cheats disabled.") } "godmode" -> { + if (!(scriptingScope.isInGame)) { appendOut("Must be in-game for this command!"); return out } if (cheats) { - var godmode = if (args.size > 1) args[1].toBoolean() else !(scriptingScope.gameInfo.gameParameters.godMode) - scriptingScope.gameInfo.gameParameters.godMode = godmode - out = "${if (godmode) "Enabled" else "Disabled"} godmode." + var godmode = if (args.size > 1) args[1].toBoolean() else !(scriptingScope.gameInfo!!.gameParameters.godMode) + scriptingScope.gameInfo!!.gameParameters.godMode = godmode + appendOut("${if (godmode) "Enabled" else "Disabled"} godmode.") } else { - out = "Cheats must be enabled to use this command!" + appendOut("Cheats must be enabled to use this command!") } } "godview" -> { + if (!(scriptingScope.isInGame)) { appendOut("Must be in-game for this command!"); return out } if (cheats) { - var godview = if (args.size > 1) args[1].toBoolean() else !(scriptingScope.uncivGame.viewEntireMapForDebug) - scriptingScope.uncivGame.viewEntireMapForDebug = godview - out = "${if (godview) "Enabled" else "Disabled"} whole map visibility." + var godview = if (args.size > 1) args[1].toBoolean() else !(scriptingScope.uncivGame!!.viewEntireMapForDebug) + scriptingScope.uncivGame!!.viewEntireMapForDebug = godview + appendOut("${if (godview) "Enabled" else "Disabled"} whole map visibility.") } else { - out = "Cheats must be enabled to use this command!" + appendOut("Cheats must be enabled to use this command!") } } "inspectpath" -> { + if (!(scriptingScope.isInGame)) { appendOut("Must be in-game for this command!"); return out } if (cheats) { val detailed = args.size > 1 && args[1] == "detailed" val startindex = if (detailed) 2 else 1 @@ -175,10 +186,10 @@ class HardcodedScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend else "${obj}" } catch (e: Exception) { - out = "Error accessing: ${e}" + appendOut("Error accessing: ${e}") } } else { - out = "Cheats must be enabled to use this command!" + appendOut("Cheats must be enabled to use this command!") } } "setpath" -> { @@ -191,15 +202,16 @@ class HardcodedScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend parseKotlinPath(path), value ) - out = "Set ${path} to ${value}." + appendOut("Set ${path} to ${value}.") } catch (e: Exception) { - out = "Error setting: ${e}" + appendOut("Error setting: ${e}") } } else { - out = "Cheats must be enabled to use this command!" + appendOut("Cheats must be enabled to use this command!") } } "simulatetoturn" -> { + if (!(scriptingScope.isInGame)) { appendOut("Must be in-game for this command!"); return out } if (cheats) { var numturn = 0 if (args.size > 1) { @@ -209,25 +221,27 @@ class HardcodedScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend out += "Invalid number: ${args[1]}\n" } } - scriptingScope.uncivGame.simulateUntilTurnForDebug = numturn + scriptingScope.uncivGame!!.simulateUntilTurnForDebug = numturn out += "Will automatically simulate game until turn ${numturn} after this turn.\nThe map will not update until completed." } else { - out = "Cheats must be enabled to use this command!" + appendOut("Cheats must be enabled to use this command!") } } "supercharge" -> { + if (!(scriptingScope.isInGame)) { appendOut("Must be in-game for this command!"); return out } if (cheats) { - var supercharge = if (args.size > 1) args[1].toBoolean() else !(scriptingScope.uncivGame.superchargedForDebug) - scriptingScope.uncivGame.superchargedForDebug = supercharge - out = "${if (supercharge) "Enabled" else "Disabled"} stats supercharge." + var supercharge = if (args.size > 1) args[1].toBoolean() else !(scriptingScope.uncivGame!!.superchargedForDebug) + scriptingScope.uncivGame!!.superchargedForDebug = supercharge + appendOut("${if (supercharge) "Enabled" else "Disabled"} stats supercharge.") } else { - out = "Cheats must be enabled to use this command!" + appendOut("Cheats must be enabled to use this command!") } } else -> { - out = "The command ${args[0]} is either not known or not implemented." + appendOut("The command ${args[0]} is either not known or not implemented.") } } - return "\n> ${command}\n${out}" + return out + //return "\n> ${command}\n${out}" } } diff --git a/core/src/com/unciv/scripting/ScriptingScope.kt b/core/src/com/unciv/scripting/ScriptingScope.kt index 9cf7c8b70dec4..ac3d1c969a923 100644 --- a/core/src/com/unciv/scripting/ScriptingScope.kt +++ b/core/src/com/unciv/scripting/ScriptingScope.kt @@ -4,7 +4,9 @@ import com.unciv.logic.GameInfo import com.unciv.logic.civilization.CivilizationInfo import com.unciv.UncivGame -class ScriptingScope(var civInfo: CivilizationInfo, var gameInfo: GameInfo, var uncivGame: UncivGame) { +class ScriptingScope(var civInfo: CivilizationInfo?, var gameInfo: GameInfo?, var uncivGame: UncivGame?) { + val isInGame: Boolean + get() = (civInfo != null && gameInfo != null && uncivGame != null) // Holds references to all internal game data that the console has access to. // Mostly `.civInfo`/.`gameInfo`, but could be cool to E.G. allow loading and making saves through CLI/API too. // Also where to put any `PlayerAPI`, `CheatAPI`, `ModAPI`, etc. diff --git a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt index d8d508199206f..cfbb9733ba2aa 100644 --- a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt +++ b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt @@ -200,8 +200,8 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas consoleScreen = ConsoleScreen( ScriptingState( ScriptingScope( - selectedCiv, - gameInfo, + null, + null, UncivGame.Current ) ), From acff07d3cbaf9facd3b25bac8e42fabb9dd493c6 Mon Sep 17 00:00:00 2001 From: will-ca Date: Tue, 2 Nov 2021 04:38:42 +0000 Subject: [PATCH 13/93] Make ConsoleScreen and ScriptingState persistent per UncivGame. Enable CLI console in main menu. --- core/src/com/unciv/MainMenuScreen.kt | 22 +++++++++++++--- core/src/com/unciv/UncivGame.kt | 25 +++++++++++++++++++ .../src/com/unciv/scripting/ScriptingScope.kt | 2 +- .../src/com/unciv/scripting/ScriptingState.kt | 17 +++++++++++++ .../src/com/unciv/ui/console/ConsoleScreen.kt | 2 +- .../com/unciv/ui/worldscreen/WorldScreen.kt | 23 ++++++++--------- 6 files changed, 72 insertions(+), 19 deletions(-) diff --git a/core/src/com/unciv/MainMenuScreen.kt b/core/src/com/unciv/MainMenuScreen.kt index bbfca4afeb4b5..a6041dc76882f 100644 --- a/core/src/com/unciv/MainMenuScreen.kt +++ b/core/src/com/unciv/MainMenuScreen.kt @@ -1,6 +1,7 @@ package com.unciv import com.badlogic.gdx.Gdx +import com.badlogic.gdx.Input import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.scenes.scene2d.Touchable import com.badlogic.gdx.scenes.scene2d.actions.Actions @@ -14,9 +15,11 @@ import com.unciv.logic.map.MapSizeNew import com.unciv.logic.map.MapType import com.unciv.logic.map.mapgenerator.MapGenerator import com.unciv.models.ruleset.RulesetCache +import com.unciv.scripting.ScriptingState import com.unciv.ui.MultiplayerScreen import com.unciv.ui.mapeditor.* import com.unciv.models.metadata.GameSetupInfo +import com.unciv.ui.console.ConsoleScreen import com.unciv.ui.newgamescreen.NewGameScreen import com.unciv.ui.pickerscreens.ModManagementScreen import com.unciv.ui.saves.LoadGameScreen @@ -29,6 +32,12 @@ class MainMenuScreen: BaseScreen() { private val backgroundTable = Table().apply { background=ImageGetter.getBackground(Color.WHITE) } private val singleColumn = isCrampedPortrait() + private val consoleScreen: ConsoleScreen + get() = game.consoleScreen + + private val scriptingState: ScriptingState + get() = game.scriptingState + /** Create one **Main Menu Button** including onClick/key binding * @param text The text to display on the button * @param icon The path of the icon to display on the button @@ -55,7 +64,7 @@ class MainMenuScreen: BaseScreen() { keyPressDispatcher[key] = function table.addTooltip(key, 32f) } - + table.pack() return table } @@ -147,6 +156,11 @@ class MainMenuScreen: BaseScreen() { } ExitGamePopup(this) } + + keyPressDispatcher[Input.Keys.GRAVE] = { consoleScreen.openConsole() } + consoleScreen.closeAction = { game.setScreen(this) } + scriptingState.gameInfo = null + scriptingState.civInfo = null } @@ -168,10 +182,10 @@ class MainMenuScreen: BaseScreen() { screen.game.setScreen(newMapScreen) screen.dispose() } - val newMapButton = screen.getMenuButton("New map", "OtherIcons/New", 'n', true, newMapAction) + val newMapButton = screen.getMenuButton("New map", "OtherIcons/New", 'n', true, newMapAction) newMapButton.background = tableBackground add(newMapButton).row() - keyPressDispatcher['n'] = newMapAction + keyPressDispatcher['n'] = newMapAction val loadMapAction = { val loadMapScreen = SaveAndLoadMapScreen(null, false, screen) @@ -179,7 +193,7 @@ class MainMenuScreen: BaseScreen() { screen.game.setScreen(loadMapScreen) screen.dispose() } - val loadMapButton = screen.getMenuButton("Load map", "OtherIcons/Load", 'l', true, loadMapAction) + val loadMapButton = screen.getMenuButton("Load map", "OtherIcons/Load", 'l', true, loadMapAction) loadMapButton.background = tableBackground add(loadMapButton).row() keyPressDispatcher['l'] = loadMapAction diff --git a/core/src/com/unciv/UncivGame.kt b/core/src/com/unciv/UncivGame.kt index d2230cd1ef592..8d284c36654b4 100644 --- a/core/src/com/unciv/UncivGame.kt +++ b/core/src/com/unciv/UncivGame.kt @@ -13,6 +13,9 @@ import com.unciv.models.metadata.GameSettings import com.unciv.models.ruleset.RulesetCache import com.unciv.models.tilesets.TileSetCache import com.unciv.models.translations.Translations +import com.unciv.scripting.ScriptingScope +import com.unciv.scripting.ScriptingState +import com.unciv.ui.console.ConsoleScreen import com.unciv.ui.LanguagePickerScreen import com.unciv.ui.audio.MusicController import com.unciv.ui.audio.MusicMood @@ -71,6 +74,9 @@ class UncivGame(parameters: UncivGameParameters) : Game() { val translations = Translations() + lateinit var scriptingState: ScriptingState + lateinit var consoleScreen: ConsoleScreen + override fun create() { Gdx.input.setCatchKey(Input.Keys.BACK, true) if (Gdx.app.type != Application.ApplicationType.Desktop) { @@ -129,6 +135,8 @@ class UncivGame(parameters: UncivGameParameters) : Game() { } } crashController = CrashController.Impl(crashReportSender) + + createScripting() } fun loadGame(gameInfo: GameInfo) { @@ -146,6 +154,23 @@ class UncivGame(parameters: UncivGameParameters) : Game() { } } + fun createScripting() { + + scriptingState = ScriptingState( + ScriptingScope( + null, + null, + this + ) + ) + + consoleScreen = ConsoleScreen( + scriptingState, + { } + ) + + } + fun setScreen(screen: BaseScreen) { Gdx.input.inputProcessor = screen.stage super.setScreen(screen) diff --git a/core/src/com/unciv/scripting/ScriptingScope.kt b/core/src/com/unciv/scripting/ScriptingScope.kt index ac3d1c969a923..55d5ab6ea9a24 100644 --- a/core/src/com/unciv/scripting/ScriptingScope.kt +++ b/core/src/com/unciv/scripting/ScriptingScope.kt @@ -10,5 +10,5 @@ class ScriptingScope(var civInfo: CivilizationInfo?, var gameInfo: GameInfo?, va // Holds references to all internal game data that the console has access to. // Mostly `.civInfo`/.`gameInfo`, but could be cool to E.G. allow loading and making saves through CLI/API too. // Also where to put any `PlayerAPI`, `CheatAPI`, `ModAPI`, etc. - // For `LuaScriptingBackend`, `UpyScriptingBackend`, `QjsScriptingBackend`, etc, this should probably directly mirror the wrappers exposed to the scripting language. + // For `LuaScriptingBackend`, `UpyScriptingBackend`, `QjsScriptingBackend`, etc, the hierarchy of data under this class definition should probably directly mirror the wrappers in the namespace exposed to the scripting language. } diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index db4359f73ffe9..daeb606fef5a4 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -1,5 +1,8 @@ package com.unciv.scripting +import com.unciv.logic.GameInfo +import com.unciv.logic.civilization.CivilizationInfo +import com.unciv.UncivGame import kotlin.collections.ArrayList import kotlin.math.max import kotlin.math.min @@ -18,6 +21,20 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr var activeCommandHistory:Int = 0 + + var civInfo: CivilizationInfo? + get() = scriptingScope.civInfo + set(value) { scriptingScope.civInfo = value } + + var gameInfo: GameInfo? + get() = scriptingScope.gameInfo + set(value) { scriptingScope.gameInfo = value } + + var uncivGame: UncivGame? + get() = scriptingScope.uncivGame + set(value) { scriptingScope.uncivGame = value } + + init { if (initialBackendType != null) { echo(spawnBackend(initialBackendType)) diff --git a/core/src/com/unciv/ui/console/ConsoleScreen.kt b/core/src/com/unciv/ui/console/ConsoleScreen.kt index 677a2085b9893..c970aa879a651 100644 --- a/core/src/com/unciv/ui/console/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/console/ConsoleScreen.kt @@ -16,7 +16,7 @@ import com.unciv.ui.utils.* import com.unciv.ui.utils.AutoScrollPane as ScrollPane -class ConsoleScreen(val scriptingState:ScriptingState, val closeAction: ()->Unit): CameraStageBaseScreen() { +class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Unit): CameraStageBaseScreen() { private val lineHeight = 30f diff --git a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt index cfbb9733ba2aa..0bfcb3e5f65d1 100644 --- a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt +++ b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt @@ -13,8 +13,6 @@ import com.badlogic.gdx.scenes.scene2d.ui.Button import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.TextButton import com.badlogic.gdx.utils.Align -import com.unciv.scripting.ScriptingScope -import com.unciv.scripting.ScriptingState import com.unciv.UncivGame import com.unciv.logic.GameInfo import com.unciv.logic.GameSaver @@ -27,6 +25,7 @@ import com.unciv.models.Tutorial import com.unciv.models.UncivSound import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.translations.tr +import com.unciv.scripting.ScriptingState import com.unciv.ui.cityscreen.CityScreen import com.unciv.ui.civilopedia.CivilopediaScreen import com.unciv.ui.console.ConsoleScreen @@ -89,7 +88,11 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas private val notificationsScroll: NotificationsScroll var shouldUpdate = false - private var consoleScreen: ConsoleScreen + private val consoleScreen: ConsoleScreen + get() = game.consoleScreen + + private val scriptingState: ScriptingState + get() = game.scriptingState companion object { /** Switch for console logging of next turn duration */ @@ -197,16 +200,10 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas // know what the viewing civ is. shouldUpdate = true - consoleScreen = ConsoleScreen( - ScriptingState( - ScriptingScope( - null, - null, - UncivGame.Current - ) - ), - { game.setWorldScreen() } - ) + consoleScreen.closeAction = { game.setWorldScreen() } + scriptingState.gameInfo = gameInfo + scriptingState.civInfo = selectedCiv + } private fun stopMultiPlayerRefresher() { From 4d7c602c4b6969e2eecfcac7628af6707f3dd118 Mon Sep 17 00:00:00 2001 From: will-ca Date: Tue, 2 Nov 2021 05:10:18 +0000 Subject: [PATCH 14/93] Remake `ConsoleScreen()` but keep `ScriptingState()` on window resize. --- .../src/com/unciv/ui/console/ConsoleScreen.kt | 35 ++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/core/src/com/unciv/ui/console/ConsoleScreen.kt b/core/src/com/unciv/ui/console/ConsoleScreen.kt index c970aa879a651..718d6e8a5c434 100644 --- a/core/src/com/unciv/ui/console/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/console/ConsoleScreen.kt @@ -42,6 +42,9 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un private val downButton: Image = ImageGetter.getImage("OtherIcons/Down") private val runButton: TextButton = "ENTER".toTextButton() + private val layoutUpdators = ArrayList<() -> Unit>() + private var isOpen = false + init { backendsAdders.add("Launch new backend:".toLabel()).padRight(30f) @@ -57,7 +60,8 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un backendsAdders.left() - topBar.add(backendsScroll).minWidth(stage.width - closeButton.getPrefWidth()) + val cell_backendsScroll = topBar.add(backendsScroll) + layoutUpdators.add( { cell_backendsScroll.minWidth(stage.width - closeButton.getPrefWidth()) } ) topBar.add(closeButton) printHistory.left() @@ -75,14 +79,19 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un inputControls.add(downButton.surroundWithCircle(40f)) inputControls.add(runButton) - inputBar.add(inputField).minWidth(stage.width - inputControls.getPrefWidth()) + val cell_inputField = inputBar.add(inputField) + layoutUpdators.add( { cell_inputField.minWidth(stage.width - inputControls.getPrefWidth()) } ) inputBar.add(inputControls) - layoutTable.setSize(stage.width, stage.height) + layoutUpdators.add( { layoutTable.setSize(stage.width, stage.height) } ) - layoutTable.add(topBar).minWidth(stage.width).row() + val cell_topBar = layoutTable.add(topBar) + layoutUpdators.add( { cell_topBar.minWidth(stage.width) } ) + cell_topBar.row() - layoutTable.add(middleSplit).minWidth(stage.width).minHeight(stage.height - topBar.getPrefHeight() - inputBar.getPrefHeight()).row() + val cell_middleSplit = layoutTable.add(middleSplit) + layoutUpdators.add( { cell_middleSplit.minWidth(stage.width).minHeight(stage.height - topBar.getPrefHeight() - inputBar.getPrefHeight()) } ) + cell_middleSplit.row() layoutTable.add(inputBar) @@ -101,6 +110,8 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un onBackButtonClicked({ closeConsole() }) closeButton.onClick({ closeConsole() }) + updateLayout() + stage.addActor(layoutTable) echoHistory() @@ -109,16 +120,21 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un } fun updateLayout() { + for (func in layoutUpdators) { + func() + } } fun openConsole() { game.setScreen(this) keyPressDispatcher.install(stage) + this.isOpen = true } fun closeConsole() { closeAction() keyPressDispatcher.uninstall() + this.isOpen = false } private fun updateRunning() { @@ -207,6 +223,15 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un echo(scriptingState.exec(inputField.text)) setText("") } + + override fun resize(width: Int, height: Int) { + if (stage.viewport.screenWidth != width || stage.viewport.screenHeight != height) { // Right. Actually resizing seems painful. + game.consoleScreen = ConsoleScreen(scriptingState, closeAction) + if (isOpen) { + game.consoleScreen.openConsole() + } + } + } } // Screen, or widget? Screen would create whole new context for hotkeys and such, I think? Widget would be nice for From 405c66b357a0019b59d2462ab032640ae9546566 Mon Sep 17 00:00:00 2001 From: will-ca Date: Tue, 2 Nov 2021 05:23:04 +0000 Subject: [PATCH 15/93] Include Python IPC experiment. --- .../scripting/_experiments/python_backend.kt | 0 .../scripting/_experiments/pythonmain.py | 181 ++++++++++++++++++ .../src/com/unciv/ui/console/ConsoleScreen.kt | 2 +- 3 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 core/src/com/unciv/scripting/_experiments/python_backend.kt create mode 100644 core/src/com/unciv/scripting/_experiments/pythonmain.py diff --git a/core/src/com/unciv/scripting/_experiments/python_backend.kt b/core/src/com/unciv/scripting/_experiments/python_backend.kt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/core/src/com/unciv/scripting/_experiments/pythonmain.py b/core/src/com/unciv/scripting/_experiments/pythonmain.py new file mode 100644 index 0000000000000..f478c09cbd7f6 --- /dev/null +++ b/core/src/com/unciv/scripting/_experiments/pythonmain.py @@ -0,0 +1,181 @@ +import sys, json + + +def ResolvePath(path, scope): + return eval(path, scope, scope) + raise NotImplementedError() + + +class AutoCompleteManager: + def __init__(self, scope=None): + self.scope = globals() if scope is None else scope + def Evaled(self, path): + return eval(path, self.scope, self.scope) + def GetAutocomplete(self, command): + """Return either a sequence of full autocomplete matches or a help string for a given command.""" + try: + if ')' in command: + return () + attrbase, attrjoin, attrleaf = command.rpartition('.') + if '(' in attrleaf: + functionbase, functionjoin, functionleaf = attrleaf.rpartition('(') + fbase = attrbase + attrjoin + functionbase + fobj = self.Evaled(fbase) + return '\n'.join((fbase+"(", str(type(fobj)), getattr(fobj, '__doc__', '') or "No documentation available.")) + elif '[' in attrleaf: + keybase, keyjoin, keyleaf = attrleaf.rpartition('[') + kbase = attrbase + attrjoin + keybase + kbaseobj = self.Evaled(kbase) + if hasattr(kbaseobj, 'keys'): + if not keyleaf: + return tuple(kbase+keyjoin+repr(k)+']' for k in kbaseobj.keys()) + if keyleaf[-1] == ']': + return () + quote = keyleaf[0] + kleaf = keyleaf[1:] + if quote in '\'"': + return tuple(kbase+keyjoin+quote+k+quote+']' for k in kbaseobj.keys() if k.startswith(kleaf)) + return tuple(kbase+keyjoin+n+']' for n in self.GetAutocomplete(keyleaf)) + else: + return tuple([attrbase+attrjoin+a for a in (dir(self.Evaled(attrbase)) if attrbase else self.scope) if a.startswith(attrleaf)]) + except (NameError, AttributeError, KeyError, IndexError, SyntaxError) as e: + return "No autocompletion found: "+repr(e) + + + + +class ForeignError(RuntimeError): + pass + +class ForeignPacket: + def __init__(self, action, identifier, data): + self.action = action + self.identifier = identifier + self.data = data + def __repr__(self): + return self.__class__.__name__+"(**"+str(self.as_dict())+")" + @classmethod + def deserialized(cls, serialized): + return cls(**json.loads(serialized)) + def enforce_type(self, expect_action=None, expect_identifier=None): + if expect_action is not None and self.action != expect_action: + raise ForeignError("Expected foreign data of action "+repr(expect_action)+", got "+repr(self)+".") + if expect_identifier is not None and self.identifier != expect_identifier: + raise ForeignError("Expected foreign data with identifier "+repr(expect_identifier)+", got "+repr(self)+".") + return self + def as_dict(self): + return { + 'action': self.action, + 'identifier': self.identifier, + 'data': self.data + } + def serialized(self): + return json.dumps(self.as_dict()) + + +class ForeignActionManager: + def __init__(self, sender=None, receiver=None): + if sender is not None: + self.sender = sender + if receiver is not None: + self.receiver = receiver + def sender(self, message): + try: + print(message, file=sys.stdout, flush=True) + except TypeError: + #No flush on `micropython`. + print(message, file=sys.stdout) + def receiver(self): + return sys.stdin.readline() +# def EncodeForeignAction(self, action, identifier=None, **data): +# return ForeignPacket( +# action = action, +# identifier = identifier, +# data = data +# ).serialized() + +class ForeignActionSender(ForeignActionManager): + def MakeUniqueID(self): + return 4 # Chosen by fair dice roll. Guaranteed to be random. + def SendForeignCall(self, path, args, kwargs): + identifier = self.MakeUniqueID() + self.sender(ForeignPacket('call', identifier, {'path':path, 'args':args, 'kwargs':kwargs}).serialized()) + return ForeignPacket.deserialized(self.receiver()).enforce_type('call_response', identifier).data + def SendForeignRead(self, path): + identifier = self.MakeUniqueID() + self.sender(ForeignPacket('read', identifier, {'path':path}).serialized()) + return ForeignPacket.deserialized(self.receiver()).enforce_type('read_response', identifier).data + def SendForeignAssign(self, path, value): + identifier = self.MakeUniqueID() + self.sender(ForeignPacket('assign', identifier, {'path':path, 'value':value}).serialized()) + return ForeignPacket.deserialized(self.receiver()).enforce_type('assign_response', identifier).data + def MakeForeignFunction(self, callpath, callargs): + pass + +class ForeignActionReceiver(ForeignActionManager): + def __init__(self, sender=None, receiver=None, scope=None): + ForeignActionManager.__init__(self, sender=sender, receiver=receiver) + self.scope = globals() if scope is None else scope + def foreignCallEvaluator(func): + def _foreignCallEvaluator(self, *args, **kwargs): + try: + pass + except: + pass + def ResolvePath(self, path): + return eval(path, self.scope, self.scope) + # NOTE: This should be replaced with recursive parsing into `getattr()` and `[]`/`.__getitem__`/`.__getindex__` if arbitrary code has the chance to be passed. + def EvalForeignCall(self, packet): + packet.enforce_type('call') + return self.ResolvePath(packet.data['path'])(*packet.data['args'], **packet.data['kwargs']) + def EvalForeignRead(self, packet): + packet.enforce_type('read') + return self.ResolvePath(packet.data['path']) + def ExecForeignAssign(self, packet): + packet.enforce_type('assign') + path = packet.data['path'] + value = packet.data['value'] + if path[-1] == ']': + spath = path.rpartition('[') + self.ResolvePath(spath[0])[spath[-1][:-1]] = value + else: + spath = path.rpartition('.') + if spath[0]: + setattr(self.ResolvePath(spath[0]), spath[-1], value) + else: + self.scope[spath[-1]] = value + def RespondForeignAction(self, request): + decoded = ForeignPacket.deserialized(request) + action = decoded.action + raction = None + rdata = None + if action == 'call': + rdata = self.EvalForeignCall(decoded) + raction = 'call_response' + elif action == 'read': + rdata = self.EvalForeignRead(decoded) + raction = 'read_response' + elif action == 'assign': + rdata = self.ExecForeignAssign(decoded) + raction = 'assign_response' + else: + raise ForeignError("Unknown action type for foreign action request: " + repr(decoded)) + self.sender(ForeignPacket(raction, decoded.identifier, rdata).serialized()) + def AwaitForeignAction(self): + self.RespondForeignAction(self.receiver()) + def ForeignREPL(self): + while True: + self.AwaitForeignAction() + + +class ForeignActionAutocompleter(ForeignActionManager): + pass + + +class ForeignActionTransceiver(ForeignActionReceiver, ForeignActionSender): + pass + + +#python | micropython -c "import sys; [print('FMF: '+sys.stdin.readline()) for i in range(10)]" +#python3 -ic 'from CommTest import *; t=ForeignActionTransceiver()' | micropython -i -c 'from CommTest import *; t=ForeignActionTransceiver(); t.ForeignREPL()' +#t.SendForeignRead("ForeignActionTransceiver.__name__") diff --git a/core/src/com/unciv/ui/console/ConsoleScreen.kt b/core/src/com/unciv/ui/console/ConsoleScreen.kt index 718d6e8a5c434..eb01b822d61f3 100644 --- a/core/src/com/unciv/ui/console/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/console/ConsoleScreen.kt @@ -234,4 +234,4 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un } } -// Screen, or widget? Screen would create whole new context for hotkeys and such, I think? Widget would be nice for +// Screen, widget, or popup? From 1a2c5ea4e4c2f6d936d5204d91f5aca56f900e72 Mon Sep 17 00:00:00 2001 From: will-ca Date: Tue, 2 Nov 2021 23:41:15 +0000 Subject: [PATCH 16/93] Add parsing reflective console backend. --- core/src/com/unciv/MainMenuScreen.kt | 1 + core/src/com/unciv/UncivGame.kt | 3 +- .../com/unciv/scripting/ScriptingBackend.kt | 117 +++++++++++++++--- .../unciv/scripting/ScriptingProtocolUtils.kt | 69 +++++++---- .../src/com/unciv/scripting/ScriptingScope.kt | 9 +- .../src/com/unciv/scripting/ScriptingState.kt | 15 ++- .../com/unciv/ui/worldscreen/WorldScreen.kt | 1 + 7 files changed, 169 insertions(+), 46 deletions(-) diff --git a/core/src/com/unciv/MainMenuScreen.kt b/core/src/com/unciv/MainMenuScreen.kt index a6041dc76882f..810a2e96d7928 100644 --- a/core/src/com/unciv/MainMenuScreen.kt +++ b/core/src/com/unciv/MainMenuScreen.kt @@ -161,6 +161,7 @@ class MainMenuScreen: BaseScreen() { consoleScreen.closeAction = { game.setScreen(this) } scriptingState.gameInfo = null scriptingState.civInfo = null + scriptingState.worldScreen = null } diff --git a/core/src/com/unciv/UncivGame.kt b/core/src/com/unciv/UncivGame.kt index 8d284c36654b4..11eced5be2dfb 100644 --- a/core/src/com/unciv/UncivGame.kt +++ b/core/src/com/unciv/UncivGame.kt @@ -160,7 +160,8 @@ class UncivGame(parameters: UncivGameParameters) : Game() { ScriptingScope( null, null, - this + this, + null ) ) diff --git a/core/src/com/unciv/scripting/ScriptingBackend.kt b/core/src/com/unciv/scripting/ScriptingBackend.kt index 9e5bbc2a7aa02..1bd3831ba37df 100644 --- a/core/src/com/unciv/scripting/ScriptingBackend.kt +++ b/core/src/com/unciv/scripting/ScriptingBackend.kt @@ -4,7 +4,7 @@ import kotlin.reflect.full.* import java.util.* -data class AutocompleteResults(val isHelpText:Boolean, val matches:List, val helpText:String) +data class AutocompleteResults(val matches:List, val isHelpText:Boolean = false, val helpText:String = "") interface ScriptingBackend_metadata { @@ -25,12 +25,12 @@ open class ScriptingBackend(val scriptingScope:ScriptingScope) { open fun motd(): String { // Message to print on launch. - return "\n\nWelcome to the Unciv CLI!\nYou are currently running the dummy backend, which will echo all commands but never do anything." + return "\n\nWelcome to the Unciv CLI!\nYou are currently running the dummy backend, which will echo all commands but never do anything.\n" } open fun getAutocomplete(command: String): AutocompleteResults { // Return either a `List` of autocomplete matches, or a - return AutocompleteResults(false, listOf(command+"_autocomplete"), "") + return AutocompleteResults(listOf(command+"_autocomplete")) } open fun exec(command: String): String { @@ -73,7 +73,7 @@ class HardcodedScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend var cheats:Boolean = false override fun motd(): String { - return "\n\nWelcome to the hardcoded demo backend.\n\nPlease run \"help\" or press [TAB] to see a list of available commands.\nPress [TAB] at any time to see help for currently typed command.\n\nPlease note that the available commands are meant as a DEMO for the CLI." + return "\n\nWelcome to the hardcoded demo backend.\n\nPlease run \"help\" or press [TAB] to see a list of available commands.\nPress [TAB] at any time to see help for currently typed command.\n\nPlease note that the available commands are meant as a DEMO for the CLI.\n" } fun getCommandHelpText(command: String): String { @@ -86,9 +86,9 @@ class HardcodedScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend override fun getAutocomplete(command: String): AutocompleteResults{ if (' ' in command) { - return AutocompleteResults(true, listOf(), getCommandHelpText(command.split(' ')[0])) + return AutocompleteResults(listOf(), true, getCommandHelpText(command.split(' ')[0])) } else { - return AutocompleteResults(false, commandshelp.keys.filter({ c -> c.startsWith(command) }).map({ c -> c + " " }), "") + return AutocompleteResults(commandshelp.keys.filter({ c -> c.startsWith(command) }).map({ c -> c + " " })) } } @@ -180,11 +180,13 @@ class HardcodedScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend val path = (if (args.size > startindex) args.slice(startindex..args.size-1) else listOf()).joinToString(" ") try { var obj = evalKotlinString(scriptingScope, path) - out = + val isnull = obj == null + appendOut( if (detailed) - "Type: ${obj::class.qualifiedName}\n\nValue: ${obj}\n\nMembers: ${obj::class.members.map{it.name}}\n" + "Type: ${if (isnull) null else obj!!::class.qualifiedName}\n\nValue: ${obj}\n\nMembers: ${if (isnull) null else obj!!::class.members.map{it.name}}\n" else "${obj}" + ) } catch (e: Exception) { appendOut("Error accessing: ${e}") } @@ -218,11 +220,11 @@ class HardcodedScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend try { numturn = args[1].toInt() } catch (e: NumberFormatException) { - out += "Invalid number: ${args[1]}\n" + appendOut("Invalid number: ${args[1]}\n") } } scriptingScope.uncivGame!!.simulateUntilTurnForDebug = numturn - out += "Will automatically simulate game until turn ${numturn} after this turn.\nThe map will not update until completed." + appendOut("Will automatically simulate game until turn ${numturn} after this turn.\nThe map will not update until completed.") } else { appendOut("Cheats must be enabled to use this command!") } @@ -246,6 +248,93 @@ class HardcodedScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend } +class ReflectiveScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend(scriptingScope) { + + companion object Metadata: ScriptingBackend_metadata { + override fun new(scriptingScope:ScriptingScope) = ReflectiveScriptingBackend(scriptingScope) + override val displayname:String = "Reflective" + } + + private val commandparams = mapOf("get" to 1, "set" to 2, "typeof" to 1) //showprivates? + private val examples = listOf( + "get gameInfo.civilizations[1].policies.adoptedPolicies", + "set 5 civInfo.tech.freeTechs", + "set 1 civInfo.cities[0].health", + "set 5 gameInfo.turns", + "get civInfo.addGold(1337)", + "set 2000 worldScreen.bottomUnitTable.selectedUnit.promotions.XP", + "get worldScreen.bottomUnitTable.selectedCity.population.setPopulation(25)", + "set \"Cattle\" worldScreen.mapHolder.selectedTile.resource", + "set \"Krakatoa\" worldScreen.mapHolder.selectedTile.naturalWonder" + ) + + override fun motd(): String { + return "\n\nWelcome to the reflective Unciv CLI backend.\n\nCommands you enter will be parsed as a path consisting of property reads, key and index accesses, function calls, and string, numeric, boolean, and null literals.\nKeys, indices, and function arguments are parsed recursively.\nProperties can be both read from and written to.\n\nExamples:\n${examples.map({"> ${it}"}).joinToString("\n")}\n\nPress [TAB] at any time to trigger autocompletion for all known leaf names at the currently entered path.\n" + } + + override fun getAutocomplete(command: String): AutocompleteResults { + try { + var comm = commandparams.keys.find{ command.startsWith(it+" ") } + if (comm != null) { + val params = command.drop(comm.length+1).split(' ', limit=commandparams[comm]!!) + val workingcode = params[params.size-1] + val workingpath = parseKotlinPath(workingcode) + if (workingpath.any{ it.type == PathElementType.Call }) { + return AutocompleteResults(listOf(), true, "No autocomplete available for function calls.") + } + val leafname = if (workingpath.size > 0) workingpath[workingpath.size - 1].name else "" + val prefix = command.dropLast(leafname.length) + val branchobj = resolveInstancePath(scriptingScope, workingpath.slice(0..workingpath.size-2)) + return AutocompleteResults( + branchobj!!::class.members + .map{ it.name } + .filter{ it.startsWith(leafname) } + .map{ prefix + it } + ) + } + return AutocompleteResults(commandparams.keys.filter{ it.startsWith(command) }.map{ it+" " }) + } catch (e: Exception) { + return AutocompleteResults(listOf(), true, "Could not get autocompletion: ${e}") + } + } + + override fun exec(command: String): String{ + var parts = command.split(' ', limit=2) + var out = "\n> ${command}\n" + fun appendOut(text: String) { + out += text + "\n" + } + try { + when (parts[0]) { + "get" -> { + appendOut("${evalKotlinString(scriptingScope, parts[1])}") + } + "set" -> { + var setparts = parts[1].split(' ', limit=2) + var value = evalKotlinString(scriptingScope, setparts[0]) + setInstancePath( + scriptingScope, + parseKotlinPath(setparts[1]), + value + ) + appendOut("Set ${setparts[1]} to ${value}") + } + "typeof" -> { + var obj = evalKotlinString(scriptingScope, parts[1]) + appendOut("${if (obj == null) null else obj!!::class.qualifiedName}") + } + else -> { + appendOut("Unknown command: ${parts[0]}") + } + } + } catch (e: Exception) { + appendOut("Error evaluating command: ${e}") + } + return out + } +} + + class QjsScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend(scriptingScope) { companion object Metadata: ScriptingBackend_metadata { @@ -254,7 +343,7 @@ class QjsScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend(scrip } override fun motd(): String { - return "\n\nWelcome to the QuickJS Unciv CLI, which doesn't currently run QuickJS but might one day!" + return "\n\nWelcome to the QuickJS Unciv CLI, which doesn't currently run QuickJS but might one day!\n" } } @@ -267,7 +356,7 @@ class LuaScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend(scrip } override fun motd(): String { - return "\n\nWelcome to the Lua Unciv CLI, which doesn't currently run Lua but might one day!" + return "\n\nWelcome to the Lua Unciv CLI, which doesn't currently run Lua but might one day!\n" } } @@ -280,7 +369,7 @@ class UpyScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend(scrip } override fun motd(): String { - return "\n\nWelcome to the MicroPython Unciv CLI, which doesn't currently run MicroPython but might one day!" + return "\n\nWelcome to the MicroPython Unciv CLI, which doesn't currently run MicroPython but might one day!\n" } } @@ -288,7 +377,7 @@ class UpyScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend(scrip enum class ScriptingBackendType(val metadata:ScriptingBackend_metadata) { Dummy(ScriptingBackend), Hardcoded(HardcodedScriptingBackend), - //Reflective(), + Reflective(ReflectiveScriptingBackend), QuickJS(QjsScriptingBackend), Lua(LuaScriptingBackend), MicroPython(UpyScriptingBackend) diff --git a/core/src/com/unciv/scripting/ScriptingProtocolUtils.kt b/core/src/com/unciv/scripting/ScriptingProtocolUtils.kt index 20bb56c6400fd..b85dca8137aaf 100644 --- a/core/src/com/unciv/scripting/ScriptingProtocolUtils.kt +++ b/core/src/com/unciv/scripting/ScriptingProtocolUtils.kt @@ -2,30 +2,31 @@ package com.unciv.scripting import kotlin.collections.ArrayList import kotlin.reflect.KProperty1 +import kotlin.reflect.KFunction import kotlin.reflect.KMutableProperty1 import java.util.* @Suppress("UNCHECKED_CAST") -fun readInstanceProperty(instance: Any, propertyName: String): R { +fun readInstanceProperty(instance: Any, propertyName: String): R? { // From https://stackoverflow.com/a/35539628/12260302 val property = instance::class.members .first { it.name == propertyName } as KProperty1 - return property.get(instance) as R + return property.get(instance) as R? } -fun readInstanceItem(instance: Any, keyOrIndex: Any): Any { +fun readInstanceItem(instance: Any, keyOrIndex: Any): Any? { if (keyOrIndex is Int) { - return (instance as List)[keyOrIndex]!! + return (instance as List)[keyOrIndex] } else { - return (instance as Map)[keyOrIndex]!! + return (instance as Map)[keyOrIndex] } } -fun setInstanceProperty(instance: Any, propertyName: String, value: T): Unit { +fun setInstanceProperty(instance: Any, propertyName: String, value: T?): Unit { val property = instance::class.members - .first { it.name == propertyName } as KMutableProperty1 + .first { it.name == propertyName } as KMutableProperty1 property.set(instance, value) } @@ -51,6 +52,8 @@ data class PathElement( val type: PathElementType, val name: String, //val args: Collection, + //For IPC with an actual interpreter, it should be possible to pass JSON arrays of basic types instead of just parsing the string. + //Mostly I'm not sure how and where to cleanly determine whether to use the args field, or parse the string field. val doEval: Boolean = false ) @@ -79,6 +82,7 @@ fun parseKotlinPath(text: String): List { path.add(PathElement(PathElementType.Property, curr_name.joinToString(""))) } curr_name.clear() + just_closed_brackets = false continue } if (char in brackettypes) { @@ -88,13 +92,12 @@ fun parseKotlinPath(text: String): List { curr_name.clear() curr_brackets = brackettypes[char]!! curr_bracketdepth += 1 + just_closed_brackets = false continue } curr_name.add(char) } - if (just_closed_brackets) { - just_closed_brackets = false - } + just_closed_brackets = false if (curr_bracketdepth > 0) { if (char == curr_brackets[1]) { curr_bracketdepth -= 1 @@ -126,20 +129,22 @@ fun parseKotlinPath(text: String): List { } -fun resolveInstancePath(instance: Any, path: List): Any { - var obj = instance - print("\n") - path.map({print(it);print("\n")}) +fun stringifyKotlinPath() { +} + + +fun resolveInstancePath(instance: Any, path: List): Any? { + var obj: Any? = instance for (element in path) { when (element.type) { PathElementType.Property -> { - obj = readInstanceProperty(obj, element.name) + obj = readInstanceProperty(obj!!, element.name) } PathElementType.Key -> { obj = readInstanceItem( - obj, + obj!!, if (element.doEval) - evalKotlinString(instance, element.name) + evalKotlinString(instance!!, element.name)!! else element.name ) @@ -156,35 +161,45 @@ fun resolveInstancePath(instance: Any, path: List): Any { } -fun evalKotlinString(scope: Any, string: String): Any{ - if (string.length > 1 && string.startsWith('"') && string.endsWith('"')) { - return string.slice(1..string.length-2) +fun evalKotlinString(scope: Any, string: String): Any? { + val trimmed = string.trim(' ') + if (trimmed == "null") { + return null + } + if (trimmed == "true") { + return true + } + if (trimmed == "false") { + return false + } + if (trimmed.length > 1 && trimmed.startsWith('"') && trimmed.endsWith('"')) { + return trimmed.slice(1..trimmed.length-2) } - val asint = string.toIntOrNull() + val asint = trimmed.toIntOrNull() if (asint != null) { return asint } - val asfloat = string.toFloatOrNull() + val asfloat = trimmed.toFloatOrNull() if (asfloat != null) { return asfloat } - return resolveInstancePath(scope, parseKotlinPath(string)) + return resolveInstancePath(scope, parseKotlinPath(trimmed)) } -fun setInstancePath(instance: Any, path: List, value: Any): Unit { +fun setInstancePath(instance: Any, path: List, value: Any?): Unit { val leafobj = resolveInstancePath(instance, path.slice(0..path.size-2)) val leafelement = path[path.size - 1] when (leafelement.type) { PathElementType.Property -> { - setInstanceProperty(leafobj, leafelement.name, value) + setInstanceProperty(leafobj!!, leafelement.name, value) } PathElementType.Key -> { throw UnsupportedOperationException("Keys not implemented.") leafobj = readInstanceItem( - leafobj, + leafobj!!, if (leafelement.doEval) - evalKotlinString(instance, leafelement.name) + evalKotlinString(instance, leafelement.name)!! else leafelement.name ) diff --git a/core/src/com/unciv/scripting/ScriptingScope.kt b/core/src/com/unciv/scripting/ScriptingScope.kt index 55d5ab6ea9a24..06e524a097498 100644 --- a/core/src/com/unciv/scripting/ScriptingScope.kt +++ b/core/src/com/unciv/scripting/ScriptingScope.kt @@ -3,12 +3,19 @@ package com.unciv.scripting import com.unciv.logic.GameInfo import com.unciv.logic.civilization.CivilizationInfo import com.unciv.UncivGame +import com.unciv.ui.worldscreen.WorldScreen -class ScriptingScope(var civInfo: CivilizationInfo?, var gameInfo: GameInfo?, var uncivGame: UncivGame?) { +class ScriptingScope( + var civInfo: CivilizationInfo?, + var gameInfo: GameInfo?, + var uncivGame: UncivGame?, + var worldScreen: WorldScreen? + ) { val isInGame: Boolean get() = (civInfo != null && gameInfo != null && uncivGame != null) // Holds references to all internal game data that the console has access to. // Mostly `.civInfo`/.`gameInfo`, but could be cool to E.G. allow loading and making saves through CLI/API too. // Also where to put any `PlayerAPI`, `CheatAPI`, `ModAPI`, etc. // For `LuaScriptingBackend`, `UpyScriptingBackend`, `QjsScriptingBackend`, etc, the hierarchy of data under this class definition should probably directly mirror the wrappers in the namespace exposed to the scripting language. + // `WorldScreen` would give access to `UnitTable.selectedUnit`, `MapHolder.selectedTile`, etc. } diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index daeb606fef5a4..0888df12304ec 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -3,6 +3,7 @@ package com.unciv.scripting import com.unciv.logic.GameInfo import com.unciv.logic.civilization.CivilizationInfo import com.unciv.UncivGame +import com.unciv.ui.worldscreen.WorldScreen import kotlin.collections.ArrayList import kotlin.math.max import kotlin.math.min @@ -34,6 +35,10 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr get() = scriptingScope.uncivGame set(value) { scriptingScope.uncivGame = value } + var worldScreen: WorldScreen? + get() = scriptingScope.worldScreen + set(value) { scriptingScope.worldScreen = value } + init { if (initialBackendType != null) { @@ -45,7 +50,9 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr var backend:ScriptingBackend = SpawnNamedScriptingBackend(backendtype, scriptingScope) scriptingBackends.add(backend) activeBackend = scriptingBackends.size - 1 - return backend.motd() + var motd = backend.motd() + echo(motd) + return motd } fun switchToBackend(index: Int) { @@ -77,7 +84,7 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr fun getAutocomplete(command: String): AutocompleteResults { if (!(hasBackend())) { - return AutocompleteResults(false, listOf(), "") + return AutocompleteResults(listOf(), false, "") } return getActiveBackend().getAutocomplete(command) } @@ -92,7 +99,9 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr } fun exec(command: String): String { - commandHistory.add(command) + if (command.length > 0) { + commandHistory.add(command) + } var out:String if (hasBackend()) { out = getActiveBackend().exec(command) diff --git a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt index 0bfcb3e5f65d1..6414b5d1782f4 100644 --- a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt +++ b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt @@ -203,6 +203,7 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas consoleScreen.closeAction = { game.setWorldScreen() } scriptingState.gameInfo = gameInfo scriptingState.civInfo = selectedCiv + scriptingState.worldScreen = this } From 5abeacd5d7dadefd007a0dfc47fd58102f023bae Mon Sep 17 00:00:00 2001 From: will-ca Date: Wed, 3 Nov 2021 01:10:57 +0000 Subject: [PATCH 17/93] Comments, apparently. --- core/src/com/unciv/scripting/ScriptingState.kt | 8 ++++---- core/src/com/unciv/ui/console/ConsoleScreen.kt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index 0888df12304ec..d62a18f3eab8d 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -12,13 +12,13 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr val scriptingBackends:ArrayList = ArrayList() - val outputHistory:ArrayList = ArrayList() // Not implemented - val commandHistory:ArrayList = ArrayList() // Not implemented + val outputHistory:ArrayList = ArrayList() + val commandHistory:ArrayList = ArrayList() var activeBackend:Int = 0 - var maxOutputHistory:Int = 50 - var maxCommandHistory:Int = 50 + var maxOutputHistory:Int = 50 // Not implemented + var maxCommandHistory:Int = 50 // Not implemented var activeCommandHistory:Int = 0 diff --git a/core/src/com/unciv/ui/console/ConsoleScreen.kt b/core/src/com/unciv/ui/console/ConsoleScreen.kt index eb01b822d61f3..ae71bbe9c09c8 100644 --- a/core/src/com/unciv/ui/console/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/console/ConsoleScreen.kt @@ -228,7 +228,7 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un if (stage.viewport.screenWidth != width || stage.viewport.screenHeight != height) { // Right. Actually resizing seems painful. game.consoleScreen = ConsoleScreen(scriptingState, closeAction) if (isOpen) { - game.consoleScreen.openConsole() + game.consoleScreen.openConsole() // If this leads to race conditions or some such due to occurring at the same time as other screens' resize methods, then probably close the ConsoleScreen() instead. } } } From 82c80751cafeff0481b9a12eaeeb3e1efcb98a6d Mon Sep 17 00:00:00 2001 From: will-ca Date: Wed, 3 Nov 2021 07:02:20 +0000 Subject: [PATCH 18/93] Options menu to enable console, defaulting to OFF. --- core/src/com/unciv/MainMenuScreen.kt | 2 +- core/src/com/unciv/UncivGame.kt | 8 +++++++- core/src/com/unciv/models/metadata/GameSettings.kt | 2 ++ core/src/com/unciv/ui/worldscreen/WorldScreen.kt | 2 +- .../src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt | 5 +++++ 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/core/src/com/unciv/MainMenuScreen.kt b/core/src/com/unciv/MainMenuScreen.kt index 810a2e96d7928..a99eada0ec7ef 100644 --- a/core/src/com/unciv/MainMenuScreen.kt +++ b/core/src/com/unciv/MainMenuScreen.kt @@ -157,7 +157,7 @@ class MainMenuScreen: BaseScreen() { ExitGamePopup(this) } - keyPressDispatcher[Input.Keys.GRAVE] = { consoleScreen.openConsole() } + keyPressDispatcher[Input.Keys.GRAVE] = { game.setConsoleScreen() } consoleScreen.closeAction = { game.setScreen(this) } scriptingState.gameInfo = null scriptingState.civInfo = null diff --git a/core/src/com/unciv/UncivGame.kt b/core/src/com/unciv/UncivGame.kt index 11eced5be2dfb..d22b92ff286e1 100644 --- a/core/src/com/unciv/UncivGame.kt +++ b/core/src/com/unciv/UncivGame.kt @@ -75,7 +75,7 @@ class UncivGame(parameters: UncivGameParameters) : Game() { val translations = Translations() lateinit var scriptingState: ScriptingState - lateinit var consoleScreen: ConsoleScreen + lateinit var consoleScreen: ConsoleScreen // Keep same ConsoleScreen() when possible, to avoid having to manually persist/restore history, input field, etc. override fun create() { Gdx.input.setCatchKey(Input.Keys.BACK, true) @@ -184,6 +184,12 @@ class UncivGame(parameters: UncivGameParameters) : Game() { Gdx.graphics.requestRendering() } + fun setConsoleScreen() { + if (settings.enableScriptingConsole) { + consoleScreen.openConsole() + } + } + // This is ALWAYS called after create() on Android - google "Android life cycle" override fun resume() { super.resume() diff --git a/core/src/com/unciv/models/metadata/GameSettings.kt b/core/src/com/unciv/models/metadata/GameSettings.kt index 130bf5af7f5ab..91f7b5df0720a 100644 --- a/core/src/com/unciv/models/metadata/GameSettings.kt +++ b/core/src/com/unciv/models/metadata/GameSettings.kt @@ -53,6 +53,8 @@ class GameSettings { var showExperimentalWorldWrap = false // We're keeping this as a config due to ANR problems on Android phones for people who don't know what they're doing :/ + var enableScriptingConsole = false + var lastOverviewPage: String = "Cities" var allowAndroidPortrait = false // Opt-in to allow Unciv to follow a screen rotation to portrait diff --git a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt index 6414b5d1782f4..5265d33750cb3 100644 --- a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt +++ b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt @@ -253,7 +253,7 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas } // Space and N are assigned in createNextTurnButton - keyPressDispatcher[Input.Keys.GRAVE] = { consoleScreen.openConsole() } // CLI console + keyPressDispatcher[Input.Keys.GRAVE] = { game.setConsoleScreen() } // CLI console keyPressDispatcher[Input.Keys.F1] = { game.setScreen(CivilopediaScreen(gameInfo.ruleSet, this)) } keyPressDispatcher['E'] = { game.setScreen(EmpireOverviewScreen(selectedCiv)) } // Empire overview last used page /* diff --git a/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt b/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt index c3f32016ace71..c056eef45de25 100644 --- a/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt +++ b/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt @@ -246,6 +246,11 @@ class OptionsPopup(val previousScreen: BaseScreen) : Popup(previousScreen) { settings.showExperimentalWorldWrap = it } + addYesNoRow("Enable scripting console\nEXPERIMENTAL - PRESS ~ TO ACTIVATE.", + settings.enableScriptingConsole) { + settings.enableScriptingConsole = it + } + if (previousScreen.game.limitOrientationsHelper != null) { addYesNoRow("Enable portrait orientation", settings.allowAndroidPortrait) { settings.allowAndroidPortrait = it From 55dac3ab382bcf830debd0e2a3421f044284da6a Mon Sep 17 00:00:00 2001 From: will-ca Date: Wed, 3 Nov 2021 18:07:23 +0000 Subject: [PATCH 19/93] Add system Python backend. --- core/src/com/unciv/MainMenuScreen.kt | 2 +- core/src/com/unciv/UncivGame.kt | 2 +- .../com/unciv/scripting/ScriptingBackend.kt | 122 +++++++++++++++--- .../unciv/scripting/ScriptingProtocolUtils.kt | 3 + .../src/com/unciv/scripting/ScriptingState.kt | 4 +- .../ConsoleScreen.kt | 35 ++++- .../com/unciv/ui/worldscreen/WorldScreen.kt | 2 +- 7 files changed, 142 insertions(+), 28 deletions(-) rename core/src/com/unciv/ui/{console => consolescreen}/ConsoleScreen.kt (84%) diff --git a/core/src/com/unciv/MainMenuScreen.kt b/core/src/com/unciv/MainMenuScreen.kt index a99eada0ec7ef..585f12ec2e66e 100644 --- a/core/src/com/unciv/MainMenuScreen.kt +++ b/core/src/com/unciv/MainMenuScreen.kt @@ -19,7 +19,7 @@ import com.unciv.scripting.ScriptingState import com.unciv.ui.MultiplayerScreen import com.unciv.ui.mapeditor.* import com.unciv.models.metadata.GameSetupInfo -import com.unciv.ui.console.ConsoleScreen +import com.unciv.ui.consolescreen.ConsoleScreen import com.unciv.ui.newgamescreen.NewGameScreen import com.unciv.ui.pickerscreens.ModManagementScreen import com.unciv.ui.saves.LoadGameScreen diff --git a/core/src/com/unciv/UncivGame.kt b/core/src/com/unciv/UncivGame.kt index d22b92ff286e1..676c08ae9c025 100644 --- a/core/src/com/unciv/UncivGame.kt +++ b/core/src/com/unciv/UncivGame.kt @@ -15,7 +15,7 @@ import com.unciv.models.tilesets.TileSetCache import com.unciv.models.translations.Translations import com.unciv.scripting.ScriptingScope import com.unciv.scripting.ScriptingState -import com.unciv.ui.console.ConsoleScreen +import com.unciv.ui.consolescreen.ConsoleScreen import com.unciv.ui.LanguagePickerScreen import com.unciv.ui.audio.MusicController import com.unciv.ui.audio.MusicMood diff --git a/core/src/com/unciv/scripting/ScriptingBackend.kt b/core/src/com/unciv/scripting/ScriptingBackend.kt index 1bd3831ba37df..0004ce8b8a6f6 100644 --- a/core/src/com/unciv/scripting/ScriptingBackend.kt +++ b/core/src/com/unciv/scripting/ScriptingBackend.kt @@ -1,6 +1,8 @@ package com.unciv.scripting import kotlin.reflect.full.* +//import kotlin.text.* +import java.io.* import java.util.* @@ -13,7 +15,7 @@ interface ScriptingBackend_metadata { } -open class ScriptingBackend(val scriptingScope:ScriptingScope) { +open class ScriptingBackend(val scriptingScope: ScriptingScope) { companion object Metadata: ScriptingBackend_metadata { override fun new(scriptingScope: ScriptingScope) = ScriptingBackend(scriptingScope) @@ -28,7 +30,7 @@ open class ScriptingBackend(val scriptingScope:ScriptingScope) { return "\n\nWelcome to the Unciv CLI!\nYou are currently running the dummy backend, which will echo all commands but never do anything.\n" } - open fun getAutocomplete(command: String): AutocompleteResults { + open fun getAutocomplete(command: String, cursorPos: Int? = null): AutocompleteResults { // Return either a `List` of autocomplete matches, or a return AutocompleteResults(listOf(command+"_autocomplete")) } @@ -45,10 +47,10 @@ open class ScriptingBackend(val scriptingScope:ScriptingScope) { } -class HardcodedScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend(scriptingScope) { +class HardcodedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackend(scriptingScope) { companion object Metadata: ScriptingBackend_metadata { - override fun new(scriptingScope:ScriptingScope) = HardcodedScriptingBackend(scriptingScope) + override fun new(scriptingScope: ScriptingScope) = HardcodedScriptingBackend(scriptingScope) override val displayname:String = "Hardcoded" } @@ -84,7 +86,7 @@ class HardcodedScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend } } - override fun getAutocomplete(command: String): AutocompleteResults{ + override fun getAutocomplete(command: String, cursorPos: Int?): AutocompleteResults{ if (' ' in command) { return AutocompleteResults(listOf(), true, getCommandHelpText(command.split(' ')[0])) } else { @@ -248,10 +250,10 @@ class HardcodedScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend } -class ReflectiveScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend(scriptingScope) { +class ReflectiveScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackend(scriptingScope) { companion object Metadata: ScriptingBackend_metadata { - override fun new(scriptingScope:ScriptingScope) = ReflectiveScriptingBackend(scriptingScope) + override fun new(scriptingScope: ScriptingScope) = ReflectiveScriptingBackend(scriptingScope) override val displayname:String = "Reflective" } @@ -272,7 +274,7 @@ class ReflectiveScriptingBackend(scriptingScope:ScriptingScope): ScriptingBacken return "\n\nWelcome to the reflective Unciv CLI backend.\n\nCommands you enter will be parsed as a path consisting of property reads, key and index accesses, function calls, and string, numeric, boolean, and null literals.\nKeys, indices, and function arguments are parsed recursively.\nProperties can be both read from and written to.\n\nExamples:\n${examples.map({"> ${it}"}).joinToString("\n")}\n\nPress [TAB] at any time to trigger autocompletion for all known leaf names at the currently entered path.\n" } - override fun getAutocomplete(command: String): AutocompleteResults { + override fun getAutocomplete(command: String, cursorPos: Int?): AutocompleteResults { try { var comm = commandparams.keys.find{ command.startsWith(it+" ") } if (comm != null) { @@ -335,10 +337,10 @@ class ReflectiveScriptingBackend(scriptingScope:ScriptingScope): ScriptingBacken } -class QjsScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend(scriptingScope) { +class QjsScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackend(scriptingScope) { companion object Metadata: ScriptingBackend_metadata { - override fun new(scriptingScope:ScriptingScope) = QjsScriptingBackend(scriptingScope) + override fun new(scriptingScope: ScriptingScope) = QjsScriptingBackend(scriptingScope) override val displayname:String = "QuickJS" } @@ -348,10 +350,10 @@ class QjsScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend(scrip } -class LuaScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend(scriptingScope) { +class LuaScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackend(scriptingScope) { companion object Metadata: ScriptingBackend_metadata { - override fun new(scriptingScope:ScriptingScope) = LuaScriptingBackend(scriptingScope) + override fun new(scriptingScope: ScriptingScope) = LuaScriptingBackend(scriptingScope) override val displayname:String = "Lua" } @@ -361,10 +363,10 @@ class LuaScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend(scrip } -class UpyScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend(scriptingScope) { +class UpyScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackend(scriptingScope) { companion object Metadata: ScriptingBackend_metadata { - override fun new(scriptingScope:ScriptingScope) = UpyScriptingBackend(scriptingScope) + override fun new(scriptingScope: ScriptingScope) = UpyScriptingBackend(scriptingScope) override val displayname:String = "MicroPython" } @@ -374,16 +376,104 @@ class UpyScriptingBackend(scriptingScope:ScriptingScope): ScriptingBackend(scrip } +class SpyScripingBackend(scriptingScope: ScriptingScope): ScriptingBackend(scriptingScope) { + + companion object Metadata: ScriptingBackend_metadata { + override fun new(scriptingScope: ScriptingScope) = SpyScripingBackend(scriptingScope) + override val displayname:String = "System Python" + } + + var pyProcess: java.lang.Process? = null + + var pyInStream: BufferedReader? = null + var pyOutStream: BufferedWriter? = null + + var pyProcessLaunchFail = "" + + init { + try { + // With pipes, Python automatically execs STDIN: `python3 -c 'print("print(5+5)")' | python3`. + // When run in interactive mode, it doesn't do this though: `python3` `echo 'print(5)\n' > /proc/$(pgrep python)/fd/0` + // For now, I'm going to have it manually loop through output to get around this: `echo -e "import sys\nwhile True: exec(sys.stdin.readline())" > pyloop.py; python3 pyloop.py` `echo 'print(5)\n' > /proc/$(pgrep python)/fd/0` + pyProcess = Runtime.getRuntime().exec(arrayOf("python3", "-u", "-X", "utf8", "-c", "import sys\nprint('sys.implementation == '+str(sys.implementation))\nwhile True:\n\tline=sys.stdin.readline()\n\ttry:\n\t\ttry:\n\t\t\tcode=compile(line, 'STDIN', 'eval')\n\t\texcept SyntaxError:\n\t\t\texec(line)\n\t\telse:\n\t\t\tprint(eval(code))\n\texcept Exception as e:\n\t\tprint(e)")) + pyInStream = BufferedReader(InputStreamReader(pyProcess!!.getInputStream())) + pyOutStream = BufferedWriter(OutputStreamWriter(pyProcess!!.getOutputStream())) + } catch (e: Exception) { + pyProcess = null + pyProcessLaunchFail = e.toString() + } + } + + fun getPyOutput(block:Boolean = true): List { + val lines = ArrayList() + if (pyOutStream != null) { + val input = pyInStream!! + if (block) { + lines.add(input.readLine()) + } + while (input.ready()) { + lines.add(input.readLine()) + } + } + return lines + } + + override fun motd(): String { + return "\n\nWelcome to the CPython Unciv CLI. Currently, this backend relies on launching the system Python 3 installation.\n\n${getPyOutput().joinToString("\n")}\n\n" + } + + override fun exec(command: String): String { + var out = "\n>>> ${command}\n" + if (pyProcess == null) { + out += "No Python process. Error on launch: ${pyProcessLaunchFail}\n" + } else { + //var comm = command.toByteArray(Charsets.UTF_8) + //print("${comm}\n${comm.size}\n${comm.decodeToString()}\n") + //var outstream = pyProcess!!.getOutputStream() + //outstream.write("${command}\n".toByteArray(Charsets.UTF_8)) + //outstream.flush() + //var instream = pyProcess!!.getInputStream() + //var l = instream.available() + //var ba = ByteArray(l) + //instream.read(ba, 0, l) + //out += "${ba}\n${ba.size}\n${ba.decodeToString()}\n" + + pyOutStream!!.write("${command}\n") + pyOutStream!!.flush() + out += getPyOutput().joinToString("\n") + + + //var inputreader = BufferedReader(InputStreamReader(instream)) + //out += inputreader.readLine() + } + return out + } + + override fun terminate(): Boolean { + try { + if (pyProcess != null) { + pyProcess!!.destroy() + } + return true + } catch (e: Exception) { + return false + } + } + +} + + enum class ScriptingBackendType(val metadata:ScriptingBackend_metadata) { Dummy(ScriptingBackend), Hardcoded(HardcodedScriptingBackend), Reflective(ReflectiveScriptingBackend), QuickJS(QjsScriptingBackend), Lua(LuaScriptingBackend), - MicroPython(UpyScriptingBackend) + MicroPython(UpyScriptingBackend), + SystemPython(SpyScripingBackend) } -fun SpawnNamedScriptingBackend(backendtype:ScriptingBackendType, scriptingScope:ScriptingScope): ScriptingBackend { +fun SpawnNamedScriptingBackend(backendtype:ScriptingBackendType, scriptingScope: ScriptingScope): ScriptingBackend { return backendtype.metadata.new(scriptingScope) } diff --git a/core/src/com/unciv/scripting/ScriptingProtocolUtils.kt b/core/src/com/unciv/scripting/ScriptingProtocolUtils.kt index b85dca8137aaf..4591a6f5f445a 100644 --- a/core/src/com/unciv/scripting/ScriptingProtocolUtils.kt +++ b/core/src/com/unciv/scripting/ScriptingProtocolUtils.kt @@ -213,3 +213,6 @@ fun setInstancePath(instance: Any, path: List, value: Any?): Unit { } } + +class ScriptingReplSubprocess(exec: Array) { +} diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index d62a18f3eab8d..0171b6946db09 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -82,11 +82,11 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr outputHistory.add(text) } - fun getAutocomplete(command: String): AutocompleteResults { + fun getAutocomplete(command: String, cursorPos: Int? = null): AutocompleteResults { if (!(hasBackend())) { return AutocompleteResults(listOf(), false, "") } - return getActiveBackend().getAutocomplete(command) + return getActiveBackend().getAutocomplete(command, cursorPos) } fun navigateHistory(increment: Int): String { diff --git a/core/src/com/unciv/ui/console/ConsoleScreen.kt b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt similarity index 84% rename from core/src/com/unciv/ui/console/ConsoleScreen.kt rename to core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt index ae71bbe9c09c8..34093aef14cb1 100644 --- a/core/src/com/unciv/ui/console/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt @@ -1,4 +1,4 @@ -package com.unciv.ui.console +package com.unciv.ui.consolescreen import com.badlogic.gdx.Input import com.badlogic.gdx.graphics.Color @@ -14,6 +14,7 @@ import com.unciv.scripting.ScriptingBackendType import com.unciv.scripting.ScriptingState import com.unciv.ui.utils.* import com.unciv.ui.utils.AutoScrollPane as ScrollPane +import kotlin.math.max class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Unit): CameraStageBaseScreen() { @@ -45,6 +46,10 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un private val layoutUpdators = ArrayList<() -> Unit>() private var isOpen = false + var input: String + get() = inputField.text + set(value: String) { inputField.setText(value) } + init { backendsAdders.add("Launch new backend:".toLabel()).padRight(30f) @@ -165,9 +170,17 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un printHistory.clearChildren() } - private fun setText(text:String) { + private fun setText(text: String, cursormode: SetTextCursorMode=SetTextCursorMode.End) { + val originaltext = inputField.text + val originalcursorpos = inputField.getCursorPosition() inputField.setText(text) - inputField.setCursorPosition(inputField.text.length) + when (cursormode) { + (SetTextCursorMode.End) -> { inputField.setCursorPosition(inputField.text.length) } + (SetTextCursorMode.Unchanged) -> {} + (SetTextCursorMode.Insert) -> { inputField.setCursorPosition(max(0, inputField.text.length-(originaltext.length-originalcursorpos))) } + (SetTextCursorMode.SelectAll) -> { throw Exception("NotImplemented.") } + (SetTextCursorMode.SelectAfter) -> { throw Exception("NotImplemented.") } + } } private fun echoHistory() { @@ -178,7 +191,7 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un private fun autocomplete() { var input = inputField.text - var results = scriptingState.getAutocomplete(input) + var results = scriptingState.getAutocomplete(input, inputField.getCursorPosition()) if (results.isHelpText) { echo(results.helpText) return @@ -186,13 +199,14 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un if (results.matches.size < 1) { return } else if (results.matches.size == 1) { - setText(results.matches[0]) + setText(results.matches[0], SetTextCursorMode.Insert) } else { echo("") for (m in results.matches) { echo(m) } - var minmatch = input + //var minmatch = input //Checking against the current input would prevent autoinsertion from working for autocomplete backends that support getting results from the middle of the current input. + var minmatch = "" var chosenresult = results.matches.first({true}) for (l in input.length..chosenresult.length) { var longer = chosenresult.slice(0..l) @@ -202,7 +216,7 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un break } } - setText(minmatch) + setText(minmatch, SetTextCursorMode.Insert) } } @@ -232,6 +246,13 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un } } } + enum class SetTextCursorMode() { + End(), + Unchanged(), + Insert(), + SelectAll(), + SelectAfter() + } } // Screen, widget, or popup? diff --git a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt index 5265d33750cb3..440034080e51f 100644 --- a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt +++ b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt @@ -28,7 +28,7 @@ import com.unciv.models.translations.tr import com.unciv.scripting.ScriptingState import com.unciv.ui.cityscreen.CityScreen import com.unciv.ui.civilopedia.CivilopediaScreen -import com.unciv.ui.console.ConsoleScreen +import com.unciv.ui.consolescreen.ConsoleScreen import com.unciv.ui.overviewscreen.EmpireOverviewScreen import com.unciv.ui.pickerscreens.* import com.unciv.ui.saves.LoadGameScreen From 4cf48a6c31afe0fd6ad896e7906012a42f19d0b3 Mon Sep 17 00:00:00 2001 From: will-ca Date: Wed, 3 Nov 2021 19:56:18 +0000 Subject: [PATCH 20/93] Added `SubprocessReplManager` utility class and `SubprocessScriptingBackend` base class. --- .../com/unciv/scripting/ScriptingBackend.kt | 125 +++++++----------- .../unciv/scripting/ScriptingProtocolUtils.kt | 72 +++++++++- 2 files changed, 122 insertions(+), 75 deletions(-) diff --git a/core/src/com/unciv/scripting/ScriptingBackend.kt b/core/src/com/unciv/scripting/ScriptingBackend.kt index 0004ce8b8a6f6..4c19cb7872509 100644 --- a/core/src/com/unciv/scripting/ScriptingBackend.kt +++ b/core/src/com/unciv/scripting/ScriptingBackend.kt @@ -2,7 +2,6 @@ package com.unciv.scripting import kotlin.reflect.full.* //import kotlin.text.* -import java.io.* import java.util.* @@ -376,88 +375,66 @@ class UpyScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackend(scri } -class SpyScripingBackend(scriptingScope: ScriptingScope): ScriptingBackend(scriptingScope) { - - companion object Metadata: ScriptingBackend_metadata { - override fun new(scriptingScope: ScriptingScope) = SpyScripingBackend(scriptingScope) - override val displayname:String = "System Python" - } - - var pyProcess: java.lang.Process? = null +open class SubprocessScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackend(scriptingScope) { - var pyInStream: BufferedReader? = null - var pyOutStream: BufferedWriter? = null + open val processCmd = arrayOf("") - var pyProcessLaunchFail = "" - - init { - try { - // With pipes, Python automatically execs STDIN: `python3 -c 'print("print(5+5)")' | python3`. - // When run in interactive mode, it doesn't do this though: `python3` `echo 'print(5)\n' > /proc/$(pgrep python)/fd/0` - // For now, I'm going to have it manually loop through output to get around this: `echo -e "import sys\nwhile True: exec(sys.stdin.readline())" > pyloop.py; python3 pyloop.py` `echo 'print(5)\n' > /proc/$(pgrep python)/fd/0` - pyProcess = Runtime.getRuntime().exec(arrayOf("python3", "-u", "-X", "utf8", "-c", "import sys\nprint('sys.implementation == '+str(sys.implementation))\nwhile True:\n\tline=sys.stdin.readline()\n\ttry:\n\t\ttry:\n\t\t\tcode=compile(line, 'STDIN', 'eval')\n\t\texcept SyntaxError:\n\t\t\texec(line)\n\t\telse:\n\t\t\tprint(eval(code))\n\texcept Exception as e:\n\t\tprint(e)")) - pyInStream = BufferedReader(InputStreamReader(pyProcess!!.getInputStream())) - pyOutStream = BufferedWriter(OutputStreamWriter(pyProcess!!.getOutputStream())) - } catch (e: Exception) { - pyProcess = null - pyProcessLaunchFail = e.toString() - } - } - - fun getPyOutput(block:Boolean = true): List { - val lines = ArrayList() - if (pyOutStream != null) { - val input = pyInStream!! - if (block) { - lines.add(input.readLine()) - } - while (input.ready()) { - lines.add(input.readLine()) - } - } - return lines - } + lateinit var subprocessReplManager: SubprocessReplManager - override fun motd(): String { - return "\n\nWelcome to the CPython Unciv CLI. Currently, this backend relies on launching the system Python 3 installation.\n\n${getPyOutput().joinToString("\n")}\n\n" + fun startProcess() { + subprocessReplManager = SubprocessReplManager(processCmd) + subprocessReplManager.startProc() } override fun exec(command: String): String { - var out = "\n>>> ${command}\n" - if (pyProcess == null) { - out += "No Python process. Error on launch: ${pyProcessLaunchFail}\n" + if (subprocessReplManager.isRunning) { + return "${subprocessReplManager.evalCode("${command}\n").joinToString("\n")}\n" } else { - //var comm = command.toByteArray(Charsets.UTF_8) - //print("${comm}\n${comm.size}\n${comm.decodeToString()}\n") - //var outstream = pyProcess!!.getOutputStream() - //outstream.write("${command}\n".toByteArray(Charsets.UTF_8)) - //outstream.flush() - //var instream = pyProcess!!.getInputStream() - //var l = instream.available() - //var ba = ByteArray(l) - //instream.read(ba, 0, l) - //out += "${ba}\n${ba.size}\n${ba.decodeToString()}\n" - - pyOutStream!!.write("${command}\n") - pyOutStream!!.flush() - out += getPyOutput().joinToString("\n") - - - //var inputreader = BufferedReader(InputStreamReader(instream)) - //out += inputreader.readLine() + return "No process." } - return out + } +} + + +class SpyScriptingBackend(scriptingScope: ScriptingScope): SubprocessScriptingBackend(scriptingScope) { + + companion object Metadata: ScriptingBackend_metadata { + override fun new(scriptingScope: ScriptingScope) = SpyScriptingBackend(scriptingScope) + override val displayname:String = "System Python" + } + + val pyBoilerPlate = """ + import sys, io + print('sys.implementation == ' + str(sys.implementation)) + stdout = sys.stdout + while True: + line = sys.stdin.readline() + out = sys.stdout = io.StringIO() # Won't work with MicroPython. I think it's slotted? + print(">>> " + line) + try: + try: + code = compile(line, 'STDIN', 'eval') + except SyntaxError: + exec(compile(line, 'STDIN', 'exec')) + else: + print(eval(code)) + except Exception as e: + print(repr(e), file=stdout) + else: + print(out.getvalue(), file=stdout) + """.trimIndent() + + override val processCmd = arrayOf("python3", "-u", "-X", "utf8", "-c", pyBoilerPlate) + // With pipes, Python automatically execs STDIN: `python3 -c 'print("print(5+5)")' | python3`. + // When run in interactive mode, it doesn't do this though: `python3` `echo 'print(5)\n' > /proc/$(pgrep python)/fd/0` + // For now, I'm going to have it manually loop through output to get around this: `echo -e "import sys\nwhile True: exec(sys.stdin.readline())" > pyloop.py; python3 pyloop.py` `echo 'print(5)\n' > /proc/$(pgrep python)/fd/0` + + init { + startProcess() } - override fun terminate(): Boolean { - try { - if (pyProcess != null) { - pyProcess!!.destroy() - } - return true - } catch (e: Exception) { - return false - } + override fun motd(): String { + return "\n\nWelcome to the CPython Unciv CLI. Currently, this backend relies on launching the system Python 3 installation.\n\n${subprocessReplManager.getProcOutput(block=true).joinToString("\n")}\n\n" } } @@ -470,7 +447,7 @@ enum class ScriptingBackendType(val metadata:ScriptingBackend_metadata) { QuickJS(QjsScriptingBackend), Lua(LuaScriptingBackend), MicroPython(UpyScriptingBackend), - SystemPython(SpyScripingBackend) + SystemPython(SpyScriptingBackend) } diff --git a/core/src/com/unciv/scripting/ScriptingProtocolUtils.kt b/core/src/com/unciv/scripting/ScriptingProtocolUtils.kt index 4591a6f5f445a..f358205559a62 100644 --- a/core/src/com/unciv/scripting/ScriptingProtocolUtils.kt +++ b/core/src/com/unciv/scripting/ScriptingProtocolUtils.kt @@ -4,6 +4,7 @@ import kotlin.collections.ArrayList import kotlin.reflect.KProperty1 import kotlin.reflect.KFunction import kotlin.reflect.KMutableProperty1 +import java.io.* import java.util.* @@ -214,5 +215,74 @@ fun setInstancePath(instance: Any, path: List, value: Any?): Unit { } -class ScriptingReplSubprocess(exec: Array) { +class SubprocessReplManager(val processCmd: Array) { + + var process: java.lang.Process? = null + + var inStream: BufferedReader? = null + var outStream: BufferedWriter? = null + + var processLaunchFail = "" + + val isRunning + get() = process != null && process!!.isAlive() + + + fun startProc() { + if (isRunning) { + throw RuntimeException("Process is already running: ${process}") + } + try { + process = Runtime.getRuntime().exec(processCmd) + } catch (e: Exception) { + process = null + processLaunchFail = e.toString() + return + } + inStream = BufferedReader(InputStreamReader(process!!.getInputStream())) + outStream = BufferedWriter(OutputStreamWriter(process!!.getOutputStream())) + } + + fun stopProc(): Exception? { + try { + if (isRunning) { + process!!.destroy() + //Close streams + } + } catch (e: Exception) { + return e + } + process = null + inStream = null + outStream = null + return null + } + + fun getProcOutput(block:Boolean = true): List { + val lines = ArrayList() + if (isRunning) { + val input = inStream!! + if (block) { + lines.add(input.readLine()) + } + while (input.ready()) { + lines.add(input.readLine()) + } + } + return lines + } + + fun sendProcInput(code: String) { + outStream!!.write(code) + outStream!!.flush() + } + + fun evalCode(code: String): List { + if (!isRunning) { + throw RuntimeException("No process: ${process}\nException on launch: ${processLaunchFail}") + } else { + sendProcInput(code) + return getProcOutput(block=true) + } + } } From 7511a92ccccdf2819e788c48da450643ae5b1bc6 Mon Sep 17 00:00:00 2001 From: will-ca Date: Thu, 4 Nov 2021 05:13:35 +0000 Subject: [PATCH 21/93] Split up classes. Prepare to add two-way IPC or different external interpreters. --- android/assets/scripting/Scripts.json | 6 + .../assets/scripting/python/main.py | 21 ++- android/assets/scripting/qjs/main.js | 13 ++ .../com/unciv/scripting/ScriptingBackend.kt | 163 ++++++++++++------ ...hon_backend.kt => ScriptingBackendType.kt} | 0 .../src/com/unciv/scripting/ScriptingState.kt | 31 +++- .../backends/HardcodedScriptingBackends.kt | 0 .../backends/ScriptingBackendBase.kt | 0 .../scripting/protocol/ScriptingProtocol.kt | 12 ++ .../protocol/ScriptingReplManager.kt | 59 +++++++ .../protocol/ScriptingReplReflector.kt | 8 + .../scripting/protocol/SubprocessBlackbox.kt | 77 +++++++++ .../Reflection.kt} | 76 +------- .../src/com/unciv/scripting/utils/Blackbox.kt | 37 ++++ .../unciv/scripting/utils/SourceManager.kt | 3 + .../unciv/ui/consolescreen/ConsoleScreen.kt | 22 +-- 16 files changed, 381 insertions(+), 147 deletions(-) create mode 100644 android/assets/scripting/Scripts.json rename core/src/com/unciv/scripting/_experiments/pythonmain.py => android/assets/scripting/python/main.py (93%) create mode 100644 android/assets/scripting/qjs/main.js rename core/src/com/unciv/scripting/{_experiments/python_backend.kt => ScriptingBackendType.kt} (100%) create mode 100644 core/src/com/unciv/scripting/backends/HardcodedScriptingBackends.kt create mode 100644 core/src/com/unciv/scripting/backends/ScriptingBackendBase.kt create mode 100644 core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt create mode 100644 core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt create mode 100644 core/src/com/unciv/scripting/protocol/ScriptingReplReflector.kt create mode 100644 core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt rename core/src/com/unciv/scripting/{ScriptingProtocolUtils.kt => reflection/Reflection.kt} (77%) create mode 100644 core/src/com/unciv/scripting/utils/Blackbox.kt create mode 100644 core/src/com/unciv/scripting/utils/SourceManager.kt diff --git a/android/assets/scripting/Scripts.json b/android/assets/scripting/Scripts.json new file mode 100644 index 0000000000000..0081c0c27067b --- /dev/null +++ b/android/assets/scripting/Scripts.json @@ -0,0 +1,6 @@ +{ + python: { + main.py: null + } + +} diff --git a/core/src/com/unciv/scripting/_experiments/pythonmain.py b/android/assets/scripting/python/main.py similarity index 93% rename from core/src/com/unciv/scripting/_experiments/pythonmain.py rename to android/assets/scripting/python/main.py index f478c09cbd7f6..68f654055b9d3 100644 --- a/core/src/com/unciv/scripting/_experiments/pythonmain.py +++ b/android/assets/scripting/python/main.py @@ -1,4 +1,4 @@ -import sys, json +import sys, io, json def ResolvePath(path, scope): @@ -179,3 +179,22 @@ class ForeignActionTransceiver(ForeignActionReceiver, ForeignActionSender): #python | micropython -c "import sys; [print('FMF: '+sys.stdin.readline()) for i in range(10)]" #python3 -ic 'from CommTest import *; t=ForeignActionTransceiver()' | micropython -i -c 'from CommTest import *; t=ForeignActionTransceiver(); t.ForeignREPL()' #t.SendForeignRead("ForeignActionTransceiver.__name__") + + +print('sys.implementation == ' + str(sys.implementation)) +stdout = sys.stdout +while True: + line = sys.stdin.readline() + out = sys.stdout = io.StringIO() # Won't work with MicroPython. I think it's slotted? + print(">>> " + line) + try: + try: + code = compile(line, 'STDIN', 'eval') + except SyntaxError: + exec(compile(line, 'STDIN', 'exec')) + else: + print(eval(code)) + except Exception as e: + print(repr(e), file=stdout) + else: + print(out.getvalue(), file=stdout) diff --git a/android/assets/scripting/qjs/main.js b/android/assets/scripting/qjs/main.js new file mode 100644 index 0000000000000..f1a2e3d97ecc1 --- /dev/null +++ b/android/assets/scripting/qjs/main.js @@ -0,0 +1,13 @@ +//import("std"); + +while (true) { + let line = std.in.getline(); + let out = `qjs > ${line}\n`; + try { + out += String(eval(line)); + } catch (e) { + out += String(e) + } + out += "\n" + std.out.puts(out) +} diff --git a/core/src/com/unciv/scripting/ScriptingBackend.kt b/core/src/com/unciv/scripting/ScriptingBackend.kt index 4c19cb7872509..233ddaf0db954 100644 --- a/core/src/com/unciv/scripting/ScriptingBackend.kt +++ b/core/src/com/unciv/scripting/ScriptingBackend.kt @@ -1,7 +1,11 @@ package com.unciv.scripting +import com.badlogic.gdx.Gdx + +import com.unciv.scripting.reflection.* +import com.unciv.scripting.protocol.ScriptingReplManager +import com.unciv.scripting.protocol.SubprocessBlackbox import kotlin.reflect.full.* -//import kotlin.text.* import java.util.* @@ -9,17 +13,18 @@ data class AutocompleteResults(val matches:List, val isHelpText:Boolean interface ScriptingBackend_metadata { - fun new(scriptingScope: ScriptingScope): ScriptingBackend + fun new(scriptingScope: ScriptingScope): ScriptingBackendBase val displayname:String } -open class ScriptingBackend(val scriptingScope: ScriptingScope) { +open class ScriptingBackendBase(val scriptingScope: ScriptingScope) { companion object Metadata: ScriptingBackend_metadata { - override fun new(scriptingScope: ScriptingScope) = ScriptingBackend(scriptingScope) + override fun new(scriptingScope: ScriptingScope) = ScriptingBackendBase(scriptingScope) override val displayname:String = "Dummy" } + val metadata: ScriptingBackend_metadata get(): ScriptingBackend_metadata = this::class.companionObjectInstance as ScriptingBackend_metadata @@ -39,14 +44,14 @@ open class ScriptingBackend(val scriptingScope: ScriptingScope) { return command } - open fun terminate(): Boolean { - // Return `true` on successful termination, `false` otherwise. - return true + open fun terminate(): Exception? { + // Return `null` on successful termination, an `Exception()` otherwise. + return null } } -class HardcodedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackend(scriptingScope) { +class HardcodedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackendBase(scriptingScope) { companion object Metadata: ScriptingBackend_metadata { override fun new(scriptingScope: ScriptingScope) = HardcodedScriptingBackend(scriptingScope) @@ -74,7 +79,7 @@ class HardcodedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacken var cheats:Boolean = false override fun motd(): String { - return "\n\nWelcome to the hardcoded demo backend.\n\nPlease run \"help\" or press [TAB] to see a list of available commands.\nPress [TAB] at any time to see help for currently typed command.\n\nPlease note that the available commands are meant as a DEMO for the CLI.\n" + return "\n\nWelcome to the hardcoded demo CLI backend.\n\nPlease run \"help\" or press [TAB] to see a list of available commands.\nPress [TAB] at any time to see help for currently typed command.\n\nPlease note that the available commands are meant as a DEMO for the CLI.\n" } fun getCommandHelpText(command: String): String { @@ -249,7 +254,7 @@ class HardcodedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacken } -class ReflectiveScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackend(scriptingScope) { +class ReflectiveScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackendBase(scriptingScope) { companion object Metadata: ScriptingBackend_metadata { override fun new(scriptingScope: ScriptingScope) = ReflectiveScriptingBackend(scriptingScope) @@ -336,7 +341,7 @@ class ReflectiveScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacke } -class QjsScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackend(scriptingScope) { +/*class QjsScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackendBase(scriptingScope) { companion object Metadata: ScriptingBackend_metadata { override fun new(scriptingScope: ScriptingScope) = QjsScriptingBackend(scriptingScope) @@ -346,10 +351,10 @@ class QjsScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackend(scri override fun motd(): String { return "\n\nWelcome to the QuickJS Unciv CLI, which doesn't currently run QuickJS but might one day!\n" } -} +}*/ -class LuaScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackend(scriptingScope) { +/*class LuaScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackendBase(scriptingScope) { companion object Metadata: ScriptingBackend_metadata { override fun new(scriptingScope: ScriptingScope) = LuaScriptingBackend(scriptingScope) @@ -359,10 +364,10 @@ class LuaScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackend(scri override fun motd(): String { return "\n\nWelcome to the Lua Unciv CLI, which doesn't currently run Lua but might one day!\n" } -} - +}*/ -class UpyScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackend(scriptingScope) { +/* +class UpyScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackendBase(scriptingScope) { companion object Metadata: ScriptingBackend_metadata { override fun new(scriptingScope: ScriptingScope) = UpyScriptingBackend(scriptingScope) @@ -373,25 +378,42 @@ class UpyScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackend(scri return "\n\nWelcome to the MicroPython Unciv CLI, which doesn't currently run MicroPython but might one day!\n" } } +*/ - -open class SubprocessScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackend(scriptingScope) { +open class SubprocessScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackendBase(scriptingScope) { open val processCmd = arrayOf("") - lateinit var subprocessReplManager: SubprocessReplManager + open val replSoftExitCode = "" + + lateinit var replManager: ScriptingReplManager fun startProcess() { - subprocessReplManager = SubprocessReplManager(processCmd) - subprocessReplManager.startProc() + replManager = ScriptingReplManager(scriptingScope, SubprocessBlackbox(processCmd)) } override fun exec(command: String): String { - if (subprocessReplManager.isRunning) { - return "${subprocessReplManager.evalCode("${command}\n").joinToString("\n")}\n" - } else { - return "No process." + try { + return "${replManager.evalCode("${command}\n").joinToString("\n")}\n" + } catch (e: RuntimeException) { + return "${e}\n" + } + } + + open fun softStopProcess() { + replManager.evalCode(replSoftExitCode) + } + + fun hardStopProcess(): Exception? { + return replManager.terminate() + } + + override fun terminate(): Exception? { + try { + softStopProcess() + } catch (e: Exception) { } + return hardStopProcess() } } @@ -403,54 +425,87 @@ class SpyScriptingBackend(scriptingScope: ScriptingScope): SubprocessScriptingBa override val displayname:String = "System Python" } - val pyBoilerPlate = """ - import sys, io - print('sys.implementation == ' + str(sys.implementation)) - stdout = sys.stdout - while True: - line = sys.stdin.readline() - out = sys.stdout = io.StringIO() # Won't work with MicroPython. I think it's slotted? - print(">>> " + line) - try: - try: - code = compile(line, 'STDIN', 'eval') - except SyntaxError: - exec(compile(line, 'STDIN', 'exec')) - else: - print(eval(code)) - except Exception as e: - print(repr(e), file=stdout) - else: - print(out.getvalue(), file=stdout) - """.trimIndent() + val pyBoilerPlate = Gdx.files.internal("scripting/python/main.py").readString(Charsets.UTF_8.name()) override val processCmd = arrayOf("python3", "-u", "-X", "utf8", "-c", pyBoilerPlate) // With pipes, Python automatically execs STDIN: `python3 -c 'print("print(5+5)")' | python3`. - // When run in interactive mode, it doesn't do this though: `python3` `echo 'print(5)\n' > /proc/$(pgrep python)/fd/0` - // For now, I'm going to have it manually loop through output to get around this: `echo -e "import sys\nwhile True: exec(sys.stdin.readline())" > pyloop.py; python3 pyloop.py` `echo 'print(5)\n' > /proc/$(pgrep python)/fd/0` + // When run in interactive mode, it doesn't do this though: `python3` `echo 'print(5)\n' > /proc/$(pgrep python)/fd/0`. + // As such, I have it manually loop through STDIN to get around this: `echo -e "import sys\nwhile True: exec(sys.stdin.readline())" > pyloop.py; python3 pyloop.py` `echo 'print(5)\n' > /proc/$(pgrep python)/fd/0`. + override val replSoftExitCode = """ + try: + exit() + finally: + print("Exiting.") + """.trimIndent() init { startProcess() } override fun motd(): String { - return "\n\nWelcome to the CPython Unciv CLI. Currently, this backend relies on launching the system Python 3 installation.\n\n${subprocessReplManager.getProcOutput(block=true).joinToString("\n")}\n\n" + return "\n\nWelcome to the CPython Unciv CLI. Currently, this backend relies on launching the system Python 3 installation.\n\n${replManager.blackbox.readAll(block=true).joinToString("\n")}\n\n" + } + +} + + +class SqjsScriptingBackend(scriptingScope: ScriptingScope): SubprocessScriptingBackend(scriptingScope) { + + companion object Metadata: ScriptingBackend_metadata { + override fun new(scriptingScope: ScriptingScope) = SqjsScriptingBackend(scriptingScope) + override val displayname:String = "System QuickJS" + } + + val jsBoilerPlate = """print(1); print(5); while (true) { print(String(eval(std.in.getline()))+"\n") }""".trimIndent() + + override val processCmd = arrayOf("qjs", "--std", "--eval", jsBoilerPlate) + + init { + startProcess() } + + override fun softStopProcess() {} + +} + +class SluaScriptingBackend(scriptingScope: ScriptingScope): SubprocessScriptingBackend(scriptingScope) { + + companion object Metadata: ScriptingBackend_metadata { + override fun new(scriptingScope: ScriptingScope) = SluaScriptingBackend(scriptingScope) + override val displayname:String = "System LUA" + } + + val luaBoilerPlate = """ + while true do + io.stdout:write(io.stdin:read()) + end + """.trimIndent() + + override val processCmd = arrayOf("lua", "-e", luaBoilerPlate) + + init { + startProcess() + } + + override fun softStopProcess() {} + } enum class ScriptingBackendType(val metadata:ScriptingBackend_metadata) { - Dummy(ScriptingBackend), + Dummy(ScriptingBackendBase), Hardcoded(HardcodedScriptingBackend), Reflective(ReflectiveScriptingBackend), - QuickJS(QjsScriptingBackend), - Lua(LuaScriptingBackend), - MicroPython(UpyScriptingBackend), - SystemPython(SpyScriptingBackend) + //QuickJS(QjsScriptingBackend), + //Lua(LuaScriptingBackend), + //MicroPython(UpyScriptingBackend), + SystemPython(SpyScriptingBackend), + SystemQuickJS(SqjsScriptingBackend), + SystemLua(SluaScriptingBackend) } -fun SpawnNamedScriptingBackend(backendtype:ScriptingBackendType, scriptingScope: ScriptingScope): ScriptingBackend { +fun SpawnNamedScriptingBackend(backendtype:ScriptingBackendType, scriptingScope: ScriptingScope): ScriptingBackendBase { return backendtype.metadata.new(scriptingScope) } diff --git a/core/src/com/unciv/scripting/_experiments/python_backend.kt b/core/src/com/unciv/scripting/ScriptingBackendType.kt similarity index 100% rename from core/src/com/unciv/scripting/_experiments/python_backend.kt rename to core/src/com/unciv/scripting/ScriptingBackendType.kt diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index 0171b6946db09..ed261fbc68dee 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -8,9 +8,27 @@ import kotlin.collections.ArrayList import kotlin.math.max import kotlin.math.min +/* +``` +UncivGame(): + ScriptingState(): + ScriptingScope(): + civInfo + gameInfo + uncivGame + worldScreen + *ScriptingBackend(): + scriptingScope + ?ScriptingReplManager(): + Blackbox() + ConsoleScreen(): + scriptingState +``` +*/ + class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: ScriptingBackendType? = null){ - val scriptingBackends:ArrayList = ArrayList() + val scriptingBackends:ArrayList = ArrayList() val outputHistory:ArrayList = ArrayList() val commandHistory:ArrayList = ArrayList() @@ -47,7 +65,7 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr } fun spawnBackend(backendtype: ScriptingBackendType): String { - var backend:ScriptingBackend = SpawnNamedScriptingBackend(backendtype, scriptingScope) + var backend:ScriptingBackendBase = SpawnNamedScriptingBackend(backendtype, scriptingScope) scriptingBackends.add(backend) activeBackend = scriptingBackends.size - 1 var motd = backend.motd() @@ -59,22 +77,23 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr activeBackend = max(0, min(scriptingBackends.size - 1, index)) } - fun termBackend(index: Int) { + fun termBackend(index: Int): Exception? { if (!(0 <= index && index < scriptingBackends.size)) { - return // Maybe checking should be better done and unified, and raise a warning when out of bounds. + throw IndexOutOfBoundsException()// Maybe checking should be better done and unified. Also, I don't love the idea of an exposed method being able to trigger a crash, but I had this fail silently before, which would probably be worse. } val result = scriptingBackends[index].terminate() - if (result) { + if (result == null) { scriptingBackends.removeAt(index) activeBackend = min(activeBackend, scriptingBackends.size - 1) } + return result } fun hasBackend(): Boolean { return scriptingBackends.size > 0 } - fun getActiveBackend(): ScriptingBackend { + fun getActiveBackend(): ScriptingBackendBase { return scriptingBackends[activeBackend] } diff --git a/core/src/com/unciv/scripting/backends/HardcodedScriptingBackends.kt b/core/src/com/unciv/scripting/backends/HardcodedScriptingBackends.kt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/core/src/com/unciv/scripting/backends/ScriptingBackendBase.kt b/core/src/com/unciv/scripting/backends/ScriptingBackendBase.kt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt new file mode 100644 index 0000000000000..7633e6bf6ff60 --- /dev/null +++ b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt @@ -0,0 +1,12 @@ +package com.unciv.scripting.protocol + +import com.unciv.scripting.ScriptingScope +import com.unciv.scripting.utils.Blackbox + +/* +JSON packets: +*/ + + + + diff --git a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt new file mode 100644 index 0000000000000..59dcaebf25c62 --- /dev/null +++ b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt @@ -0,0 +1,59 @@ +package com.unciv.scripting.protocol + +import com.unciv.scripting.ScriptingScope +import com.unciv.scripting.utils.Blackbox + + +/* +1. A scripted action is initiated from the Kotlin side, by sending a command string to the script interpreter. +2. While the script interpreter is running, it has a chance to request values from the Kotlin side by sending back packets encoding attribute/property, key, and call stacks. +3. When the Kotlin side receives a request for a value, it uses reflection to access the requested property or call the requested method, and it sends the result to the script interpreter. +4. When the script interpreter finishes running, it sends a special packet to the Kotlin side. It then sends the REPL output of the command to the Kotlin side. +5. When the Kotlin interpreter receives the packet marking the end of the command run, it stops listening for value requests packets. It then receives the next value, and passes it back to the display/handler. + +``` +fun ExecuteCommand(command): + SendToInterpreter(command:String) + while True: + packet:Packet = ReceiveFromInterpreter().parsed() + if isPropertyrequest(packet): + SendToInterpreter(ResolvePacket(scriptingScope, packet)) + else if isCommandEndPacket(packet): + break + PrintToConsole(ReceiveFromInterpreter():String) +``` + +The "packets" should probably all be encoded as strings, probably JSON. The technique used to connect the script interpreter to the Kotlin code shouldn't matter. As long as it's wrapped up in and implements the `Blackbox` interface, IPC/embedding based on pipes, STDIN/STDOUT, sockets, queues, embedding, JNI, etc. should all be interchangeable. + +I'm not sure if there'd be much point to or a good technique for letting the script interpreter run constantly and initiate actions on its own, instead of waiting for commands from the Kotlin side. + +You would presumably have to interrupt the main Kotlin-side thread anyway in order to safely run any actions initiated by the script interpreter— Which means that you may as well just register a handler to call the script interpreter at that point. + +Plus, letting the script interpreter run completely in parallel would probably introduce potential for all sorts of issues with no-deterministic synchronicity, and performance issues Calling the script interpreter from +*/ + + +class ScriptingReplManager(val scriptingScope: ScriptingScope, val blackbox: Blackbox) { + + fun whileEval() { + return + } + + fun runCode(code: String) { + blackbox.write(code) + } + + fun evalCode(code: String): List { + if (!blackbox.readyForWrite) { + throw IllegalStateException("REPL not ready: ${blackbox}") + } else { + runCode(code) + whileEval() + return blackbox.readAll(block=true) + } + } + + fun terminate(): Exception? { + return blackbox.stop() + } +} diff --git a/core/src/com/unciv/scripting/protocol/ScriptingReplReflector.kt b/core/src/com/unciv/scripting/protocol/ScriptingReplReflector.kt new file mode 100644 index 0000000000000..297b9024e6b43 --- /dev/null +++ b/core/src/com/unciv/scripting/protocol/ScriptingReplReflector.kt @@ -0,0 +1,8 @@ +package com.unciv.scripting.protocol + +import com.unciv.scripting.ScriptingScope + + +class ScriptingReplReflector(scriptingScope: ScriptingScope) { +} + diff --git a/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt b/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt new file mode 100644 index 0000000000000..e351606d95886 --- /dev/null +++ b/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt @@ -0,0 +1,77 @@ +package com.unciv.scripting.protocol + +import com.unciv.scripting.utils.Blackbox +import java.io.* + + +class SubprocessBlackbox(val processCmd: Array): Blackbox { + + var process: java.lang.Process? = null + + var inStream: BufferedReader? = null + var outStream: BufferedWriter? = null + + var processLaunchFail = "" + + override val isAlive: Boolean + get() = process != null && process!!.isAlive() + + override val readyForWrite: Boolean + get() = isAlive + + override val readyForRead: Int + get() = if (isAlive && inStream!!.ready()) 1 else 0 + + init { + start() + } + + override fun toString(): String { + return "${this::class.simpleName}(process=${process})" + } + + override fun start() { + if (isAlive) { + throw RuntimeException("Process is already running: ${process}") + } + try { + process = Runtime.getRuntime().exec(processCmd) + } catch (e: Exception) { + process = null + processLaunchFail = e.toString() + return + } + inStream = BufferedReader(InputStreamReader(process!!.getInputStream())) + outStream = BufferedWriter(OutputStreamWriter(process!!.getOutputStream())) + } + + override fun stop(): Exception? { + try { + if (isAlive) { + process!!.destroy() + inStream!!.close() + outStream!!.close() + } + } catch (e: Exception) { + return e + } + process = null + inStream = null + outStream = null + return null + } + + override fun read(block: Boolean): String { + if (block || readyForRead > 0) { + return inStream!!.readLine() + } else { + throw IllegalStateException("Empty STDOUT for ${process}.") + } + } + + override fun write(string: String) { + outStream!!.write(string) + outStream!!.flush() + } + +} diff --git a/core/src/com/unciv/scripting/ScriptingProtocolUtils.kt b/core/src/com/unciv/scripting/reflection/Reflection.kt similarity index 77% rename from core/src/com/unciv/scripting/ScriptingProtocolUtils.kt rename to core/src/com/unciv/scripting/reflection/Reflection.kt index f358205559a62..35e0ab7946726 100644 --- a/core/src/com/unciv/scripting/ScriptingProtocolUtils.kt +++ b/core/src/com/unciv/scripting/reflection/Reflection.kt @@ -1,10 +1,9 @@ -package com.unciv.scripting +package com.unciv.scripting.reflection import kotlin.collections.ArrayList import kotlin.reflect.KProperty1 import kotlin.reflect.KFunction import kotlin.reflect.KMutableProperty1 -import java.io.* import java.util.* @@ -213,76 +212,3 @@ fun setInstancePath(instance: Any, path: List, value: Any?): Unit { } } } - - -class SubprocessReplManager(val processCmd: Array) { - - var process: java.lang.Process? = null - - var inStream: BufferedReader? = null - var outStream: BufferedWriter? = null - - var processLaunchFail = "" - - val isRunning - get() = process != null && process!!.isAlive() - - - fun startProc() { - if (isRunning) { - throw RuntimeException("Process is already running: ${process}") - } - try { - process = Runtime.getRuntime().exec(processCmd) - } catch (e: Exception) { - process = null - processLaunchFail = e.toString() - return - } - inStream = BufferedReader(InputStreamReader(process!!.getInputStream())) - outStream = BufferedWriter(OutputStreamWriter(process!!.getOutputStream())) - } - - fun stopProc(): Exception? { - try { - if (isRunning) { - process!!.destroy() - //Close streams - } - } catch (e: Exception) { - return e - } - process = null - inStream = null - outStream = null - return null - } - - fun getProcOutput(block:Boolean = true): List { - val lines = ArrayList() - if (isRunning) { - val input = inStream!! - if (block) { - lines.add(input.readLine()) - } - while (input.ready()) { - lines.add(input.readLine()) - } - } - return lines - } - - fun sendProcInput(code: String) { - outStream!!.write(code) - outStream!!.flush() - } - - fun evalCode(code: String): List { - if (!isRunning) { - throw RuntimeException("No process: ${process}\nException on launch: ${processLaunchFail}") - } else { - sendProcInput(code) - return getProcOutput(block=true) - } - } -} diff --git a/core/src/com/unciv/scripting/utils/Blackbox.kt b/core/src/com/unciv/scripting/utils/Blackbox.kt new file mode 100644 index 0000000000000..2ab344b7451fb --- /dev/null +++ b/core/src/com/unciv/scripting/utils/Blackbox.kt @@ -0,0 +1,37 @@ +package com.unciv.scripting.utils + + +interface Blackbox { + + fun start() { } + + fun stop(): Exception? { return null } // Return null on success, or return an Exception() on failure. Because there might be normal situations where a "black box" isn't viable to cleanly shut down, I'm thinking that letting exceptions be returned will let those situations be distinguished from actual errors. E.G.: Making an invalid API call to a requests library should still throw the Exception as usual, but making the right call only to find out that the network's down or a process is frozen would be a more "normal" and uncontrollable situation, so in that case the exception should be a return value instead of thrown. + + val isAlive: Boolean + get() = false + + val readyForRead: Int // Return approximate number of items ready to be read. Should try to always return 0 correctly, but may return 1 if a greater number of items are available. + get() = 0 + + val readyForWrite: Boolean + get() = false + + fun read(block: Boolean = true): String // Return a single string if either blocking or ready to read, or throw an IllegalStateException() otherwise. + + fun readAll(block: Boolean = true, limit: Int = 0): List { // Read out all lines up to a limit if greater than zero, returning an empty list if none are available and no blocking is allowed + val lines = ArrayList() + var i = 0 + if (block) { + lines.add(read(block=true)) + i += 1 + } + while (readyForRead > 0 && (limit == 0 || i < limit)) { + lines.add(read(block=true)) + i += 1 + } + return lines + } + + fun write(string: String) + +} diff --git a/core/src/com/unciv/scripting/utils/SourceManager.kt b/core/src/com/unciv/scripting/utils/SourceManager.kt new file mode 100644 index 0000000000000..227e350d8aef5 --- /dev/null +++ b/core/src/com/unciv/scripting/utils/SourceManager.kt @@ -0,0 +1,3 @@ +package com.unciv.scripting.utils + + diff --git a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt index 34093aef14cb1..7975596ca36ea 100644 --- a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt @@ -9,7 +9,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.TextButton import com.badlogic.gdx.scenes.scene2d.ui.TextField import com.unciv.Constants -import com.unciv.scripting.ScriptingBackend +import com.unciv.scripting.ScriptingBackendBase import com.unciv.scripting.ScriptingBackendType import com.unciv.scripting.ScriptingState import com.unciv.ui.utils.* @@ -19,8 +19,6 @@ import kotlin.math.max class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Unit): CameraStageBaseScreen() { - private val lineHeight = 30f - private val layoutTable: Table = Table() private val topBar: Table = Table() @@ -52,7 +50,7 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un init { - backendsAdders.add("Launch new backend:".toLabel()).padRight(30f) + backendsAdders.add("Launch new backend:".toLabel()).padRight(30f).padLeft(20f) for (backendtype in ScriptingBackendType.values()) { var backendadder = backendtype.metadata.displayname.toTextButton() backendadder.onClick({ @@ -178,8 +176,8 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un (SetTextCursorMode.End) -> { inputField.setCursorPosition(inputField.text.length) } (SetTextCursorMode.Unchanged) -> {} (SetTextCursorMode.Insert) -> { inputField.setCursorPosition(max(0, inputField.text.length-(originaltext.length-originalcursorpos))) } - (SetTextCursorMode.SelectAll) -> { throw Exception("NotImplemented.") } - (SetTextCursorMode.SelectAfter) -> { throw Exception("NotImplemented.") } + (SetTextCursorMode.SelectAll) -> { throw UnsupportedOperationException("NotImplemented.") } + (SetTextCursorMode.SelectAfter) -> { throw UnsupportedOperationException("NotImplemented.") } } } @@ -190,8 +188,9 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un } private fun autocomplete() { - var input = inputField.text - var results = scriptingState.getAutocomplete(input, inputField.getCursorPosition()) + val original = inputField.text + val cursorpos = inputField.getCursorPosition() + var results = scriptingState.getAutocomplete(input, cursorpos) if (results.isHelpText) { echo(results.helpText) return @@ -205,10 +204,10 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un for (m in results.matches) { echo(m) } - //var minmatch = input //Checking against the current input would prevent autoinsertion from working for autocomplete backends that support getting results from the middle of the current input. + //var minmatch = original //Checking against the current input would prevent autoinsertion from working for autocomplete backends that support getting results from the middle of the current input. var minmatch = "" var chosenresult = results.matches.first({true}) - for (l in input.length..chosenresult.length) { + for (l in original.length-1..chosenresult.length-1) { var longer = chosenresult.slice(0..l) if (results.matches.all { it.startsWith(longer) }) { minmatch = longer @@ -216,7 +215,8 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un break } } - setText(minmatch, SetTextCursorMode.Insert) + setText(minmatch + original.slice(cursorpos..original.length-1), SetTextCursorMode.Insert) + // Splice the longest starting substring with the text after the cursor, to let autocomplete implementations work on the middle of current input. } } From e036dbb2f880491cc896f1cac64b26586120b311 Mon Sep 17 00:00:00 2001 From: will-ca Date: Fri, 5 Nov 2021 09:52:04 +0000 Subject: [PATCH 22/93] Copy temporary file trees for each backend instance. Enable JS and Lua backends. Move some stuff around. --- android/assets/scripting/Scripts.json | 6 - android/assets/scripting/SharedData.json | 3 + .../assets/scripting/enginedata/lua/main.lua | 16 + .../scripting/{ => enginedata}/python/main.py | 13 +- .../enginedata/python/unciv/__init__.py | 1 + .../scripting/{ => enginedata}/qjs/main.js | 4 + android/assets/scripting/lua.json | 4 + android/assets/scripting/python.json | 5 + android/assets/scripting/qjs.json | 4 + .../scripting/shareddata/ScriptAPI.json | 1 + core/src/com/unciv/UncivGame.kt | 2 +- .../com/unciv/scripting/ScriptingBackend.kt | 130 +++---- .../src/com/unciv/scripting/ScriptingScope.kt | 2 +- .../src/com/unciv/scripting/ScriptingState.kt | 21 +- .../{ => bases}/ScriptingBackendBase.kt | 0 .../unciv/scripting/reflection/Reflection.kt | 338 +++++++++--------- .../unciv/scripting/utils/SourceManager.kt | 59 +++ 17 files changed, 334 insertions(+), 275 deletions(-) delete mode 100644 android/assets/scripting/Scripts.json create mode 100644 android/assets/scripting/SharedData.json create mode 100644 android/assets/scripting/enginedata/lua/main.lua rename android/assets/scripting/{ => enginedata}/python/main.py (96%) create mode 100644 android/assets/scripting/enginedata/python/unciv/__init__.py rename android/assets/scripting/{ => enginedata}/qjs/main.js (55%) create mode 100644 android/assets/scripting/lua.json create mode 100644 android/assets/scripting/python.json create mode 100644 android/assets/scripting/qjs.json create mode 100644 android/assets/scripting/shareddata/ScriptAPI.json rename core/src/com/unciv/scripting/backends/{ => bases}/ScriptingBackendBase.kt (100%) diff --git a/android/assets/scripting/Scripts.json b/android/assets/scripting/Scripts.json deleted file mode 100644 index 0081c0c27067b..0000000000000 --- a/android/assets/scripting/Scripts.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - python: { - main.py: null - } - -} diff --git a/android/assets/scripting/SharedData.json b/android/assets/scripting/SharedData.json new file mode 100644 index 0000000000000..18f8196212c9a --- /dev/null +++ b/android/assets/scripting/SharedData.json @@ -0,0 +1,3 @@ +[ + ScriptAPI.json +] diff --git a/android/assets/scripting/enginedata/lua/main.lua b/android/assets/scripting/enginedata/lua/main.lua new file mode 100644 index 0000000000000..ce1779f704789 --- /dev/null +++ b/android/assets/scripting/enginedata/lua/main.lua @@ -0,0 +1,16 @@ + +io.stdout:setvbuf('full') + +io.stdout:write("\n\nWelcome to the Lua Unciv CLI. Currently, this backend relies on launching the system `lua` command.\n\nRunning ".._VERSION..".\n\n") +io.stdout:flush() + +while true do + _in = io.stdin:read() + io.stdout:write("> ".._in.."\n") + _, _out = pcall(load(_in)) + if not _ then + _, _out = pcall(load("return ".._in)) + end + io.stdout:write((_out or "").."\n") + io.stdout:flush() +end diff --git a/android/assets/scripting/python/main.py b/android/assets/scripting/enginedata/python/main.py similarity index 96% rename from android/assets/scripting/python/main.py rename to android/assets/scripting/enginedata/python/main.py index 68f654055b9d3..cc419bc60a95e 100644 --- a/android/assets/scripting/python/main.py +++ b/android/assets/scripting/enginedata/python/main.py @@ -181,7 +181,14 @@ class ForeignActionTransceiver(ForeignActionReceiver, ForeignActionSender): #t.SendForeignRead("ForeignActionTransceiver.__name__") -print('sys.implementation == ' + str(sys.implementation)) +print(f""" + +Welcome to the CPython Unciv CLI. Currently, this backend relies on launching the system `python3` command. + +sys.implementation == {str(sys.implementation)} + +""") + stdout = sys.stdout while True: line = sys.stdin.readline() @@ -195,6 +202,6 @@ class ForeignActionTransceiver(ForeignActionReceiver, ForeignActionSender): else: print(eval(code)) except Exception as e: - print(repr(e), file=stdout) + print(repr(e), file=stdout, flush=True) else: - print(out.getvalue(), file=stdout) + print(out.getvalue(), file=stdout, flush=True) diff --git a/android/assets/scripting/enginedata/python/unciv/__init__.py b/android/assets/scripting/enginedata/python/unciv/__init__.py new file mode 100644 index 0000000000000..01c0f3f45dfed --- /dev/null +++ b/android/assets/scripting/enginedata/python/unciv/__init__.py @@ -0,0 +1 @@ +### diff --git a/android/assets/scripting/qjs/main.js b/android/assets/scripting/enginedata/qjs/main.js similarity index 55% rename from android/assets/scripting/qjs/main.js rename to android/assets/scripting/enginedata/qjs/main.js index f1a2e3d97ecc1..fc896132e627c 100644 --- a/android/assets/scripting/qjs/main.js +++ b/android/assets/scripting/enginedata/qjs/main.js @@ -1,5 +1,8 @@ //import("std"); +std.out.puts("\n\nWelcome to the QuickJS Unciv CLI. Currently, this backend relies on launching the system `qjs` command.\n\n") +std.out.flush() + while (true) { let line = std.in.getline(); let out = `qjs > ${line}\n`; @@ -10,4 +13,5 @@ while (true) { } out += "\n" std.out.puts(out) + std.out.flush() } diff --git a/android/assets/scripting/lua.json b/android/assets/scripting/lua.json new file mode 100644 index 0000000000000..505dba85fa4e0 --- /dev/null +++ b/android/assets/scripting/lua.json @@ -0,0 +1,4 @@ +[ + unciv/ + main.lua +] diff --git a/android/assets/scripting/python.json b/android/assets/scripting/python.json new file mode 100644 index 0000000000000..1a6b4bd707019 --- /dev/null +++ b/android/assets/scripting/python.json @@ -0,0 +1,5 @@ +[ + unciv/ + unciv/__init__.py + main.py +] diff --git a/android/assets/scripting/qjs.json b/android/assets/scripting/qjs.json new file mode 100644 index 0000000000000..dc62efc764afb --- /dev/null +++ b/android/assets/scripting/qjs.json @@ -0,0 +1,4 @@ +[ + unciv/ + main.js +] diff --git a/android/assets/scripting/shareddata/ScriptAPI.json b/android/assets/scripting/shareddata/ScriptAPI.json new file mode 100644 index 0000000000000..0967ef424bce6 --- /dev/null +++ b/android/assets/scripting/shareddata/ScriptAPI.json @@ -0,0 +1 @@ +{} diff --git a/core/src/com/unciv/UncivGame.kt b/core/src/com/unciv/UncivGame.kt index 676c08ae9c025..b02f7cbfcc668 100644 --- a/core/src/com/unciv/UncivGame.kt +++ b/core/src/com/unciv/UncivGame.kt @@ -74,7 +74,7 @@ class UncivGame(parameters: UncivGameParameters) : Game() { val translations = Translations() - lateinit var scriptingState: ScriptingState + lateinit var scriptingState: ScriptingState // Could probably replace these with lazies. lateinit var consoleScreen: ConsoleScreen // Keep same ConsoleScreen() when possible, to avoid having to manually persist/restore history, input field, etc. override fun create() { diff --git a/core/src/com/unciv/scripting/ScriptingBackend.kt b/core/src/com/unciv/scripting/ScriptingBackend.kt index 233ddaf0db954..d35016a7a36fb 100644 --- a/core/src/com/unciv/scripting/ScriptingBackend.kt +++ b/core/src/com/unciv/scripting/ScriptingBackend.kt @@ -1,10 +1,12 @@ package com.unciv.scripting import com.badlogic.gdx.Gdx +import com.badlogic.gdx.files.FileHandle -import com.unciv.scripting.reflection.* +import com.unciv.scripting.reflection.Reflection import com.unciv.scripting.protocol.ScriptingReplManager import com.unciv.scripting.protocol.SubprocessBlackbox +import com.unciv.scripting.utils.SourceManager import kotlin.reflect.full.* import java.util.* @@ -185,7 +187,7 @@ class HardcodedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacken val startindex = if (detailed) 2 else 1 val path = (if (args.size > startindex) args.slice(startindex..args.size-1) else listOf()).joinToString(" ") try { - var obj = evalKotlinString(scriptingScope, path) + var obj = Reflection.evalKotlinString(scriptingScope, path) val isnull = obj == null appendOut( if (detailed) @@ -204,10 +206,10 @@ class HardcodedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacken if (cheats) { try { val path = (if (args.size > 2) args.slice(2..args.size-1) else listOf()).joinToString(" ") - val value = evalKotlinString(scriptingScope, args[1]) - setInstancePath( + val value = Reflection.evalKotlinString(scriptingScope, args[1]) + Reflection.setInstancePath( scriptingScope, - parseKotlinPath(path), + Reflection.parseKotlinPath(path), value ) appendOut("Set ${path} to ${value}.") @@ -284,13 +286,13 @@ class ReflectiveScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacke if (comm != null) { val params = command.drop(comm.length+1).split(' ', limit=commandparams[comm]!!) val workingcode = params[params.size-1] - val workingpath = parseKotlinPath(workingcode) - if (workingpath.any{ it.type == PathElementType.Call }) { + val workingpath = Reflection.parseKotlinPath(workingcode) + if (workingpath.any{ it.type == Reflection.PathElementType.Call }) { return AutocompleteResults(listOf(), true, "No autocomplete available for function calls.") } val leafname = if (workingpath.size > 0) workingpath[workingpath.size - 1].name else "" val prefix = command.dropLast(leafname.length) - val branchobj = resolveInstancePath(scriptingScope, workingpath.slice(0..workingpath.size-2)) + val branchobj = Reflection.resolveInstancePath(scriptingScope, workingpath.slice(0..workingpath.size-2)) return AutocompleteResults( branchobj!!::class.members .map{ it.name } @@ -313,20 +315,20 @@ class ReflectiveScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacke try { when (parts[0]) { "get" -> { - appendOut("${evalKotlinString(scriptingScope, parts[1])}") + appendOut("${Reflection.evalKotlinString(scriptingScope, parts[1])}") } "set" -> { var setparts = parts[1].split(' ', limit=2) - var value = evalKotlinString(scriptingScope, setparts[0]) - setInstancePath( + var value = Reflection.evalKotlinString(scriptingScope, setparts[0]) + Reflection.setInstancePath( scriptingScope, - parseKotlinPath(setparts[1]), + Reflection.parseKotlinPath(setparts[1]), value ) appendOut("Set ${setparts[1]} to ${value}") } "typeof" -> { - var obj = evalKotlinString(scriptingScope, parts[1]) + var obj = Reflection.evalKotlinString(scriptingScope, parts[1]) appendOut("${if (obj == null) null else obj!!::class.qualifiedName}") } else -> { @@ -341,56 +343,22 @@ class ReflectiveScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacke } -/*class QjsScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackendBase(scriptingScope) { - - companion object Metadata: ScriptingBackend_metadata { - override fun new(scriptingScope: ScriptingScope) = QjsScriptingBackend(scriptingScope) - override val displayname:String = "QuickJS" - } - - override fun motd(): String { - return "\n\nWelcome to the QuickJS Unciv CLI, which doesn't currently run QuickJS but might one day!\n" - } -}*/ - - -/*class LuaScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackendBase(scriptingScope) { - - companion object Metadata: ScriptingBackend_metadata { - override fun new(scriptingScope: ScriptingScope) = LuaScriptingBackend(scriptingScope) - override val displayname:String = "Lua" - } - - override fun motd(): String { - return "\n\nWelcome to the Lua Unciv CLI, which doesn't currently run Lua but might one day!\n" - } -}*/ - -/* -class UpyScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackendBase(scriptingScope) { - - companion object Metadata: ScriptingBackend_metadata { - override fun new(scriptingScope: ScriptingScope) = UpyScriptingBackend(scriptingScope) - override val displayname:String = "MicroPython" - } - - override fun motd(): String { - return "\n\nWelcome to the MicroPython Unciv CLI, which doesn't currently run MicroPython but might one day!\n" - } +open class EnvironmentedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackendBase(scriptingScope) { + + open val engine = "" + + val folderHandle: FileHandle by lazy { SourceManager.setupInterpreterEnvironment(engine) } + } -*/ -open class SubprocessScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackendBase(scriptingScope) { + +open class SubprocessScriptingBackend(scriptingScope: ScriptingScope): EnvironmentedScriptingBackend(scriptingScope) { open val processCmd = arrayOf("") - open val replSoftExitCode = "" - - lateinit var replManager: ScriptingReplManager + val replManager: ScriptingReplManager by lazy { ScriptingReplManager(scriptingScope, SubprocessBlackbox(processCmd)) } - fun startProcess() { - replManager = ScriptingReplManager(scriptingScope, SubprocessBlackbox(processCmd)) - } + open val replSoftExitCode = "" override fun exec(command: String): String { try { @@ -424,27 +392,19 @@ class SpyScriptingBackend(scriptingScope: ScriptingScope): SubprocessScriptingBa override fun new(scriptingScope: ScriptingScope) = SpyScriptingBackend(scriptingScope) override val displayname:String = "System Python" } - - val pyBoilerPlate = Gdx.files.internal("scripting/python/main.py").readString(Charsets.UTF_8.name()) - - override val processCmd = arrayOf("python3", "-u", "-X", "utf8", "-c", pyBoilerPlate) - // With pipes, Python automatically execs STDIN: `python3 -c 'print("print(5+5)")' | python3`. - // When run in interactive mode, it doesn't do this though: `python3` `echo 'print(5)\n' > /proc/$(pgrep python)/fd/0`. - // As such, I have it manually loop through STDIN to get around this: `echo -e "import sys\nwhile True: exec(sys.stdin.readline())" > pyloop.py; python3 pyloop.py` `echo 'print(5)\n' > /proc/$(pgrep python)/fd/0`. + + override val engine = "python" + + override val processCmd by lazy { arrayOf("python3", "-u", "-X", "utf8", folderHandle.child("main.py").toString()) } + override val replSoftExitCode = """ try: exit() finally: print("Exiting.") """.trimIndent() - - init { - startProcess() - } - override fun motd(): String { - return "\n\nWelcome to the CPython Unciv CLI. Currently, this backend relies on launching the system Python 3 installation.\n\n${replManager.blackbox.readAll(block=true).joinToString("\n")}\n\n" - } + override fun motd() = replManager.blackbox.readAll(block=true).joinToString("\n") } @@ -456,16 +416,14 @@ class SqjsScriptingBackend(scriptingScope: ScriptingScope): SubprocessScriptingB override val displayname:String = "System QuickJS" } - val jsBoilerPlate = """print(1); print(5); while (true) { print(String(eval(std.in.getline()))+"\n") }""".trimIndent() - - override val processCmd = arrayOf("qjs", "--std", "--eval", jsBoilerPlate) + override val engine = "qjs" - init { - startProcess() - } + override val processCmd by lazy { arrayOf("qjs", "--std", "--script", folderHandle.child("main.js").toString()) } override fun softStopProcess() {} + override fun motd() = replManager.blackbox.readAll(block=true).joinToString("\n") + } @@ -473,23 +431,17 @@ class SluaScriptingBackend(scriptingScope: ScriptingScope): SubprocessScriptingB companion object Metadata: ScriptingBackend_metadata { override fun new(scriptingScope: ScriptingScope) = SluaScriptingBackend(scriptingScope) - override val displayname:String = "System LUA" + override val displayname:String = "System Lua" } - val luaBoilerPlate = """ - while true do - io.stdout:write(io.stdin:read()) - end - """.trimIndent() + override val engine = "lua" - override val processCmd = arrayOf("lua", "-e", luaBoilerPlate) - - init { - startProcess() - } + override val processCmd by lazy { arrayOf("lua", folderHandle.child("main.lua").toString()) } override fun softStopProcess() {} + override fun motd() = replManager.blackbox.readAll(block=true).joinToString("\n") + } @@ -497,8 +449,6 @@ enum class ScriptingBackendType(val metadata:ScriptingBackend_metadata) { Dummy(ScriptingBackendBase), Hardcoded(HardcodedScriptingBackend), Reflective(ReflectiveScriptingBackend), - //QuickJS(QjsScriptingBackend), - //Lua(LuaScriptingBackend), //MicroPython(UpyScriptingBackend), SystemPython(SpyScriptingBackend), SystemQuickJS(SqjsScriptingBackend), diff --git a/core/src/com/unciv/scripting/ScriptingScope.kt b/core/src/com/unciv/scripting/ScriptingScope.kt index 06e524a097498..51ee9cde2cd38 100644 --- a/core/src/com/unciv/scripting/ScriptingScope.kt +++ b/core/src/com/unciv/scripting/ScriptingScope.kt @@ -17,5 +17,5 @@ class ScriptingScope( // Mostly `.civInfo`/.`gameInfo`, but could be cool to E.G. allow loading and making saves through CLI/API too. // Also where to put any `PlayerAPI`, `CheatAPI`, `ModAPI`, etc. // For `LuaScriptingBackend`, `UpyScriptingBackend`, `QjsScriptingBackend`, etc, the hierarchy of data under this class definition should probably directly mirror the wrappers in the namespace exposed to the scripting language. - // `WorldScreen` would give access to `UnitTable.selectedUnit`, `MapHolder.selectedTile`, etc. + // `WorldScreen` gives access to `UnitTable.selectedUnit`, `MapHolder.selectedTile`, etc. Useful for contextual operations. } diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index ed261fbc68dee..8b83fa2da7a36 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -10,19 +10,28 @@ import kotlin.math.min /* ``` +The major classes added and changed by this PR are structured as follows. UpperCamelCase() and parentheses means a new instantiation of a class. lowerCamelCase means a reference to an already-existing instance. An asterisk at the start of an item means zero or multiple instances of that class may be held. A question mark at the start of an item means that it may not exist in all implementations of the parent base class/interface. A question mark at the end of an item means that it is nullable, or otherwise may not be set in all states. + UncivGame(): - ScriptingState(): + ScriptingState(): // Persistent per UncivGame(). ScriptingScope(): - civInfo - gameInfo + civInfo? // These are set by WorldScreen init, and unset by MainMenuScreen. + gameInfo? uncivGame - worldScreen + worldScreen? *ScriptingBackend(): scriptingScope ?ScriptingReplManager(): - Blackbox() - ConsoleScreen(): + Blackbox() // Common interface to wrap foreign interpreter with pipes, STDIN/STDOUT, queues, sockets, embedding, JNI, etc. + ?folderHandler: setupInterpreterEnvironment() // If used, a temporary directory with file structure copied from engine and shared folders in `assets/scripting`. + ConsoleScreen(): // Persistent as long as window isn't resized. Recreates itself and restores most of its state from scriptingState if resized. scriptingState +WorldScreen(): + consoleScreen + scriptingState // ScriptingState has getters and setters that wrap scriptingScope, which WorldScreen uses to update game info. +MainMenuScreen(): + consoleScreen + scriptingState // Same as for worldScreen. ``` */ diff --git a/core/src/com/unciv/scripting/backends/ScriptingBackendBase.kt b/core/src/com/unciv/scripting/backends/bases/ScriptingBackendBase.kt similarity index 100% rename from core/src/com/unciv/scripting/backends/ScriptingBackendBase.kt rename to core/src/com/unciv/scripting/backends/bases/ScriptingBackendBase.kt diff --git a/core/src/com/unciv/scripting/reflection/Reflection.kt b/core/src/com/unciv/scripting/reflection/Reflection.kt index 35e0ab7946726..04504cc7dfe28 100644 --- a/core/src/com/unciv/scripting/reflection/Reflection.kt +++ b/core/src/com/unciv/scripting/reflection/Reflection.kt @@ -7,208 +7,210 @@ import kotlin.reflect.KMutableProperty1 import java.util.* -@Suppress("UNCHECKED_CAST") -fun readInstanceProperty(instance: Any, propertyName: String): R? { - // From https://stackoverflow.com/a/35539628/12260302 - val property = instance::class.members - .first { it.name == propertyName } as KProperty1 - return property.get(instance) as R? -} +object Reflection { + @Suppress("UNCHECKED_CAST") + fun readInstanceProperty(instance: Any, propertyName: String): R? { + // From https://stackoverflow.com/a/35539628/12260302 + val property = instance::class.members + .first { it.name == propertyName } as KProperty1 + return property.get(instance) as R? + } -fun readInstanceItem(instance: Any, keyOrIndex: Any): Any? { - if (keyOrIndex is Int) { - return (instance as List)[keyOrIndex] - } else { - return (instance as Map)[keyOrIndex] + fun readInstanceItem(instance: Any, keyOrIndex: Any): Any? { + if (keyOrIndex is Int) { + return (instance as List)[keyOrIndex] + } else { + return (instance as Map)[keyOrIndex] + } } -} -fun setInstanceProperty(instance: Any, propertyName: String, value: T?): Unit { - val property = instance::class.members - .first { it.name == propertyName } as KMutableProperty1 - property.set(instance, value) -} + fun setInstanceProperty(instance: Any, propertyName: String, value: T?): Unit { + val property = instance::class.members + .first { it.name == propertyName } as KMutableProperty1 + property.set(instance, value) + } -interface PathElementArg { - val value: Any -} + interface PathElementArg { + val value: Any + } -data class PathElementArgString(override val value: String): PathElementArg -data class PathElementArgInt(override val value: Int): PathElementArg -data class PathElementArgFloat(override val value: Float): PathElementArg -data class PathElementArgBoolean(override val value: Boolean): PathElementArg + data class PathElementArgString(override val value: String): PathElementArg + data class PathElementArgInt(override val value: Int): PathElementArg + data class PathElementArgFloat(override val value: Float): PathElementArg + data class PathElementArgBoolean(override val value: Boolean): PathElementArg -enum class PathElementType() { - Property(), - Key(), - //Index(), - Call() -} + enum class PathElementType() { + Property(), + Key(), + //Index(), + Call() + } -data class PathElement( - val type: PathElementType, - val name: String, - //val args: Collection, - //For IPC with an actual interpreter, it should be possible to pass JSON arrays of basic types instead of just parsing the string. - //Mostly I'm not sure how and where to cleanly determine whether to use the args field, or parse the string field. - val doEval: Boolean = false -) - - -private val brackettypes: Map = mapOf( - '[' to "[]", - '(' to "()" -) - -private val bracketmeanings: Map = mapOf( - "[]" to PathElementType.Key, - "()" to PathElementType.Call -) - -fun parseKotlinPath(text: String): List { - var path:MutableList = ArrayList() - var curr_type = PathElementType.Property - var curr_name = ArrayList() - var curr_brackets = "" - var curr_bracketdepth = 0 - var just_closed_brackets = true - for (char in text) { - if (curr_bracketdepth == 0) { - if (char == '.') { - if (!just_closed_brackets) { - path.add(PathElement(PathElementType.Property, curr_name.joinToString(""))) + data class PathElement( + val type: PathElementType, + val name: String, + //val args: Collection, + //For IPC with an actual interpreter, it should be possible to pass JSON arrays of basic types instead of just parsing the string. + //Mostly I'm not sure how and where to cleanly determine whether to use the args field, or parse the string field. + val doEval: Boolean = false + ) + + + private val brackettypes: Map = mapOf( + '[' to "[]", + '(' to "()" + ) + + private val bracketmeanings: Map = mapOf( + "[]" to PathElementType.Key, + "()" to PathElementType.Call + ) + + fun parseKotlinPath(text: String): List { + var path:MutableList = ArrayList() + var curr_type = PathElementType.Property + var curr_name = ArrayList() + var curr_brackets = "" + var curr_bracketdepth = 0 + var just_closed_brackets = true + for (char in text) { + if (curr_bracketdepth == 0) { + if (char == '.') { + if (!just_closed_brackets) { + path.add(PathElement(PathElementType.Property, curr_name.joinToString(""))) + } + curr_name.clear() + just_closed_brackets = false + continue + } + if (char in brackettypes) { + if (!just_closed_brackets) { + path.add(PathElement(PathElementType.Property, curr_name.joinToString(""))) + } + curr_name.clear() + curr_brackets = brackettypes[char]!! + curr_bracketdepth += 1 + just_closed_brackets = false + continue } - curr_name.clear() - just_closed_brackets = false - continue + curr_name.add(char) } - if (char in brackettypes) { - if (!just_closed_brackets) { - path.add(PathElement(PathElementType.Property, curr_name.joinToString(""))) + just_closed_brackets = false + if (curr_bracketdepth > 0) { + if (char == curr_brackets[1]) { + curr_bracketdepth -= 1 + if (curr_bracketdepth == 0) { + path.add(PathElement( + bracketmeanings[curr_brackets]!!, + curr_name.joinToString(""), + true + )) + curr_brackets = "" + curr_name.clear() + just_closed_brackets = true + continue + } + } else if (char == curr_brackets[0]) { + curr_bracketdepth += 1 } - curr_name.clear() - curr_brackets = brackettypes[char]!! - curr_bracketdepth += 1 - just_closed_brackets = false - continue + curr_name.add(char) } - curr_name.add(char) } - just_closed_brackets = false + if (!just_closed_brackets && curr_bracketdepth == 0) { + path.add(PathElement(PathElementType.Property, curr_name.joinToString(""))) + curr_name.clear() + } if (curr_bracketdepth > 0) { - if (char == curr_brackets[1]) { - curr_bracketdepth -= 1 - if (curr_bracketdepth == 0) { - path.add(PathElement( - bracketmeanings[curr_brackets]!!, - curr_name.joinToString(""), - true - )) - curr_brackets = "" - curr_name.clear() - just_closed_brackets = true - continue - } - } else if (char == curr_brackets[0]) { - curr_bracketdepth += 1 - } - curr_name.add(char) + throw IllegalArgumentException("Unclosed parentheses.") } + return path } - if (!just_closed_brackets && curr_bracketdepth == 0) { - path.add(PathElement(PathElementType.Property, curr_name.joinToString(""))) - curr_name.clear() + + + fun stringifyKotlinPath() { } - if (curr_bracketdepth > 0) { - throw IllegalArgumentException("Unclosed parentheses.") + + + fun resolveInstancePath(instance: Any, path: List): Any? { + var obj: Any? = instance + for (element in path) { + when (element.type) { + PathElementType.Property -> { + obj = readInstanceProperty(obj!!, element.name) + } + PathElementType.Key -> { + obj = readInstanceItem( + obj!!, + if (element.doEval) + evalKotlinString(instance!!, element.name)!! + else + element.name + ) + } + PathElementType.Call -> { + throw UnsupportedOperationException("Calls not implemented.") + } + else -> { + throw UnsupportedOperationException("Unknown path element type: ${element.type}") + } + } + } + return obj } - return path -} -fun stringifyKotlinPath() { -} + fun evalKotlinString(scope: Any, string: String): Any? { + val trimmed = string.trim(' ') + if (trimmed == "null") { + return null + } + if (trimmed == "true") { + return true + } + if (trimmed == "false") { + return false + } + if (trimmed.length > 1 && trimmed.startsWith('"') && trimmed.endsWith('"')) { + return trimmed.slice(1..trimmed.length-2) + } + val asint = trimmed.toIntOrNull() + if (asint != null) { + return asint + } + val asfloat = trimmed.toFloatOrNull() + if (asfloat != null) { + return asfloat + } + return resolveInstancePath(scope, parseKotlinPath(trimmed)) + } -fun resolveInstancePath(instance: Any, path: List): Any? { - var obj: Any? = instance - for (element in path) { - when (element.type) { + fun setInstancePath(instance: Any, path: List, value: Any?): Unit { + val leafobj = resolveInstancePath(instance, path.slice(0..path.size-2)) + val leafelement = path[path.size - 1] + when (leafelement.type) { PathElementType.Property -> { - obj = readInstanceProperty(obj!!, element.name) + setInstanceProperty(leafobj!!, leafelement.name, value) } PathElementType.Key -> { - obj = readInstanceItem( - obj!!, - if (element.doEval) - evalKotlinString(instance!!, element.name)!! + throw UnsupportedOperationException("Keys not implemented.") + leafobj = readInstanceItem( + leafobj!!, + if (leafelement.doEval) + evalKotlinString(instance, leafelement.name)!! else - element.name + leafelement.name ) } PathElementType.Call -> { - throw UnsupportedOperationException("Calls not implemented.") + throw UnsupportedOperationException("Cannot assign to function call.") } else -> { - throw UnsupportedOperationException("Unknown path element type: ${element.type}") + throw UnsupportedOperationException("Unknown path element type: ${leafelement.type}") } } } - return obj -} - - -fun evalKotlinString(scope: Any, string: String): Any? { - val trimmed = string.trim(' ') - if (trimmed == "null") { - return null - } - if (trimmed == "true") { - return true - } - if (trimmed == "false") { - return false - } - if (trimmed.length > 1 && trimmed.startsWith('"') && trimmed.endsWith('"')) { - return trimmed.slice(1..trimmed.length-2) - } - val asint = trimmed.toIntOrNull() - if (asint != null) { - return asint - } - val asfloat = trimmed.toFloatOrNull() - if (asfloat != null) { - return asfloat - } - return resolveInstancePath(scope, parseKotlinPath(trimmed)) -} - - -fun setInstancePath(instance: Any, path: List, value: Any?): Unit { - val leafobj = resolveInstancePath(instance, path.slice(0..path.size-2)) - val leafelement = path[path.size - 1] - when (leafelement.type) { - PathElementType.Property -> { - setInstanceProperty(leafobj!!, leafelement.name, value) - } - PathElementType.Key -> { - throw UnsupportedOperationException("Keys not implemented.") - leafobj = readInstanceItem( - leafobj!!, - if (leafelement.doEval) - evalKotlinString(instance, leafelement.name)!! - else - leafelement.name - ) - } - PathElementType.Call -> { - throw UnsupportedOperationException("Cannot assign to function call.") - } - else -> { - throw UnsupportedOperationException("Unknown path element type: ${leafelement.type}") - } - } } diff --git a/core/src/com/unciv/scripting/utils/SourceManager.kt b/core/src/com/unciv/scripting/utils/SourceManager.kt index 227e350d8aef5..c4406efda58b3 100644 --- a/core/src/com/unciv/scripting/utils/SourceManager.kt +++ b/core/src/com/unciv/scripting/utils/SourceManager.kt @@ -1,3 +1,62 @@ package com.unciv.scripting.utils +import com.badlogic.gdx.Gdx +import com.badlogic.gdx.files.FileHandle +import com.unciv.JsonParser +import kotlin.concurrent.thread + +object SourceManager { + + val scriptingAssets = Gdx.files.internal("scripting/") + val enginedataAssets = scriptingAssets.child("enginedata/") + + val shareddataAssets = scriptingAssets.child("shareddata/") + val shareddataAssetsList = scriptingAssets.child("SharedData.json") + + val shareddataFilelist = JsonParser().getFromJson(Array::class.java, shareddataAssetsList) + + private fun getEngineFilelistPath(engine: String): FileHandle { + return scriptingAssets.child("${engine}.json") + } + + private fun getEngineFilelist(engine: String): Array { + // https://github.com/libgdx/libgdx/issues/4074 + // https://github.com/libgdx/libgdx/wiki/Reading-and-writing-JSON + // Apparently identifying and listing internal directories doesn't work on Desktop, as all assets are put on the classpath. + // So all the files for an engine should be listed in a flat .JSON instead. + return JsonParser().getFromJson( + Array::class.java, + getEngineFilelistPath(engine) + ) + } + + private fun getEngineLibraries(engine: String): FileHandle { + return enginedataAssets.child("${engine}/") + } + + fun setupInterpreterEnvironment(engine: String): FileHandle { + val enginedir = getEngineLibraries(engine) + val outdir = FileHandle.tempDirectory("unciv-${engine}_") + fun addfile(sourcedir: FileHandle, path: String) { + var target = outdir.child(path) + if (path.endsWith("/")) { + target.mkdirs() + } else { + sourcedir.child(path).copyTo(target) + } + } + for (fp in shareddataFilelist) { + addfile(shareddataAssets, fp) + } + for (fp in getEngineFilelist(engine)) { + addfile(enginedir, fp) + } + Runtime.getRuntime().addShutdownHook( + thread(start = false, name = "Delete ${outdir.toString()}") { + outdir.deleteDirectory() + } + ) + return outdir + } +} From ac42cf9325f81564b9d8aa58cd3dbae5c6a0ef93 Mon Sep 17 00:00:00 2001 From: will-ca Date: Fri, 5 Nov 2021 17:06:50 +0000 Subject: [PATCH 23/93] Implement reflective function calls. --- .../com/unciv/scripting/ScriptingBackend.kt | 5 ++ .../protocol/ScriptingReplReflector.kt | 21 ++++++ .../unciv/scripting/reflection/Reflection.kt | 73 +++++++++++++++---- 3 files changed, 83 insertions(+), 16 deletions(-) diff --git a/core/src/com/unciv/scripting/ScriptingBackend.kt b/core/src/com/unciv/scripting/ScriptingBackend.kt index d35016a7a36fb..87d64f7b4084e 100644 --- a/core/src/com/unciv/scripting/ScriptingBackend.kt +++ b/core/src/com/unciv/scripting/ScriptingBackend.kt @@ -348,6 +348,8 @@ open class EnvironmentedScriptingBackend(scriptingScope: ScriptingScope): Script open val engine = "" val folderHandle: FileHandle by lazy { SourceManager.setupInterpreterEnvironment(engine) } + // This requires the overridden values for `engine`, so setting it in the constructor causes a null error. + // Also, SubprocessScriptingBackend inherits from this, but not all subclasses of SubprocessScriptingBackend might need it. So as long as it's not accessed, it won't be intialized. } @@ -357,6 +359,8 @@ open class SubprocessScriptingBackend(scriptingScope: ScriptingScope): Environme open val processCmd = arrayOf("") val replManager: ScriptingReplManager by lazy { ScriptingReplManager(scriptingScope, SubprocessBlackbox(processCmd)) } + // Was originally a method that could be called by subclasses' constructors. This seems cleaner. Subclasses don't even have to define any functions this way. + // Downside: Potential latency on first command, or possibly depending on `motd()` for immediate initialization. open val replSoftExitCode = "" @@ -396,6 +400,7 @@ class SpyScriptingBackend(scriptingScope: ScriptingScope): SubprocessScriptingBa override val engine = "python" override val processCmd by lazy { arrayOf("python3", "-u", "-X", "utf8", folderHandle.child("main.py").toString()) } + // Hm. I suppose this probably doesn't actually need to be lazy, as long as folderHandle is lazy and already available. override val replSoftExitCode = """ try: diff --git a/core/src/com/unciv/scripting/protocol/ScriptingReplReflector.kt b/core/src/com/unciv/scripting/protocol/ScriptingReplReflector.kt index 297b9024e6b43..417a9944739c1 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingReplReflector.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingReplReflector.kt @@ -1,8 +1,29 @@ package com.unciv.scripting.protocol import com.unciv.scripting.ScriptingScope +import java.lang.System class ScriptingReplReflector(scriptingScope: ScriptingScope) { + + companion object { + + val kotlinObjectIdPrefix = "_unciv-kt-obj@" + + fun idKotlinObject(value: Any?): String { + //Don't depend on parsing this for extracting information. + //It's basically a readable/dumb UUID. + //Actually, it might be better to just use a UUID. I think that was actually my original plan. + return "${kotlinObjectIdPrefix}${System.identityHashCode(value)}:${if (value == null) "null" else value!!::class.qualifiedName}/${value.toString()}" + } + + fun isKotlinObject(value: Any?): Boolean { + return value is String && value!!.startsWith(kotlinObjectIdPrefix) + } + } + + val ScriptingObjectCache = mutableMapOf() + + fun resolvePath() { } } diff --git a/core/src/com/unciv/scripting/reflection/Reflection.kt b/core/src/com/unciv/scripting/reflection/Reflection.kt index 04504cc7dfe28..28a57d932d905 100644 --- a/core/src/com/unciv/scripting/reflection/Reflection.kt +++ b/core/src/com/unciv/scripting/reflection/Reflection.kt @@ -1,9 +1,10 @@ package com.unciv.scripting.reflection import kotlin.collections.ArrayList -import kotlin.reflect.KProperty1 +import kotlin.reflect.KCallable import kotlin.reflect.KFunction import kotlin.reflect.KMutableProperty1 +import kotlin.reflect.KProperty1 import java.util.* @@ -15,6 +16,12 @@ object Reflection { .first { it.name == propertyName } as KProperty1 return property.get(instance) as R? } + + fun readInstanceMethod(instance: Any, methodName: String): KCallable { + val method = instance::class.members + .first { it.name == methodName } as KCallable + return method + } fun readInstanceItem(instance: Any, keyOrIndex: Any): Any? { if (keyOrIndex is Int) { @@ -31,7 +38,7 @@ object Reflection { property.set(instance, value) } - + /* interface PathElementArg { val value: Any } @@ -40,22 +47,20 @@ object Reflection { data class PathElementArgInt(override val value: Int): PathElementArg data class PathElementArgFloat(override val value: Float): PathElementArg data class PathElementArgBoolean(override val value: Boolean): PathElementArg - + */ enum class PathElementType() { Property(), Key(), - //Index(), Call() } data class PathElement( val type: PathElementType, val name: String, - //val args: Collection, - //For IPC with an actual interpreter, it should be possible to pass JSON arrays of basic types instead of just parsing the string. - //Mostly I'm not sure how and where to cleanly determine whether to use the args field, or parse the string field. - val doEval: Boolean = false + val doEval: Boolean = false, + val params: List = listOf() + //For key and index accesses, and function calls, evaluate `name` instead of using `params`. ) @@ -69,18 +74,21 @@ object Reflection { "()" to PathElementType.Call ) - fun parseKotlinPath(text: String): List { + fun parseKotlinPath(code: String): List { var path:MutableList = ArrayList() var curr_type = PathElementType.Property var curr_name = ArrayList() var curr_brackets = "" var curr_bracketdepth = 0 var just_closed_brackets = true - for (char in text) { + for (char in code) { if (curr_bracketdepth == 0) { if (char == '.') { if (!just_closed_brackets) { - path.add(PathElement(PathElementType.Property, curr_name.joinToString(""))) + path.add(PathElement( + PathElementType.Property, + curr_name.joinToString("") + )) } curr_name.clear() just_closed_brackets = false @@ -88,7 +96,10 @@ object Reflection { } if (char in brackettypes) { if (!just_closed_brackets) { - path.add(PathElement(PathElementType.Property, curr_name.joinToString(""))) + path.add(PathElement( + PathElementType.Property, + curr_name.joinToString("") + )) } curr_name.clear() curr_brackets = brackettypes[char]!! @@ -120,7 +131,10 @@ object Reflection { } } if (!just_closed_brackets && curr_bracketdepth == 0) { - path.add(PathElement(PathElementType.Property, curr_name.joinToString(""))) + path.add(PathElement( + PathElementType.Property, + curr_name.joinToString("") + )) curr_name.clear() } if (curr_bracketdepth > 0) { @@ -132,14 +146,33 @@ object Reflection { fun stringifyKotlinPath() { } + + private val closingbrackets = null + + fun splitToplevelExprs(code: String): List { + return code.split(',').map{ it.trim(' ') } + var segs = ArrayList() + val bracketdepths = mutableMapOf( + *brackettypes.keys.map{ it to 0 }.toTypedArray() + ) + //TODO: Actually try to parse for parenthesization, strings, etc. + } fun resolveInstancePath(instance: Any, path: List): Any? { var obj: Any? = instance + var lastobj0: Any? = null + var lastobj1: Any? = null // Keep the second last object traversed, for function calls to bind to. for (element in path) { + lastobj1 = lastobj0 + lastobj0 = obj when (element.type) { PathElementType.Property -> { - obj = readInstanceProperty(obj!!, element.name) + try { + obj = readInstanceProperty(obj!!, element.name) + } catch (e: ClassCastException) { + obj = readInstanceMethod(obj!!, element.name) + } } PathElementType.Key -> { obj = readInstanceItem( @@ -147,11 +180,19 @@ object Reflection { if (element.doEval) evalKotlinString(instance!!, element.name)!! else - element.name + element.params[0] ) } PathElementType.Call -> { - throw UnsupportedOperationException("Calls not implemented.") + obj = (obj as KCallable).call( + lastobj1!!, + *( + if (element.doEval) + splitToplevelExprs(element.name).map{ evalKotlinString(instance!!, it) } + else + element.params + ).toTypedArray() + ) } else -> { throw UnsupportedOperationException("Unknown path element type: ${element.type}") From f6f05c57846433bbacf856a1ec3a57d439b4470d Mon Sep 17 00:00:00 2001 From: will-ca Date: Sun, 7 Nov 2021 07:04:01 +0000 Subject: [PATCH 24/93] Implement IPC protocol for existing functionality. --- .gitignore | 6 + .../assets/scripting/enginedata/lua/main.lua | 10 +- .../scripting/enginedata/python/main.py | 243 ++++-------------- .../enginedata/python/unciv/__init__.py | 5 +- .../enginedata/python/unciv/autocompletion.py | 38 +++ .../scripting/enginedata/python/unciv/ipc.py | 167 ++++++++++++ .../enginedata/python/unciv/wrapping.py | 156 +++++++++++ .../assets/scripting/enginedata/qjs/main.js | 5 +- android/assets/scripting/python.json | 3 + .../shareddata/ScriptAPIConstants.json | 3 + android/proguard-rules.pro | 2 + build.gradle.kts | 7 + .../com/unciv/scripting/ScriptingBackend.kt | 174 +++++++++---- .../src/com/unciv/scripting/ScriptingState.kt | 52 ++-- .../protocol/ScriptingObjectIndex.kt | 47 ++++ .../scripting/protocol/ScriptingProtocol.kt | 188 +++++++++++++- .../protocol/ScriptingReplManager.kt | 107 +++++--- .../protocol/ScriptingReplReflector.kt | 26 +- .../scripting/protocol/SubprocessBlackbox.kt | 4 +- .../unciv/scripting/reflection/Reflection.kt | 17 +- .../unciv/scripting/utils/ApiSpecGenerator.kt | 106 ++++++++ .../src/com/unciv/scripting/utils/Blackbox.kt | 9 +- .../unciv/scripting/utils/SourceManager.kt | 1 + .../unciv/ui/consolescreen/ConsoleScreen.kt | 7 +- 24 files changed, 1032 insertions(+), 351 deletions(-) create mode 100644 android/assets/scripting/enginedata/python/unciv/autocompletion.py create mode 100644 android/assets/scripting/enginedata/python/unciv/ipc.py create mode 100644 android/assets/scripting/enginedata/python/unciv/wrapping.py create mode 100644 android/assets/scripting/shareddata/ScriptAPIConstants.json create mode 100644 core/src/com/unciv/scripting/protocol/ScriptingObjectIndex.kt create mode 100644 core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt diff --git a/.gitignore b/.gitignore index 32bf49eb39f5e..3a82f827567ca 100644 --- a/.gitignore +++ b/.gitignore @@ -142,3 +142,9 @@ android/assets/music/ # Visual Studio Code .vscode/ + +# Unciv +GameSettings.json + +# Python +*.pyc diff --git a/android/assets/scripting/enginedata/lua/main.lua b/android/assets/scripting/enginedata/lua/main.lua index ce1779f704789..9d344968f8f4c 100644 --- a/android/assets/scripting/enginedata/lua/main.lua +++ b/android/assets/scripting/enginedata/lua/main.lua @@ -1,15 +1,17 @@ io.stdout:setvbuf('full') -io.stdout:write("\n\nWelcome to the Lua Unciv CLI. Currently, this backend relies on launching the system `lua` command.\n\nRunning ".._VERSION..".\n\n") -io.stdout:flush() + +function motd () + return "\n\nWelcome to the Lua Unciv CLI. Currently, this backend relies on launching the system `lua` command.\n\nRunning ".._VERSION..".\n\n" +end while true do _in = io.stdin:read() io.stdout:write("> ".._in.."\n") - _, _out = pcall(load(_in)) + _, _out = pcall(load("return ".._in)) if not _ then - _, _out = pcall(load("return ".._in)) + _, _out = pcall(load(_in)) end io.stdout:write((_out or "").."\n") io.stdout:flush() diff --git a/android/assets/scripting/enginedata/python/main.py b/android/assets/scripting/enginedata/python/main.py index cc419bc60a95e..22539c5b484a2 100644 --- a/android/assets/scripting/enginedata/python/main.py +++ b/android/assets/scripting/enginedata/python/main.py @@ -1,207 +1,52 @@ -import sys, io, json -def ResolvePath(path, scope): - return eval(path, scope, scope) - raise NotImplementedError() +try: + import sys, json - -class AutoCompleteManager: - def __init__(self, scope=None): - self.scope = globals() if scope is None else scope - def Evaled(self, path): - return eval(path, self.scope, self.scope) - def GetAutocomplete(self, command): - """Return either a sequence of full autocomplete matches or a help string for a given command.""" - try: - if ')' in command: - return () - attrbase, attrjoin, attrleaf = command.rpartition('.') - if '(' in attrleaf: - functionbase, functionjoin, functionleaf = attrleaf.rpartition('(') - fbase = attrbase + attrjoin + functionbase - fobj = self.Evaled(fbase) - return '\n'.join((fbase+"(", str(type(fobj)), getattr(fobj, '__doc__', '') or "No documentation available.")) - elif '[' in attrleaf: - keybase, keyjoin, keyleaf = attrleaf.rpartition('[') - kbase = attrbase + attrjoin + keybase - kbaseobj = self.Evaled(kbase) - if hasattr(kbaseobj, 'keys'): - if not keyleaf: - return tuple(kbase+keyjoin+repr(k)+']' for k in kbaseobj.keys()) - if keyleaf[-1] == ']': - return () - quote = keyleaf[0] - kleaf = keyleaf[1:] - if quote in '\'"': - return tuple(kbase+keyjoin+quote+k+quote+']' for k in kbaseobj.keys() if k.startswith(kleaf)) - return tuple(kbase+keyjoin+n+']' for n in self.GetAutocomplete(keyleaf)) - else: - return tuple([attrbase+attrjoin+a for a in (dir(self.Evaled(attrbase)) if attrbase else self.scope) if a.startswith(attrleaf)]) - except (NameError, AttributeError, KeyError, IndexError, SyntaxError) as e: - return "No autocompletion found: "+repr(e) - - - - -class ForeignError(RuntimeError): - pass - -class ForeignPacket: - def __init__(self, action, identifier, data): - self.action = action - self.identifier = identifier - self.data = data - def __repr__(self): - return self.__class__.__name__+"(**"+str(self.as_dict())+")" - @classmethod - def deserialized(cls, serialized): - return cls(**json.loads(serialized)) - def enforce_type(self, expect_action=None, expect_identifier=None): - if expect_action is not None and self.action != expect_action: - raise ForeignError("Expected foreign data of action "+repr(expect_action)+", got "+repr(self)+".") - if expect_identifier is not None and self.identifier != expect_identifier: - raise ForeignError("Expected foreign data with identifier "+repr(expect_identifier)+", got "+repr(self)+".") - return self - def as_dict(self): - return { - 'action': self.action, - 'identifier': self.identifier, - 'data': self.data - } - def serialized(self): - return json.dumps(self.as_dict()) - - -class ForeignActionManager: - def __init__(self, sender=None, receiver=None): - if sender is not None: - self.sender = sender - if receiver is not None: - self.receiver = receiver - def sender(self, message): - try: - print(message, file=sys.stdout, flush=True) - except TypeError: - #No flush on `micropython`. - print(message, file=sys.stdout) - def receiver(self): - return sys.stdin.readline() -# def EncodeForeignAction(self, action, identifier=None, **data): -# return ForeignPacket( -# action = action, -# identifier = identifier, -# data = data -# ).serialized() - -class ForeignActionSender(ForeignActionManager): - def MakeUniqueID(self): - return 4 # Chosen by fair dice roll. Guaranteed to be random. - def SendForeignCall(self, path, args, kwargs): - identifier = self.MakeUniqueID() - self.sender(ForeignPacket('call', identifier, {'path':path, 'args':args, 'kwargs':kwargs}).serialized()) - return ForeignPacket.deserialized(self.receiver()).enforce_type('call_response', identifier).data - def SendForeignRead(self, path): - identifier = self.MakeUniqueID() - self.sender(ForeignPacket('read', identifier, {'path':path}).serialized()) - return ForeignPacket.deserialized(self.receiver()).enforce_type('read_response', identifier).data - def SendForeignAssign(self, path, value): - identifier = self.MakeUniqueID() - self.sender(ForeignPacket('assign', identifier, {'path':path, 'value':value}).serialized()) - return ForeignPacket.deserialized(self.receiver()).enforce_type('assign_response', identifier).data - def MakeForeignFunction(self, callpath, callargs): - pass - -class ForeignActionReceiver(ForeignActionManager): - def __init__(self, sender=None, receiver=None, scope=None): - ForeignActionManager.__init__(self, sender=sender, receiver=receiver) - self.scope = globals() if scope is None else scope - def foreignCallEvaluator(func): - def _foreignCallEvaluator(self, *args, **kwargs): - try: - pass - except: - pass - def ResolvePath(self, path): - return eval(path, self.scope, self.scope) - # NOTE: This should be replaced with recursive parsing into `getattr()` and `[]`/`.__getitem__`/`.__getindex__` if arbitrary code has the chance to be passed. - def EvalForeignCall(self, packet): - packet.enforce_type('call') - return self.ResolvePath(packet.data['path'])(*packet.data['args'], **packet.data['kwargs']) - def EvalForeignRead(self, packet): - packet.enforce_type('read') - return self.ResolvePath(packet.data['path']) - def ExecForeignAssign(self, packet): - packet.enforce_type('assign') - path = packet.data['path'] - value = packet.data['value'] - if path[-1] == ']': - spath = path.rpartition('[') - self.ResolvePath(spath[0])[spath[-1][:-1]] = value - else: - spath = path.rpartition('.') - if spath[0]: - setattr(self.ResolvePath(spath[0]), spath[-1], value) - else: - self.scope[spath[-1]] = value - def RespondForeignAction(self, request): - decoded = ForeignPacket.deserialized(request) - action = decoded.action - raction = None - rdata = None - if action == 'call': - rdata = self.EvalForeignCall(decoded) - raction = 'call_response' - elif action == 'read': - rdata = self.EvalForeignRead(decoded) - raction = 'read_response' - elif action == 'assign': - rdata = self.ExecForeignAssign(decoded) - raction = 'assign_response' - else: - raise ForeignError("Unknown action type for foreign action request: " + repr(decoded)) - self.sender(ForeignPacket(raction, decoded.identifier, rdata).serialized()) - def AwaitForeignAction(self): - self.RespondForeignAction(self.receiver()) - def ForeignREPL(self): - while True: - self.AwaitForeignAction() - - -class ForeignActionAutocompleter(ForeignActionManager): - pass - - -class ForeignActionTransceiver(ForeignActionReceiver, ForeignActionSender): - pass - - -#python | micropython -c "import sys; [print('FMF: '+sys.stdin.readline()) for i in range(10)]" -#python3 -ic 'from CommTest import *; t=ForeignActionTransceiver()' | micropython -i -c 'from CommTest import *; t=ForeignActionTransceiver(); t.ForeignREPL()' -#t.SendForeignRead("ForeignActionTransceiver.__name__") - - -print(f""" + stdout = sys.stdout + + import unciv + + foreignActionSender = unciv.ipc.ForeignActionSender() + + foreignScope = unciv.wrapping.ForeignObject('') + + class ForeignActionReplReceiver(unciv.ipc.ForeignActionReceiver): + @unciv.ipc.receiverMethod('motd', 'motd_response') + def EvalForeignMotd(self, packet): + return f""" Welcome to the CPython Unciv CLI. Currently, this backend relies on launching the system `python3` command. sys.implementation == {str(sys.implementation)} -""") - -stdout = sys.stdout -while True: - line = sys.stdin.readline() - out = sys.stdout = io.StringIO() # Won't work with MicroPython. I think it's slotted? - print(">>> " + line) - try: - try: - code = compile(line, 'STDIN', 'eval') - except SyntaxError: - exec(compile(line, 'STDIN', 'exec')) - else: - print(eval(code)) - except Exception as e: - print(repr(e), file=stdout, flush=True) - else: - print(out.getvalue(), file=stdout, flush=True) +""" + @unciv.ipc.receiverMethod('autocomplete', 'autocomplete_response') + def EvalForeignAutocomplete(self, packet): + return "AC." + @unciv.ipc.receiverMethod('exec', 'exec_response') + def EvalForeignExec(self, packet): + line = packet.data + with unciv.ipc.FakeStdout() as fakeout: + print(f">>> {str(line)}") + try: + try: + code = compile(line, 'STDIN', 'eval') + except SyntaxError: + exec(compile(line, 'STDIN', 'exec'), self.scope, self.scope) + else: + print(eval(code, self.scope, self.scope)) + except Exception as e: + return repr(e) + else: + return fakeout.getvalue() + + @unciv.ipc.receiverMethod('terminate', 'terminate_response') + def EvalForeignTerminate(self, packet): + return None + + foreignActionReceiver = ForeignActionReplReceiver(scope=globals()) + foreignActionReceiver.ForeignREPL() + +except Exception as e: + print(f"Fatal error in Python interepreter: {repr(e)}", file=stdout, flush=True) diff --git a/android/assets/scripting/enginedata/python/unciv/__init__.py b/android/assets/scripting/enginedata/python/unciv/__init__.py index 01c0f3f45dfed..040cb0c845af6 100644 --- a/android/assets/scripting/enginedata/python/unciv/__init__.py +++ b/android/assets/scripting/enginedata/python/unciv/__init__.py @@ -1 +1,4 @@ -### +__all__ = ['autocompletion', 'ipc', 'wrapping'] +#Unsupported by Micropython + +from . import autocompletion, ipc, wrapping diff --git a/android/assets/scripting/enginedata/python/unciv/autocompletion.py b/android/assets/scripting/enginedata/python/unciv/autocompletion.py new file mode 100644 index 0000000000000..d8ca37c2b00ba --- /dev/null +++ b/android/assets/scripting/enginedata/python/unciv/autocompletion.py @@ -0,0 +1,38 @@ +"""Advanced autocompletion. Returns keys for """ + + +class AutoCompleteManager: + def __init__(self, scope=None): + self.scope = globals() if scope is None else scope + def Evaled(self, path): + return eval(path, self.scope, self.scope) + def GetAutocomplete(self, command): + """Return either a sequence of full autocomplete matches or a help string for a given command.""" + try: + if ')' in command: + return () + attrbase, attrjoin, attrleaf = command.rpartition('.') + if '(' in attrleaf: + functionbase, functionjoin, functionleaf = attrleaf.rpartition('(') + fbase = attrbase + attrjoin + functionbase + fobj = self.Evaled(fbase) + return '\n'.join((fbase+"(", str(type(fobj)), getattr(fobj, '__doc__', '') or "No documentation available.")) + elif '[' in attrleaf: + keybase, keyjoin, keyleaf = attrleaf.rpartition('[') + kbase = attrbase + attrjoin + keybase + kbaseobj = self.Evaled(kbase) + if hasattr(kbaseobj, 'keys'): + if not keyleaf: + return tuple(kbase+keyjoin+repr(k)+']' for k in kbaseobj.keys()) + if keyleaf[-1] == ']': + return () + quote = keyleaf[0] + kleaf = keyleaf[1:] + if quote in '\'"': + return tuple(kbase+keyjoin+quote+k+quote+']' for k in kbaseobj.keys() if k.startswith(kleaf)) + return tuple(kbase+keyjoin+n+']' for n in self.GetAutocomplete(keyleaf)) + else: + return tuple([attrbase+attrjoin+a for a in (dir(self.Evaled(attrbase)) if attrbase else self.scope) if a.startswith(attrleaf)]) + except (NameError, AttributeError, KeyError, IndexError, SyntaxError) as e: + return "No autocompletion found: "+repr(e) + diff --git a/android/assets/scripting/enginedata/python/unciv/ipc.py b/android/assets/scripting/enginedata/python/unciv/ipc.py new file mode 100644 index 0000000000000..3279570ce1c38 --- /dev/null +++ b/android/assets/scripting/enginedata/python/unciv/ipc.py @@ -0,0 +1,167 @@ +import sys, io, json + + +#def ResolvePath(path, scope): +## return eval(path, scope, scope) +# raise NotImplementedError() + + + +class ForeignError(RuntimeError): + pass + +class ForeignPacket: + def __init__(self, action, identifier, data, flags=()): + self.action = action + self.identifier = identifier + self.data = data + self.flags = flags + def __repr__(self): + return self.__class__.__name__+"(**"+str(self.as_dict())+")" + @classmethod + def deserialized(cls, serialized): + return cls(**json.loads(serialized)) + def enforce_type(self, expect_action=None, expect_identifier=None): + if expect_action is not None and self.action != expect_action: + raise ForeignError("Expected foreign data of action "+repr(expect_action)+", got "+repr(self)+".") + if expect_identifier is not None and self.identifier != expect_identifier: + raise ForeignError("Expected foreign data with identifier "+repr(expect_identifier)+", got "+repr(self)+".") + return self + def as_dict(self): + return { + 'action': self.action, + 'identifier': self.identifier, + 'data': self.data, + 'flags': (*self.flags,) + } + def serialized(self): + return json.dumps(self.as_dict()) + +# Flags: 'FinishEval', 'BeginIteration', 'StopIteration' + + +class ForeignActionManager: + def __init__(self, sender=None, receiver=None): + if sender is not None: + self.sender = sender + if receiver is not None: + self.receiver = receiver + def sender(self, message): + try: + print(message, file=sys.stdout, flush=True) + except TypeError: + #No flush on `micropython`. + print(message, file=sys.stdout) + def receiver(self): + return sys.stdin.readline() +# def EncodeForeignAction(self, action, identifier=None, **data): +# return ForeignPacket( +# action = action, +# identifier = identifier, +# data = data +# ).serialized() + + +class ForeignActionSender(ForeignActionManager): + def MakeUniqueID(self): + return 4 # Chosen by fair dice roll. Guaranteed to be random. + def SendForeignAction(self, actionparams, responsetype): + identifier = self.MakeUniqueID() + self.sender(ForeignPacket(**actionparams, identifier=identifier).serialized()) + return ForeignPacket.deserialized(self.receiver()).enforce_type(responsetype, identifier).data + + +def receiverMethod(action, response): + def receiverMethodDec(func): + func.__foreignActionReceiver = (action, response) + #Won't work on Upy, I think. + return func + return receiverMethodDec + +class ForeignActionReceiver(ForeignActionManager): + def __init__(self, sender=None, receiver=None, scope=None): + ForeignActionManager.__init__(self, sender=sender, receiver=receiver) + self.scope = globals() if scope is None else scope + self._responders = {} + for name in dir(self): + value = getattr(self, name) + action, response = getattr(value, '__foreignActionReceiver', (None, None)) + if action: + self._responders[action] = (value, response) + def RespondForeignAction(self, request): + decoded = ForeignPacket.deserialized(request) + action = decoded.action + raction = None + rdata = None + if action in self._responders: + method, raction = self._responders[action] + rdata = method(decoded) + else: + raise ForeignError("Unknown action type for foreign action request: " + repr(decoded)) + self.sender(ForeignPacket(raction, decoded.identifier, rdata).serialized()) + def AwaitForeignAction(self): + self.RespondForeignAction(self.receiver()) + def ForeignREPL(self): + while True: + self.AwaitForeignAction() + + +class FakeStdout: + def __init__(self): + self.stdout = sys.stdout + def __enter__(self): + self.fakeout = sys.stdout = io.StringIO() # Won't work with MicroPython. I think it's slotted? + return self.fakeout + def __exit__(self, *exc): + sys.stdout = self.stdout + +#class ForeignActionBindingSender(ForeignActionSender): +# #Probably easier to just define these as needed in the classes that call them. +# def SendForeignCall(self, path, args, kwargs): +# return self.SendForeignAction({'action': ('call', 'data': {'path':path, 'args':args, 'kwargs':kwargs}}, 'call_response') +# def SendForeignRead(self, path): +# identifier = self.MakeUniqueID() +# return self.SendForeignAction({'action': 'read', 'data': {'path':path}}, 'read_response') +# def SendForeignAssign(self, path, value): +# return self.SendForeignAction({'action': 'assign', 'data': {'path':path, 'value':value}}) +# def MakeForeignFunction(self, callpath, callargs): +# pass + +#class ForeignActionBindingReceiver(ForeignActionReceiver): +# # This is nice to have, but basically useless, right? In the current model, there shouldn't be any circumstances where the Kotlin code explicitly changes or is even aware of the state of the scripting interpreter, since the Kotlin side has to deal with any number of languages, all the data lives on the Kotlin side and it's thus the scripting interpreter's job to request what it needs, and all communication is through standardized requests for either Kotlin-side reflection or a handful of REPL functions and raw code eval on the scripting side +# def foreignCallEvaluator(func): +# def _foreignCallEvaluator(self, *args, **kwargs): +# try: +# pass +# except: +# pass +# def ResolvePath(self, path): +# return eval(path, self.scope, self.scope) +# # NOTE: This should be replaced with recursive parsing into `getattr()` and `[]`/`.__getitem__`/`.__getindex__` if arbitrary code has the chance to be passed. +# def EvalForeignCall(self, packet): +# packet.enforce_type('call') +# return self.ResolvePath(packet.data['path'])(*packet.data['args'], **packet.data['kwargs']) +# def EvalForeignRead(self, packet): +# packet.enforce_type('read') +# return self.ResolvePath(packet.data['path']) +# def ExecForeignAssign(self, packet): +# packet.enforce_type('assign') +# path = packet.data['path'] +# value = packet.data['value'] +# if path[-1] == ']': +# spath = path.rpartition('[') +# self.ResolvePath(spath[0])[spath[-1][:-1]] = value +# else: +# spath = path.rpartition('.') +# if spath[0]: +# setattr(self.ResolvePath(spath[0]), spath[-1], value) +# else: +# self.scope[spath[-1]] = value + + +#class ForeignActionAutocompleter(ForeignActionManager): +# pass + +#class ForeignActionTransceiver(ForeignActionReceiver, ForeignActionSender): +# pass + diff --git a/android/assets/scripting/enginedata/python/unciv/wrapping.py b/android/assets/scripting/enginedata/python/unciv/wrapping.py new file mode 100644 index 0000000000000..f1bfa3bc39d2c --- /dev/null +++ b/android/assets/scripting/enginedata/python/unciv/wrapping.py @@ -0,0 +1,156 @@ +import json, sys +stdout = sys.stdout + +from . import ipc + +class ForeignRequestMethod: + """Decorator for methods that return values from foreign requests.""" + def __init__(self, func): + self.func = func + try: + self.__name__, self.__doc__ = func.__name__, func.__doc__ + except AttributeError: + pass + def __get__(self, obj, cls): + def meth(*a, **kw): + actionparams, responsetype, responseparser = self.func(obj, *a, **kw) + response = obj._foreignrequester(actionparams, responsetype) + if responseparser and callable(responseparser): + response = responseparser(response) + return response + try: + meth.__name__, meth.__doc__ = self.func.__name__, self.func.__doc__ + except AttributeError: + pass + return meth + +#class ForeignSelfMethod: +# """Decorator for methods that use foreign values for `self`.""" +# def __init__(self, func): +# self.func = func +# def __get__(self, obj, cls): +# return lambda *a, **kw: self.func(obj._getvalue(), *a, **kw) + +def ForeignResolvingFunc(func): + """Decorator for functions that resolve foreign objects as arguments.""" + def _func(*args, **kwargs): + return f( + *[a._getvalue() if isinstance(ForeignObject) else a for a in args], + **{k: v._getvalue() if isinstance(ForeignObject) else v for k, v in kwargs.items()} + ) + try: + _func.__name__, _func.__doc__ = func.__name__, func.__doc__ + except AttributeError: + pass + return _func + +def dummyForeignRequester(actionparams, responsetype): + return actionparams, responsetype + +def stdForeignRequester(actionparams, responsetype): + request = ipc.ForeignPacket(**actionparams) + requestjson = request.serialized() + try: + print(requestjson, file=stdout, flush=True) + except TypeError: + print(requestjson, file=stdout) + response = ipc.ForeignPacket.deserialized(sys.stdin.readline()) + assert request.identifier == response.identifier, f"Mismatched identifier: {request.identifier} != {response.identifier}" + assert response.action == responsetype, f"Mismatched response type: {response.action} != {responsetype}" + return response + + + +class ForeignObject: + def __init__(self, path, foreignrequester=dummyForeignRequester): + object.__setattr__(self, '_path', (*path,)) + object.__setattr__(self, '_foreignrequester', foreignrequester) + def __repr__(self): + return f"{self.__class__.__name__}({repr(self._getpath())}):{self._getvalue()}" + def _getpath(self): + return ''.join(self._path) + def __getattr__(self, name): + self.__class__ + self._path + return self.__class__((*self._path, f".{name}")) + def __getitem__(self, key): + return self.__class__((*self._path, f"[{json.dumps(key)}]")) + @ForeignRequestMethod + def _getvalue(self): + return ({ + 'action': 'read', + 'data': { + 'path': self._getpath() + } + }, + 'read_response', + None) + @ForeignRequestMethod + def __dir__(self): + return ({ + 'action': 'dir', + 'data': { + 'path': self._getpath() + } + }, + 'dir_response', + None) +# @ForeignRequestMethod +# @property +# def __doc__(self): +# return ({ +# 'action': 'args', +# 'data': { +# 'path': self._getpath() +# } +# }, +# 'args_response', +# None) + @ForeignRequestMethod + def __setattr__(self, name, value): + return ({ + 'action': 'assign', + 'data': { + 'path': getattr(self, name)._getpath(), + 'value': value + } + }, + 'assign_response', + None) + @ForeignRequestMethod + def __call__(self, *args, **kwargs): + return ({ + 'action': 'call', + 'data': { + 'args': args, + 'kwargs': kwargs + } + }, + 'call_response', + None) + @ForeignRequestMethod + def __setitem__(self, key, value): + return ({ + 'action': 'assign', + 'data': { + 'path': self[key]._getpath(), + 'value': value + } + }, + 'assign_response', + None) + def keys(self): + raise NotImplemented() + return { + '' + } + +#class ForeignScope: +# _path = () +# def __init__(self, attrcls=ForeignObject, foreignrequester=dummyForeignRequester): +# self._attrcls = attrcls +# self._foreignrequester = foreignrequester +# def __getattr__(self, name): +# return self._attrcls(name, self._foreignrequester) +# __dir__ = ForeignObject.__dir__ + diff --git a/android/assets/scripting/enginedata/qjs/main.js b/android/assets/scripting/enginedata/qjs/main.js index fc896132e627c..d21112d4a89ae 100644 --- a/android/assets/scripting/enginedata/qjs/main.js +++ b/android/assets/scripting/enginedata/qjs/main.js @@ -1,7 +1,8 @@ //import("std"); -std.out.puts("\n\nWelcome to the QuickJS Unciv CLI. Currently, this backend relies on launching the system `qjs` command.\n\n") -std.out.flush() +function motd() { + return "\n\nWelcome to the QuickJS Unciv CLI. Currently, this backend relies on launching the system `qjs` command.\n\n" +} while (true) { let line = std.in.getline(); diff --git a/android/assets/scripting/python.json b/android/assets/scripting/python.json index 1a6b4bd707019..891cfc1a471a4 100644 --- a/android/assets/scripting/python.json +++ b/android/assets/scripting/python.json @@ -1,5 +1,8 @@ [ unciv/ unciv/__init__.py + unciv/autocompletion.py + unciv/ipc.py + unciv/wrapping.py main.py ] diff --git a/android/assets/scripting/shareddata/ScriptAPIConstants.json b/android/assets/scripting/shareddata/ScriptAPIConstants.json new file mode 100644 index 0000000000000..24652c3bbec4d --- /dev/null +++ b/android/assets/scripting/shareddata/ScriptAPIConstants.json @@ -0,0 +1,3 @@ +{ + kotlinObjectTokenPrefix: "_unciv-kt-obj@" +} diff --git a/android/proguard-rules.pro b/android/proguard-rules.pro index 1dfea15e6e299..f2bc3d885ee8e 100644 --- a/android/proguard-rules.pro +++ b/android/proguard-rules.pro @@ -31,3 +31,5 @@ -keepclassmembers class com.badlogic.gdx.backends.android.AndroidInput* { (com.badlogic.gdx.Application, android.content.Context, java.lang.Object, com.badlogic.gdx.backends.android.AndroidApplicationConfiguration); } + +# TODO: Probably need this: https://github.com/Kotlin/kotlinx.serialization/blob/master/README.md#android diff --git a/build.gradle.kts b/build.gradle.kts index b5f6c4f2ac9ac..24098c60320e5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -20,6 +20,7 @@ buildscript { mavenLocal() mavenCentral() maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots/") } + //maven { url = uri("https://kotlin.bintray.com/kotlinx") } gradlePluginPortal() maven{ url = uri("https://jitpack.io") } // for the anuken packr } @@ -31,12 +32,14 @@ buildscript { // This is for wrapping the .jar file into a standalone executable classpath("com.github.anuken:packr:-SNAPSHOT") + classpath(kotlin("serialization:${com.unciv.build.BuildConfig.kotlinVersion}")) } } allprojects { apply(plugin = "eclipse") apply(plugin = "idea") + apply(plugin = "kotlinx-serialization") version = "1.0.1" @@ -63,6 +66,7 @@ project(":desktop") { dependencies { "implementation"(project(":core")) "implementation"("org.jetbrains.kotlin:kotlin-reflect:${com.unciv.build.BuildConfig.kotlinVersion}") // Seem to need here as well as in `:core`, or else fails to find class def at run time. + "implementation"("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.0") "implementation"("com.badlogicgames.gdx:gdx-backend-lwjgl3:${gdxVersion}") "implementation"("com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop") @@ -84,6 +88,7 @@ project(":android") { dependencies { "implementation"(project(":core")) "implementation"("org.jetbrains.kotlin:kotlin-reflect:${com.unciv.build.BuildConfig.kotlinVersion}") + "implementation"("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.0") "implementation"("com.badlogicgames.gdx:gdx-backend-android:$gdxVersion") natives("com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-armeabi-v7a") natives("com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-arm64-v8a") @@ -99,6 +104,7 @@ project(":ios") { dependencies { "implementation"(project(":core")) "implementation"("org.jetbrains.kotlin:kotlin-reflect:${com.unciv.build.BuildConfig.kotlinVersion}") + "implementation"("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.0") "implementation"("com.mobidevelop.robovm:robovm-rt:$roboVMVersion") "implementation"("com.mobidevelop.robovm:robovm-cocoatouch:$roboVMVersion") "implementation"("com.badlogicgames.gdx:gdx-backend-robovm:$gdxVersion") @@ -113,6 +119,7 @@ project(":core") { dependencies { "implementation"("com.badlogicgames.gdx:gdx:$gdxVersion") "implementation"("org.jetbrains.kotlin:kotlin-reflect:${com.unciv.build.BuildConfig.kotlinVersion}") // Used for scripting API backends. + "implementation"("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.0") } diff --git a/core/src/com/unciv/scripting/ScriptingBackend.kt b/core/src/com/unciv/scripting/ScriptingBackend.kt index 87d64f7b4084e..d6598344720d6 100644 --- a/core/src/com/unciv/scripting/ScriptingBackend.kt +++ b/core/src/com/unciv/scripting/ScriptingBackend.kt @@ -6,12 +6,15 @@ import com.badlogic.gdx.files.FileHandle import com.unciv.scripting.reflection.Reflection import com.unciv.scripting.protocol.ScriptingReplManager import com.unciv.scripting.protocol.SubprocessBlackbox +import com.unciv.scripting.utils.ApiSpecGenerator +import com.unciv.scripting.utils.Blackbox +import com.unciv.scripting.utils.DummyBlackbox import com.unciv.scripting.utils.SourceManager import kotlin.reflect.full.* import java.util.* -data class AutocompleteResults(val matches:List, val isHelpText:Boolean = false, val helpText:String = "") +data class AutocompleteResults(val matches:List = listOf(), val isHelpText:Boolean = false, val helpText:String = "") interface ScriptingBackend_metadata { @@ -20,36 +23,41 @@ interface ScriptingBackend_metadata { } -open class ScriptingBackendBase(val scriptingScope: ScriptingScope) { +interface ScriptingBackend { - companion object Metadata: ScriptingBackend_metadata { - override fun new(scriptingScope: ScriptingScope) = ScriptingBackendBase(scriptingScope) - override val displayname:String = "Dummy" - } - - val metadata: ScriptingBackend_metadata - get(): ScriptingBackend_metadata = this::class.companionObjectInstance as ScriptingBackend_metadata - - - open fun motd(): String { + fun motd(): String { // Message to print on launch. return "\n\nWelcome to the Unciv CLI!\nYou are currently running the dummy backend, which will echo all commands but never do anything.\n" } - open fun getAutocomplete(command: String, cursorPos: Int? = null): AutocompleteResults { + fun autocomplete(command: String, cursorPos: Int? = null): AutocompleteResults { // Return either a `List` of autocomplete matches, or a return AutocompleteResults(listOf(command+"_autocomplete")) } - open fun exec(command: String): String { + fun exec(command: String): String { // Execute code and return output. return command } - open fun terminate(): Exception? { + fun terminate(): Exception? { // Return `null` on successful termination, an `Exception()` otherwise. return null } + +} + + +open class ScriptingBackendBase(val scriptingScope: ScriptingScope): ScriptingBackend { + + companion object Metadata: ScriptingBackend_metadata { + override fun new(scriptingScope: ScriptingScope) = ScriptingBackendBase(scriptingScope) + override val displayname:String = "Dummy" + } + + val metadata: ScriptingBackend_metadata + get(): ScriptingBackend_metadata = this::class.companionObjectInstance as ScriptingBackend_metadata + } @@ -92,7 +100,7 @@ class HardcodedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacken } } - override fun getAutocomplete(command: String, cursorPos: Int?): AutocompleteResults{ + override fun autocomplete(command: String, cursorPos: Int?): AutocompleteResults{ if (' ' in command) { return AutocompleteResults(listOf(), true, getCommandHelpText(command.split(' ')[0])) } else { @@ -280,11 +288,14 @@ class ReflectiveScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacke return "\n\nWelcome to the reflective Unciv CLI backend.\n\nCommands you enter will be parsed as a path consisting of property reads, key and index accesses, function calls, and string, numeric, boolean, and null literals.\nKeys, indices, and function arguments are parsed recursively.\nProperties can be both read from and written to.\n\nExamples:\n${examples.map({"> ${it}"}).joinToString("\n")}\n\nPress [TAB] at any time to trigger autocompletion for all known leaf names at the currently entered path.\n" } - override fun getAutocomplete(command: String, cursorPos: Int?): AutocompleteResults { + override fun autocomplete(command: String, cursorPos: Int?): AutocompleteResults { try { var comm = commandparams.keys.find{ command.startsWith(it+" ") } if (comm != null) { val params = command.drop(comm.length+1).split(' ', limit=commandparams[comm]!!) + //val prefix + //val workingcode + //val suffix val workingcode = params[params.size-1] val workingpath = Reflection.parseKotlinPath(workingcode) if (workingpath.any{ it.type == Reflection.PathElementType.Call }) { @@ -299,8 +310,9 @@ class ReflectiveScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacke .filter{ it.startsWith(leafname) } .map{ prefix + it } ) + } else { + return AutocompleteResults(commandparams.keys.filter{ it.startsWith(command) }.map{ it+" " }) } - return AutocompleteResults(commandparams.keys.filter{ it.startsWith(command) }.map{ it+" " }) } catch (e: Exception) { return AutocompleteResults(listOf(), true, "Could not get autocompletion: ${e}") } @@ -331,6 +343,9 @@ class ReflectiveScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacke var obj = Reflection.evalKotlinString(scriptingScope, parts[1]) appendOut("${if (obj == null) null else obj!!::class.qualifiedName}") } + "examples" -> { + throw RuntimeException("Not implemented.") + } else -> { appendOut("Unknown command: ${parts[0]}") } @@ -349,47 +364,61 @@ open class EnvironmentedScriptingBackend(scriptingScope: ScriptingScope): Script val folderHandle: FileHandle by lazy { SourceManager.setupInterpreterEnvironment(engine) } // This requires the overridden values for `engine`, so setting it in the constructor causes a null error. - // Also, SubprocessScriptingBackend inherits from this, but not all subclasses of SubprocessScriptingBackend might need it. So as long as it's not accessed, it won't be intialized. + // Also, BlackboxScriptingBackend inherits from this, but not all subclasses of BlackboxScriptingBackend might need it. So as long as it's not accessed, it won't be intialized. } -open class SubprocessScriptingBackend(scriptingScope: ScriptingScope): EnvironmentedScriptingBackend(scriptingScope) { +open class BlackboxScriptingBackend(scriptingScope: ScriptingScope): EnvironmentedScriptingBackend(scriptingScope) { - open val processCmd = arrayOf("") + open val blackbox: Blackbox by lazy { DummyBlackbox() } - val replManager: ScriptingReplManager by lazy { ScriptingReplManager(scriptingScope, SubprocessBlackbox(processCmd)) } - // Was originally a method that could be called by subclasses' constructors. This seems cleaner. Subclasses don't even have to define any functions this way. + val replManager: ScriptingReplManager by lazy { ScriptingReplManager(scriptingScope, blackbox) } + // Was originally a method that could be called by subclasses' constructors. This seems cleaner. Subclasses don't even have to define any functions this way. And the liberal use of `lazy` should naturally make sure the properties will always be initialized in the right order. // Downside: Potential latency on first command, or possibly depending on `motd()` for immediate initialization. - open val replSoftExitCode = "" - - override fun exec(command: String): String { + override fun motd(): String { try { - return "${replManager.evalCode("${command}\n").joinToString("\n")}\n" - } catch (e: RuntimeException) { - return "${e}\n" + return replManager.motd() + } catch (e: Exception) { + return "No MOTD for ${engine} backend: ${e}\n" } } - open fun softStopProcess() { - replManager.evalCode(replSoftExitCode) + override fun autocomplete(command: String, cursorPos: Int?): AutocompleteResults { + try { + return replManager.autocomplete(command, cursorPos) + } catch (e: Exception) { + return AutocompleteResults(isHelpText = true, helpText = "Autocomplete error: ${e}") + } } - fun hardStopProcess(): Exception? { - return replManager.terminate() + override fun exec(command: String): String { + try { + return replManager.exec("${command}\n") + } catch (e: RuntimeException) { + return "${e}" + } } - + override fun terminate(): Exception? { try { - softStopProcess() + return replManager.terminate() } catch (e: Exception) { + return e } - return hardStopProcess() } } +open class SubprocessScriptingBackend(scriptingScope: ScriptingScope): BlackboxScriptingBackend(scriptingScope) { + + open val processCmd = arrayOf("") + + override val blackbox by lazy { SubprocessBlackbox(processCmd) } +} + + class SpyScriptingBackend(scriptingScope: ScriptingScope): SubprocessScriptingBackend(scriptingScope) { companion object Metadata: ScriptingBackend_metadata { @@ -399,18 +428,8 @@ class SpyScriptingBackend(scriptingScope: ScriptingScope): SubprocessScriptingBa override val engine = "python" - override val processCmd by lazy { arrayOf("python3", "-u", "-X", "utf8", folderHandle.child("main.py").toString()) } - // Hm. I suppose this probably doesn't actually need to be lazy, as long as folderHandle is lazy and already available. - - override val replSoftExitCode = """ - try: - exit() - finally: - print("Exiting.") - """.trimIndent() - - override fun motd() = replManager.blackbox.readAll(block=true).joinToString("\n") - + override val processCmd = arrayOf("python3", "-u", "-X", "utf8", folderHandle.child("main.py").toString()) + } @@ -423,11 +442,8 @@ class SqjsScriptingBackend(scriptingScope: ScriptingScope): SubprocessScriptingB override val engine = "qjs" - override val processCmd by lazy { arrayOf("qjs", "--std", "--script", folderHandle.child("main.js").toString()) } - - override fun softStopProcess() {} + override val processCmd = arrayOf("qjs", "--std", "--script", folderHandle.child("main.js").toString()) - override fun motd() = replManager.blackbox.readAll(block=true).joinToString("\n") } @@ -441,12 +457,56 @@ class SluaScriptingBackend(scriptingScope: ScriptingScope): SubprocessScriptingB override val engine = "lua" - override val processCmd by lazy { arrayOf("lua", folderHandle.child("main.lua").toString()) } + override val processCmd = arrayOf("lua", folderHandle.child("main.lua").toString()) + +} + + +class DevToolsScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackendBase(scriptingScope) { + + companion object Metadata: ScriptingBackend_metadata { + override fun new(scriptingScope: ScriptingScope) = DevToolsScriptingBackend(scriptingScope) + override val displayname:String = "DevTools" + } + + val commands = listOf( + "PrintFlatApiDefs", + "PrintClassApiDefs", + "WriteOutApiFile ", + "WriteOutApiFile android/assets/scripting/shareddata/ScriptAPI.json" + ) - override fun softStopProcess() {} + override fun motd() = """ - override fun motd() = replManager.blackbox.readAll(block=true).joinToString("\n") + You have launched the DevTools CLI backend." + This tool is meant to help update code files. + + Available commands: + """.trimIndent()+"\n"+commands.map{ "> ${it}" }.joinToString("\n")+"\n\n" + override fun autocomplete(command: String, cursorPos: Int?) = AutocompleteResults(commands.filter{ it.startsWith(command) }) + + override fun exec(command: String): String { + val commv = command.split(' ', limit=2) + var out = "> ${command}\n" + try { + when (commv[0]) { + "PrintFlatApiDefs" -> { + out += ApiSpecGenerator(scriptingScope).generateFlatApi().toString() + "\n" + } + "PrintClassApiDefs" -> { + out += ApiSpecGenerator(scriptingScope).generateClassApi().toString() + "\n" + } + else -> { + out += "Unknown command: ${commv[0]}\n" + } + } + + } catch (e: Exception) { + out += e.toString() + } + return out + } } @@ -457,7 +517,9 @@ enum class ScriptingBackendType(val metadata:ScriptingBackend_metadata) { //MicroPython(UpyScriptingBackend), SystemPython(SpyScriptingBackend), SystemQuickJS(SqjsScriptingBackend), - SystemLua(SluaScriptingBackend) + SystemLua(SluaScriptingBackend), + DevTools(DevToolsScriptingBackend), + //For running ApiSpecGenerator. Comment in releases. Uncomment if needed. } diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index 8b83fa2da7a36..00c722682e10b 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -9,30 +9,30 @@ import kotlin.math.max import kotlin.math.min /* -``` -The major classes added and changed by this PR are structured as follows. UpperCamelCase() and parentheses means a new instantiation of a class. lowerCamelCase means a reference to an already-existing instance. An asterisk at the start of an item means zero or multiple instances of that class may be held. A question mark at the start of an item means that it may not exist in all implementations of the parent base class/interface. A question mark at the end of an item means that it is nullable, or otherwise may not be set in all states. - -UncivGame(): - ScriptingState(): // Persistent per UncivGame(). - ScriptingScope(): - civInfo? // These are set by WorldScreen init, and unset by MainMenuScreen. - gameInfo? - uncivGame - worldScreen? - *ScriptingBackend(): - scriptingScope - ?ScriptingReplManager(): - Blackbox() // Common interface to wrap foreign interpreter with pipes, STDIN/STDOUT, queues, sockets, embedding, JNI, etc. - ?folderHandler: setupInterpreterEnvironment() // If used, a temporary directory with file structure copied from engine and shared folders in `assets/scripting`. - ConsoleScreen(): // Persistent as long as window isn't resized. Recreates itself and restores most of its state from scriptingState if resized. - scriptingState -WorldScreen(): - consoleScreen - scriptingState // ScriptingState has getters and setters that wrap scriptingScope, which WorldScreen uses to update game info. -MainMenuScreen(): - consoleScreen - scriptingState // Same as for worldScreen. -``` + ``` + The major classes added and changed by this PR are structured as follows. UpperCamelCase() and parentheses means a new instantiation of a class. lowerCamelCase means a reference to an already-existing instance. An asterisk at the start of an item means zero or multiple instances of that class may be held. A question mark at the start of an item means that it may not exist in all implementations of the parent base class/interface. A question mark at the end of an item means that it is nullable, or otherwise may not be set in all states. + + UncivGame(): + ScriptingState(): // Persistent per UncivGame(). + ScriptingScope(): + civInfo? // These are set by WorldScreen init, and unset by MainMenuScreen. + gameInfo? + uncivGame + worldScreen? + *ScriptingBackend(): + scriptingScope + ?ScriptingReplManager(): + Blackbox() // Common interface to wrap foreign interpreter with pipes, STDIN/STDOUT, queues, sockets, embedding, JNI, etc. + ?folderHandler: setupInterpreterEnvironment() // If used, a temporary directory with file structure copied from engine and shared folders in `assets/scripting`. + ConsoleScreen(): // Persistent as long as window isn't resized. Recreates itself and restores most of its state from scriptingState if resized. + scriptingState + WorldScreen(): + consoleScreen + scriptingState // ScriptingState has getters and setters that wrap scriptingScope, which WorldScreen uses to update game info. + MainMenuScreen(): + consoleScreen + scriptingState // Same as for worldScreen. + ``` */ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: ScriptingBackendType? = null){ @@ -110,11 +110,11 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr outputHistory.add(text) } - fun getAutocomplete(command: String, cursorPos: Int? = null): AutocompleteResults { + fun autocomplete(command: String, cursorPos: Int? = null): AutocompleteResults { if (!(hasBackend())) { return AutocompleteResults(listOf(), false, "") } - return getActiveBackend().getAutocomplete(command, cursorPos) + return getActiveBackend().autocomplete(command, cursorPos) } fun navigateHistory(increment: Int): String { diff --git a/core/src/com/unciv/scripting/protocol/ScriptingObjectIndex.kt b/core/src/com/unciv/scripting/protocol/ScriptingObjectIndex.kt new file mode 100644 index 0000000000000..d40d0099d298a --- /dev/null +++ b/core/src/com/unciv/scripting/protocol/ScriptingObjectIndex.kt @@ -0,0 +1,47 @@ +package com.unciv.scripting.protocol + +import kotlin.collections.MutableMap +import java.lang.ref.WeakReference +import java.util.UUID + + +object ScriptingObjectIndex { + + val objs = mutableMapOf>() + + val kotlinObjectIdPrefix = "_unciv-kt-obj@" // load from ScriptAPIConstants.json + + fun idKotlinObject(value: Any?): String { + //Don't depend on parsing this for extracting information. + //It's basically a readable/dumb UUID. + //Actually, it might be better to just use a UUID. I think that was actually my original plan. + return "${kotlinObjectIdPrefix}${System.identityHashCode(value)}:${if (value == null) "null" else value::class.qualifiedName}/${value.toString()}_${UUID.randomUUID().toString()}" + } + + fun isKotlinToken(value: Any?): Boolean { + return value is String && value.startsWith(kotlinObjectIdPrefix) + } + + fun clean(): Unit { + for ((t, o) in objs) { + if (o.get() == null) { + objs.remove(t) + } + } + } + + fun getToken(obj: Any?): String { + val token = idKotlinObject(obj) + objs[token] = WeakReference(obj) + return token + } + + fun getReal(token: Any?): Any? { + if (isKotlinToken(token)) { + return objs[token]!!.get() + } else { + return token + } + } + +} diff --git a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt index 7633e6bf6ff60..839e60a1b3497 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt @@ -1,12 +1,196 @@ package com.unciv.scripting.protocol +import com.unciv.scripting.AutocompleteResults +import com.unciv.scripting.ScriptingBackend import com.unciv.scripting.ScriptingScope -import com.unciv.scripting.utils.Blackbox +import com.unciv.scripting.reflection.Reflection +//import com.badlogic.gdx.utils.Json +import kotlin.random.Random +//import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +//import kotlinx.serialization.descriptors.SerialDescriptor +//import kotlinx.serialization.descriptors.PrimitiveKind +//import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +//import kotlinx.serialization.encoding.Decoder +//import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonNull +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.encodeToString +import kotlinx.serialization.decodeFromString +import java.util.UUID /* -JSON packets: + A single IPC action consists of one request packet and one response packet. + A request packet should always be followed by a response packet if it has an action. + If a request packet has a null action, then it does not need to be followed by a response. This is to let flags be sent without generating useless responses. + + However, responses do not have to be sent in the same order as their correspond requests. New requests can be sent out while old ones are left "open"— E.G., if creating a response requires requesting new information. + + Both the Kotlin side and the script interpreter can send and receive packets, but not necessarily at all times. + + (The current loop is described in a comment in ScriptingReplManager.kt. Kotlin initiates a scripting exec, during which the script interpreter can request values from Kotlin, and at the end of which the script interpreter sends its STDOUT response.) + + A single packet is a standard JSON string of one of the form: + + { + 'action': String?, + 'identifier': String?, + 'data': Any?, + 'flags': Collection + } + + Identifiers should be set to a unique value in each request. + Each response should check have the same identifier as its corresponding request. + Upon receiving a response, both its action and identifier should be checked to match the relevant request. + + Some action types, data formats, and expected response data formats for packets sent from the Kotlin side to the script interpreter include: + 'motd': null -> 'motd_response': String + 'autocomplete': {'command': String, 'cursorpos': Int} -> 'autocomplete_response': Collection or String //List of matches, or help text to print. + 'exec': String -> 'exec_response': String + 'terminate': null -> 'terminate_response': String? //Error message or null. + These are basically a mirror of ScriptingBackendBase, so the same interface can be implemented in the scripting language. + + Some action types, data formats, and expected response types and formats for packets sent from the script interpreter to the Kotlin side include: + 'read': {'path': String} -> 'read_reponse': {'value': Any?, 'exception': String?} //Attribute/property access. + 'call': {'path': String, 'args': Collection, 'kwargs': Map} -> 'call_response': {'value': Any?, 'exception': String?} //Method/function call. + 'assign': {'path': String, 'value': Any} -> 'assign_response': String? //Error message or null. + 'dir': {'path': String} -> 'dir_response': {'value': Collection, 'exception': String?} //Names of all members/properties/attributes/methods. + //'items': {'path': Strign} -> 'keys_response': {} + //'args': {'path'} -> 'args_response': Collection> //Names and types of arguments accepted by a function. + + Flags are string values for communicating extra information that doesn't need a separate packet or response. Depending on the flag and action, they may be contextual to the packet, or they may not. I think I see them mostly as a way to semantically separate meta-communication about the protocol from actual requests for actions: + 'PassMic' //Indicates that the sending side will now begin listening instead, and the receiving side should send the next request. Sent by Kotlin side at start of script execution to allow script to request values, and should be sent by script interpreter immediately before sending response with results of execution. + + Thus, at the IPC level, all foreign backends will actually use the same language, which is this JSON-based protocol. Differences between Python, JS, Lua, etc. will all be down to how they interpret the "exec", "autocomplete", and "motd" actions differently, which each high-level scripting language is free to implement as works best for it. + */ +//object AnySerializer: KSerializer { +// override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Any", PrimitiveKind.STRING) +// +// override fun deserialize(decoder: Decoder): Any? { +// return decoder.decodeString() +// } +// +// override fun serialize(encoder: Encoder, value: Any?): Unit { +// encoder.encodeString("Test") +// } +// +// //Could put the ScriptingObjectIndex stuff here. +//} + +//class BoxSerializer(private val dataSerializer: KSerializer) : KSerializer { +// override val descriptor: SerialDescriptor = dataSerializer.descriptor +// override fun serialize(encoder: Encoder, value: Any?) = dataSerializer.serialize(encoder, 5) +// override fun deserialize(decoder: Decoder): Any? = (dataSerializer.deserialize(decoder)) +// //Could put the ScriptingObjectIndex stuff here. +// //It'd probably be easier to just use the LibGDX JSON tools. +//} + +val json = Json { + explicitNulls = true; //Disable these if it becomes a problem. + encodeDefaults = true +} + +@Serializable +data class ScriptingPacket( + var action: String?, + var identifier: String?, +// @Serializable(with = BoxSerializer::class) + var data: JsonElement? = null, + var flags: Collection = listOf() +) { + companion object { + fun fromJson(string: String): ScriptingPacket = json.decodeFromString(string) + } + + fun toJson() = json.encodeToString(this) +} + + +class ScriptingProtocol(val scriptingScope: ScriptingScope) { + + companion object { + fun transformPath(path: List): List { + return listOf() + } + + fun makeUniqueId(): String { + return "${System.nanoTime()}-${Random.nextBits(30)}-${UUID.randomUUID().toString()}" + } + + fun enforceIsResponse(original: ScriptingPacket, response: ScriptingPacket): ScriptingPacket { + if (!( + ((response.action == null && original.action == null) || response.action == original.action.toString() + "_reponse") + || response.identifier == original.identifier + )) { + throw IllegalStateException("Scripting packet response does not match request ID and type: ${original}, ${response}") + } + return response + } + } + + object makeActionRequests { + fun motd() = ScriptingPacket( + "motd", + makeUniqueId() + ) + fun autocomplete(command: String, cursorPos: Int?) = ScriptingPacket( + "autocomplete", + makeUniqueId(), + JsonObject(mapOf("command" to JsonPrimitive(command), "cursorpos" to JsonPrimitive(cursorPos))) + ) + fun exec(command: String) = ScriptingPacket( + "exec", + makeUniqueId(), + JsonPrimitive(command) + ) + fun terminate() = ScriptingPacket( + "terminate", + makeUniqueId() + ) + } + + object parseActionResponses { + fun motd(packet: ScriptingPacket): String = (packet.data as JsonPrimitive).content + fun autocomplete(packet: ScriptingPacket): AutocompleteResults = + if (packet.data is JsonArray) + AutocompleteResults((packet.data as List).map{ (it as JsonPrimitive).content }) + else + AutocompleteResults(listOf(), true, (packet.data as JsonPrimitive).content) + fun exec(packet: ScriptingPacket): String = (packet.data as JsonPrimitive).content + fun terminate(packet: ScriptingPacket): Exception? = if (packet.data == JsonNull || packet.data == null) null else RuntimeException((packet.data as JsonPrimitive).content) + } + + fun makeActionResponse(packet: ScriptingPacket): ScriptingPacket { + var action: String? = null + var data: JsonElement? = null + var flags = mutableListOf() + when (packet.action) { + "read" -> { + action = "read_response" + } + "call" -> { + action = "call_response" + } + "assign" -> { + action = "assign_response" + } + "dir" -> { + action = "dir_response" + } + else -> { + throw IllegalArgumentException("Unknown action received in scripting packet: ${packet.action}") + } + } + return ScriptingPacket(action, packet.identifier, data, flags) + } + fun resolvePath() { } +} diff --git a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt index 59dcaebf25c62..18141da73d596 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt @@ -1,39 +1,46 @@ package com.unciv.scripting.protocol +import com.unciv.scripting.AutocompleteResults +import com.unciv.scripting.ScriptingBackend import com.unciv.scripting.ScriptingScope +import com.unciv.scripting.protocol.ScriptingObjectIndex +import com.unciv.scripting.protocol.ScriptingPacket +import com.unciv.scripting.protocol.ScriptingProtocol import com.unciv.scripting.utils.Blackbox /* -1. A scripted action is initiated from the Kotlin side, by sending a command string to the script interpreter. -2. While the script interpreter is running, it has a chance to request values from the Kotlin side by sending back packets encoding attribute/property, key, and call stacks. -3. When the Kotlin side receives a request for a value, it uses reflection to access the requested property or call the requested method, and it sends the result to the script interpreter. -4. When the script interpreter finishes running, it sends a special packet to the Kotlin side. It then sends the REPL output of the command to the Kotlin side. -5. When the Kotlin interpreter receives the packet marking the end of the command run, it stops listening for value requests packets. It then receives the next value, and passes it back to the display/handler. - -``` -fun ExecuteCommand(command): - SendToInterpreter(command:String) - while True: - packet:Packet = ReceiveFromInterpreter().parsed() - if isPropertyrequest(packet): - SendToInterpreter(ResolvePacket(scriptingScope, packet)) - else if isCommandEndPacket(packet): - break - PrintToConsole(ReceiveFromInterpreter():String) -``` - -The "packets" should probably all be encoded as strings, probably JSON. The technique used to connect the script interpreter to the Kotlin code shouldn't matter. As long as it's wrapped up in and implements the `Blackbox` interface, IPC/embedding based on pipes, STDIN/STDOUT, sockets, queues, embedding, JNI, etc. should all be interchangeable. - -I'm not sure if there'd be much point to or a good technique for letting the script interpreter run constantly and initiate actions on its own, instead of waiting for commands from the Kotlin side. - -You would presumably have to interrupt the main Kotlin-side thread anyway in order to safely run any actions initiated by the script interpreter— Which means that you may as well just register a handler to call the script interpreter at that point. - -Plus, letting the script interpreter run completely in parallel would probably introduce potential for all sorts of issues with no-deterministic synchronicity, and performance issues Calling the script interpreter from + 1. A scripted action is initiated from the Kotlin side, by sending a command string to the script interpreter. + 2. While the script interpreter is running, it has a chance to request values from the Kotlin side by sending back packets encoding attribute/property, key, and call stacks. + 3. When the Kotlin side receives a request for a value, it uses reflection to access the requested property or call the requested method, and it sends the result to the script interpreter. + 4. When the script interpreter finishes running, it sends a special packet to the Kotlin side. It then sends the REPL output of the command to the Kotlin side. + 5. When the Kotlin interpreter receives the packet marking the end of the command run, it stops listening for value requests packets. It then receives the next value, and passes it back to the display/handler. + + ``` + fun ExecuteCommand(command): + SendToInterpreter(command:String) + while True: + packet:Packet = ReceiveFromInterpreter().parsed() + if isPropertyrequest(packet): + SendToInterpreter(ResolvePacket(scriptingScope, packet)) + else if isCommandEndPacket(packet): + break + PrintToConsole(ReceiveFromInterpreter():String) + ``` + + The "packets" should probably all be encoded as strings, probably JSON. The technique used to connect the script interpreter to the Kotlin code shouldn't matter. As long as it's wrapped up in and implements the `Blackbox` interface, IPC/embedding based on pipes, STDIN/STDOUT, sockets, queues, embedding, JNI, etc. should all be interchangeable. + + I'm not sure if there'd be much point to or a good technique for letting the script interpreter run constantly and initiate actions on its own, instead of waiting for commands from the Kotlin side. + + You would presumably have to interrupt the main Kotlin-side thread anyway in order to safely run any actions initiated by the script interpreter— Which means that you may as well just register a handler to call the script interpreter at that point. + + Plus, letting the script interpreter run completely in parallel would probably introduce potential for all sorts of issues with no-deterministic synchronicity, and performance issues Calling the script interpreter from */ -class ScriptingReplManager(val scriptingScope: ScriptingScope, val blackbox: Blackbox) { +class ScriptingReplManager(val scriptingScope: ScriptingScope, val blackbox: Blackbox): ScriptingBackend { + + val scriptingProtocol = ScriptingProtocol(scriptingScope) fun whileEval() { return @@ -43,17 +50,57 @@ class ScriptingReplManager(val scriptingScope: ScriptingScope, val blackbox: Bla blackbox.write(code) } - fun evalCode(code: String): List { + fun getRequestResponse(packetToSend: ScriptingPacket, enforceValidity: Boolean = true): ScriptingPacket { + blackbox.write(packetToSend.toJson() + "\n") + val response = ScriptingPacket.fromJson(blackbox.read(block=true)) + if (enforceValidity) { + ScriptingProtocol.enforceIsResponse(packetToSend, response) + } + return response + } + + override fun motd(): String { + return ScriptingProtocol.parseActionResponses.motd( + getRequestResponse( + ScriptingProtocol.makeActionRequests.motd() + ) + ) + return "${exec("motd()\n")}\n" + } + + override fun autocomplete(command: String, cursorPos: Int?): AutocompleteResults { + return ScriptingProtocol.parseActionResponses.autocomplete( + getRequestResponse( + ScriptingProtocol.makeActionRequests.autocomplete(command, cursorPos) + ) + ) + return AutocompleteResults() + } + + override fun exec(command: String): String { if (!blackbox.readyForWrite) { throw IllegalStateException("REPL not ready: ${blackbox}") } else { - runCode(code) + return ScriptingProtocol.parseActionResponses.exec( + getRequestResponse( + ScriptingProtocol.makeActionRequests.exec(command) + ) + ) + runCode(command) whileEval() - return blackbox.readAll(block=true) + return blackbox.readAll(block=true).joinToString("\n") } } - fun terminate(): Exception? { + override fun terminate(): Exception? { + val msg = ScriptingProtocol.parseActionResponses.terminate( + getRequestResponse( + ScriptingProtocol.makeActionRequests.terminate() + ) + ) + if (msg != null) { + return RuntimeException(msg) + } return blackbox.stop() } } diff --git a/core/src/com/unciv/scripting/protocol/ScriptingReplReflector.kt b/core/src/com/unciv/scripting/protocol/ScriptingReplReflector.kt index 417a9944739c1..c9d84a5ffa27e 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingReplReflector.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingReplReflector.kt @@ -4,26 +4,10 @@ import com.unciv.scripting.ScriptingScope import java.lang.System -class ScriptingReplReflector(scriptingScope: ScriptingScope) { +//class ScriptingReplReflector(scriptingScope: ScriptingScope) { - companion object { - - val kotlinObjectIdPrefix = "_unciv-kt-obj@" - - fun idKotlinObject(value: Any?): String { - //Don't depend on parsing this for extracting information. - //It's basically a readable/dumb UUID. - //Actually, it might be better to just use a UUID. I think that was actually my original plan. - return "${kotlinObjectIdPrefix}${System.identityHashCode(value)}:${if (value == null) "null" else value!!::class.qualifiedName}/${value.toString()}" - } - - fun isKotlinObject(value: Any?): Boolean { - return value is String && value!!.startsWith(kotlinObjectIdPrefix) - } - } - - val ScriptingObjectCache = mutableMapOf() - - fun resolvePath() { } -} +// val objectIndex = mutableMapOf() +// fun resolvePath() { } +//} +// Replace with/roll into ScriptingProtocol diff --git a/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt b/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt index e351606d95886..7d83fa1460721 100644 --- a/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt +++ b/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt @@ -11,7 +11,7 @@ class SubprocessBlackbox(val processCmd: Array): Blackbox { var inStream: BufferedReader? = null var outStream: BufferedWriter? = null - var processLaunchFail = "" + var processLaunchFail: String? = null override val isAlive: Boolean get() = process != null && process!!.isAlive() @@ -27,7 +27,7 @@ class SubprocessBlackbox(val processCmd: Array): Blackbox { } override fun toString(): String { - return "${this::class.simpleName}(process=${process})" + return "${this::class.simpleName}(process=${process}).apply{ inStream=${inStream}; outStream=${outStream}; processLaunchFail=${processLaunchFail} }" } override fun start() { diff --git a/core/src/com/unciv/scripting/reflection/Reflection.kt b/core/src/com/unciv/scripting/reflection/Reflection.kt index 28a57d932d905..3ff14b9fe0261 100644 --- a/core/src/com/unciv/scripting/reflection/Reflection.kt +++ b/core/src/com/unciv/scripting/reflection/Reflection.kt @@ -2,7 +2,6 @@ package com.unciv.scripting.reflection import kotlin.collections.ArrayList import kotlin.reflect.KCallable -import kotlin.reflect.KFunction import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KProperty1 import java.util.* @@ -59,8 +58,8 @@ object Reflection { val type: PathElementType, val name: String, val doEval: Boolean = false, - val params: List = listOf() //For key and index accesses, and function calls, evaluate `name` instead of using `params`. + val params: List = listOf() ) @@ -149,7 +148,19 @@ object Reflection { private val closingbrackets = null - fun splitToplevelExprs(code: String): List { + data class OpenBracket( + val char: Char, + var offset: Int + ) + + //class OpenBracketIterator() { + //} + + + //fun getOpenBracketStack() { + //} + + fun splitToplevelExprs(code: String, delimiters: String = ","): List { return code.split(',').map{ it.trim(' ') } var segs = ArrayList() val bracketdepths = mutableMapOf( diff --git a/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt b/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt new file mode 100644 index 0000000000000..e4b18dc0fb16d --- /dev/null +++ b/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt @@ -0,0 +1,106 @@ +package com.unciv.scripting.utils + +import com.unciv.scripting.ScriptingScope +import kotlin.reflect.KCallable +import kotlin.reflect.KClass +import kotlin.reflect.KFunction +import kotlin.reflect.KProperty1 +import com.badlogic.gdx.utils.Json + + +// Automatically running this should probably be a part of the build process. +// Probably do whatever is done with `TranslationFileWriter`. + +data class ApiSpecDef( + var path: String, + var isIterable: Boolean, + var isMapping: Boolean, + var isCallable: Boolean, + var callableArgs: List, + // These are the basic values that are needed to implement a scripting API. + + var _type: String? = null, + //_docstring: String? = null, + var _repeatedReferenceTo: String? = null, + var _isJsonType: Boolean? = null, + var _iterableValueType: String? = null, + var _mappingKeyType: String? = null, + var _mappingValueType: String? = null, + var _callableReturnType: String? = null, + var _callableArgTypes: List? = null + //var _callableArgDefaults: List + // These values can be used to enhance behaviour in a scripting API, and may be needed for generating bindings in some languages. +) + +fun makeMemberSpecDef(member: KCallable<*>): ApiSpecDef { + //val kclass = member.returnType.classifier as KClass<*> + val submembers = mutableSetOf() + /*for (m in kclass.members) { + try { + submembers.add( m.name ) + } catch (e: Exception) { + println("Error accessing name of property ${m}.") + } + }*/ + //val tmp: Collection = kclass.members.filter{ it.name != null }.map{ it.name!! } + //submembers.addAll(tmp) + //Using a straight `.map` + return ApiSpecDef( + path = member.name, + isIterable = "iterator" in submembers, + isMapping = "get" in submembers, + isCallable = member is KFunction, + callableArgs = member.parameters.map{ it.name } + ) +} + + +class ApiSpecGenerator(val scriptingScope: ScriptingScope) { + + fun isUncivClass(cls: KClass<*>): Boolean { + return cls.qualifiedName!!.startsWith("com.unciv") + } + + fun getAllUncivClasses(): Set> { + val searchclasses = mutableListOf>(scriptingScope::class) + val encounteredclasses = mutableSetOf>() + var i: Int = 0 + while (i < searchclasses.size) { + var cls = searchclasses[i] + for (m in cls.members) { + var kclass: KClass<*>? + try { + kclass = (m.returnType.classifier as KClass<*>) + } catch (e: Exception) { + println("Skipping property ${m.name} in ${cls.qualifiedName} because of ${e}") + continue + } + //kclass.members //Directly accessing kclass.members gets a `KotlinInternalReflectionError`, but iterating through `searchclasses` seems to work just fine. + if (isUncivClass(kclass!!) && kclass!! !in encounteredclasses) { + encounteredclasses.add(kclass!!) + searchclasses.add(kclass!!) + } + } + i += 1 + } + return encounteredclasses + } + + fun generateFlatApi(): List { + return scriptingScope::class.members.map{ it.name } + } + + fun generateClassApi(): Map> { + // Provide options for the scripting languages. This function + val classes = getAllUncivClasses() + var c = 0 + val output = mutableMapOf>( + *classes.map{ + it.qualifiedName!! to it.members.map{ c += 1; makeMemberSpecDef(it) } + }.toTypedArray() + ) + println("\nGathered ${c} property specifications across ${classes.size} classes.\n") + return output + } +} + diff --git a/core/src/com/unciv/scripting/utils/Blackbox.kt b/core/src/com/unciv/scripting/utils/Blackbox.kt index 2ab344b7451fb..3e35f423da61d 100644 --- a/core/src/com/unciv/scripting/utils/Blackbox.kt +++ b/core/src/com/unciv/scripting/utils/Blackbox.kt @@ -5,7 +5,7 @@ interface Blackbox { fun start() { } - fun stop(): Exception? { return null } // Return null on success, or return an Exception() on failure. Because there might be normal situations where a "black box" isn't viable to cleanly shut down, I'm thinking that letting exceptions be returned will let those situations be distinguished from actual errors. E.G.: Making an invalid API call to a requests library should still throw the Exception as usual, but making the right call only to find out that the network's down or a process is frozen would be a more "normal" and uncontrollable situation, so in that case the exception should be a return value instead of thrown. + fun stop(): Exception? = null // Return null on success, or return an Exception() on failure. Because there might be normal situations where a "black box" isn't viable to cleanly shut down, I'm thinking that letting exceptions be returned will let those situations be distinguished from actual errors. E.G.: Making an invalid API call to a requests library should still throw the Exception as usual, but making the right call only to find out that the network's down or a process is frozen would be a more "normal" and uncontrollable situation, so in that case the exception should be a return value instead of thrown. val isAlive: Boolean get() = false @@ -16,7 +16,7 @@ interface Blackbox { val readyForWrite: Boolean get() = false - fun read(block: Boolean = true): String // Return a single string if either blocking or ready to read, or throw an IllegalStateException() otherwise. + fun read(block: Boolean = true): String = ""// Return a single string if either blocking or ready to read, or throw an IllegalStateException() otherwise. fun readAll(block: Boolean = true, limit: Int = 0): List { // Read out all lines up to a limit if greater than zero, returning an empty list if none are available and no blocking is allowed val lines = ArrayList() @@ -32,6 +32,9 @@ interface Blackbox { return lines } - fun write(string: String) + fun write(string: String) { } } + +class DummyBlackbox(): Blackbox { +} diff --git a/core/src/com/unciv/scripting/utils/SourceManager.kt b/core/src/com/unciv/scripting/utils/SourceManager.kt index c4406efda58b3..a4b5cc34abdea 100644 --- a/core/src/com/unciv/scripting/utils/SourceManager.kt +++ b/core/src/com/unciv/scripting/utils/SourceManager.kt @@ -53,6 +53,7 @@ object SourceManager { addfile(enginedir, fp) } Runtime.getRuntime().addShutdownHook( + // Delete on JVM shutdown, not on backend object destruction/termination. The copied files shouldn't be huge anyway, and I trust the shutdown hook to be run more reliably. thread(start = false, name = "Delete ${outdir.toString()}") { outdir.deleteDirectory() } diff --git a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt index 7975596ca36ea..2122618482473 100644 --- a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt @@ -156,8 +156,11 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un }) var termbutton = ImageGetter.getImage("OtherIcons/Stop") termbutton.onClick({ - scriptingState.termBackend(index) + val exc: Exception? = scriptingState.termBackend(index) updateRunning() + if (exc != null) { + echo("Failed to stop ${backend.metadata.displayname} backend: ${exc.toString()}") + } }) runningList.add(termbutton.surroundWithCircle(40f)).row() i += 1 @@ -190,7 +193,7 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un private fun autocomplete() { val original = inputField.text val cursorpos = inputField.getCursorPosition() - var results = scriptingState.getAutocomplete(input, cursorpos) + var results = scriptingState.autocomplete(input, cursorpos) if (results.isHelpText) { echo(results.helpText) return From 3c268d8fb31621e51d1ed6d211cbf856c818597d Mon Sep 17 00:00:00 2001 From: will-ca Date: Sun, 7 Nov 2021 11:39:33 +0000 Subject: [PATCH 25/93] Python bindings for accessing, indexing, assigning, calling, and performing autocomplete on most Unciv objects. --- .../scripting/enginedata/python/main.py | 19 ++- .../enginedata/python/unciv/__init__.py | 4 +- .../enginedata/python/unciv/autocompletion.py | 6 +- .../scripting/enginedata/python/unciv/ipc.py | 38 +++-- .../enginedata/python/unciv/utils.py | 8 + .../enginedata/python/unciv/wrapping.py | 47 +++--- android/assets/scripting/python.json | 1 + .../src/com/unciv/scripting/ScriptingState.kt | 3 + .../protocol/ScriptingObjectIndex.kt | 10 +- .../scripting/protocol/ScriptingProtocol.kt | 156 +++++++++++++++++- .../protocol/ScriptingReplManager.kt | 37 ++++- .../protocol/ScriptingReplReflector.kt | 13 -- .../scripting/protocol/SubprocessBlackbox.kt | 14 +- .../unciv/scripting/reflection/Reflection.kt | 4 +- 14 files changed, 274 insertions(+), 86 deletions(-) create mode 100644 android/assets/scripting/enginedata/python/unciv/utils.py delete mode 100644 core/src/com/unciv/scripting/protocol/ScriptingReplReflector.kt diff --git a/android/assets/scripting/enginedata/python/main.py b/android/assets/scripting/enginedata/python/main.py index 22539c5b484a2..9337171d33ac5 100644 --- a/android/assets/scripting/enginedata/python/main.py +++ b/android/assets/scripting/enginedata/python/main.py @@ -7,9 +7,12 @@ import unciv + foreignActionSender = unciv.ipc.ForeignActionSender() - foreignScope = unciv.wrapping.ForeignObject('') + foreignScope = {n: unciv.wrapping.ForeignObject(n, foreignrequester=foreignActionSender.GetForeignActionResponse) for n in ('civInfo', 'gameInfo', 'uncivGame', 'worldScreen', 'isInGame')} + + foreignAutocompleter = unciv.autocompletion.AutocompleteManager(foreignScope) class ForeignActionReplReceiver(unciv.ipc.ForeignActionReceiver): @unciv.ipc.receiverMethod('motd', 'motd_response') @@ -23,10 +26,14 @@ def EvalForeignMotd(self, packet): """ @unciv.ipc.receiverMethod('autocomplete', 'autocomplete_response') def EvalForeignAutocomplete(self, packet): - return "AC." + assert 'PassMic' in packet.flags, f"Expected 'PassMic' in packet flags: {packet}" + res = foreignAutocompleter.GetAutocomplete(packet.data["command"]) + foreignActionSender.SendForeignAction({'action':None, 'identifier': None, 'data':None, 'flags':('PassMic',)}) + return res @unciv.ipc.receiverMethod('exec', 'exec_response') def EvalForeignExec(self, packet): line = packet.data + assert 'PassMic' in packet.flags, f"Expected 'PassMic' in packet flags: {packet}" with unciv.ipc.FakeStdout() as fakeout: print(f">>> {str(line)}") try: @@ -37,16 +44,18 @@ def EvalForeignExec(self, packet): else: print(eval(code, self.scope, self.scope)) except Exception as e: - return repr(e) + return unciv.utils.formatException(e) else: return fakeout.getvalue() + finally: + foreignActionSender.SendForeignAction({'action':None, 'identifier': None, 'data':None, 'flags':('PassMic',)}) @unciv.ipc.receiverMethod('terminate', 'terminate_response') def EvalForeignTerminate(self, packet): return None - foreignActionReceiver = ForeignActionReplReceiver(scope=globals()) + foreignActionReceiver = ForeignActionReplReceiver(scope=foreignScope) foreignActionReceiver.ForeignREPL() except Exception as e: - print(f"Fatal error in Python interepreter: {repr(e)}", file=stdout, flush=True) + print(f"Fatal error in Python interepreter: {unciv.utils.formatException(e)}", file=stdout, flush=True) diff --git a/android/assets/scripting/enginedata/python/unciv/__init__.py b/android/assets/scripting/enginedata/python/unciv/__init__.py index 040cb0c845af6..de1e592509c3c 100644 --- a/android/assets/scripting/enginedata/python/unciv/__init__.py +++ b/android/assets/scripting/enginedata/python/unciv/__init__.py @@ -1,4 +1,4 @@ -__all__ = ['autocompletion', 'ipc', 'wrapping'] +__all__ = ['autocompletion', 'ipc', 'utils', 'wrapping'] #Unsupported by Micropython -from . import autocompletion, ipc, wrapping +from . import autocompletion, ipc, utils, wrapping diff --git a/android/assets/scripting/enginedata/python/unciv/autocompletion.py b/android/assets/scripting/enginedata/python/unciv/autocompletion.py index d8ca37c2b00ba..a112b594fabcc 100644 --- a/android/assets/scripting/enginedata/python/unciv/autocompletion.py +++ b/android/assets/scripting/enginedata/python/unciv/autocompletion.py @@ -1,7 +1,9 @@ """Advanced autocompletion. Returns keys for """ +from . import utils -class AutoCompleteManager: + +class AutocompleteManager: def __init__(self, scope=None): self.scope = globals() if scope is None else scope def Evaled(self, path): @@ -34,5 +36,5 @@ def GetAutocomplete(self, command): else: return tuple([attrbase+attrjoin+a for a in (dir(self.Evaled(attrbase)) if attrbase else self.scope) if a.startswith(attrleaf)]) except (NameError, AttributeError, KeyError, IndexError, SyntaxError) as e: - return "No autocompletion found: "+repr(e) + return "No autocompletion found: "+utils.formatException(e) diff --git a/android/assets/scripting/enginedata/python/unciv/ipc.py b/android/assets/scripting/enginedata/python/unciv/ipc.py index 3279570ce1c38..356cadda8f7f6 100644 --- a/android/assets/scripting/enginedata/python/unciv/ipc.py +++ b/android/assets/scripting/enginedata/python/unciv/ipc.py @@ -1,11 +1,13 @@ -import sys, io, json +import sys, io, json, time, random +stdout = sys.stdout #def ResolvePath(path, scope): ## return eval(path, scope, scope) # raise NotImplementedError() - +def MakeUniqueId(): + return f"{time.time_ns()}-{random.getrandbits(30)}" class ForeignError(RuntimeError): pass @@ -48,10 +50,10 @@ def __init__(self, sender=None, receiver=None): self.receiver = receiver def sender(self, message): try: - print(message, file=sys.stdout, flush=True) + print(message, file=stdout, flush=True) except TypeError: #No flush on `micropython`. - print(message, file=sys.stdout) + print(message, file=stdout) def receiver(self): return sys.stdin.readline() # def EncodeForeignAction(self, action, identifier=None, **data): @@ -63,12 +65,15 @@ def receiver(self): class ForeignActionSender(ForeignActionManager): - def MakeUniqueID(self): - return 4 # Chosen by fair dice roll. Guaranteed to be random. - def SendForeignAction(self, actionparams, responsetype): - identifier = self.MakeUniqueID() - self.sender(ForeignPacket(**actionparams, identifier=identifier).serialized()) - return ForeignPacket.deserialized(self.receiver()).enforce_type(responsetype, identifier).data +# def MakeUniqueID(self): +# return 4 # Chosen by fair dice roll. Guaranteed to be random. + def SendForeignAction(self, actionparams): + self.sender(ForeignPacket(**actionparams).serialized()) + + def GetForeignActionResponse(self, actionparams, responsetype): + identifier = MakeUniqueId() + self.SendForeignAction({**actionparams, 'identifier': identifier}) + return ForeignPacket.deserialized(self.receiver()).enforce_type(responsetype, identifier) def receiverMethod(action, response): @@ -99,8 +104,13 @@ def RespondForeignAction(self, request): else: raise ForeignError("Unknown action type for foreign action request: " + repr(decoded)) self.sender(ForeignPacket(raction, decoded.identifier, rdata).serialized()) - def AwaitForeignAction(self): + def AwaitForeignAction(self):#, *, ignoreempty=True): self.RespondForeignAction(self.receiver()) +# while True: +# line = self.receiver() +# if line or not ignoreempty: +# self.RespondForeignAction(line) +# break def ForeignREPL(self): while True: self.AwaitForeignAction() @@ -118,12 +128,12 @@ def __exit__(self, *exc): #class ForeignActionBindingSender(ForeignActionSender): # #Probably easier to just define these as needed in the classes that call them. # def SendForeignCall(self, path, args, kwargs): -# return self.SendForeignAction({'action': ('call', 'data': {'path':path, 'args':args, 'kwargs':kwargs}}, 'call_response') +# return self.GetForeignActionResponse({'action': ('call', 'data': {'path':path, 'args':args, 'kwargs':kwargs}}, 'call_response') # def SendForeignRead(self, path): # identifier = self.MakeUniqueID() -# return self.SendForeignAction({'action': 'read', 'data': {'path':path}}, 'read_response') +# return self.GetForeignActionResponse({'action': 'read', 'data': {'path':path}}, 'read_response') # def SendForeignAssign(self, path, value): -# return self.SendForeignAction({'action': 'assign', 'data': {'path':path, 'value':value}}) +# return self.GetForeignActionResponse({'action': 'assign', 'data': {'path':path, 'value':value}}) # def MakeForeignFunction(self, callpath, callargs): # pass diff --git a/android/assets/scripting/enginedata/python/unciv/utils.py b/android/assets/scripting/enginedata/python/unciv/utils.py new file mode 100644 index 0000000000000..adcbaaeb8950f --- /dev/null +++ b/android/assets/scripting/enginedata/python/unciv/utils.py @@ -0,0 +1,8 @@ +def formatException(exception): + try: + #Won't work on Upy. + import traceback + return "".join(traceback.format_exception(type(exception), exception, exception.__traceback__)) + except: + return repr(exception) + diff --git a/android/assets/scripting/enginedata/python/unciv/wrapping.py b/android/assets/scripting/enginedata/python/unciv/wrapping.py index f1bfa3bc39d2c..e8e9dbe9c6f7d 100644 --- a/android/assets/scripting/enginedata/python/unciv/wrapping.py +++ b/android/assets/scripting/enginedata/python/unciv/wrapping.py @@ -44,22 +44,20 @@ def _func(*args, **kwargs): pass return _func + def dummyForeignRequester(actionparams, responsetype): return actionparams, responsetype - -def stdForeignRequester(actionparams, responsetype): - request = ipc.ForeignPacket(**actionparams) - requestjson = request.serialized() - try: - print(requestjson, file=stdout, flush=True) - except TypeError: - print(requestjson, file=stdout) - response = ipc.ForeignPacket.deserialized(sys.stdin.readline()) - assert request.identifier == response.identifier, f"Mismatched identifier: {request.identifier} != {response.identifier}" - assert response.action == responsetype, f"Mismatched response type: {response.action} != {responsetype}" - return response +def foreignValueParser(packet): + if packet.data["exception"] is not None: + raise ipc.ForeignError(packet.data["exception"]) + return packet.data["value"] + +def foreignErrmsgChecker(packet): + if packet.data is not None: + raise ipc.ForeignError(packet.data) + class ForeignObject: def __init__(self, path, foreignrequester=dummyForeignRequester): @@ -72,9 +70,9 @@ def _getpath(self): def __getattr__(self, name): self.__class__ self._path - return self.__class__((*self._path, f".{name}")) + return self.__class__((*self._path, f".{name}"), self._foreignrequester) def __getitem__(self, key): - return self.__class__((*self._path, f"[{json.dumps(key)}]")) + return self.__class__((*self._path, f"[{json.dumps(key)}]"), self._foreignrequester) @ForeignRequestMethod def _getvalue(self): return ({ @@ -84,7 +82,7 @@ def _getvalue(self): } }, 'read_response', - None) + foreignValueParser) @ForeignRequestMethod def __dir__(self): return ({ @@ -94,7 +92,7 @@ def __dir__(self): } }, 'dir_response', - None) + foreignValueParser) # @ForeignRequestMethod # @property # def __doc__(self): @@ -116,18 +114,19 @@ def __setattr__(self, name, value): } }, 'assign_response', - None) + foreignErrmsgChecker) @ForeignRequestMethod def __call__(self, *args, **kwargs): return ({ 'action': 'call', 'data': { + 'path': self._getpath(), 'args': args, 'kwargs': kwargs } }, 'call_response', - None) + foreignValueParser) @ForeignRequestMethod def __setitem__(self, key, value): return ({ @@ -138,12 +137,12 @@ def __setitem__(self, key, value): } }, 'assign_response', - None) - def keys(self): - raise NotImplemented() - return { - '' - } + foreignValueParser) +# def keys(self): +# raise NotImplemented() +# return { +# '' +# } #class ForeignScope: # _path = () diff --git a/android/assets/scripting/python.json b/android/assets/scripting/python.json index 891cfc1a471a4..17101ee7da6e7 100644 --- a/android/assets/scripting/python.json +++ b/android/assets/scripting/python.json @@ -3,6 +3,7 @@ unciv/__init__.py unciv/autocompletion.py unciv/ipc.py + unciv/utils.py unciv/wrapping.py main.py ] diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index 00c722682e10b..5b41cfbbd4ba0 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -23,6 +23,8 @@ import kotlin.math.min scriptingScope ?ScriptingReplManager(): Blackbox() // Common interface to wrap foreign interpreter with pipes, STDIN/STDOUT, queues, sockets, embedding, JNI, etc. + ScriptingProtocol(): + scriptingScope ?folderHandler: setupInterpreterEnvironment() // If used, a temporary directory with file structure copied from engine and shared folders in `assets/scripting`. ConsoleScreen(): // Persistent as long as window isn't resized. Recreates itself and restores most of its state from scriptingState if resized. scriptingState @@ -32,6 +34,7 @@ import kotlin.math.min MainMenuScreen(): consoleScreen scriptingState // Same as for worldScreen. + ScriptingObjectIndex() ``` */ diff --git a/core/src/com/unciv/scripting/protocol/ScriptingObjectIndex.kt b/core/src/com/unciv/scripting/protocol/ScriptingObjectIndex.kt index d40d0099d298a..1947f12ecd5be 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingObjectIndex.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingObjectIndex.kt @@ -7,18 +7,18 @@ import java.util.UUID object ScriptingObjectIndex { - val objs = mutableMapOf>() + private val objs = mutableMapOf>() - val kotlinObjectIdPrefix = "_unciv-kt-obj@" // load from ScriptAPIConstants.json + private val kotlinObjectIdPrefix = "_unciv-kt-obj@" // load from ScriptAPIConstants.json - fun idKotlinObject(value: Any?): String { + private fun idKotlinObject(value: Any?): String { //Don't depend on parsing this for extracting information. //It's basically a readable/dumb UUID. //Actually, it might be better to just use a UUID. I think that was actually my original plan. return "${kotlinObjectIdPrefix}${System.identityHashCode(value)}:${if (value == null) "null" else value::class.qualifiedName}/${value.toString()}_${UUID.randomUUID().toString()}" } - fun isKotlinToken(value: Any?): Boolean { + private fun isKotlinToken(value: Any?): Boolean { return value is String && value.startsWith(kotlinObjectIdPrefix) } @@ -31,12 +31,14 @@ object ScriptingObjectIndex { } fun getToken(obj: Any?): String { + clean() val token = idKotlinObject(obj) objs[token] = WeakReference(obj) return token } fun getReal(token: Any?): Any? { + clean() if (isKotlinToken(token)) { return objs[token]!!.get() } else { diff --git a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt index 839e60a1b3497..b3e5b0f4f60dd 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt @@ -26,7 +26,7 @@ import java.util.UUID /* A single IPC action consists of one request packet and one response packet. A request packet should always be followed by a response packet if it has an action. - If a request packet has a null action, then it does not need to be followed by a response. This is to let flags be sent without generating useless responses. + If a request packet has a null action, then it should not be followed by a response. This is to let flags be sent without generating useless responses. However, responses do not have to be sent in the same order as their correspond requests. New requests can be sent out while old ones are left "open"— E.G., if creating a response requires requesting new information. @@ -61,6 +61,7 @@ import java.util.UUID 'dir': {'path': String} -> 'dir_response': {'value': Collection, 'exception': String?} //Names of all members/properties/attributes/methods. //'items': {'path': Strign} -> 'keys_response': {} //'args': {'path'} -> 'args_response': Collection> //Names and types of arguments accepted by a function. + //'length': {'path': String} -> 'length_response': Int Flags are string values for communicating extra information that doesn't need a separate packet or response. Depending on the flag and action, they may be contextual to the packet, or they may not. I think I see them mostly as a way to semantically separate meta-communication about the protocol from actual requests for actions: 'PassMic' //Indicates that the sending side will now begin listening instead, and the receiving side should send the next request. Sent by Kotlin side at start of script execution to allow script to request values, and should be sent by script interpreter immediately before sending response with results of execution. @@ -109,6 +110,8 @@ data class ScriptingPacket( } fun toJson() = json.encodeToString(this) + + fun hasFlag(flag: ScriptingProtocol.KnownFlag) = flag.value in flags } @@ -116,12 +119,23 @@ class ScriptingProtocol(val scriptingScope: ScriptingScope) { companion object { fun transformPath(path: List): List { - return listOf() + return path.map{ Reflection.PathElement( + type = it.type, + name = it.name, + doEval = it.doEval, + params = it.params.map( { p -> ScriptingObjectIndex.getReal(p) }) + ) } } fun makeUniqueId(): String { return "${System.nanoTime()}-${Random.nextBits(30)}-${UUID.randomUUID().toString()}" } + + val responseTypes = mapOf( + "motd" to "motd_response", + "autocomplete" to "autocomplete_response", + "exec" to "exec_response" + ) fun enforceIsResponse(original: ScriptingPacket, response: ScriptingPacket): ScriptingPacket { if (!( @@ -132,6 +146,55 @@ class ScriptingProtocol(val scriptingScope: ScriptingScope) { } return response } + + fun getJsonElement(value: T?): JsonElement { + if (value is JsonElement) { + return value + } + if (value is Map<*, *>) { + return JsonObject( (value as Map).mapValues{ getJsonElement(it.value) } ) + } + if (value is Collection<*>) { + return JsonArray(value.map{ getJsonElement(it) }) + } + if (value is String) { + return JsonPrimitive(value as String) + } + if (value is Int || value is Long || value is Float || value is Double) { + return JsonPrimitive(value as Number) + } + if (value is Boolean) { + return JsonPrimitive(value as Boolean) + } + if (value == null) { + return JsonNull + } + return JsonPrimitive(ScriptingObjectIndex.getToken(value)) + } + + fun getJsonReal(value: JsonElement): Any? { + if (value is JsonNull || value == JsonNull) { + return null + } + if (value is JsonArray) { + return (value as List).map{ getJsonReal(it) }// as List + } + if (value is JsonObject) { + return (value as Map).mapValues{ getJsonReal(it.value) }// as Map + } + if (value is JsonPrimitive) { + val v = value as JsonPrimitive + if (v.isString) { + return ScriptingObjectIndex.getReal(v.content) + } else { + return v.content.toIntOrNull() + ?: v.content.toDoubleOrNull() + ?: v.content.toBooleanStrictOrNull() + ?: v.content + } + } + throw IllegalArgumentException("Unrecognized type of JsonElement: ${value::class}/${value}") + } } object makeActionRequests { @@ -142,12 +205,14 @@ class ScriptingProtocol(val scriptingScope: ScriptingScope) { fun autocomplete(command: String, cursorPos: Int?) = ScriptingPacket( "autocomplete", makeUniqueId(), - JsonObject(mapOf("command" to JsonPrimitive(command), "cursorpos" to JsonPrimitive(cursorPos))) + JsonObject(mapOf("command" to JsonPrimitive(command), "cursorpos" to JsonPrimitive(cursorPos))), + listOf(KnownFlag.PassMic.value) ) fun exec(command: String) = ScriptingPacket( "exec", makeUniqueId(), - JsonPrimitive(command) + JsonPrimitive(command), + listOf(KnownFlag.PassMic.value) ) fun terminate() = ScriptingPacket( "terminate", @@ -157,31 +222,110 @@ class ScriptingProtocol(val scriptingScope: ScriptingScope) { object parseActionResponses { fun motd(packet: ScriptingPacket): String = (packet.data as JsonPrimitive).content + fun autocomplete(packet: ScriptingPacket): AutocompleteResults = if (packet.data is JsonArray) AutocompleteResults((packet.data as List).map{ (it as JsonPrimitive).content }) else AutocompleteResults(listOf(), true, (packet.data as JsonPrimitive).content) + fun exec(packet: ScriptingPacket): String = (packet.data as JsonPrimitive).content - fun terminate(packet: ScriptingPacket): Exception? = if (packet.data == JsonNull || packet.data == null) null else RuntimeException((packet.data as JsonPrimitive).content) + + fun terminate(packet: ScriptingPacket): Exception? = + if (packet.data == JsonNull || packet.data == null) + null + else + RuntimeException((packet.data as JsonPrimitive).content) + } + + enum class KnownFlag(val value: String) { + PassMic("PassMic") } fun makeActionResponse(packet: ScriptingPacket): ScriptingPacket { var action: String? = null var data: JsonElement? = null var flags = mutableListOf() + var value: JsonElement = JsonNull + var exception: JsonElement = JsonNull when (packet.action) { "read" -> { action = "read_response" + try { + value = getJsonElement( + Reflection.evalKotlinString( + scriptingScope, + (((packet.data as JsonObject)["path"]) as JsonPrimitive).content + ) + ) + } catch (e: Exception) { + value = JsonNull + exception = JsonPrimitive(e.toString()) + } + data = JsonObject(mapOf( + "value" to value, + "exception" to exception + )) } "call" -> { action = "call_response" + try { + var path = Reflection.parseKotlinPath((((packet.data as JsonObject)["path"]) as JsonPrimitive).content) as MutableList + val params = getJsonReal((packet.data as JsonObject)["args"]!!) as List + val kwparams = getJsonReal((packet.data as JsonObject)["kwargs"]!!) as Map + if (kwparams.size > 0) { + throw UnsupportedOperationException("Keyword arguments are not currently supported: ${kwparams}") + } + path.add(Reflection.PathElement( + type = Reflection.PathElementType.Call, + name = "", + doEval = false, + params = params + )) + value = getJsonElement( + Reflection.resolveInstancePath( + scriptingScope, + transformPath(path) + ) + ) + } catch (e: Exception) { + value = JsonNull + exception = JsonPrimitive(e.toString()) + } + data = JsonObject(mapOf( + "value" to value, + "exception" to exception + )) } "assign" -> { action = "assign_response" + try { + Reflection.setInstancePath( + scriptingScope, + Reflection.parseKotlinPath((((packet.data as JsonObject)["path"]) as JsonPrimitive).content), + getJsonReal((packet.data as JsonObject)["value"]!!) + ) + } catch (e: Exception) { + exception = JsonPrimitive(e.toString()) + } + data = exception } "dir" -> { action = "dir_response" + try { + val leaf = Reflection.evalKotlinString( + scriptingScope, + (((packet.data as JsonObject)["path"]) as JsonPrimitive).content + ) + value = getJsonElement(leaf!!::class.members.map{it.name}) + } catch (e: Exception) { + value = JsonNull + exception = JsonPrimitive(e.toString()) + } + data = JsonObject(mapOf( + "value" to value, + "exception" to exception + )) } else -> { throw IllegalArgumentException("Unknown action received in scripting packet: ${packet.action}") @@ -190,7 +334,7 @@ class ScriptingProtocol(val scriptingScope: ScriptingScope) { return ScriptingPacket(action, packet.identifier, data, flags) } - fun resolvePath() { } +// fun resolvePath() { } } diff --git a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt index 18141da73d596..50c1f3f0bfc87 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt @@ -50,8 +50,9 @@ class ScriptingReplManager(val scriptingScope: ScriptingScope, val blackbox: Bla blackbox.write(code) } - fun getRequestResponse(packetToSend: ScriptingPacket, enforceValidity: Boolean = true): ScriptingPacket { + fun getRequestResponse(packetToSend: ScriptingPacket, enforceValidity: Boolean = true, execLoop: () -> Unit = fun(){}): ScriptingPacket { blackbox.write(packetToSend.toJson() + "\n") + execLoop() val response = ScriptingPacket.fromJson(blackbox.read(block=true)) if (enforceValidity) { ScriptingProtocol.enforceIsResponse(packetToSend, response) @@ -59,6 +60,19 @@ class ScriptingReplManager(val scriptingScope: ScriptingScope, val blackbox: Bla return response } + fun foreignExecLoop() { + while (true) { + val request = ScriptingPacket.fromJson(blackbox.read(block=true)) + if (request.action != null) { + val response = scriptingProtocol.makeActionResponse(request) + blackbox.write(response.toJson() + "\n") + } + if (request.hasFlag(ScriptingProtocol.KnownFlag.PassMic)) { + break + } + } + } + override fun motd(): String { return ScriptingProtocol.parseActionResponses.motd( getRequestResponse( @@ -71,7 +85,8 @@ class ScriptingReplManager(val scriptingScope: ScriptingScope, val blackbox: Bla override fun autocomplete(command: String, cursorPos: Int?): AutocompleteResults { return ScriptingProtocol.parseActionResponses.autocomplete( getRequestResponse( - ScriptingProtocol.makeActionRequests.autocomplete(command, cursorPos) + ScriptingProtocol.makeActionRequests.autocomplete(command, cursorPos), + execLoop = { foreignExecLoop() } ) ) return AutocompleteResults() @@ -83,7 +98,8 @@ class ScriptingReplManager(val scriptingScope: ScriptingScope, val blackbox: Bla } else { return ScriptingProtocol.parseActionResponses.exec( getRequestResponse( - ScriptingProtocol.makeActionRequests.exec(command) + ScriptingProtocol.makeActionRequests.exec(command), + execLoop = { foreignExecLoop() } ) ) runCode(command) @@ -93,13 +109,16 @@ class ScriptingReplManager(val scriptingScope: ScriptingScope, val blackbox: Bla } override fun terminate(): Exception? { - val msg = ScriptingProtocol.parseActionResponses.terminate( - getRequestResponse( - ScriptingProtocol.makeActionRequests.terminate() + try { + val msg = ScriptingProtocol.parseActionResponses.terminate( + getRequestResponse( + ScriptingProtocol.makeActionRequests.terminate() + ) ) - ) - if (msg != null) { - return RuntimeException(msg) + if (msg != null) { + return RuntimeException(msg) + } + } catch (e: Exception) { } return blackbox.stop() } diff --git a/core/src/com/unciv/scripting/protocol/ScriptingReplReflector.kt b/core/src/com/unciv/scripting/protocol/ScriptingReplReflector.kt deleted file mode 100644 index c9d84a5ffa27e..0000000000000 --- a/core/src/com/unciv/scripting/protocol/ScriptingReplReflector.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.unciv.scripting.protocol - -import com.unciv.scripting.ScriptingScope -import java.lang.System - - -//class ScriptingReplReflector(scriptingScope: ScriptingScope) { - -// val objectIndex = mutableMapOf() - -// fun resolvePath() { } -//} -// Replace with/roll into ScriptingProtocol diff --git a/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt b/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt index 7d83fa1460721..918ff754e7bf9 100644 --- a/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt +++ b/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt @@ -49,15 +49,19 @@ class SubprocessBlackbox(val processCmd: Array): Blackbox { try { if (isAlive) { process!!.destroy() - inStream!!.close() - outStream!!.close() } } catch (e: Exception) { return e + } finally { + try { + inStream!!.close() + outStream!!.close() + } catch (e: Exception) { + } + process = null + inStream = null + outStream = null } - process = null - inStream = null - outStream = null return null } diff --git a/core/src/com/unciv/scripting/reflection/Reflection.kt b/core/src/com/unciv/scripting/reflection/Reflection.kt index 3ff14b9fe0261..2ae371e82be83 100644 --- a/core/src/com/unciv/scripting/reflection/Reflection.kt +++ b/core/src/com/unciv/scripting/reflection/Reflection.kt @@ -59,7 +59,7 @@ object Reflection { val name: String, val doEval: Boolean = false, //For key and index accesses, and function calls, evaluate `name` instead of using `params`. - val params: List = listOf() + val params: List = listOf() ) @@ -191,7 +191,7 @@ object Reflection { if (element.doEval) evalKotlinString(instance!!, element.name)!! else - element.params[0] + element.params[0]!! ) } PathElementType.Call -> { From 17c74fc848035a03970a944cc34cfb884a82de46 Mon Sep 17 00:00:00 2001 From: will-ca Date: Mon, 8 Nov 2021 02:30:23 +0000 Subject: [PATCH 26/93] Protocol changes. Python iteration over Kotlin objects. --- .gitignore | 1 + .../scripting/enginedata/python/main.py | 48 +++ .../scripting/enginedata/python/unciv/ipc.py | 10 +- .../enginedata/python/unciv/wrapping.py | 94 ++++- core/src/com/unciv/UncivGame.kt | 34 ++ .../src/com/unciv/scripting/ScriptingScope.kt | 2 + .../src/com/unciv/scripting/ScriptingState.kt | 7 +- .../scripting/protocol/ScriptingProtocol.kt | 321 ++++++++++-------- .../protocol/ScriptingReplManager.kt | 7 +- .../unciv/scripting/reflection/Reflection.kt | 8 +- .../ScriptingObjectIndex.kt | 8 +- .../unciv/scripting/utils/TokenizingJson.kt | 139 ++++++++ 12 files changed, 505 insertions(+), 174 deletions(-) rename core/src/com/unciv/scripting/{protocol => utils}/ScriptingObjectIndex.kt (78%) create mode 100644 core/src/com/unciv/scripting/utils/TokenizingJson.kt diff --git a/.gitignore b/.gitignore index 3a82f827567ca..a0af9db6286e1 100644 --- a/.gitignore +++ b/.gitignore @@ -148,3 +148,4 @@ GameSettings.json # Python *.pyc +__pycache__ diff --git a/android/assets/scripting/enginedata/python/main.py b/android/assets/scripting/enginedata/python/main.py index 9337171d33ac5..a7d25af8a4579 100644 --- a/android/assets/scripting/enginedata/python/main.py +++ b/android/assets/scripting/enginedata/python/main.py @@ -1,4 +1,44 @@ +""" +There are basically two types of Python objects in this API: + + * Wrappers. + * Tokens. + +--- + +A **wrapper** is an object that stores a list of attribute names, keys, and function call parameters corresponding to a path in the Kotlin/JVM namespace. E.G.: `"civInfo.civilizations[0].population.setPopulation"` is a string representation of a wrapped path that begins with two attribute accesses, followed by one array index, and two more attribute names. + +A wrapper object does not store any more values than that. When it is evaluated, it uses a simple IPC protocol to request an up-to-date real value from the game's Kotlin code. + +Because the wrapper class implements many Magic Methods, many programming idioms common in Python are possible with them. + +Accessing an attribute on a wrapper object returns a new wrapper object that has an additional name set to "Property" at the end of its path list. Performing an array or dictionary index returns a new wrapper with an additional "Key" element in its path list. + +Calling a wrapper object as a function or method also creates a new path list with an extra "Call" element at the end. But in this case, the new path list is immediately sent to Kotlin/the JVM instead of being used in a new wrapper object, and the returned value is the result from the requested function call in the Kotlin/JVM namespace. + +Likewise, assigning to an attribute or an index/key on a wrapper object sends an IPC request to assign to the Kotlin/JVM object at its path, instead of modifying the wrapper object. + +When a Kotlin/JVM object implements a property for size or keys, the Python wrapper for it can also be iterated like a `tuple` or a `dict`, such as in `for` loops anlist comprehensions. + +--- + +A **token** is a string that has been generated by `ScriptingObjectIndex.kt` to represent a Kotlin object. +When a path requested by a script resolves to immutable primative like a number or boolean, or something that is otherwise practical to serialize, then the return is usually a real object. + +However, if the value requested is an instance of a complicated Kotlin/JVM class, then the IPC protocol instead creates a unique string to identify it. + +The original object is stored in the JVM in a mapping as a weak reference. The string doesn't have (m)any special properties as a Python object. But if it is sent back to Kotlin/the JVM at any point, then it will be parsed and substitued with the original object (provided the original object still exists). + +This is meant to allow Kotlin/JVM objects to be, E.G., used as function arguments and mapping keys from scripts. + +--- + +Some of this may not yet be fully implemented. + +The Python-specific behaviour is also not standardized, and doesn't have to be copied exactly in any other languages. Some other design may be more suited for ECMAScript, Lua, and other possible backends. + +""" try: import sys, json @@ -12,6 +52,12 @@ foreignScope = {n: unciv.wrapping.ForeignObject(n, foreignrequester=foreignActionSender.GetForeignActionResponse) for n in ('civInfo', 'gameInfo', 'uncivGame', 'worldScreen', 'isInGame')} + class fsdebug: + pass + + fsdebug = fsdebug() + fsdebug.__dict__ = foreignScope + foreignAutocompleter = unciv.autocompletion.AutocompleteManager(foreignScope) class ForeignActionReplReceiver(unciv.ipc.ForeignActionReceiver): @@ -55,7 +101,9 @@ def EvalForeignTerminate(self, packet): return None foreignActionReceiver = ForeignActionReplReceiver(scope=foreignScope) + foreignActionReceiver.ForeignREPL() + # Disable this to run manually with `python3 -i main.py` for debug. except Exception as e: print(f"Fatal error in Python interepreter: {unciv.utils.formatException(e)}", file=stdout, flush=True) diff --git a/android/assets/scripting/enginedata/python/unciv/ipc.py b/android/assets/scripting/enginedata/python/unciv/ipc.py index 356cadda8f7f6..8c2f59e8d236c 100644 --- a/android/assets/scripting/enginedata/python/unciv/ipc.py +++ b/android/assets/scripting/enginedata/python/unciv/ipc.py @@ -6,6 +6,14 @@ ## return eval(path, scope, scope) # raise NotImplementedError() + +class IpcJsonEncoder(json.JSONEncoder): + def default(self, obj): + if hasattr(obj.__class__, '__ipcjson__'): + return obj.__ipcjson__() + return json.JSONEncoder.default(self, obj) + + def MakeUniqueId(): return f"{time.time_ns()}-{random.getrandbits(30)}" @@ -37,7 +45,7 @@ def as_dict(self): 'flags': (*self.flags,) } def serialized(self): - return json.dumps(self.as_dict()) + return json.dumps(self.as_dict(), cls=IpcJsonEncoder) # Flags: 'FinishEval', 'BeginIteration', 'StopIteration' diff --git a/android/assets/scripting/enginedata/python/unciv/wrapping.py b/android/assets/scripting/enginedata/python/unciv/wrapping.py index e8e9dbe9c6f7d..5b2e5225bfd7b 100644 --- a/android/assets/scripting/enginedata/python/unciv/wrapping.py +++ b/android/assets/scripting/enginedata/python/unciv/wrapping.py @@ -59,20 +59,50 @@ def foreignErrmsgChecker(packet): raise ipc.ForeignError(packet.data) +def makePathElement(ttype='Property', name='', params=()): + assert ttype in ('Property', 'Key', 'Call'), f"{repr(ttype)} not a valid path element type." + return {'type': ttype, 'name': name, 'params': params} + +def stringPathList(pathlist): + items = [] + for p in pathlist: + if p['type'] == 'Property': + items.append(f".{p['name']}") + if p['type'] == 'Key': + items.append(f"[{p['params'][0]}]") + if p['type'] == 'Call': + items.append(f"({', '.join(p['params'])}])") + return "".join(items) + + +def real(obj): + if instanceof(obj, ForeignObject): + return obj._getvalue() + return obj + + class ForeignObject: def __init__(self, path, foreignrequester=dummyForeignRequester): - object.__setattr__(self, '_path', (*path,)) + object.__setattr__(self, '_path', (makePathElement(name=path),) if isinstance(path, str) else tuple(path)) object.__setattr__(self, '_foreignrequester', foreignrequester) def __repr__(self): - return f"{self.__class__.__name__}({repr(self._getpath())}):{self._getvalue()}" + return f"{self.__class__.__name__}({stringPathList(self._getpath())}):{self._getvalue()}" + def __ipcjson__(self): + return self._getvalue() def _getpath(self): - return ''.join(self._path) + return tuple(self._path) + #return ''.join(self._path) def __getattr__(self, name): - self.__class__ - self._path - return self.__class__((*self._path, f".{name}"), self._foreignrequester) + return self.__class__((*self._path, makePathElement(name=name)), self._foreignrequester) def __getitem__(self, key): - return self.__class__((*self._path, f"[{json.dumps(key)}]"), self._foreignrequester) + return self.__class__((*self._path, makePathElement(ttype='Key', params=(key,))), self._foreignrequester) +# def __hash__(self): +# return hash(stringPathList(self._getpath())) + def __iter__(self): + try: + return iter(self.keys()) + except: + return (self[i] for i in range(0, len(self))) @ForeignRequestMethod def _getvalue(self): return ({ @@ -116,16 +146,14 @@ def __setattr__(self, name, value): 'assign_response', foreignErrmsgChecker) @ForeignRequestMethod - def __call__(self, *args, **kwargs): + def __call__(self, *args): return ({ - 'action': 'call', + 'action': 'read', 'data': { - 'path': self._getpath(), - 'args': args, - 'kwargs': kwargs + 'path': (*self._getpath(), makePathElement(ttype='Call', params=args)), } }, - 'call_response', + 'read_response', foreignValueParser) @ForeignRequestMethod def __setitem__(self, key, value): @@ -137,12 +165,42 @@ def __setitem__(self, key, value): } }, 'assign_response', + foreignErrmsgChecker) + @ForeignRequestMethod + def __len__(self): + return ({ + 'action': 'length', + 'data': { + 'path': self._getpath(), + } + }, + 'length_response', + foreignValueParser) + @ForeignRequestMethod + def __contains__(self, item): + return ({ + 'action': 'contains', + 'data': { + 'path': self._getpath(), + 'value': item + } + }, + 'contains_response', foreignValueParser) -# def keys(self): -# raise NotImplemented() -# return { -# '' -# } + @ForeignRequestMethod + def keys(self): + return ({ + 'action': 'keys', + 'data': { + 'path': self._getpath(), + } + }, + 'keys_response', + foreignValueParser) + def values(self): + return (self[k] for k in self.keys()) + def entries(self): + return ((k, self[k]) for k in self.keys()) #class ForeignScope: # _path = () diff --git a/core/src/com/unciv/UncivGame.kt b/core/src/com/unciv/UncivGame.kt index b02f7cbfcc668..e1be8539be35e 100644 --- a/core/src/com/unciv/UncivGame.kt +++ b/core/src/com/unciv/UncivGame.kt @@ -25,6 +25,12 @@ import com.unciv.ui.worldscreen.WorldScreen import java.util.* import kotlin.concurrent.thread +import com.unciv.scripting.utils.TokenizingJson +import com.unciv.scripting.reflection.Reflection + +import kotlinx.serialization.json.Json +import kotlinx.serialization.encodeToString +import kotlinx.serialization.decodeFromString class UncivGame(parameters: UncivGameParameters) : Game() { @@ -78,6 +84,34 @@ class UncivGame(parameters: UncivGameParameters) : Game() { lateinit var consoleScreen: ConsoleScreen // Keep same ConsoleScreen() when possible, to avoid having to manually persist/restore history, input field, etc. override fun create() { + + + println(TokenizingJson.json.encodeToString(TokenizingJson.TokenizingSerializer, "ABC.")) + println(TokenizingJson.json.encodeToString(TokenizingJson.TokenizingSerializer, null)) + println(TokenizingJson.json.encodeToString(TokenizingJson.TokenizingSerializer, Int)) + var pe = Reflection.PathElement( + Reflection.PathElementType.Property, + "Name", + false, + listOf( + 1, + 2, + "B", + mapOf("A" to 2, "B" to 5), + listOf("A", 2, listOf(6,7,8)), + null, + UncivGameParameters("Five") + ) + ) + println("\npe ${pe}") + var pejs = TokenizingJson.json.encodeToString(TokenizingJson.TokenizingSerializer, pe) + println("\npejs ${pejs}") + var pejs2 = TokenizingJson.json.encodeToString(pe) + println("\npejs2 ${pejs2}") + var peo:Any? = TokenizingJson.json.decodeFromString(pejs2) + println("\npeo ${peo}\n") + + Gdx.input.setCatchKey(Input.Keys.BACK, true) if (Gdx.app.type != Application.ApplicationType.Desktop) { viewEntireMapForDebug = false diff --git a/core/src/com/unciv/scripting/ScriptingScope.kt b/core/src/com/unciv/scripting/ScriptingScope.kt index 51ee9cde2cd38..d1bb836648ba3 100644 --- a/core/src/com/unciv/scripting/ScriptingScope.kt +++ b/core/src/com/unciv/scripting/ScriptingScope.kt @@ -18,4 +18,6 @@ class ScriptingScope( // Also where to put any `PlayerAPI`, `CheatAPI`, `ModAPI`, etc. // For `LuaScriptingBackend`, `UpyScriptingBackend`, `QjsScriptingBackend`, etc, the hierarchy of data under this class definition should probably directly mirror the wrappers in the namespace exposed to the scripting language. // `WorldScreen` gives access to `UnitTable.selectedUnit`, `MapHolder.selectedTile`, etc. Useful for contextual operations. + + // Hm. I got some `java.util.ConcurrentModificationException`s from Python shortly after loading a save. A lock might be useful. Then again, the exception needs to be (and is) handled in the CLI anyway, and the modding API shouldn't be trying to call handlers when the game is locked anyway. } diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index 5b41cfbbd4ba0..41662511bac26 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -10,7 +10,7 @@ import kotlin.math.min /* ``` - The major classes added and changed by this PR are structured as follows. UpperCamelCase() and parentheses means a new instantiation of a class. lowerCamelCase means a reference to an already-existing instance. An asterisk at the start of an item means zero or multiple instances of that class may be held. A question mark at the start of an item means that it may not exist in all implementations of the parent base class/interface. A question mark at the end of an item means that it is nullable, or otherwise may not be set in all states. + The major classes involved in the scripting API are structured as follows. UpperCamelCase() and parentheses means a new instantiation of a class. lowerCamelCase means a reference to an already-existing instance. An asterisk at the start of an item means zero or multiple instances of that class may be held. A question mark at the start of an item means that it may not exist in all implementations of the parent base class/interface. A question mark at the end of an item means that it is nullable, or otherwise may not be available in all states. UncivGame(): ScriptingState(): // Persistent per UncivGame(). @@ -23,6 +23,7 @@ import kotlin.math.min scriptingScope ?ScriptingReplManager(): Blackbox() // Common interface to wrap foreign interpreter with pipes, STDIN/STDOUT, queues, sockets, embedding, JNI, etc. + scriptingScope ScriptingProtocol(): scriptingScope ?folderHandler: setupInterpreterEnvironment() // If used, a temporary directory with file structure copied from engine and shared folders in `assets/scripting`. @@ -34,7 +35,9 @@ import kotlin.math.min MainMenuScreen(): consoleScreen scriptingState // Same as for worldScreen. - ScriptingObjectIndex() + ScriptingObjectIndex() // Holds WeakRefs used by ScriptingProtocol. Unserializable objects get strings as placeholders, and then turned back into into objects if seen again. + Reflection() // Used by some hard-coded scripting backends, and essential to dynamic bindings in ScriptingProtocol(). + SourceManager() // Source of the folderHandler and setupInterpreterEnvironment() above. ``` */ diff --git a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt index b3e5b0f4f60dd..8294383a9786d 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt @@ -4,23 +4,19 @@ import com.unciv.scripting.AutocompleteResults import com.unciv.scripting.ScriptingBackend import com.unciv.scripting.ScriptingScope import com.unciv.scripting.reflection.Reflection -//import com.badlogic.gdx.utils.Json +import com.unciv.scripting.utils.TokenizingJson +import com.unciv.scripting.utils.ScriptingObjectIndex import kotlin.random.Random -//import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable -//import kotlinx.serialization.descriptors.SerialDescriptor -//import kotlinx.serialization.descriptors.PrimitiveKind -//import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -//import kotlinx.serialization.encoding.Decoder -//import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.encodeToString +import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonNull import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.encodeToString -import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.decodeFromJsonElement import java.util.UUID /* @@ -28,13 +24,17 @@ import java.util.UUID A request packet should always be followed by a response packet if it has an action. If a request packet has a null action, then it should not be followed by a response. This is to let flags be sent without generating useless responses. - However, responses do not have to be sent in the same order as their correspond requests. New requests can be sent out while old ones are left "open"— E.G., if creating a response requires requesting new information. + Responses do not have to be sent in the same order as their corresponding requests. New requests can be sent out while old ones are left "open"— E.G., if creating a response requires requesting new information. + + (So far, I think all the requests and responses follow a "stack" model, though. If request B is sent out before response A is received, then response B still gets received before response A, like parentheses. The time two requests remain open can be completely nested subsets or supersets, or they can be completely separated in series, but they don't partially overlap.) + + (That said, none of these are hard requirements. If you want to do something fancy with coroutines or whatever and dispatch to multiple open request handlers, and you can make it both stable and language-aggnostic, go right ahead.) Both the Kotlin side and the script interpreter can send and receive packets, but not necessarily at all times. - (The current loop is described in a comment in ScriptingReplManager.kt. Kotlin initiates a scripting exec, during which the script interpreter can request values from Kotlin, and at the end of which the script interpreter sends its STDOUT response.) + (The current loop is described in a comment in ScriptingReplManager.kt. Kotlin initiates a scripting exec, during which the script interpreter can request values from Kotlin, and at the end of which the script interpreter sends its STDOUT response to the Kotlin side.) - A single packet is a standard JSON string of one of the form: + A single packet is a standard JSON string of the form: { 'action': String?, @@ -44,72 +44,80 @@ import java.util.UUID } Identifiers should be set to a unique value in each request. - Each response should check have the same identifier as its corresponding request. + Each response should have the same identifier as its corresponding request. Upon receiving a response, both its action and identifier should be checked to match the relevant request. Some action types, data formats, and expected response data formats for packets sent from the Kotlin side to the script interpreter include: - 'motd': null -> 'motd_response': String - 'autocomplete': {'command': String, 'cursorpos': Int} -> 'autocomplete_response': Collection or String //List of matches, or help text to print. - 'exec': String -> 'exec_response': String - 'terminate': null -> 'terminate_response': String? //Error message or null. - These are basically a mirror of ScriptingBackendBase, so the same interface can be implemented in the scripting language. + + 'motd': null -> + 'motd_response': String + + 'autocomplete': {'command': String, 'cursorpos': Int} -> + 'autocomplete_response': Collection or String + //List of matches, or help text to print. + + 'exec': String -> + 'exec_response': String + //REPL print. + + 'terminate': null -> + 'terminate_response': String? + //Error message or null. + + The above are basically a mirror of ScriptingBackendBase, so the same interface can be implemented in the scripting language. Some action types, data formats, and expected response types and formats for packets sent from the script interpreter to the Kotlin side include: - 'read': {'path': String} -> 'read_reponse': {'value': Any?, 'exception': String?} //Attribute/property access. - 'call': {'path': String, 'args': Collection, 'kwargs': Map} -> 'call_response': {'value': Any?, 'exception': String?} //Method/function call. - 'assign': {'path': String, 'value': Any} -> 'assign_response': String? //Error message or null. - 'dir': {'path': String} -> 'dir_response': {'value': Collection, 'exception': String?} //Names of all members/properties/attributes/methods. - //'items': {'path': Strign} -> 'keys_response': {} - //'args': {'path'} -> 'args_response': Collection> //Names and types of arguments accepted by a function. - //'length': {'path': String} -> 'length_response': Int + + 'read': {'path': List<{'type':String, 'name':String, 'params':List}>} -> + 'read_reponse': {'value': Any?, 'exception': String?} + //Attribute/property access, by list of `PathElement` properties. + + //'call': {'path': List<{'type':String, 'name':String, 'params':List}>, 'args': Collection, 'kwargs': Map} -> + //'call_response': {'value': Any?, 'exception': String?} + //Method/function call. Deprecated. Instead, use `"action":"read"` with a "path" that has `"type":"call"` element(s). + + 'assign': {'path': List<{'type':String, 'name':String, 'params':List}>, 'value': Any} -> + 'assign_response': String? + //Error message or null. + + 'dir': {'path': List<{'type':String, 'name':String, 'params':List}>} -> + 'dir_response': {'value': Collection, 'exception': String?} + //Names of all members/properties/attributes/methods. + + 'keys': {'path': List<{'type':String, 'name':String, 'params':List}>} -> + 'keys_response': {'value': Collection, 'exception': String?} + + //'args': {'path'} -> + //'args_response': Collection> + //Names and types of arguments accepted by a function. + + 'length': {'path': List<{'type':String, 'name':String, 'params':List}>} -> + 'length_response': {'value': Int?, 'exception': String?} + + 'contains': {'path': List<{'type':String, 'name':String, 'params':List}>, 'value': Any?} -> + 'contains_response': {'value': Boolean?, 'exception': String?} Flags are string values for communicating extra information that doesn't need a separate packet or response. Depending on the flag and action, they may be contextual to the packet, or they may not. I think I see them mostly as a way to semantically separate meta-communication about the protocol from actual requests for actions: - 'PassMic' //Indicates that the sending side will now begin listening instead, and the receiving side should send the next request. Sent by Kotlin side at start of script execution to allow script to request values, and should be sent by script interpreter immediately before sending response with results of execution. + 'PassMic' //Indicates that the sending side will now begin listening instead, and the receiving side should send the next request. Sent by Kotlin side at start of script execution and autocompletion to allow script to request values, and should be sent by script interpreter immediately before sending response with results of execution. Thus, at the IPC level, all foreign backends will actually use the same language, which is this JSON-based protocol. Differences between Python, JS, Lua, etc. will all be down to how they interpret the "exec", "autocomplete", and "motd" actions differently, which each high-level scripting language is free to implement as works best for it. */ -//object AnySerializer: KSerializer { -// override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Any", PrimitiveKind.STRING) -// -// override fun deserialize(decoder: Decoder): Any? { -// return decoder.decodeString() -// } -// -// override fun serialize(encoder: Encoder, value: Any?): Unit { -// encoder.encodeString("Test") -// } -// -// //Could put the ScriptingObjectIndex stuff here. -//} - -//class BoxSerializer(private val dataSerializer: KSerializer) : KSerializer { -// override val descriptor: SerialDescriptor = dataSerializer.descriptor -// override fun serialize(encoder: Encoder, value: Any?) = dataSerializer.serialize(encoder, 5) -// override fun deserialize(decoder: Decoder): Any? = (dataSerializer.deserialize(decoder)) -// //Could put the ScriptingObjectIndex stuff here. -// //It'd probably be easier to just use the LibGDX JSON tools. -//} - -val json = Json { - explicitNulls = true; //Disable these if it becomes a problem. - encodeDefaults = true -} @Serializable data class ScriptingPacket( var action: String?, var identifier: String?, -// @Serializable(with = BoxSerializer::class) var data: JsonElement? = null, + // This can have arbitrary structure, values/types, and depth. So since it lives so close to the IPC protocol anyway, JsonElement is the easiest way to represent and serialize it. var flags: Collection = listOf() ) { companion object { - fun fromJson(string: String): ScriptingPacket = json.decodeFromString(string) + fun fromJson(string: String): ScriptingPacket = TokenizingJson.json.decodeFromString(string) } - fun toJson() = json.encodeToString(this) + fun toJson() = TokenizingJson.json.encodeToString(this) fun hasFlag(flag: ScriptingProtocol.KnownFlag) = flag.value in flags } @@ -118,14 +126,15 @@ data class ScriptingPacket( class ScriptingProtocol(val scriptingScope: ScriptingScope) { companion object { - fun transformPath(path: List): List { - return path.map{ Reflection.PathElement( - type = it.type, - name = it.name, - doEval = it.doEval, - params = it.params.map( { p -> ScriptingObjectIndex.getReal(p) }) - ) } - } +// fun transformPath(path: List): List { +// // Kinda redundant, since PathElement is automatically sent through ScriptingObjectIndex by TokenizingSerializer anyway. +// return path.map{ Reflection.PathElement( +// type = it.type, +// name = it.name, +// doEval = it.doEval, +// params = it.params.map( { p -> ScriptingObjectIndex.getReal(p) }) +// ) } +// } fun makeUniqueId(): String { return "${System.nanoTime()}-${Random.nextBits(30)}-${UUID.randomUUID().toString()}" @@ -147,54 +156,12 @@ class ScriptingProtocol(val scriptingScope: ScriptingScope) { return response } - fun getJsonElement(value: T?): JsonElement { - if (value is JsonElement) { - return value - } - if (value is Map<*, *>) { - return JsonObject( (value as Map).mapValues{ getJsonElement(it.value) } ) - } - if (value is Collection<*>) { - return JsonArray(value.map{ getJsonElement(it) }) - } - if (value is String) { - return JsonPrimitive(value as String) - } - if (value is Int || value is Long || value is Float || value is Double) { - return JsonPrimitive(value as Number) - } - if (value is Boolean) { - return JsonPrimitive(value as Boolean) - } - if (value == null) { - return JsonNull - } - return JsonPrimitive(ScriptingObjectIndex.getToken(value)) - } - - fun getJsonReal(value: JsonElement): Any? { - if (value is JsonNull || value == JsonNull) { - return null - } - if (value is JsonArray) { - return (value as List).map{ getJsonReal(it) }// as List - } - if (value is JsonObject) { - return (value as Map).mapValues{ getJsonReal(it.value) }// as Map - } - if (value is JsonPrimitive) { - val v = value as JsonPrimitive - if (v.isString) { - return ScriptingObjectIndex.getReal(v.content) - } else { - return v.content.toIntOrNull() - ?: v.content.toDoubleOrNull() - ?: v.content.toBooleanStrictOrNull() - ?: v.content - } - } - throw IllegalArgumentException("Unrecognized type of JsonElement: ${value::class}/${value}") - } +// fun decodeJsonPathElement(value: JsonElement): Reflection.PathElement { +// return TokenizingJson.json.decodeFromJsonElement(JsonElement) +// } +// +// fun decodeJsonPathList(value: JsonElement): List { +// } } object makeActionRequests { @@ -252,10 +219,10 @@ class ScriptingProtocol(val scriptingScope: ScriptingScope) { "read" -> { action = "read_response" try { - value = getJsonElement( - Reflection.evalKotlinString( + value = TokenizingJson.getJsonElement( + Reflection.resolveInstancePath( scriptingScope, - (((packet.data as JsonObject)["path"]) as JsonPrimitive).content + TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) ) ) } catch (e: Exception) { @@ -267,27 +234,58 @@ class ScriptingProtocol(val scriptingScope: ScriptingScope) { "exception" to exception )) } - "call" -> { - action = "call_response" +// "call" -> { +// // Deprecated. Instead, use `"action":"read"` with a "path" that has `"type":"call"` element(s). +// action = "call_response" +// try { +// var path = TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) +// val params = TokenizingJson.getJsonReal((packet.data as JsonObject)["args"]!!) as List +// val kwparams = TokenizingJson.getJsonReal((packet.data as JsonObject)["kwargs"]!!) as Map +// if (kwparams.size > 0) { +// throw UnsupportedOperationException("Keyword arguments are not currently supported: ${kwparams}") +// } +// path.add(Reflection.PathElement( +// type = Reflection.PathElementType.Call, +// name = "", +// doEval = false, +// params = params +// )) +// value = TokenizingJson.getJsonElement( +// Reflection.resolveInstancePath( +// scriptingScope, +// transformPath(path) +// ) +// ) +// } catch (e: Exception) { +// value = JsonNull +// exception = JsonPrimitive(e.toString()) +// } +// data = JsonObject(mapOf( +// "value" to value, +// "exception" to exception +// )) +// } + "assign" -> { + action = "assign_response" try { - var path = Reflection.parseKotlinPath((((packet.data as JsonObject)["path"]) as JsonPrimitive).content) as MutableList - val params = getJsonReal((packet.data as JsonObject)["args"]!!) as List - val kwparams = getJsonReal((packet.data as JsonObject)["kwargs"]!!) as Map - if (kwparams.size > 0) { - throw UnsupportedOperationException("Keyword arguments are not currently supported: ${kwparams}") - } - path.add(Reflection.PathElement( - type = Reflection.PathElementType.Call, - name = "", - doEval = false, - params = params - )) - value = getJsonElement( - Reflection.resolveInstancePath( - scriptingScope, - transformPath(path) - ) + Reflection.setInstancePath( + scriptingScope, + TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!), + TokenizingJson.getJsonReal((packet.data as JsonObject)["value"]!!) + ) + } catch (e: Exception) { + exception = JsonPrimitive(e.toString()) + } + data = exception + } + "dir" -> { + action = "dir_response" + try { + val leaf = Reflection.resolveInstancePath( + scriptingScope, + TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) ) + value = TokenizingJson.getJsonElement(leaf!!::class.members.map{it.name}) } catch (e: Exception) { value = JsonNull exception = JsonPrimitive(e.toString()) @@ -297,27 +295,62 @@ class ScriptingProtocol(val scriptingScope: ScriptingScope) { "exception" to exception )) } - "assign" -> { - action = "assign_response" + "length" -> { + action = "length_response" try { - Reflection.setInstancePath( + val leaf = Reflection.resolveInstancePath( scriptingScope, - Reflection.parseKotlinPath((((packet.data as JsonObject)["path"]) as JsonPrimitive).content), - getJsonReal((packet.data as JsonObject)["value"]!!) + TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) ) + try { + value = TokenizingJson.getJsonElement((leaf as Array<*>).size) + // Once/If I get Reflection working with properties/Transients/whatever, I could replace these casts with a single reflective call. + } catch (e: Exception) { + try { + value = TokenizingJson.getJsonElement((leaf as Map<*, *>).size) + } catch (e: Exception) { + value = TokenizingJson.getJsonElement((leaf as Collection<*>).size) + } + } } catch (e: Exception) { + value = JsonNull exception = JsonPrimitive(e.toString()) } - data = exception + data = JsonObject(mapOf( + "value" to value, + "exception" to exception + )) } - "dir" -> { - action = "dir_response" + "keys" -> { + action = "keys_response" + try { + val leaf = Reflection.resolveInstancePath( + scriptingScope, + TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) + ) + value = TokenizingJson.getJsonElement((leaf as Map).keys) + } catch (e: Exception) { + value = JsonNull + exception = JsonPrimitive(e.toString()) + } + data = JsonObject(mapOf( + "value" to value, + "exception" to exception + )) + } + "contains" -> { + action = "contains_response" try { - val leaf = Reflection.evalKotlinString( + val leaf = Reflection.resolveInstancePath( scriptingScope, - (((packet.data as JsonObject)["path"]) as JsonPrimitive).content + TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) ) - value = getJsonElement(leaf!!::class.members.map{it.name}) + val _check = TokenizingJson.getJsonReal((packet.data as JsonObject)["value"]!!) + try { + value = TokenizingJson.getJsonElement(_check in (leaf as Map)) + } catch (e: Exception) { + value = TokenizingJson.getJsonElement(_check in (leaf as Collection)) + } } catch (e: Exception) { value = JsonNull exception = JsonPrimitive(e.toString()) diff --git a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt index 50c1f3f0bfc87..45301d1cc8f40 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt @@ -3,10 +3,10 @@ package com.unciv.scripting.protocol import com.unciv.scripting.AutocompleteResults import com.unciv.scripting.ScriptingBackend import com.unciv.scripting.ScriptingScope -import com.unciv.scripting.protocol.ScriptingObjectIndex import com.unciv.scripting.protocol.ScriptingPacket import com.unciv.scripting.protocol.ScriptingProtocol import com.unciv.scripting.utils.Blackbox +//import com.unciv.scripting.utils.ScriptingObjectIndex /* @@ -14,7 +14,7 @@ import com.unciv.scripting.utils.Blackbox 2. While the script interpreter is running, it has a chance to request values from the Kotlin side by sending back packets encoding attribute/property, key, and call stacks. 3. When the Kotlin side receives a request for a value, it uses reflection to access the requested property or call the requested method, and it sends the result to the script interpreter. 4. When the script interpreter finishes running, it sends a special packet to the Kotlin side. It then sends the REPL output of the command to the Kotlin side. - 5. When the Kotlin interpreter receives the packet marking the end of the command run, it stops listening for value requests packets. It then receives the next value, and passes it back to the display/handler. + 5. When the Kotlin interpreter receives the packet marking the end of the command run, it stops listening for value requests packets. It then receives the commnad result as the next value, and passes it back to the display/handler. ``` fun ExecuteCommand(command): @@ -28,7 +28,7 @@ import com.unciv.scripting.utils.Blackbox PrintToConsole(ReceiveFromInterpreter():String) ``` - The "packets" should probably all be encoded as strings, probably JSON. The technique used to connect the script interpreter to the Kotlin code shouldn't matter. As long as it's wrapped up in and implements the `Blackbox` interface, IPC/embedding based on pipes, STDIN/STDOUT, sockets, queues, embedding, JNI, etc. should all be interchangeable. + The "packets" should probably all be encoded as strings, probably JSON. The technique used to connect the script interpreter to the Kotlin code shouldn't matter, as long as it's wrapped up in and implements the `Blackbox` interface. IPC/embedding based on pipes, STDIN/STDOUT, sockets, queues, embedding, JNI, etc. should all be interchangeable. I'm not sure if there'd be much point to or a good technique for letting the script interpreter run constantly and initiate actions on its own, instead of waiting for commands from the Kotlin side. @@ -61,6 +61,7 @@ class ScriptingReplManager(val scriptingScope: ScriptingScope, val blackbox: Bla } fun foreignExecLoop() { + // Lists to request for values from the black box, and replies to them, during script execution. while (true) { val request = ScriptingPacket.fromJson(blackbox.read(block=true)) if (request.action != null) { diff --git a/core/src/com/unciv/scripting/reflection/Reflection.kt b/core/src/com/unciv/scripting/reflection/Reflection.kt index 2ae371e82be83..cf69dd43bb1c0 100644 --- a/core/src/com/unciv/scripting/reflection/Reflection.kt +++ b/core/src/com/unciv/scripting/reflection/Reflection.kt @@ -1,9 +1,12 @@ package com.unciv.scripting.reflection +import com.unciv.scripting.utils.TokenizingJson import kotlin.collections.ArrayList import kotlin.reflect.KCallable import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KProperty1 +//import kotlinx.serialization.Contextual +import kotlinx.serialization.Serializable import java.util.* @@ -54,12 +57,15 @@ object Reflection { Call() } + @Serializable data class PathElement( val type: PathElementType, val name: String, val doEval: Boolean = false, //For key and index accesses, and function calls, evaluate `name` instead of using `params`. - val params: List = listOf() + //Default should be false, so deserialized JSON path lists are configured correctly in ScriptingProtocol.kt. + val params: List<@Serializable(with=TokenizingJson.TokenizingSerializer::class) Any?> = listOf() +// val params: List<@Contextual Any?> = listOf() ) diff --git a/core/src/com/unciv/scripting/protocol/ScriptingObjectIndex.kt b/core/src/com/unciv/scripting/utils/ScriptingObjectIndex.kt similarity index 78% rename from core/src/com/unciv/scripting/protocol/ScriptingObjectIndex.kt rename to core/src/com/unciv/scripting/utils/ScriptingObjectIndex.kt index 1947f12ecd5be..f07b6d8396148 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingObjectIndex.kt +++ b/core/src/com/unciv/scripting/utils/ScriptingObjectIndex.kt @@ -1,4 +1,4 @@ -package com.unciv.scripting.protocol +package com.unciv.scripting.utils import kotlin.collections.MutableMap import java.lang.ref.WeakReference @@ -12,10 +12,8 @@ object ScriptingObjectIndex { private val kotlinObjectIdPrefix = "_unciv-kt-obj@" // load from ScriptAPIConstants.json private fun idKotlinObject(value: Any?): String { - //Don't depend on parsing this for extracting information. - //It's basically a readable/dumb UUID. - //Actually, it might be better to just use a UUID. I think that was actually my original plan. - return "${kotlinObjectIdPrefix}${System.identityHashCode(value)}:${if (value == null) "null" else value::class.qualifiedName}/${value.toString()}_${UUID.randomUUID().toString()}" + //Try to keep this human-informing, but don't parse it to extracting information. + return "${kotlinObjectIdPrefix}${System.identityHashCode(value)}:${if (value == null) "null" else value::class.qualifiedName}/${value.toString()}:${UUID.randomUUID().toString()}" } private fun isKotlinToken(value: Any?): Boolean { diff --git a/core/src/com/unciv/scripting/utils/TokenizingJson.kt b/core/src/com/unciv/scripting/utils/TokenizingJson.kt new file mode 100644 index 0000000000000..f54a5b1da9aa5 --- /dev/null +++ b/core/src/com/unciv/scripting/utils/TokenizingJson.kt @@ -0,0 +1,139 @@ +package com.unciv.scripting.utils + +import kotlinx.serialization.* +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonDecoder +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonNull +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.decodeFromJsonElement +import kotlinx.serialization.modules.SerializersModule + + + +object TokenizingJson { + + // Json serialization that accepts `Any?`, and converts non-primitive values to string keys from `ScriptingObjectIndex`. + +// class ObjectReifyingSerializer: KSerializer { +// override val descriptor = SerialDescriptor("Reifying", delegateSerializer) +// } + + + object TokenizingSerializer: KSerializer { + + // Adapted from https://stackoverflow.com/a/66158603/12260302 + + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Any?") + + @Suppress("UNCHECKED_CAST") + private val dataTypeSerializers: Map> = + //Tried replacing this with a generic `fun serialize` and `serializer`. The change in signature prevents `serialize` from being overridden correctly, I guess. + mapOf( + "Boolean" to serializer(), + "Byte" to serializer(), + "Char" to serializer(), + "Double" to serializer(), + "Float" to serializer(), + "Int" to serializer(), + "Long" to serializer(), + "Short" to serializer(), + "String" to serializer() + // Only these types will be serialized. Everything else will be replaced with a string key from ScriptingObjectIndex. + ).mapValues { (_, v) -> v as KSerializer } + +// private fun getDataTypeSerializer(value: T): KSerializer { +// return serializer() as KSerializer +// //Right. T needs to be known at compile time. +// } + + override fun serialize(encoder: Encoder, value: Any?) { + if (value == null) { + encoder.encodeNull() + } else { + val classname = value!!::class.simpleName!! + if (classname in dataTypeSerializers) { + encoder.encodeSerializableValue(dataTypeSerializers[value!!::class.simpleName!!]!!, value!!) + } else { + encoder.encodeString(ScriptingObjectIndex.getToken(value!!)) + } + } + } + + override fun deserialize(decoder: Decoder): Any? { + if (decoder is JsonDecoder) { + val jsonLiteral = (decoder as JsonDecoder).decodeJsonElement() + val rawval: Any? = getJsonReal(jsonLiteral) + return ScriptingObjectIndex.getReal(rawval) + } else { + throw UnsupportedOperationException("Decoding is not supported by TokenizingSerializer for ${decoder::class.simpleName}.") + } + } + } + +// val serializersModule = SerializersModule { +// contextual(TokenizingSerializer); +// contextual(DynamicLookupSerializer()) +// } + + val json = Json { + explicitNulls = true; //Disable these if it becomes a problem. + encodeDefaults = true; +// serializersModule = serializersModule + } + + + fun getJsonElement(value: T?): JsonElement { + if (value is JsonElement) { + return value + } + if (value is Map<*, *>) { + return JsonObject( (value as Map).mapValues{ getJsonElement(it.value) } ) + } + if (value is Collection<*>) { + return JsonArray(value.map{ getJsonElement(it) }) + } + if (value is String) { + return JsonPrimitive(value as String) + } + if (value is Int || value is Long || value is Float || value is Double) { + return JsonPrimitive(value as Number) + } + if (value is Boolean) { + return JsonPrimitive(value as Boolean) + } + if (value == null) { + return JsonNull + } + return JsonPrimitive(ScriptingObjectIndex.getToken(value)) + } + + fun getJsonReal(value: JsonElement): Any? { + if (value is JsonNull || value == JsonNull) { + return null + } + if (value is JsonArray) { + return (value as List).map{ getJsonReal(it) }// as List + } + if (value is JsonObject) { + return (value as Map).mapValues{ getJsonReal(it.value) }// as Map + } + if (value is JsonPrimitive) { + val v = value as JsonPrimitive + if (v.isString) { + return ScriptingObjectIndex.getReal(v.content) + } else { + return v.content.toIntOrNull() + ?: v.content.toDoubleOrNull() + ?: v.content.toBooleanStrictOrNull() + ?: v.content + } + } + throw IllegalArgumentException("Unrecognized type of JsonElement: ${value::class}/${value}") + } +} From 4e427a624a28b2f82ea573af18b42a332a87e79e Mon Sep 17 00:00:00 2001 From: will-ca Date: Mon, 8 Nov 2021 03:17:57 +0000 Subject: [PATCH 27/93] Fix ConcurrentModificationException() from ScriptingObjectIndex's .clean() method. --- .../assets/scripting/enginedata/python/main.py | 16 +++++++++++----- .../enginedata/python/unciv/__init__.py | 4 ++-- .../scripting/enginedata/python/unciv/api.py | 9 +++++++++ .../enginedata/python/unciv/wrapping.py | 6 ------ android/assets/scripting/python.json | 1 + .../scripting/utils/ScriptingObjectIndex.kt | 6 +++++- 6 files changed, 28 insertions(+), 14 deletions(-) create mode 100644 android/assets/scripting/enginedata/python/unciv/api.py diff --git a/android/assets/scripting/enginedata/python/main.py b/android/assets/scripting/enginedata/python/main.py index a7d25af8a4579..b9c6c16dcec92 100644 --- a/android/assets/scripting/enginedata/python/main.py +++ b/android/assets/scripting/enginedata/python/main.py @@ -36,11 +36,15 @@ Some of this may not yet be fully implemented. -The Python-specific behaviour is also not standardized, and doesn't have to be copied exactly in any other languages. Some other design may be more suited for ECMAScript, Lua, and other possible backends. +The Python-specific behaviour is also not meant as a hard standard, in that it doesn't have to be copied exactly in any other languages. Some other design may be more suited for ECMAScript, Lua, and other possible backends. If implementing another language, I think some attempt should still be made to keep a similar API and feature parity, though. """ try: + + # This is all a very messy code structure, but it's literally the most surface-facing part of the entire scripting API on which nothing depends and failures are easy to debug, and since it's making changes to `sys`, I'm not sure how I feel about hiding parts of it in a module. + # Otherwise, I would clean it up a bit and move it into `api.py`. + import sys, json stdout = sys.stdout @@ -50,15 +54,17 @@ foreignActionSender = unciv.ipc.ForeignActionSender() - foreignScope = {n: unciv.wrapping.ForeignObject(n, foreignrequester=foreignActionSender.GetForeignActionResponse) for n in ('civInfo', 'gameInfo', 'uncivGame', 'worldScreen', 'isInGame')} + apiScope = {n: unciv.wrapping.ForeignObject(n, foreignrequester=foreignActionSender.GetForeignActionResponse) for n in ('civInfo', 'gameInfo', 'uncivGame', 'worldScreen', 'isInGame')} + + apiScope.update({k: getattr(unciv.api, k) for k in ('real',)}) class fsdebug: pass fsdebug = fsdebug() - fsdebug.__dict__ = foreignScope + fsdebug.__dict__ = apiScope - foreignAutocompleter = unciv.autocompletion.AutocompleteManager(foreignScope) + foreignAutocompleter = unciv.autocompletion.AutocompleteManager(apiScope) class ForeignActionReplReceiver(unciv.ipc.ForeignActionReceiver): @unciv.ipc.receiverMethod('motd', 'motd_response') @@ -100,7 +106,7 @@ def EvalForeignExec(self, packet): def EvalForeignTerminate(self, packet): return None - foreignActionReceiver = ForeignActionReplReceiver(scope=foreignScope) + foreignActionReceiver = ForeignActionReplReceiver(scope=apiScope) foreignActionReceiver.ForeignREPL() # Disable this to run manually with `python3 -i main.py` for debug. diff --git a/android/assets/scripting/enginedata/python/unciv/__init__.py b/android/assets/scripting/enginedata/python/unciv/__init__.py index de1e592509c3c..defdba015f21f 100644 --- a/android/assets/scripting/enginedata/python/unciv/__init__.py +++ b/android/assets/scripting/enginedata/python/unciv/__init__.py @@ -1,4 +1,4 @@ -__all__ = ['autocompletion', 'ipc', 'utils', 'wrapping'] +__all__ = ['autocompletion', 'ipc', 'utils', 'wrapping', 'api'] #Unsupported by Micropython -from . import autocompletion, ipc, utils, wrapping +from . import autocompletion, ipc, utils, wrapping, api diff --git a/android/assets/scripting/enginedata/python/unciv/api.py b/android/assets/scripting/enginedata/python/unciv/api.py new file mode 100644 index 0000000000000..5916608686c2d --- /dev/null +++ b/android/assets/scripting/enginedata/python/unciv/api.py @@ -0,0 +1,9 @@ + + +from . import wrapping + +def real(obj): + """Evaluate a foreign object wrapper into a real Python value, or return a value unchanged if it is not a foreign object wrapper.""" + if isinstance(obj, wrapping.ForeignObject): + return obj._getvalue() + return obj diff --git a/android/assets/scripting/enginedata/python/unciv/wrapping.py b/android/assets/scripting/enginedata/python/unciv/wrapping.py index 5b2e5225bfd7b..9617355829f91 100644 --- a/android/assets/scripting/enginedata/python/unciv/wrapping.py +++ b/android/assets/scripting/enginedata/python/unciv/wrapping.py @@ -75,12 +75,6 @@ def stringPathList(pathlist): return "".join(items) -def real(obj): - if instanceof(obj, ForeignObject): - return obj._getvalue() - return obj - - class ForeignObject: def __init__(self, path, foreignrequester=dummyForeignRequester): object.__setattr__(self, '_path', (makePathElement(name=path),) if isinstance(path, str) else tuple(path)) diff --git a/android/assets/scripting/python.json b/android/assets/scripting/python.json index 17101ee7da6e7..0c0724e2ec79c 100644 --- a/android/assets/scripting/python.json +++ b/android/assets/scripting/python.json @@ -1,6 +1,7 @@ [ unciv/ unciv/__init__.py + unciv/api.py unciv/autocompletion.py unciv/ipc.py unciv/utils.py diff --git a/core/src/com/unciv/scripting/utils/ScriptingObjectIndex.kt b/core/src/com/unciv/scripting/utils/ScriptingObjectIndex.kt index f07b6d8396148..5dbfbbf61a0db 100644 --- a/core/src/com/unciv/scripting/utils/ScriptingObjectIndex.kt +++ b/core/src/com/unciv/scripting/utils/ScriptingObjectIndex.kt @@ -21,11 +21,15 @@ object ScriptingObjectIndex { } fun clean(): Unit { + val badtokens = mutableListOf() for ((t, o) in objs) { if (o.get() == null) { - objs.remove(t) + badtokens.add(t) } } + for (t in badtokens) { + objs.remove(t) + } } fun getToken(obj: Any?): String { From 506e1faf622555430329c6aa69a9c5b9da543ef9 Mon Sep 17 00:00:00 2001 From: will-ca Date: Mon, 8 Nov 2021 19:04:39 +0000 Subject: [PATCH 28/93] Magic methods for Python. --- android/assets/scripting/SharedData.json | 1 + .../scripting/enginedata/python/main.py | 21 ++- .../scripting/enginedata/python/unciv/api.py | 35 ++++- .../enginedata/python/unciv/autocompletion.py | 7 +- .../enginedata/python/unciv/wrapping.py | 122 ++++++++++++++++-- .../shareddata/ScriptAPIConstants.json | 2 +- .../scripting/protocol/ScriptingProtocol.kt | 8 +- .../unciv/scripting/utils/SourceManager.kt | 8 +- 8 files changed, 178 insertions(+), 26 deletions(-) diff --git a/android/assets/scripting/SharedData.json b/android/assets/scripting/SharedData.json index 18f8196212c9a..75f66a169b9ef 100644 --- a/android/assets/scripting/SharedData.json +++ b/android/assets/scripting/SharedData.json @@ -1,3 +1,4 @@ [ ScriptAPI.json + ScriptAPIConstants.json ] diff --git a/android/assets/scripting/enginedata/python/main.py b/android/assets/scripting/enginedata/python/main.py index b9c6c16dcec92..6a57e262742bd 100644 --- a/android/assets/scripting/enginedata/python/main.py +++ b/android/assets/scripting/enginedata/python/main.py @@ -51,12 +51,21 @@ import unciv + try: + unciv.wrapping.ForeignObject.__doc__ = (unciv.wrapping.ForeignObject.__doc__ or "") + "\n\n\n---\n\n" + __doc__ + except: + pass foreignActionSender = unciv.ipc.ForeignActionSender() + apiScope = {n: unciv.wrapping.ForeignObject(n, foreignrequester=foreignActionSender.GetForeignActionResponse) for n in ('civInfo', 'gameInfo', 'uncivGame', 'worldScreen', 'isInGame')} + # I wanna change the protocol to automatically generate these with a `dir()` on an empty `ForeignObject()` path in `.motd()`. + + apiScope.update(unciv.api.Expose) + + apiScope['help'] = lambda thing=None: print(__doc__) if thing is None else help(thing) - apiScope.update({k: getattr(unciv.api, k) for k in ('real',)}) class fsdebug: pass @@ -111,5 +120,13 @@ def EvalForeignTerminate(self, packet): foreignActionReceiver.ForeignREPL() # Disable this to run manually with `python3 -i main.py` for debug. + raise RuntimeError("No REPL loop. Did you forget to uncomment a line in `main.py`?") + except Exception as e: - print(f"Fatal error in Python interepreter: {unciv.utils.formatException(e)}", file=stdout, flush=True) +# try: +# import unciv.utils +# exc = unciv.utils.formatException(e) +# # Disable this. A single line with undefined format is more likely to be printed than multiple. +# except: +# exc = repr(e) + print(f"Fatal error in Python interepreter: {repr(e)}", file=stdout, flush=True) diff --git a/android/assets/scripting/enginedata/python/unciv/api.py b/android/assets/scripting/enginedata/python/unciv/api.py index 5916608686c2d..d46cd08d69fd9 100644 --- a/android/assets/scripting/enginedata/python/unciv/api.py +++ b/android/assets/scripting/enginedata/python/unciv/api.py @@ -1,9 +1,42 @@ +import json, os +_enginedir = os.path.dirname(__file__) + +def _readlibfile(fp): + try: + # In an Unciv scripting backend, `SourceManager.kt` uses LibGDX to merge `shareddata/` with `enginedaata/{engine}/` in a temporary directory. + with open(os.path.join(_enginedir, "..", fp)) as file: + return file.read() + except: + # For debug with standalone Python, `shareddata` has to be accessed manually. + with open(os.path.join(_enginedir, "../../../shareddata", fp)) as file: + return file.read() + +_apiconstants = json.loads(_readlibfile("ScriptAPIConstants.json")) + + +Expose = {} + +def expose(name=None): + def _expose(obj): + Expose[name or obj.__name__] = obj + return obj + return _expose -from . import wrapping +@expose() def real(obj): """Evaluate a foreign object wrapper into a real Python value, or return a value unchanged if it is not a foreign object wrapper.""" if isinstance(obj, wrapping.ForeignObject): return obj._getvalue() return obj + +@expose() +def isForeignToken(obj): + """Return whether an object represents a token for a non-serializable foreign object.""" + resolved = real(obj) + return isinstance(resolved, str) and resolved.startswith(_apiconstants['kotlinObjectTokenPrefix']) + + +from . import wrapping +# Should only need it at run time anyway, so makes a circular import more predictable. Basically, this whole file gets prepended to `wrapping` under the `api` name. diff --git a/android/assets/scripting/enginedata/python/unciv/autocompletion.py b/android/assets/scripting/enginedata/python/unciv/autocompletion.py index a112b594fabcc..4e2b07d3d520b 100644 --- a/android/assets/scripting/enginedata/python/unciv/autocompletion.py +++ b/android/assets/scripting/enginedata/python/unciv/autocompletion.py @@ -1,9 +1,10 @@ -"""Advanced autocompletion. Returns keys for """ +import rlcompleter from . import utils class AutocompleteManager: + """Advanced autocompleter. Returns keys when accessing mappings. Implements API that returns docstrings as help text for callables.""" def __init__(self, scope=None): self.scope = globals() if scope is None else scope def Evaled(self, path): @@ -37,4 +38,6 @@ def GetAutocomplete(self, command): return tuple([attrbase+attrjoin+a for a in (dir(self.Evaled(attrbase)) if attrbase else self.scope) if a.startswith(attrleaf)]) except (NameError, AttributeError, KeyError, IndexError, SyntaxError) as e: return "No autocompletion found: "+utils.formatException(e) - + +class RlAutocompleteManager: + """Wrapper for default Python autocompleter.""" diff --git a/android/assets/scripting/enginedata/python/unciv/wrapping.py b/android/assets/scripting/enginedata/python/unciv/wrapping.py index 9617355829f91..a073c1cdf86af 100644 --- a/android/assets/scripting/enginedata/python/unciv/wrapping.py +++ b/android/assets/scripting/enginedata/python/unciv/wrapping.py @@ -1,7 +1,7 @@ -import json, sys +import json, sys, operator stdout = sys.stdout -from . import ipc +from . import ipc, api class ForeignRequestMethod: """Decorator for methods that return values from foreign requests.""" @@ -31,18 +31,36 @@ def meth(*a, **kw): # def __get__(self, obj, cls): # return lambda *a, **kw: self.func(obj._getvalue(), *a, **kw) -def ForeignResolvingFunc(func): - """Decorator for functions that resolve foreign objects as arguments.""" - def _func(*args, **kwargs): - return f( - *[a._getvalue() if isinstance(ForeignObject) else a for a in args], - **{k: v._getvalue() if isinstance(ForeignObject) else v for k, v in kwargs.items()} - ) - try: - _func.__name__, _func.__doc__ = func.__name__, func.__doc__ - except AttributeError: - pass - return _func +#def ForeignResolvingFunc(func): +# """Decorator for functions that resolve foreign objects as arguments.""" +# def _func(*args, **kwargs): +# return f( +# *[a._getvalue() if isinstance(ForeignObject) else a for a in args], +# **{k: v._getvalue() if isinstance(ForeignObject) else v for k, v in kwargs.items()} +# ) +# try: +# _func.__name__, _func.__doc__ = func.__name__, func.__doc__ +# except AttributeError: +# pass +# return _func + +def ResolvingFunction(op): + """Return a function that passes its arguments through `api.real()`.""" + def _resolvingfunction(*arguments, **keywords): + args = [api.real(a) for a in arguments] + kwargs = {k:api.real(v) for k, v in keywords.items()} + return op(*args, **kwargs) + _resolvingfunction.__name__ = op.__name__ + _resolvingfunction.__doc__ = f"{op.__doc__ or name + ' operator.'}\n\nCalls `api.real()` on all arguments." + return _resolvingfunction + +def ReversedMethod(func): + """Return a `.__rop__` version of an `.__op__` magic method function.""" + def _reversedop(a, b, *args, **kwargs): + return func(b, a, *args, **kwargs) + _reversedop.__name__ = func.__name__ + _reversedop.__doc__ = f"{func.__doc__ or name + ' operator.'}\n\nReversed version." + return _reversedop def dummyForeignRequester(actionparams, responsetype): @@ -75,7 +93,83 @@ def stringPathList(pathlist): return "".join(items) +_magicmeths = ( + '__lt__', + '__le__', + '__eq__', # Kinda undefined behaviour for comparision with Kotlin object tokens. Well, tokens are just strings that will always equal themselves, but multiple tokens can refer to the same Kotlin object. `ForeignObject()`s resolve to new tokens, that are currently uniquely generated in ScriptingObjectIndex.kt, on every `._getvalue()`, so I think even the same `ForeignObject()` will never equal itself. + '__ne__', + '__ge__', + '__gt__', + '__not__', + ('__bool__', 'truth'), +# @is # This could get messy. It's probably best to just not support identity comparison. What do you compare? JVM Kotlin value? Resolved Python value? Python data path? Token strings from ScriptingObjectIndex.kt— Which are currently randomly re-generated for multiple accesses to the same Kotlin object, and thus always unique, and which would require another protocol-level guarantee to not do that, in addition to being (kinda by design) procedurally indistinguishable from "real" resovled Python values? +# @is_not # Also, these aren't even magic methods. + '__abs__', + '__add__', + '__and__', + '__floordiv__', + '__index__', + '__inv__', + '__invert__', + '__lshift__', + '__mod__', + '__mul__', + '__matmul__', + '__neg__', + '__or__', + '__pos__', + '__pow__', + '__rshift__', + '__sub__', + '__truediv__', + '__xor__', + '__concat__', + '__contains__', # Implemented through foreign request. + '__delitem__', # Should be implemented through foreign request if it is to be supported. + '__getitem__', # Implemented through foreign request. +# @indexOf # Not actually totally sure what this is. I thought it was implemented in lists and tuples as `.index()`? +# '__setitem__', # Implemented through foreign request. +) + +_rmagicmeths = ( + '__radd__', + '__rsub__', + '__rmul__', + '__rmatmul__', + '__rtruediv__', + '__rfloordiv__', + '__rmod__', + '__rdivmod__', + '__rpow__', + '__rlshift__', + '__rrshift__', + '__rand__', + '__rxor__', + '__ror__', +) + +def ResolveForOperators(cls): + """Decorator. Adds missing magic methods to a class, which resolve their arguments with `api.real(a)`.""" + def alreadyhas(name): + return (hasattr(cls, name) and getattr(cls, name) is not getattr(object, name, None)) + for meth in _magicmeths: + if isinstance(meth, str): + name = opname = meth + else: + name, opname = meth + if not alreadyhas(name): + # Set the magic method only if neither it nor any of its base classes have already defined a custom implementation. + setattr(cls, name, ResolvingFunction(getattr(operator, opname))) + for rmeth in _rmagicmeths: + normalname = rmeth.replace('__r', '__', 1) + if not alreadyhas(rmeth) and hasattr(cls, normalname): + setattr(cls, rmeth, ReversedMethod(getattr(cls, normalname))) + return cls + + +@ResolveForOperators class ForeignObject: + """Wrapper for a forign object.""" def __init__(self, path, foreignrequester=dummyForeignRequester): object.__setattr__(self, '_path', (makePathElement(name=path),) if isinstance(path, str) else tuple(path)) object.__setattr__(self, '_foreignrequester', foreignrequester) diff --git a/android/assets/scripting/shareddata/ScriptAPIConstants.json b/android/assets/scripting/shareddata/ScriptAPIConstants.json index 24652c3bbec4d..351006a106428 100644 --- a/android/assets/scripting/shareddata/ScriptAPIConstants.json +++ b/android/assets/scripting/shareddata/ScriptAPIConstants.json @@ -1,3 +1,3 @@ { - kotlinObjectTokenPrefix: "_unciv-kt-obj@" + "kotlinObjectTokenPrefix": "_unciv-kt-obj@" } diff --git a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt index 8294383a9786d..0e44b54a37b42 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt @@ -37,10 +37,10 @@ import java.util.UUID A single packet is a standard JSON string of the form: { - 'action': String?, - 'identifier': String?, - 'data': Any?, - 'flags': Collection + "action": String?, + "identifier": String?, + "data": Any?, + "flags": Collection } Identifiers should be set to a unique value in each request. diff --git a/core/src/com/unciv/scripting/utils/SourceManager.kt b/core/src/com/unciv/scripting/utils/SourceManager.kt index a4b5cc34abdea..835420216a533 100644 --- a/core/src/com/unciv/scripting/utils/SourceManager.kt +++ b/core/src/com/unciv/scripting/utils/SourceManager.kt @@ -36,6 +36,10 @@ object SourceManager { } fun setupInterpreterEnvironment(engine: String): FileHandle { + // Creates temporary directory. + // Copies directory tree under `android/assets/scripting/shareddata/` into it, as specified by `SharedData.json` + // Copies directory tree under `android/assets/scripting/enginedata/{engine}` into it, as specified by `{engine}.json`. + // Returns `FileHandle()` for the temporary directory. val enginedir = getEngineLibraries(engine) val outdir = FileHandle.tempDirectory("unciv-${engine}_") fun addfile(sourcedir: FileHandle, path: String) { @@ -53,8 +57,8 @@ object SourceManager { addfile(enginedir, fp) } Runtime.getRuntime().addShutdownHook( - // Delete on JVM shutdown, not on backend object destruction/termination. The copied files shouldn't be huge anyway, and I trust the shutdown hook to be run more reliably. - thread(start = false, name = "Delete ${outdir.toString()}") { + // Delete temporary directory on JVM shutdown, not on backend object destruction/termination. The copied files shouldn't be huge anyway, there's no reference to a `ScriptingBackend()` here, and I trust the shutdown hook to be run more reliably. + thread(start = false, name = "Delete ${outdir.toString()}.") { outdir.deleteDirectory() } ) From b9e46a9bfdb8f5c4bd6a255fff914b80e1b1ea5b Mon Sep 17 00:00:00 2001 From: will-ca Date: Thu, 11 Nov 2021 00:34:18 +0000 Subject: [PATCH 29/93] Rewrite and greatly improve Python autocomplete. Various cleanup and restructuring. --- .../scripting/ScriptingEngineConstants.json | 39 +++ android/assets/scripting/SharedData.json | 4 - .../enginedata/python/unciv/autocompletion.py | 43 ---- .../{enginedata => enginefiles}/lua/main.lua | 0 .../python/main.py | 50 ++-- .../python/unciv/__init__.py | 0 .../python/unciv/api.py | 45 +++- .../python/unciv/autocompletion.py | 140 +++++++++++ .../python/unciv/ipc.py | 9 +- .../python/unciv/utils.py | 0 .../python/unciv/wrapping.py | 122 +++++----- .../{enginedata => enginefiles}/qjs/main.js | 0 android/assets/scripting/lua.json | 4 - android/assets/scripting/python.json | 10 - android/assets/scripting/qjs.json | 4 - .../scripting/shareddata/ScriptAPI.json | 1 - .../scripting/sharedfiles/ScriptAPI.json | 4 + .../ScriptAPIConstants.json | 0 core/src/com/unciv/UncivGame.kt | 37 +-- .../com/unciv/scripting/ScriptingBackend.kt | 106 ++++++--- .../com/unciv/scripting/ScriptingConstants.kt | 52 ++++ .../src/com/unciv/scripting/ScriptingScope.kt | 9 +- .../src/com/unciv/scripting/ScriptingState.kt | 21 +- .../scripting/protocol/ScriptingProtocol.kt | 223 ++++++++++++------ .../protocol/ScriptingReplManager.kt | 12 +- .../unciv/scripting/reflection/Reflection.kt | 10 - .../unciv/scripting/utils/ApiSpecGenerator.kt | 2 +- .../unciv/scripting/utils/ObjectTokenizer.kt | 59 +++++ .../scripting/utils/ScriptingObjectIndex.kt | 51 ---- .../unciv/scripting/utils/SourceManager.kt | 37 +-- .../scripting/utils/SyntaxHighlighter.kt | 16 ++ .../unciv/scripting/utils/TokenizingJson.kt | 16 +- .../unciv/ui/consolescreen/ConsoleScreen.kt | 12 +- 33 files changed, 723 insertions(+), 415 deletions(-) create mode 100644 android/assets/scripting/ScriptingEngineConstants.json delete mode 100644 android/assets/scripting/SharedData.json delete mode 100644 android/assets/scripting/enginedata/python/unciv/autocompletion.py rename android/assets/scripting/{enginedata => enginefiles}/lua/main.lua (100%) rename android/assets/scripting/{enginedata => enginefiles}/python/main.py (66%) rename android/assets/scripting/{enginedata => enginefiles}/python/unciv/__init__.py (100%) rename android/assets/scripting/{enginedata => enginefiles}/python/unciv/api.py (51%) create mode 100644 android/assets/scripting/enginefiles/python/unciv/autocompletion.py rename android/assets/scripting/{enginedata => enginefiles}/python/unciv/ipc.py (96%) rename android/assets/scripting/{enginedata => enginefiles}/python/unciv/utils.py (100%) rename android/assets/scripting/{enginedata => enginefiles}/python/unciv/wrapping.py (72%) rename android/assets/scripting/{enginedata => enginefiles}/qjs/main.js (100%) delete mode 100644 android/assets/scripting/lua.json delete mode 100644 android/assets/scripting/python.json delete mode 100644 android/assets/scripting/qjs.json delete mode 100644 android/assets/scripting/shareddata/ScriptAPI.json create mode 100644 android/assets/scripting/sharedfiles/ScriptAPI.json rename android/assets/scripting/{shareddata => sharedfiles}/ScriptAPIConstants.json (100%) create mode 100644 core/src/com/unciv/scripting/ScriptingConstants.kt create mode 100644 core/src/com/unciv/scripting/utils/ObjectTokenizer.kt delete mode 100644 core/src/com/unciv/scripting/utils/ScriptingObjectIndex.kt create mode 100644 core/src/com/unciv/scripting/utils/SyntaxHighlighter.kt diff --git a/android/assets/scripting/ScriptingEngineConstants.json b/android/assets/scripting/ScriptingEngineConstants.json new file mode 100644 index 0000000000000..903b25433c0cf --- /dev/null +++ b/android/assets/scripting/ScriptingEngineConstants.json @@ -0,0 +1,39 @@ +{ + engines: { + python: { + files: [ + unciv/ + unciv/__init__.py + unciv/api.py + unciv/autocompletion.py + unciv/ipc.py + unciv/utils.py + unciv/wrapping.py + main.py + ] + syntaxHighlightingRegexStack: [ + ] + } + lua: { + files: [ + unciv/ + main.lua + ] + syntaxHighlightingRegexStack: [ + ] + } + js: { + files: [ + unciv/ + main.js + ] + syntaxHighlightingRegexStack: [ + ] + } + } + + sharedfiles: [ + ScriptAPI.json + ScriptAPIConstants.json + ] +} diff --git a/android/assets/scripting/SharedData.json b/android/assets/scripting/SharedData.json deleted file mode 100644 index 75f66a169b9ef..0000000000000 --- a/android/assets/scripting/SharedData.json +++ /dev/null @@ -1,4 +0,0 @@ -[ - ScriptAPI.json - ScriptAPIConstants.json -] diff --git a/android/assets/scripting/enginedata/python/unciv/autocompletion.py b/android/assets/scripting/enginedata/python/unciv/autocompletion.py deleted file mode 100644 index 4e2b07d3d520b..0000000000000 --- a/android/assets/scripting/enginedata/python/unciv/autocompletion.py +++ /dev/null @@ -1,43 +0,0 @@ -import rlcompleter - -from . import utils - - -class AutocompleteManager: - """Advanced autocompleter. Returns keys when accessing mappings. Implements API that returns docstrings as help text for callables.""" - def __init__(self, scope=None): - self.scope = globals() if scope is None else scope - def Evaled(self, path): - return eval(path, self.scope, self.scope) - def GetAutocomplete(self, command): - """Return either a sequence of full autocomplete matches or a help string for a given command.""" - try: - if ')' in command: - return () - attrbase, attrjoin, attrleaf = command.rpartition('.') - if '(' in attrleaf: - functionbase, functionjoin, functionleaf = attrleaf.rpartition('(') - fbase = attrbase + attrjoin + functionbase - fobj = self.Evaled(fbase) - return '\n'.join((fbase+"(", str(type(fobj)), getattr(fobj, '__doc__', '') or "No documentation available.")) - elif '[' in attrleaf: - keybase, keyjoin, keyleaf = attrleaf.rpartition('[') - kbase = attrbase + attrjoin + keybase - kbaseobj = self.Evaled(kbase) - if hasattr(kbaseobj, 'keys'): - if not keyleaf: - return tuple(kbase+keyjoin+repr(k)+']' for k in kbaseobj.keys()) - if keyleaf[-1] == ']': - return () - quote = keyleaf[0] - kleaf = keyleaf[1:] - if quote in '\'"': - return tuple(kbase+keyjoin+quote+k+quote+']' for k in kbaseobj.keys() if k.startswith(kleaf)) - return tuple(kbase+keyjoin+n+']' for n in self.GetAutocomplete(keyleaf)) - else: - return tuple([attrbase+attrjoin+a for a in (dir(self.Evaled(attrbase)) if attrbase else self.scope) if a.startswith(attrleaf)]) - except (NameError, AttributeError, KeyError, IndexError, SyntaxError) as e: - return "No autocompletion found: "+utils.formatException(e) - -class RlAutocompleteManager: - """Wrapper for default Python autocompleter.""" diff --git a/android/assets/scripting/enginedata/lua/main.lua b/android/assets/scripting/enginefiles/lua/main.lua similarity index 100% rename from android/assets/scripting/enginedata/lua/main.lua rename to android/assets/scripting/enginefiles/lua/main.lua diff --git a/android/assets/scripting/enginedata/python/main.py b/android/assets/scripting/enginefiles/python/main.py similarity index 66% rename from android/assets/scripting/enginedata/python/main.py rename to android/assets/scripting/enginefiles/python/main.py index 6a57e262742bd..091cf57b3ce4a 100644 --- a/android/assets/scripting/enginedata/python/main.py +++ b/android/assets/scripting/enginefiles/python/main.py @@ -8,23 +8,25 @@ A **wrapper** is an object that stores a list of attribute names, keys, and function call parameters corresponding to a path in the Kotlin/JVM namespace. E.G.: `"civInfo.civilizations[0].population.setPopulation"` is a string representation of a wrapped path that begins with two attribute accesses, followed by one array index, and two more attribute names. -A wrapper object does not store any more values than that. When it is evaluated, it uses a simple IPC protocol to request an up-to-date real value from the game's Kotlin code. +A wrapper object does not store any more values than that. When it is evaluated, it uses a simple IPC protocol to request an up-to-date real value from the game's Kotlin code. The `unciv.api.real()` function can be used to manually get a real Python value from a foreign object wrapper. -Because the wrapper class implements many Magic Methods, many programming idioms common in Python are possible with them. +However, because the wrapper class implements many Magic Methods that automatically call its evaluation method, many programming idioms common in Python are possible with them even without manual evaluation. Comparisons, equality, arithmetic, concatenation, inplace operations and more are all supported. Accessing an attribute on a wrapper object returns a new wrapper object that has an additional name set to "Property" at the end of its path list. Performing an array or dictionary index returns a new wrapper with an additional "Key" element in its path list. -Calling a wrapper object as a function or method also creates a new path list with an extra "Call" element at the end. But in this case, the new path list is immediately sent to Kotlin/the JVM instead of being used in a new wrapper object, and the returned value is the result from the requested function call in the Kotlin/JVM namespace. +Calling a wrapper object as a function or method also creates a new path list with an extra "Call" element at the end. But in this case, the new path list is immediately sent as a request to Kotlin/the JVM instead of being used in a new wrapper object, and the returned value is the naked result from the requested function call in the Kotlin/JVM namespace. Likewise, assigning to an attribute or an index/key on a wrapper object sends an IPC request to assign to the Kotlin/JVM object at its path, instead of modifying the wrapper object. -When a Kotlin/JVM object implements a property for size or keys, the Python wrapper for it can also be iterated like a `tuple` or a `dict`, such as in `for` loops anlist comprehensions. +When a Kotlin/JVM object implements a property for size or keys, the Python wrapper for it can also be iterated like a `tuple` or a `dict`, such as in `for` loops and list comprehensions. --- -A **token** is a string that has been generated by `ScriptingObjectIndex.kt` to represent a Kotlin object. +A **token** is a string that has been generated by `ObjectTokenizer.kt` to represent a Kotlin object. -When a path requested by a script resolves to immutable primative like a number or boolean, or something that is otherwise practical to serialize, then the return is usually a real object. +The `unciv.api.isForeignToken()` function can be used to check whether a Python object is either a foreign token string or a wrapper that resolves to a foreign token string. + +When a Kotlin/JVM path requested by a script resolves to an immutable primative like a number or boolean, or something that is otherwise practical to serialize, then the value returned to Python is usually a real object. However, if the value requested is an instance of a complicated Kotlin/JVM class, then the IPC protocol instead creates a unique string to identify it. @@ -59,7 +61,7 @@ foreignActionSender = unciv.ipc.ForeignActionSender() - apiScope = {n: unciv.wrapping.ForeignObject(n, foreignrequester=foreignActionSender.GetForeignActionResponse) for n in ('civInfo', 'gameInfo', 'uncivGame', 'worldScreen', 'isInGame')} + apiScope = {}#{n: unciv.wrapping.ForeignObject(n, foreignrequester=foreignActionSender.GetForeignActionResponse) for n in ('civInfo', 'gameInfo', 'uncivGame', 'worldScreen', 'isInGame')} # I wanna change the protocol to automatically generate these with a `dir()` on an empty `ForeignObject()` path in `.motd()`. apiScope.update(unciv.api.Expose) @@ -73,23 +75,32 @@ class fsdebug: fsdebug = fsdebug() fsdebug.__dict__ = apiScope - foreignAutocompleter = unciv.autocompletion.AutocompleteManager(apiScope) + foreignAutocompleter = unciv.autocompletion.PyAutocompleteManager(apiScope, **unciv.api._autocompleterkwargs) - class ForeignActionReplReceiver(unciv.ipc.ForeignActionReceiver): + class ForeignUncivActionReplReceiver(unciv.ipc.ForeignActionReceiver): + def populateApiScope(self): + names = dir(unciv.wrapping.ForeignObject((), foreignrequester=foreignActionSender.GetForeignActionResponse)) + for n in names: + if n not in self.scope: + self.scope[n] = unciv.wrapping.ForeignObject(n, foreignrequester=foreignActionSender.GetForeignActionResponse) + def passMic(self): + """Send a 'PassMic' packet.""" + foreignActionSender.SendForeignAction({'action':None, 'identifier': None, 'data':None, 'flags':('PassMic',)}) @unciv.ipc.receiverMethod('motd', 'motd_response') def EvalForeignMotd(self, packet): + self.populateApiScope() + self.passMic() return f""" - -Welcome to the CPython Unciv CLI. Currently, this backend relies on launching the system `python3` command. - sys.implementation == {str(sys.implementation)} +Press [TAB] at any time to trigger autocompletion at the current cursor position, or display help text for an empty function call. + """ @unciv.ipc.receiverMethod('autocomplete', 'autocomplete_response') def EvalForeignAutocomplete(self, packet): assert 'PassMic' in packet.flags, f"Expected 'PassMic' in packet flags: {packet}" - res = foreignAutocompleter.GetAutocomplete(packet.data["command"]) - foreignActionSender.SendForeignAction({'action':None, 'identifier': None, 'data':None, 'flags':('PassMic',)}) + res = foreignAutocompleter.GetAutocomplete(packet.data["command"], packet.data["cursorpos"]) + self.passMic() return res @unciv.ipc.receiverMethod('exec', 'exec_response') def EvalForeignExec(self, packet): @@ -105,22 +116,21 @@ def EvalForeignExec(self, packet): else: print(eval(code, self.scope, self.scope)) except Exception as e: - return unciv.utils.formatException(e) - else: - return fakeout.getvalue() + print(unciv.utils.formatException(e)) finally: - foreignActionSender.SendForeignAction({'action':None, 'identifier': None, 'data':None, 'flags':('PassMic',)}) + self.passMic() + return fakeout.getvalue() @unciv.ipc.receiverMethod('terminate', 'terminate_response') def EvalForeignTerminate(self, packet): return None - foreignActionReceiver = ForeignActionReplReceiver(scope=apiScope) + foreignActionReceiver = ForeignUncivActionReplReceiver(scope=apiScope) foreignActionReceiver.ForeignREPL() # Disable this to run manually with `python3 -i main.py` for debug. - raise RuntimeError("No REPL loop. Did you forget to uncomment a line in `main.py`?") + raise RuntimeError("No REPL. Did you forget to uncomment a line in `main.py`?") except Exception as e: # try: diff --git a/android/assets/scripting/enginedata/python/unciv/__init__.py b/android/assets/scripting/enginefiles/python/unciv/__init__.py similarity index 100% rename from android/assets/scripting/enginedata/python/unciv/__init__.py rename to android/assets/scripting/enginefiles/python/unciv/__init__.py diff --git a/android/assets/scripting/enginedata/python/unciv/api.py b/android/assets/scripting/enginefiles/python/unciv/api.py similarity index 51% rename from android/assets/scripting/enginedata/python/unciv/api.py rename to android/assets/scripting/enginefiles/python/unciv/api.py index d46cd08d69fd9..b0b12336ff39d 100644 --- a/android/assets/scripting/enginedata/python/unciv/api.py +++ b/android/assets/scripting/enginefiles/python/unciv/api.py @@ -1,15 +1,17 @@ -import json, os +import json, os, builtins + +from . import ipc _enginedir = os.path.dirname(__file__) def _readlibfile(fp): try: - # In an Unciv scripting backend, `SourceManager.kt` uses LibGDX to merge `shareddata/` with `enginedaata/{engine}/` in a temporary directory. + # In an Unciv scripting backend, `SourceManager.kt` uses LibGDX to merge `sharedfiles/` with `enginedaata/{engine}/` in a temporary directory. with open(os.path.join(_enginedir, "..", fp)) as file: return file.read() except: - # For debug with standalone Python, `shareddata` has to be accessed manually. - with open(os.path.join(_enginedir, "../../../shareddata", fp)) as file: + # For debug with standalone Python, `sharedfiles` has to be accessed manually. + with open(os.path.join(_enginedir, "../../../sharedfiles", fp)) as file: return file.read() _apiconstants = json.loads(_readlibfile("ScriptAPIConstants.json")) @@ -24,11 +26,44 @@ def _expose(obj): return _expose +def _get_keys(obj): + try: + return obj.keys() + except (AttributeError, ipc.ForeignError): + return () + +def _get_doc(obj): + try: + if isinstance(obj, wrapping.ForeignObject): + doc = f"\n\n{str(obj._docstring_() or wrapping.stringPathList(obj._getpath_()))}\n\nArguments:\n" + doc += "\n".join(f"\t{argname}: {argtype}" for argname, argtype in obj._args_()) + return doc + else: + return obj.__doc__ + except AttributeError: + return None + + +@expose() +def callable(obj): + if isinstance(obj, wrapping.ForeignObject): + return obj._callable_(raise_exceptions=False) + else: + return builtins.callable(obj) + + +_autocompleterkwargs = { + 'get_keys': _get_keys, + 'get_doc': _get_doc, + 'check_callable': callable +} + + @expose() def real(obj): """Evaluate a foreign object wrapper into a real Python value, or return a value unchanged if it is not a foreign object wrapper.""" if isinstance(obj, wrapping.ForeignObject): - return obj._getvalue() + return obj._getvalue_() return obj @expose() diff --git a/android/assets/scripting/enginefiles/python/unciv/autocompletion.py b/android/assets/scripting/enginefiles/python/unciv/autocompletion.py new file mode 100644 index 0000000000000..c4e86bc530525 --- /dev/null +++ b/android/assets/scripting/enginefiles/python/unciv/autocompletion.py @@ -0,0 +1,140 @@ +import rlcompleter, itertools, keyword + +from . import utils + + +class AutocompleteManager: + def __init__( + self, + scope=None, + *, + get_keys=lambda o: o.keys() if hasattr(o, 'keys') else (), + get_doc=lambda o: getattr(o, '__doc__', None), + check_callable=lambda o: callable(o) + ): + self.scope = globals() if scope is None else scope + self.get_keys, self.get_doc, self.check_callable = get_keys, get_doc, check_callable + def GetCommandComponents(self, command): + """Try to return the the last atomic evaluable expression in a statement, everything before it, and the token at the end of everything before it.""" + #Call recursively if you need to resolve multiple values. Test string: + # abc.cde().fgh[0].ijk(lmn[1].opq["dea + lasttoken = None + prefixsplit = len(command)-1 + while prefixsplit >= 0: + char = command[prefixsplit] + if char in ')': + #Don't mess with potential function calls. + prefixsplit = 0 + lasttoken = char + break + if char in ']': + _bdepth = 1 + prefixsplit -= 1 + while _bdepth and prefixsplit: + # Skip over whole blocks of open brackets. + char = command[prefixsplit] + if char == '[': + _bdepth -= 1 + if char == ']': + _bdepth += 1 + prefixsplit -= 1 + char = None + continue + if char in '([:,;+-*/|&<>=%{~^@': + prefixsplit += 1 + lasttoken = char + break + prefixsplit -= 1 + else: + prefixsplit = 0 + # I think this will happen anyway without the break, but do it explicitly. + prefix, workingcode = command[:prefixsplit], command[prefixsplit:] + assert (not (lasttoken or prefix)) or lasttoken in ')' or prefix[-1] == lasttoken, f"{prefix, workingcode, lasttoken}" + return prefix, workingcode, lasttoken + def GetAutocomplete(self, command): + """Return either a sequence of full autocomplete matches or a help string for a given command.""" + + +class PyAutocompleteManager(AutocompleteManager): + """Advanced autocompleter. Returns keys when accessing mappings. Implements API that returns docstrings as help text for callables.""" + def Evaled(self, path): + return eval(path, self.scope, self.scope) + #Seems safe. Well, I'm checking before calling here that there's no closing brackets that could mean a function call. Let's check again, I guess. + assert ')' not in path, f"Closing brackets not currently allowed in autocomplete eval: {path}" + def GetAutocomplete(self, command, cursorpos=None): + try: + if cursorpos is None: + cursorpos = len(command) + (prefix, workingcode, lasttoken), suffix = self.GetCommandComponents(command[:cursorpos]), command[cursorpos:] + if ')' in workingcode: + # Avoid function calls. + return () + if lasttoken in {*'[('}:# Compare to set because None can't be used in string containment check. + prefix_prefix, prefix_workingcode, prefix_lasttoken = self.GetCommandComponents(prefix[:-1]) + assert prefix[-1] == lasttoken + if ')' not in prefix_workingcode: + # Avoid function calls. + if lasttoken == '[' and ((not workingcode) or workingcode[0] in '\'"'): +# return f"Return keys matching {workingcode} in {prefix_workingcode}." + key_obj = self.Evaled(prefix_workingcode) + if hasattr(key_obj, 'keys'): + if not workingcode: + return tuple(prefix+repr(k)+']' + suffix for k in self.get_keys(key_obj)) + quote = workingcode[0] + key_current = workingcode[1:] + return tuple(prefix + quote + k + quote + ']' + suffix for k in self.get_keys(key_obj) if k.startswith(key_current)) + return () + if lasttoken == '(' and (not workingcode): +# return f"Show docstring of {prefix_workingcode}." + func_obj = self.Evaled(prefix_workingcode) + return (self.get_doc(func_obj) or "No documentation available.") + "\n" +# return f"Return attributes to complete {workingcode}." + whitespaceadjusted_workingcode = workingcode.lstrip() + whitespaceadjusted_prefix = prefix + workingcode[:len(workingcode)-len(whitespaceadjusted_workingcode)] + # Move leading whitespace onto prefix, so function arguments, list items, etc, resolve correctly. + working_base, working_dot, working_leaf = whitespaceadjusted_workingcode.rpartition('.') + if working_base: + base_obj = self.Evaled(working_base) + attrs = dir(base_obj) + get_a = lambda a: getattr(base_obj, a, None) + else: + attrs = self.scope + get_a = lambda a: self.scope[a] + return tuple([ + whitespaceadjusted_prefix + + working_base + + working_dot + + ( + f"{a}[" + if self.get_keys(get_a(a)) else + f"{a}(" + if self.check_callable(get_a(a)) else + a + ) + + suffix + for a in attrs + if a.startswith(working_leaf) + ]) + except Exception as e: +# except (NameError, AttributeError, KeyError, IndexError, SyntaxError, AssertionError) as e: + return "No autocompletion found: "+utils.formatException(e) + + +class RlAutocompleteManager(AutocompleteManager): + """Autocompleter that uses the default Python autocompleter.""" + def GetAutocomplete(self, command, cursorpos=None): + #Adds brackets to everything, due to presence of dynamic `.__call__` on `ForeignObject`.. Technically, I might be able to control `callable()` by implementing a metaclass with a custom `.__getattribute__` with custom descriptors on `ForeignObject`. Perhaps such a sin is still beyond even my bumbling arrogance, though. + completer = rlcompleter.Completer(self.scope) + (prefix, workingcode, lasttoken), suffix = self.GetCommandComponents(command[:cursorpos]), command[cursorpos:] + if workingcode: + matches = [] + for i in itertools.count(): + m = completer.complete(workingcode, i) + if m is None: + break + else: + matches.append(m) + else: + matches = [*self.scope.keys()]#, *keyword.kwlist] + return tuple([prefix+m+suffix for m in matches]) + diff --git a/android/assets/scripting/enginedata/python/unciv/ipc.py b/android/assets/scripting/enginefiles/python/unciv/ipc.py similarity index 96% rename from android/assets/scripting/enginedata/python/unciv/ipc.py rename to android/assets/scripting/enginefiles/python/unciv/ipc.py index 8c2f59e8d236c..0819d5d8a2352 100644 --- a/android/assets/scripting/enginedata/python/unciv/ipc.py +++ b/android/assets/scripting/enginefiles/python/unciv/ipc.py @@ -9,8 +9,8 @@ class IpcJsonEncoder(json.JSONEncoder): def default(self, obj): - if hasattr(obj.__class__, '__ipcjson__'): - return obj.__ipcjson__() + if hasattr(obj.__class__, '_ipcjson_'): + return obj._ipcjson_() return json.JSONEncoder.default(self, obj) @@ -177,9 +177,4 @@ def __exit__(self, *exc): # self.scope[spath[-1]] = value -#class ForeignActionAutocompleter(ForeignActionManager): -# pass - -#class ForeignActionTransceiver(ForeignActionReceiver, ForeignActionSender): -# pass diff --git a/android/assets/scripting/enginedata/python/unciv/utils.py b/android/assets/scripting/enginefiles/python/unciv/utils.py similarity index 100% rename from android/assets/scripting/enginedata/python/unciv/utils.py rename to android/assets/scripting/enginefiles/python/unciv/utils.py diff --git a/android/assets/scripting/enginedata/python/unciv/wrapping.py b/android/assets/scripting/enginefiles/python/unciv/wrapping.py similarity index 72% rename from android/assets/scripting/enginedata/python/unciv/wrapping.py rename to android/assets/scripting/enginefiles/python/unciv/wrapping.py index a073c1cdf86af..58f27b113b77c 100644 --- a/android/assets/scripting/enginedata/python/unciv/wrapping.py +++ b/android/assets/scripting/enginefiles/python/unciv/wrapping.py @@ -3,6 +3,7 @@ from . import ipc, api + class ForeignRequestMethod: """Decorator for methods that return values from foreign requests.""" def __init__(self, func): @@ -15,7 +16,7 @@ def __get__(self, obj, cls): def meth(*a, **kw): actionparams, responsetype, responseparser = self.func(obj, *a, **kw) response = obj._foreignrequester(actionparams, responsetype) - if responseparser and callable(responseparser): + if callable(responseparser): response = responseparser(response) return response try: @@ -24,31 +25,19 @@ def meth(*a, **kw): pass return meth -#class ForeignSelfMethod: -# """Decorator for methods that use foreign values for `self`.""" -# def __init__(self, func): -# self.func = func -# def __get__(self, obj, cls): -# return lambda *a, **kw: self.func(obj._getvalue(), *a, **kw) - -#def ForeignResolvingFunc(func): -# """Decorator for functions that resolve foreign objects as arguments.""" -# def _func(*args, **kwargs): -# return f( -# *[a._getvalue() if isinstance(ForeignObject) else a for a in args], -# **{k: v._getvalue() if isinstance(ForeignObject) else v for k, v in kwargs.items()} -# ) -# try: -# _func.__name__, _func.__doc__ = func.__name__, func.__doc__ -# except AttributeError: -# pass -# return _func -def ResolvingFunction(op): +def ResolvingFunction(op, *, allowforeigntokens=False): """Return a function that passes its arguments through `api.real()`.""" def _resolvingfunction(*arguments, **keywords): args = [api.real(a) for a in arguments] kwargs = {k:api.real(v) for k, v in keywords.items()} + if not allowforeigntokens: + # Forbid foreign token strings from being manipulated through this operation. + for l in (args, kwargs.values()): + #TODO: Test this. + for o in l: + if api.isForeignToken(o): + raise TypeError(f"Not allowed to call `{op.__name__}()` function with foreign object token: {o}") return op(*args, **kwargs) _resolvingfunction.__name__ = op.__name__ _resolvingfunction.__doc__ = f"{op.__doc__ or name + ' operator.'}\n\nCalls `api.real()` on all arguments." @@ -67,12 +56,14 @@ def dummyForeignRequester(actionparams, responsetype): return actionparams, responsetype -def foreignValueParser(packet): - if packet.data["exception"] is not None: +def foreignValueParser(packet, *, raise_exceptions=True): + """Value parse that reads a foreign request packet fitting a common structure.""" + if packet.data["exception"] is not None and raise_exceptions: raise ipc.ForeignError(packet.data["exception"]) return packet.data["value"] def foreignErrmsgChecker(packet): + """Value parse that processes a foreign request packet fitting a simple structure.""" if packet.data is not None: raise ipc.ForeignError(packet.data) @@ -96,13 +87,13 @@ def stringPathList(pathlist): _magicmeths = ( '__lt__', '__le__', - '__eq__', # Kinda undefined behaviour for comparision with Kotlin object tokens. Well, tokens are just strings that will always equal themselves, but multiple tokens can refer to the same Kotlin object. `ForeignObject()`s resolve to new tokens, that are currently uniquely generated in ScriptingObjectIndex.kt, on every `._getvalue()`, so I think even the same `ForeignObject()` will never equal itself. + '__eq__', # Kinda undefined behaviour for comparision with Kotlin object tokens. Well, tokens are just strings that will always equal themselves, but multiple tokens can refer to the same Kotlin object. `ForeignObject()`s resolve to new tokens, that are currently uniquely generated in ObjectTokenizer.kt, on every `._getvalue_()`, so I think even the same `ForeignObject()` will never equal itself. '__ne__', '__ge__', '__gt__', '__not__', ('__bool__', 'truth'), -# @is # This could get messy. It's probably best to just not support identity comparison. What do you compare? JVM Kotlin value? Resolved Python value? Python data path? Token strings from ScriptingObjectIndex.kt— Which are currently randomly re-generated for multiple accesses to the same Kotlin object, and thus always unique, and which would require another protocol-level guarantee to not do that, in addition to being (kinda by design) procedurally indistinguishable from "real" resovled Python values? +# @is # This could get messy. It's probably best to just not support identity comparison. What do you compare? JVM Kotlin value? Resolved Python value? Python data path? Token strings from ObjectTokenizer.kt— Which are currently randomly re-generated for multiple accesses to the same Kotlin object, and thus always unique, and which would require another protocol-level guarantee to not do that, in addition to being (kinda by design) procedurally indistinguishable from "real" resovled Python values? # @is_not # Also, these aren't even magic methods. '__abs__', '__add__', @@ -159,7 +150,7 @@ def alreadyhas(name): name, opname = meth if not alreadyhas(name): # Set the magic method only if neither it nor any of its base classes have already defined a custom implementation. - setattr(cls, name, ResolvingFunction(getattr(operator, opname))) + setattr(cls, name, ResolvingFunction(getattr(operator, opname), allowforeigntokens=False)) for rmeth in _rmagicmeths: normalname = rmeth.replace('__r', '__', 1) if not alreadyhas(rmeth) and hasattr(cls, normalname): @@ -169,15 +160,15 @@ def alreadyhas(name): @ResolveForOperators class ForeignObject: - """Wrapper for a forign object.""" + """Wrapper for a foreign object.""" def __init__(self, path, foreignrequester=dummyForeignRequester): object.__setattr__(self, '_path', (makePathElement(name=path),) if isinstance(path, str) else tuple(path)) object.__setattr__(self, '_foreignrequester', foreignrequester) def __repr__(self): - return f"{self.__class__.__name__}({stringPathList(self._getpath())}):{self._getvalue()}" - def __ipcjson__(self): - return self._getvalue() - def _getpath(self): + return f"{self.__class__.__name__}({stringPathList(self._getpath_())}):{self._getvalue_()}" + def _ipcjson_(self): + return self._getvalue_() + def _getpath_(self): return tuple(self._path) #return ''.join(self._path) def __getattr__(self, name): @@ -185,49 +176,68 @@ def __getattr__(self, name): def __getitem__(self, key): return self.__class__((*self._path, makePathElement(ttype='Key', params=(key,))), self._foreignrequester) # def __hash__(self): -# return hash(stringPathList(self._getpath())) +# return hash(stringPathList(self._getpath_())) def __iter__(self): try: return iter(self.keys()) except: return (self[i] for i in range(0, len(self))) @ForeignRequestMethod - def _getvalue(self): + def _getvalue_(self): return ({ 'action': 'read', 'data': { - 'path': self._getpath() + 'path': self._getpath_() } }, 'read_response', foreignValueParser) @ForeignRequestMethod + def _callable_(self, *, raise_exceptions=True): + return ({ + 'action': 'callable', + 'data': { + 'path': self._getpath_() + } + }, + 'callable_response', + lambda packet: foreignValueParser(packet, raise_exceptions=raise_exceptions)) + @ForeignRequestMethod + def _args_(self, *, raise_exceptions=True): + return ({ + 'action': 'args', + 'data': { + 'path': self._getpath_() + } + }, + 'args_response', + lambda packet: foreignValueParser(packet, raise_exceptions=raise_exceptions)) + @ForeignRequestMethod + def _docstring_(self, *, raise_exceptions=True): + return ({ + 'action': 'docstring', + 'data': { + 'path': self._getpath_() + } + }, + 'docstring_response', + lambda packet: foreignValueParser(packet, raise_exceptions=raise_exceptions)) + @ForeignRequestMethod def __dir__(self): return ({ 'action': 'dir', 'data': { - 'path': self._getpath() + 'path': self._getpath_() } }, 'dir_response', foreignValueParser) -# @ForeignRequestMethod -# @property -# def __doc__(self): -# return ({ -# 'action': 'args', -# 'data': { -# 'path': self._getpath() -# } -# }, -# 'args_response', -# None) @ForeignRequestMethod def __setattr__(self, name, value): return ({ 'action': 'assign', 'data': { - 'path': getattr(self, name)._getpath(), + 'path': getattr(self, name)._getpath_(), 'value': value } }, @@ -238,7 +248,7 @@ def __call__(self, *args): return ({ 'action': 'read', 'data': { - 'path': (*self._getpath(), makePathElement(ttype='Call', params=args)), + 'path': (*self._getpath_(), makePathElement(ttype='Call', params=args)), } }, 'read_response', @@ -248,7 +258,7 @@ def __setitem__(self, key, value): return ({ 'action': 'assign', 'data': { - 'path': self[key]._getpath(), + 'path': self[key]._getpath_(), 'value': value } }, @@ -259,7 +269,7 @@ def __len__(self): return ({ 'action': 'length', 'data': { - 'path': self._getpath(), + 'path': self._getpath_(), } }, 'length_response', @@ -269,7 +279,7 @@ def __contains__(self, item): return ({ 'action': 'contains', 'data': { - 'path': self._getpath(), + 'path': self._getpath_(), 'value': item } }, @@ -280,7 +290,7 @@ def keys(self): return ({ 'action': 'keys', 'data': { - 'path': self._getpath(), + 'path': self._getpath_(), } }, 'keys_response', @@ -290,12 +300,4 @@ def values(self): def entries(self): return ((k, self[k]) for k in self.keys()) -#class ForeignScope: -# _path = () -# def __init__(self, attrcls=ForeignObject, foreignrequester=dummyForeignRequester): -# self._attrcls = attrcls -# self._foreignrequester = foreignrequester -# def __getattr__(self, name): -# return self._attrcls(name, self._foreignrequester) -# __dir__ = ForeignObject.__dir__ diff --git a/android/assets/scripting/enginedata/qjs/main.js b/android/assets/scripting/enginefiles/qjs/main.js similarity index 100% rename from android/assets/scripting/enginedata/qjs/main.js rename to android/assets/scripting/enginefiles/qjs/main.js diff --git a/android/assets/scripting/lua.json b/android/assets/scripting/lua.json deleted file mode 100644 index 505dba85fa4e0..0000000000000 --- a/android/assets/scripting/lua.json +++ /dev/null @@ -1,4 +0,0 @@ -[ - unciv/ - main.lua -] diff --git a/android/assets/scripting/python.json b/android/assets/scripting/python.json deleted file mode 100644 index 0c0724e2ec79c..0000000000000 --- a/android/assets/scripting/python.json +++ /dev/null @@ -1,10 +0,0 @@ -[ - unciv/ - unciv/__init__.py - unciv/api.py - unciv/autocompletion.py - unciv/ipc.py - unciv/utils.py - unciv/wrapping.py - main.py -] diff --git a/android/assets/scripting/qjs.json b/android/assets/scripting/qjs.json deleted file mode 100644 index dc62efc764afb..0000000000000 --- a/android/assets/scripting/qjs.json +++ /dev/null @@ -1,4 +0,0 @@ -[ - unciv/ - main.js -] diff --git a/android/assets/scripting/shareddata/ScriptAPI.json b/android/assets/scripting/shareddata/ScriptAPI.json deleted file mode 100644 index 0967ef424bce6..0000000000000 --- a/android/assets/scripting/shareddata/ScriptAPI.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/android/assets/scripting/sharedfiles/ScriptAPI.json b/android/assets/scripting/sharedfiles/ScriptAPI.json new file mode 100644 index 0000000000000..3cb1e5bdb3159 --- /dev/null +++ b/android/assets/scripting/sharedfiles/ScriptAPI.json @@ -0,0 +1,4 @@ +[ + "//The idea is to generate lists of all available names, types, and parameters in the scripting API scope here, flat, nested, and of classes, using `ApiSpecGenerator.kt`. That way different scripting language implemenations of the API would be able to iterate through whatever structure makes the most sense for them in order to generate bindings. However, a quick count of the number of members of all classes traversable from `ScriptingScope` suggested something like 5,400 unique members that would have to be parametrized, so for the Python bindings I opted to dynamically generate the bindings using Python's magic methods and the reflective tools in `ScriptingProtocol` instead.." +] + diff --git a/android/assets/scripting/shareddata/ScriptAPIConstants.json b/android/assets/scripting/sharedfiles/ScriptAPIConstants.json similarity index 100% rename from android/assets/scripting/shareddata/ScriptAPIConstants.json rename to android/assets/scripting/sharedfiles/ScriptAPIConstants.json diff --git a/core/src/com/unciv/UncivGame.kt b/core/src/com/unciv/UncivGame.kt index e1be8539be35e..7028d1bcd0b4a 100644 --- a/core/src/com/unciv/UncivGame.kt +++ b/core/src/com/unciv/UncivGame.kt @@ -25,17 +25,13 @@ import com.unciv.ui.worldscreen.WorldScreen import java.util.* import kotlin.concurrent.thread -import com.unciv.scripting.utils.TokenizingJson -import com.unciv.scripting.reflection.Reflection - -import kotlinx.serialization.json.Json -import kotlinx.serialization.encodeToString -import kotlinx.serialization.decodeFromString - +import com.unciv.scripting.ScriptingConstants class UncivGame(parameters: UncivGameParameters) : Game() { // we need this secondary constructor because Java code for iOS can't handle Kotlin lambda parameters constructor(version: String) : this(UncivGameParameters(version, null)) + + val ss by lazy { ScriptingConstants } val version = parameters.version private val crashReportSender = parameters.crashReportSender @@ -85,33 +81,6 @@ class UncivGame(parameters: UncivGameParameters) : Game() { override fun create() { - - println(TokenizingJson.json.encodeToString(TokenizingJson.TokenizingSerializer, "ABC.")) - println(TokenizingJson.json.encodeToString(TokenizingJson.TokenizingSerializer, null)) - println(TokenizingJson.json.encodeToString(TokenizingJson.TokenizingSerializer, Int)) - var pe = Reflection.PathElement( - Reflection.PathElementType.Property, - "Name", - false, - listOf( - 1, - 2, - "B", - mapOf("A" to 2, "B" to 5), - listOf("A", 2, listOf(6,7,8)), - null, - UncivGameParameters("Five") - ) - ) - println("\npe ${pe}") - var pejs = TokenizingJson.json.encodeToString(TokenizingJson.TokenizingSerializer, pe) - println("\npejs ${pejs}") - var pejs2 = TokenizingJson.json.encodeToString(pe) - println("\npejs2 ${pejs2}") - var peo:Any? = TokenizingJson.json.decodeFromString(pejs2) - println("\npeo ${peo}\n") - - Gdx.input.setCatchKey(Input.Keys.BACK, true) if (Gdx.app.type != Application.ApplicationType.Desktop) { viewEntireMapForDebug = false diff --git a/core/src/com/unciv/scripting/ScriptingBackend.kt b/core/src/com/unciv/scripting/ScriptingBackend.kt index d6598344720d6..889d1563ff849 100644 --- a/core/src/com/unciv/scripting/ScriptingBackend.kt +++ b/core/src/com/unciv/scripting/ScriptingBackend.kt @@ -10,6 +10,7 @@ import com.unciv.scripting.utils.ApiSpecGenerator import com.unciv.scripting.utils.Blackbox import com.unciv.scripting.utils.DummyBlackbox import com.unciv.scripting.utils.SourceManager +import com.unciv.scripting.utils.SyntaxHighlighter import kotlin.reflect.full.* import java.util.* @@ -17,21 +18,29 @@ import java.util.* data class AutocompleteResults(val matches:List = listOf(), val isHelpText:Boolean = false, val helpText:String = "") -interface ScriptingBackend_metadata { - fun new(scriptingScope: ScriptingScope): ScriptingBackendBase - val displayname:String +abstract class ScriptingBackend_metadata { + abstract fun new(scriptingScope: ScriptingScope): ScriptingBackendBase + abstract val displayName: String + val syntaxHighlighting: SyntaxHighlighter? = null + // Putting the syntax highlighters here makes the most sense semantically as they should be singletons. + // But it'd also be nice to let subclasses define ways of generating new their syntax highlighters based on their other parameters. (E.G.: Read a JSON of REGEXs, based on `EnvironmentedScriptingBackend().engine`. + // Hm. I think the solution in that case is to subclass `ScriptingBackend_metadata`, and put those properties in the companion, which I will now do. +} + +abstract class EnvironmentedScriptBackend_metadata: ScriptingBackend_metadata() { + abstract val engine: String } interface ScriptingBackend { fun motd(): String { - // Message to print on launch. + // Message to print on launch. Should be called exactly once per instance, and prior to calling any of the other methods defined here. return "\n\nWelcome to the Unciv CLI!\nYou are currently running the dummy backend, which will echo all commands but never do anything.\n" } fun autocomplete(command: String, cursorPos: Int? = null): AutocompleteResults { - // Return either a `List` of autocomplete matches, or a + // Return either an AutocompleteResults object that represents either a List of full autocompletion matches or a help string to print. return AutocompleteResults(listOf(command+"_autocomplete")) } @@ -50,22 +59,23 @@ interface ScriptingBackend { open class ScriptingBackendBase(val scriptingScope: ScriptingScope): ScriptingBackend { - companion object Metadata: ScriptingBackend_metadata { + companion object Metadata: ScriptingBackend_metadata() { override fun new(scriptingScope: ScriptingScope) = ScriptingBackendBase(scriptingScope) - override val displayname:String = "Dummy" + override val displayName:String = "Dummy" } - val metadata: ScriptingBackend_metadata + open val metadata get(): ScriptingBackend_metadata = this::class.companionObjectInstance as ScriptingBackend_metadata + // Let the companion object of the correct subclass be accessed in subclass instances. } class HardcodedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackendBase(scriptingScope) { - companion object Metadata: ScriptingBackend_metadata { + companion object Metadata: ScriptingBackend_metadata() { override fun new(scriptingScope: ScriptingScope) = HardcodedScriptingBackend(scriptingScope) - override val displayname:String = "Hardcoded" + override val displayName:String = "Hardcoded" } val commandshelp = mapOf( @@ -266,9 +276,9 @@ class HardcodedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacken class ReflectiveScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackendBase(scriptingScope) { - companion object Metadata: ScriptingBackend_metadata { + companion object Metadata: ScriptingBackend_metadata() { override fun new(scriptingScope: ScriptingScope) = ReflectiveScriptingBackend(scriptingScope) - override val displayname:String = "Reflective" + override val displayName:String = "Reflective" } private val commandparams = mapOf("get" to 1, "set" to 2, "typeof" to 1) //showprivates? @@ -358,20 +368,38 @@ class ReflectiveScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacke } -open class EnvironmentedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackendBase(scriptingScope) { +abstract class EnvironmentedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackendBase(scriptingScope) { - open val engine = "" + companion object Metadata: EnvironmentedScriptBackend_metadata() { + // Need this here, or else won't compile. + // Ideally would be able to just declare that subclasses must define a companion of the correct type, but ah well. + override val displayName = "" + override fun new(scriptingScope: ScriptingScope) = throw UnsupportedOperationException("Base scripting backend class not meant to be instantiated.") + override val engine = "" + } + + override val metadata + // Since the companion object type is different, we have to define a new getter for the subclass instance companion getter. + get() = this::class.companionObjectInstance as EnvironmentedScriptBackend_metadata - val folderHandle: FileHandle by lazy { SourceManager.setupInterpreterEnvironment(engine) } - // This requires the overridden values for `engine`, so setting it in the constructor causes a null error. + val folderHandle: FileHandle by lazy { SourceManager.setupInterpreterEnvironment(this.metadata.engine) } + // This requires the overridden values for `engine`, so setting it in the constructor causes a null error... May be fixed since moving `engine` to the companions. // Also, BlackboxScriptingBackend inherits from this, but not all subclasses of BlackboxScriptingBackend might need it. So as long as it's not accessed, it won't be intialized. } -open class BlackboxScriptingBackend(scriptingScope: ScriptingScope): EnvironmentedScriptingBackend(scriptingScope) { +abstract class BlackboxScriptingBackend(scriptingScope: ScriptingScope): EnvironmentedScriptingBackend(scriptingScope) { - open val blackbox: Blackbox by lazy { DummyBlackbox() } + companion object Metadata: EnvironmentedScriptBackend_metadata() { + // Need this here, or else won't compile. + // Ideally would be able to just declare that subclasses must define a companion of the correct type, but ah well. + override val displayName = "" + override fun new(scriptingScope: ScriptingScope) = throw UnsupportedOperationException("Base scripting backend class not meant to be instantiated.") + override val engine = "" + } + + abstract val blackbox: Blackbox val replManager: ScriptingReplManager by lazy { ScriptingReplManager(scriptingScope, blackbox) } // Was originally a method that could be called by subclasses' constructors. This seems cleaner. Subclasses don't even have to define any functions this way. And the liberal use of `lazy` should naturally make sure the properties will always be initialized in the right order. @@ -381,7 +409,7 @@ open class BlackboxScriptingBackend(scriptingScope: ScriptingScope): Environment try { return replManager.motd() } catch (e: Exception) { - return "No MOTD for ${engine} backend: ${e}\n" + return "No MOTD for ${this.metadata.engine} backend: ${e}\n" } } @@ -411,23 +439,26 @@ open class BlackboxScriptingBackend(scriptingScope: ScriptingScope): Environment } -open class SubprocessScriptingBackend(scriptingScope: ScriptingScope): BlackboxScriptingBackend(scriptingScope) { +abstract class SubprocessScriptingBackend(scriptingScope: ScriptingScope): BlackboxScriptingBackend(scriptingScope) { - open val processCmd = arrayOf("") + abstract val processCmd: Array override val blackbox by lazy { SubprocessBlackbox(processCmd) } + + override fun motd(): String { + return "\n\nWelcome to the Unciv '${displayName}' API. This backend relies on running the system `${processCmd.firstOrNull()}` command as a subprocess.\n\nIf you do not have an interactive REPL below, then please make sure the below command is valid on your system:\n\n${processCmd.joinToString(" ")}\n\n${super.motd()}\n" + } } class SpyScriptingBackend(scriptingScope: ScriptingScope): SubprocessScriptingBackend(scriptingScope) { - companion object Metadata: ScriptingBackend_metadata { + companion object Metadata: EnvironmentedScriptBackend_metadata() { override fun new(scriptingScope: ScriptingScope) = SpyScriptingBackend(scriptingScope) - override val displayname:String = "System Python" + override val displayName:String = "System Python" + override val engine = "python" } - override val engine = "python" - override val processCmd = arrayOf("python3", "-u", "-X", "utf8", folderHandle.child("main.py").toString()) } @@ -435,28 +466,25 @@ class SpyScriptingBackend(scriptingScope: ScriptingScope): SubprocessScriptingBa class SqjsScriptingBackend(scriptingScope: ScriptingScope): SubprocessScriptingBackend(scriptingScope) { - companion object Metadata: ScriptingBackend_metadata { + companion object Metadata: EnvironmentedScriptBackend_metadata() { override fun new(scriptingScope: ScriptingScope) = SqjsScriptingBackend(scriptingScope) - override val displayname:String = "System QuickJS" + override val displayName:String = "System QuickJS" + override val engine = "qjs" } - override val engine = "qjs" - override val processCmd = arrayOf("qjs", "--std", "--script", folderHandle.child("main.js").toString()) - } class SluaScriptingBackend(scriptingScope: ScriptingScope): SubprocessScriptingBackend(scriptingScope) { - companion object Metadata: ScriptingBackend_metadata { + companion object Metadata: EnvironmentedScriptBackend_metadata() { override fun new(scriptingScope: ScriptingScope) = SluaScriptingBackend(scriptingScope) - override val displayname:String = "System Lua" + override val displayName:String = "System Lua" + override val engine = "lua" } - override val engine = "lua" - override val processCmd = arrayOf("lua", folderHandle.child("main.lua").toString()) } @@ -464,16 +492,18 @@ class SluaScriptingBackend(scriptingScope: ScriptingScope): SubprocessScriptingB class DevToolsScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackendBase(scriptingScope) { - companion object Metadata: ScriptingBackend_metadata { + //Probably redundant, and can probably be removed in favour of whatever mechanism is currently used to run the translation file generator. + + companion object Metadata: ScriptingBackend_metadata() { override fun new(scriptingScope: ScriptingScope) = DevToolsScriptingBackend(scriptingScope) - override val displayname:String = "DevTools" + override val displayName:String = "DevTools" } val commands = listOf( "PrintFlatApiDefs", "PrintClassApiDefs", "WriteOutApiFile ", - "WriteOutApiFile android/assets/scripting/shareddata/ScriptAPI.json" + "WriteOutApiFile android/assets/scripting/sharedfiles/ScriptAPI.json" ) override fun motd() = """ @@ -510,7 +540,7 @@ class DevToolsScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackend } -enum class ScriptingBackendType(val metadata:ScriptingBackend_metadata) { +enum class ScriptingBackendType(val metadata: ScriptingBackend_metadata) { Dummy(ScriptingBackendBase), Hardcoded(HardcodedScriptingBackend), Reflective(ReflectiveScriptingBackend), diff --git a/core/src/com/unciv/scripting/ScriptingConstants.kt b/core/src/com/unciv/scripting/ScriptingConstants.kt new file mode 100644 index 0000000000000..c0128d1460e45 --- /dev/null +++ b/core/src/com/unciv/scripting/ScriptingConstants.kt @@ -0,0 +1,52 @@ +package com.unciv.scripting + +import com.badlogic.gdx.Gdx +import com.unciv.JsonParser + + +val ScriptingConstants: _ScriptingConstantsClasses.ScriptingConstantsClass = JsonParser().getFromJson( + _ScriptingConstantsClasses.ScriptingConstantsClass::class.java, + _ScriptingConstantsClasses.ScriptingAssetFolders.scriptingAssets.child("ScriptingEngineConstants.json") +) + + +object _ScriptingConstantsClasses{ + // Need to define classes to deserialize the JSONs into, but really this whole file should be one singleton. + // It would be better slightly with KotlinX, I think, since then I could at least use data classes and don't have to initialize all the properties. + + class ScriptingConstantsClass() { + var engines = HashMap() + // Really, these should be `val: Map<>`s in a data class constructor, not `var: HashMap<>()` in a regular class body. But GDX doesn't seem to like parsing .JSON into those, so instead let's override the accessors. + get() = field.toMap() as HashMap + set(value) = throw UnsupportedOperationException("This property is supposed to be constant.") + + var sharedfiles = ArrayList() + get() = field.toList() as ArrayList + set(value) = throw UnsupportedOperationException("This property is supposed to be constant.") + + val apiConstants = JsonParser().getFromJson(ScriptingAPIConstants::class.java, assetFolders.sharedfilesAssets.child("ScriptAPIConstants.json")) + + val assetFolders + get() = ScriptingAssetFolders + } + + class ScriptingEngineConfig(){ + // Not sure if these should be called "engines" or "languages". "Language" better reflects the actual distinction between the files and (not implemented) syntax highlighting REGEXs for each, but "engine" is less ambiguous with the the translation stuff. + var files = ArrayList() + // https://github.com/libgdx/libgdx/issues/4074 + // https://github.com/libgdx/libgdx/wiki/Reading-and-writing-JSON + // Apparently identifying and listing internal directories doesn't work on Desktop, as all assets are put on the classpath. + // So all the files for each engine engine have to be manually listed in a .JSON instead. + var syntaxHighlightingRegexStack = ArrayList() + } + + class ScriptingAPIConstants() { + var kotlinObjectTokenPrefix = "" + } + + object ScriptingAssetFolders { + val scriptingAssets = Gdx.files.internal("scripting/") + val enginefilesAssets = scriptingAssets.child("enginefiles/") + val sharedfilesAssets = scriptingAssets.child("sharedfiles/") + } +} diff --git a/core/src/com/unciv/scripting/ScriptingScope.kt b/core/src/com/unciv/scripting/ScriptingScope.kt index d1bb836648ba3..ce1d0a46e1bb9 100644 --- a/core/src/com/unciv/scripting/ScriptingScope.kt +++ b/core/src/com/unciv/scripting/ScriptingScope.kt @@ -13,11 +13,18 @@ class ScriptingScope( ) { val isInGame: Boolean get() = (civInfo != null && gameInfo != null && uncivGame != null) + + //val kObjectHelpers //TODO + // val registeredObjects: Set + // val __registeredObjectsForRepl + // fun Vector + // fun MapUnit + // Holds references to all internal game data that the console has access to. // Mostly `.civInfo`/.`gameInfo`, but could be cool to E.G. allow loading and making saves through CLI/API too. // Also where to put any `PlayerAPI`, `CheatAPI`, `ModAPI`, etc. // For `LuaScriptingBackend`, `UpyScriptingBackend`, `QjsScriptingBackend`, etc, the hierarchy of data under this class definition should probably directly mirror the wrappers in the namespace exposed to the scripting language. // `WorldScreen` gives access to `UnitTable.selectedUnit`, `MapHolder.selectedTile`, etc. Useful for contextual operations. - // Hm. I got some `java.util.ConcurrentModificationException`s from Python shortly after loading a save. A lock might be useful. Then again, the exception needs to be (and is) handled in the CLI anyway, and the modding API shouldn't be trying to call handlers when the game is locked anyway. + // } diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index 41662511bac26..9d0f193b1122a 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -9,9 +9,10 @@ import kotlin.math.max import kotlin.math.min /* - ``` + The major classes involved in the scripting API are structured as follows. UpperCamelCase() and parentheses means a new instantiation of a class. lowerCamelCase means a reference to an already-existing instance. An asterisk at the start of an item means zero or multiple instances of that class may be held. A question mark at the start of an item means that it may not exist in all implementations of the parent base class/interface. A question mark at the end of an item means that it is nullable, or otherwise may not be available in all states. - + + ``` UncivGame(): ScriptingState(): // Persistent per UncivGame(). ScriptingScope(): @@ -35,9 +36,11 @@ import kotlin.math.min MainMenuScreen(): consoleScreen scriptingState // Same as for worldScreen. - ScriptingObjectIndex() // Holds WeakRefs used by ScriptingProtocol. Unserializable objects get strings as placeholders, and then turned back into into objects if seen again. + ObjectTokenizer() // Holds WeakRefs used by ScriptingProtocol. Unserializable objects get strings as placeholders, and then turned back into into objects if seen again. Reflection() // Used by some hard-coded scripting backends, and essential to dynamic bindings in ScriptingProtocol(). SourceManager() // Source of the folderHandler and setupInterpreterEnvironment() above. + TokenizingJson() // Serializer and functions that use ObjectTokenizer. + ``` */ @@ -46,7 +49,7 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr val scriptingBackends:ArrayList = ArrayList() val outputHistory:ArrayList = ArrayList() - val commandHistory:ArrayList = ArrayList() + val commandHistory:ArrayList = ArrayList() //TODO: Private these var activeBackend:Int = 0 @@ -91,6 +94,16 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr fun switchToBackend(index: Int) { activeBackend = max(0, min(scriptingBackends.size - 1, index)) } + + fun switchToBackend(backend: ScriptingBackendBase) { + for ((i, b) in scriptingBackends.withIndex()) { + // TODO: Apparently there's a bunch of extensions like `.withIndex()`, `.indices`, and `.lastIndex` that I can use to replace a lot of stuff currently done with `.size`. + if (b == backend) { + switchToBackend(index = i) + } + } + throw IllegalArgumentException("Could not find scripting backend base: ${backend}") + } fun termBackend(index: Int): Exception? { if (!(0 <= index && index < scriptingBackends.size)) { diff --git a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt index 0e44b54a37b42..3fec0ab3616b2 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt @@ -2,11 +2,10 @@ package com.unciv.scripting.protocol import com.unciv.scripting.AutocompleteResults import com.unciv.scripting.ScriptingBackend -import com.unciv.scripting.ScriptingScope import com.unciv.scripting.reflection.Reflection import com.unciv.scripting.utils.TokenizingJson -import com.unciv.scripting.utils.ScriptingObjectIndex import kotlin.random.Random +import kotlin.reflect.KCallable import kotlinx.serialization.Serializable import kotlinx.serialization.encodeToString import kotlinx.serialization.decodeFromString @@ -34,71 +33,141 @@ import java.util.UUID (The current loop is described in a comment in ScriptingReplManager.kt. Kotlin initiates a scripting exec, during which the script interpreter can request values from Kotlin, and at the end of which the script interpreter sends its STDOUT response to the Kotlin side.) - A single packet is a standard JSON string of the form: + A single packet is a JSON string of the form: + ``` { "action": String?, "identifier": String?, "data": Any?, "flags": Collection } + ``` Identifiers should be set to a unique value in each request. Each response should have the same identifier as its corresponding request. Upon receiving a response, both its action and identifier should be checked to match the relevant request. + + --- + + The data field is allowed to represent any hierarchy, of objects of any types. + If it must represent objects that are not possible or not useful to serialize, then unique identifying token strings should be generated and sent in their place. + If those strings are received at any hierarchical depth in the data field of any later packets, then they are to be substituted with their original objects in all uses of the information from those packets. + If the original object of a received token string no longer exists, then an exception should be thrown, and handled as would any exception at the point where the object is to be accessed. + + (Caveats in practice: The scripting API design is highly asymmetric in that script interpreter needs a lot of access to the Kotlin side's state, but the Kotlin side should rarely or never need the script interpreter's state, so the script interpreter doesn't have to bother implementing its own arbitrary object serialization. Requests sent by the Kotlin side also all have very simple response formats because of this, while access to and use of complicated Kotlin-side objects is always initiated by a request from the script interpreter while in the execution loop of its REPL, so the Kotlin side bothers implementing arbitrary object tokenization only when receiving requests and not when receiving responses. Exceptions from reifying invalid received tokens on the Kotlin side should be handled as would any other exceptions at their code paths, but because such tokens are only used on the Kotlin side when preparing a response to a received request from the scripting side, that currently means sending a response packet that is marked in some way as representing an exception and then carrying on as normal.) + + --- Some action types, data formats, and expected response data formats for packets sent from the Kotlin side to the script interpreter include: + ``` 'motd': null -> 'motd_response': String + ``` + ``` 'autocomplete': {'command': String, 'cursorpos': Int} -> 'autocomplete_response': Collection or String //List of matches, or help text to print. + ``` + ``` 'exec': String -> 'exec_response': String //REPL print. + ``` + ``` 'terminate': null -> 'terminate_response': String? //Error message or null. + ``` The above are basically a mirror of ScriptingBackendBase, so the same interface can be implemented in the scripting language. + + --- Some action types, data formats, and expected response types and formats for packets sent from the script interpreter to the Kotlin side include: + ``` 'read': {'path': List<{'type':String, 'name':String, 'params':List}>} -> 'read_reponse': {'value': Any?, 'exception': String?} //Attribute/property access, by list of `PathElement` properties. + ``` + ``` //'call': {'path': List<{'type':String, 'name':String, 'params':List}>, 'args': Collection, 'kwargs': Map} -> //'call_response': {'value': Any?, 'exception': String?} - //Method/function call. Deprecated. Instead, use `"action":"read"` with a "path" that has `"type":"call"` element(s). + //Method/function call. + //Deprecated and removed. Instead, use `"action":"read"` with a "path" that has `"type":"call"` element(s) as per `PathElement`. + ``` + ``` 'assign': {'path': List<{'type':String, 'name':String, 'params':List}>, 'value': Any} -> 'assign_response': String? //Error message or null. + ``` + ``` 'dir': {'path': List<{'type':String, 'name':String, 'params':List}>} -> 'dir_response': {'value': Collection, 'exception': String?} //Names of all members/properties/attributes/methods. + ``` + ``` 'keys': {'path': List<{'type':String, 'name':String, 'params':List}>} -> 'keys_response': {'value': Collection, 'exception': String?} + ``` - //'args': {'path'} -> - //'args_response': Collection> - //Names and types of arguments accepted by a function. - + ``` 'length': {'path': List<{'type':String, 'name':String, 'params':List}>} -> 'length_response': {'value': Int?, 'exception': String?} + ``` + ``` 'contains': {'path': List<{'type':String, 'name':String, 'params':List}>, 'value': Any?} -> 'contains_response': {'value': Boolean?, 'exception': String?} + ``` + + ``` + 'callable': {'path': List<{'type':String, 'name':String, 'params':List}>} -> + 'callable_response': {'value': Boolean?, 'exception': String?} + //Used by Python autocompleter to add opening bracket to methods and function suggestions. Quite useful for exploring API at a glance. + ``` + + ``` + 'args': 'path': List<{'type':String, 'name':String, 'params':List}>} -> + 'args_response': {'value': List>?, 'exception': String?} + //Names and types of arguments accepted by a function. + //Currently just used by Python autocompleter to generate help text. + //Could also be used to control functions in scripting environment. If so, then names of types should be standardized. + ``` + + ``` + 'docstring': {'path': List<{'type':String, 'name':String, 'params':List}>} -> + 'docstring_response': {'value': String?, 'exception': String?} + //Used by Python wrappers and autocompleter to get help text showing arguments and types for callables. Useful for exploring API without having to browse code. + ``` + + The paths fields in some of the data fields mirror PathElement. + + --- Flags are string values for communicating extra information that doesn't need a separate packet or response. Depending on the flag and action, they may be contextual to the packet, or they may not. I think I see them mostly as a way to semantically separate meta-communication about the protocol from actual requests for actions: - 'PassMic' //Indicates that the sending side will now begin listening instead, and the receiving side should send the next request. Sent by Kotlin side at start of script execution and autocompletion to allow script to request values, and should be sent by script interpreter immediately before sending response with results of execution. + ``` + 'PassMic' + //Indicates that the sending side will now begin listening instead, and the receiving side should send the next request. + //Sent by Kotlin side at start of script engine startup/MOTD, autocompletion, and execution to allow script to request values, and should be sent by script interpreter immediately before sending response with results of execution. + ``` + + ``` + //'Exception' + //Indicates that this packet is associated with an error. + //Not implemented. Currently Kotlin-side exceptions while processing a request are communicated to the script interpreter with an error message at an expected place in the data field of the response, and exceptions in the script interpreter while executing a command are stringified and returned to the Kotlin side the same as any REPL output. + ``` + + --- Thus, at the IPC level, all foreign backends will actually use the same language, which is this JSON-based protocol. Differences between Python, JS, Lua, etc. will all be down to how they interpret the "exec", "autocomplete", and "motd" actions differently, which each high-level scripting language is free to implement as works best for it. @@ -113,6 +182,7 @@ data class ScriptingPacket( // This can have arbitrary structure, values/types, and depth. So since it lives so close to the IPC protocol anyway, JsonElement is the easiest way to represent and serialize it. var flags: Collection = listOf() ) { + companion object { fun fromJson(string: String): ScriptingPacket = TokenizingJson.json.decodeFromString(string) } @@ -120,21 +190,17 @@ data class ScriptingPacket( fun toJson() = TokenizingJson.json.encodeToString(this) fun hasFlag(flag: ScriptingProtocol.KnownFlag) = flag.value in flags + } -class ScriptingProtocol(val scriptingScope: ScriptingScope) { +class ScriptingProtocol(val scope: Any) { + + enum class KnownFlag(val value: String) { + PassMic("PassMic") + } companion object { -// fun transformPath(path: List): List { -// // Kinda redundant, since PathElement is automatically sent through ScriptingObjectIndex by TokenizingSerializer anyway. -// return path.map{ Reflection.PathElement( -// type = it.type, -// name = it.name, -// doEval = it.doEval, -// params = it.params.map( { p -> ScriptingObjectIndex.getReal(p) }) -// ) } -// } fun makeUniqueId(): String { return "${System.nanoTime()}-${Random.nextBits(30)}-${UUID.randomUUID().toString()}" @@ -143,31 +209,27 @@ class ScriptingProtocol(val scriptingScope: ScriptingScope) { val responseTypes = mapOf( "motd" to "motd_response", "autocomplete" to "autocomplete_response", - "exec" to "exec_response" + "exec" to "exec_response" // TODO ) fun enforceIsResponse(original: ScriptingPacket, response: ScriptingPacket): ScriptingPacket { if (!( - ((response.action == null && original.action == null) || response.action == original.action.toString() + "_reponse") - || response.identifier == original.identifier + ((response.action == null && original.action == null) || response.action == original.action.toString() + "_response") + && response.identifier == original.identifier )) { throw IllegalStateException("Scripting packet response does not match request ID and type: ${original}, ${response}") } return response } -// fun decodeJsonPathElement(value: JsonElement): Reflection.PathElement { -// return TokenizingJson.json.decodeFromJsonElement(JsonElement) -// } -// -// fun decodeJsonPathList(value: JsonElement): List { -// } } object makeActionRequests { fun motd() = ScriptingPacket( "motd", - makeUniqueId() + makeUniqueId(), + JsonNull, + listOf(KnownFlag.PassMic.value) ) fun autocomplete(command: String, cursorPos: Int?) = ScriptingPacket( "autocomplete", @@ -204,10 +266,6 @@ class ScriptingProtocol(val scriptingScope: ScriptingScope) { else RuntimeException((packet.data as JsonPrimitive).content) } - - enum class KnownFlag(val value: String) { - PassMic("PassMic") - } fun makeActionResponse(packet: ScriptingPacket): ScriptingPacket { var action: String? = null @@ -216,12 +274,14 @@ class ScriptingProtocol(val scriptingScope: ScriptingScope) { var value: JsonElement = JsonNull var exception: JsonElement = JsonNull when (packet.action) { + // There's a lot of repetition here, because I don't want to enforce any specification on what form the request and response data fields for actions must take. + // I prefer to try to keep the code for each response type independent enough to be readable on its own. "read" -> { action = "read_response" try { value = TokenizingJson.getJsonElement( Reflection.resolveInstancePath( - scriptingScope, + scope, TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) ) ) @@ -234,42 +294,11 @@ class ScriptingProtocol(val scriptingScope: ScriptingScope) { "exception" to exception )) } -// "call" -> { -// // Deprecated. Instead, use `"action":"read"` with a "path" that has `"type":"call"` element(s). -// action = "call_response" -// try { -// var path = TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) -// val params = TokenizingJson.getJsonReal((packet.data as JsonObject)["args"]!!) as List -// val kwparams = TokenizingJson.getJsonReal((packet.data as JsonObject)["kwargs"]!!) as Map -// if (kwparams.size > 0) { -// throw UnsupportedOperationException("Keyword arguments are not currently supported: ${kwparams}") -// } -// path.add(Reflection.PathElement( -// type = Reflection.PathElementType.Call, -// name = "", -// doEval = false, -// params = params -// )) -// value = TokenizingJson.getJsonElement( -// Reflection.resolveInstancePath( -// scriptingScope, -// transformPath(path) -// ) -// ) -// } catch (e: Exception) { -// value = JsonNull -// exception = JsonPrimitive(e.toString()) -// } -// data = JsonObject(mapOf( -// "value" to value, -// "exception" to exception -// )) -// } "assign" -> { action = "assign_response" try { Reflection.setInstancePath( - scriptingScope, + scope, TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!), TokenizingJson.getJsonReal((packet.data as JsonObject)["value"]!!) ) @@ -282,7 +311,7 @@ class ScriptingProtocol(val scriptingScope: ScriptingScope) { action = "dir_response" try { val leaf = Reflection.resolveInstancePath( - scriptingScope, + scope, TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) ) value = TokenizingJson.getJsonElement(leaf!!::class.members.map{it.name}) @@ -299,7 +328,7 @@ class ScriptingProtocol(val scriptingScope: ScriptingScope) { action = "length_response" try { val leaf = Reflection.resolveInstancePath( - scriptingScope, + scope, TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) ) try { @@ -325,7 +354,7 @@ class ScriptingProtocol(val scriptingScope: ScriptingScope) { action = "keys_response" try { val leaf = Reflection.resolveInstancePath( - scriptingScope, + scope, TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) ) value = TokenizingJson.getJsonElement((leaf as Map).keys) @@ -342,7 +371,7 @@ class ScriptingProtocol(val scriptingScope: ScriptingScope) { action = "contains_response" try { val leaf = Reflection.resolveInstancePath( - scriptingScope, + scope, TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) ) val _check = TokenizingJson.getJsonReal((packet.data as JsonObject)["value"]!!) @@ -360,14 +389,64 @@ class ScriptingProtocol(val scriptingScope: ScriptingScope) { "exception" to exception )) } + "callable" -> { + action = "callable_response" + try { + val leaf = Reflection.resolveInstancePath( + scope, + TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) + ) + value = TokenizingJson.getJsonElement(leaf is KCallable<*>) + } catch (e: Exception) { + value = JsonNull + exception = JsonPrimitive(e.toString()) + } + data = JsonObject(mapOf( + "value" to value, + "exception" to exception + )) + } + "args" -> { + action = "args_response" + try { + val leaf = Reflection.resolveInstancePath( + scope, + TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) + ) + value = TokenizingJson.getJsonElement((leaf as KCallable<*>).parameters.map { listOf(it.name.toString(), it.type.toString()) }) + } catch (e: Exception) { + value = JsonNull + exception = JsonPrimitive(e.toString()) + } + data = JsonObject(mapOf( + "value" to value, + "exception" to exception + )) + } + "docstring" -> { + action = "docstring_response" + try { + val leaf = Reflection.resolveInstancePath( + scope, + TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) + ) + value = TokenizingJson.getJsonElement(leaf.toString()) + } catch (e: Exception) { + value = JsonNull + exception = JsonPrimitive(e.toString()) + } + data = JsonObject(mapOf( + "value" to value, + "exception" to exception + )) + } else -> { - throw IllegalArgumentException("Unknown action received in scripting packet: ${packet.action}") + throw IllegalArgumentException("Unknown action received in scripting request packet: ${packet.action}") } } return ScriptingPacket(action, packet.identifier, data, flags) } -// fun resolvePath() { } } diff --git a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt index 45301d1cc8f40..06fee8deaf24d 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt @@ -6,7 +6,6 @@ import com.unciv.scripting.ScriptingScope import com.unciv.scripting.protocol.ScriptingPacket import com.unciv.scripting.protocol.ScriptingProtocol import com.unciv.scripting.utils.Blackbox -//import com.unciv.scripting.utils.ScriptingObjectIndex /* @@ -37,6 +36,9 @@ import com.unciv.scripting.utils.Blackbox Plus, letting the script interpreter run completely in parallel would probably introduce potential for all sorts of issues with no-deterministic synchronicity, and performance issues Calling the script interpreter from */ +//interface ScriptingReplManager { +//} TODO + class ScriptingReplManager(val scriptingScope: ScriptingScope, val blackbox: Blackbox): ScriptingBackend { @@ -61,7 +63,8 @@ class ScriptingReplManager(val scriptingScope: ScriptingScope, val blackbox: Bla } fun foreignExecLoop() { - // Lists to request for values from the black box, and replies to them, during script execution. + // Lists to requests for values from the black box, and replies to them, during script execution. + // Terminates loop after receiving a request with a the 'PassMic' flag. while (true) { val request = ScriptingPacket.fromJson(blackbox.read(block=true)) if (request.action != null) { @@ -77,10 +80,10 @@ class ScriptingReplManager(val scriptingScope: ScriptingScope, val blackbox: Bla override fun motd(): String { return ScriptingProtocol.parseActionResponses.motd( getRequestResponse( - ScriptingProtocol.makeActionRequests.motd() + ScriptingProtocol.makeActionRequests.motd(), + execLoop = { foreignExecLoop() } ) ) - return "${exec("motd()\n")}\n" } override fun autocomplete(command: String, cursorPos: Int?): AutocompleteResults { @@ -90,7 +93,6 @@ class ScriptingReplManager(val scriptingScope: ScriptingScope, val blackbox: Bla execLoop = { foreignExecLoop() } ) ) - return AutocompleteResults() } override fun exec(command: String): String { diff --git a/core/src/com/unciv/scripting/reflection/Reflection.kt b/core/src/com/unciv/scripting/reflection/Reflection.kt index cf69dd43bb1c0..1b104409183cf 100644 --- a/core/src/com/unciv/scripting/reflection/Reflection.kt +++ b/core/src/com/unciv/scripting/reflection/Reflection.kt @@ -40,16 +40,6 @@ object Reflection { property.set(instance, value) } - /* - interface PathElementArg { - val value: Any - } - - data class PathElementArgString(override val value: String): PathElementArg - data class PathElementArgInt(override val value: Int): PathElementArg - data class PathElementArgFloat(override val value: Float): PathElementArg - data class PathElementArgBoolean(override val value: Boolean): PathElementArg - */ enum class PathElementType() { Property(), diff --git a/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt b/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt index e4b18dc0fb16d..3dac52f71470a 100644 --- a/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt +++ b/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt @@ -93,7 +93,7 @@ class ApiSpecGenerator(val scriptingScope: ScriptingScope) { fun generateClassApi(): Map> { // Provide options for the scripting languages. This function val classes = getAllUncivClasses() - var c = 0 + var c = 0 // Test count. Something like 5,400, IIRC. For now, it's easier to just dynamically generate the API using Python's magic methods and the reflective tools in ScriptingProtocol. JS has proxies too, but other languages may not be so dynamic. // TBF I think some of those might have been GDX/Kotlin/JVM classes, which I should filter oout by `.qualifiedName`. val output = mutableMapOf>( *classes.map{ it.qualifiedName!! to it.members.map{ c += 1; makeMemberSpecDef(it) } diff --git a/core/src/com/unciv/scripting/utils/ObjectTokenizer.kt b/core/src/com/unciv/scripting/utils/ObjectTokenizer.kt new file mode 100644 index 0000000000000..40b38a8571642 --- /dev/null +++ b/core/src/com/unciv/scripting/utils/ObjectTokenizer.kt @@ -0,0 +1,59 @@ +package com.unciv.scripting.utils + +import com.unciv.scripting.ScriptingConstants +import kotlin.math.min +import java.lang.ref.WeakReference +import java.util.UUID + + +object ObjectTokenizer { + + private val objs = mutableMapOf>() + + private val tokenPrefix + get() = ScriptingConstants.apiConstants.kotlinObjectTokenPrefix + private val tokenMaxLength = 100 + + private fun tokenFromObject(value: Any?): String { + //Try to keep this human-informing, but don't parse it to extracting information. + var stringified = value.toString() + if (stringified.length > tokenMaxLength) { + stringified = stringified.slice(0..tokenMaxLength-4) + "..." + println("Slicing.") + } + return "${tokenPrefix}${System.identityHashCode(value)}:${if (value == null) "null" else value::class.qualifiedName}/${stringified}:${UUID.randomUUID().toString()}" + } + + private fun isToken(value: Any?): Boolean { + return value is String && value.startsWith(tokenPrefix) + } + + fun clean(): Unit { + val badtokens = mutableListOf() + for ((t, o) in objs) { + if (o.get() == null) { + badtokens.add(t) + } + } + for (t in badtokens) { + objs.remove(t) + } + } + + fun getToken(obj: Any?): String { + clean() + val token = tokenFromObject(obj) + objs[token] = WeakReference(obj) + return token + } + + fun getReal(token: Any?): Any? { + clean() + if (isToken(token)) { + return objs[token]!!.get() + } else { + return token + } + } + +} diff --git a/core/src/com/unciv/scripting/utils/ScriptingObjectIndex.kt b/core/src/com/unciv/scripting/utils/ScriptingObjectIndex.kt deleted file mode 100644 index 5dbfbbf61a0db..0000000000000 --- a/core/src/com/unciv/scripting/utils/ScriptingObjectIndex.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.unciv.scripting.utils - -import kotlin.collections.MutableMap -import java.lang.ref.WeakReference -import java.util.UUID - - -object ScriptingObjectIndex { - - private val objs = mutableMapOf>() - - private val kotlinObjectIdPrefix = "_unciv-kt-obj@" // load from ScriptAPIConstants.json - - private fun idKotlinObject(value: Any?): String { - //Try to keep this human-informing, but don't parse it to extracting information. - return "${kotlinObjectIdPrefix}${System.identityHashCode(value)}:${if (value == null) "null" else value::class.qualifiedName}/${value.toString()}:${UUID.randomUUID().toString()}" - } - - private fun isKotlinToken(value: Any?): Boolean { - return value is String && value.startsWith(kotlinObjectIdPrefix) - } - - fun clean(): Unit { - val badtokens = mutableListOf() - for ((t, o) in objs) { - if (o.get() == null) { - badtokens.add(t) - } - } - for (t in badtokens) { - objs.remove(t) - } - } - - fun getToken(obj: Any?): String { - clean() - val token = idKotlinObject(obj) - objs[token] = WeakReference(obj) - return token - } - - fun getReal(token: Any?): Any? { - clean() - if (isKotlinToken(token)) { - return objs[token]!!.get() - } else { - return token - } - } - -} diff --git a/core/src/com/unciv/scripting/utils/SourceManager.kt b/core/src/com/unciv/scripting/utils/SourceManager.kt index 835420216a533..2c604ed0784df 100644 --- a/core/src/com/unciv/scripting/utils/SourceManager.kt +++ b/core/src/com/unciv/scripting/utils/SourceManager.kt @@ -3,42 +3,21 @@ package com.unciv.scripting.utils import com.badlogic.gdx.Gdx import com.badlogic.gdx.files.FileHandle import com.unciv.JsonParser +import com.unciv.scripting.ScriptingConstants +//import com.unciv.scripting.ScriptingConstants import kotlin.concurrent.thread object SourceManager { - val scriptingAssets = Gdx.files.internal("scripting/") - val enginedataAssets = scriptingAssets.child("enginedata/") - - val shareddataAssets = scriptingAssets.child("shareddata/") - val shareddataAssetsList = scriptingAssets.child("SharedData.json") - - val shareddataFilelist = JsonParser().getFromJson(Array::class.java, shareddataAssetsList) - - private fun getEngineFilelistPath(engine: String): FileHandle { - return scriptingAssets.child("${engine}.json") - } - - private fun getEngineFilelist(engine: String): Array { - // https://github.com/libgdx/libgdx/issues/4074 - // https://github.com/libgdx/libgdx/wiki/Reading-and-writing-JSON - // Apparently identifying and listing internal directories doesn't work on Desktop, as all assets are put on the classpath. - // So all the files for an engine should be listed in a flat .JSON instead. - return JsonParser().getFromJson( - Array::class.java, - getEngineFilelistPath(engine) - ) - } - private fun getEngineLibraries(engine: String): FileHandle { - return enginedataAssets.child("${engine}/") + return ScriptingConstants.assetFolders.enginefilesAssets.child("${engine}/") } fun setupInterpreterEnvironment(engine: String): FileHandle { // Creates temporary directory. - // Copies directory tree under `android/assets/scripting/shareddata/` into it, as specified by `SharedData.json` - // Copies directory tree under `android/assets/scripting/enginedata/{engine}` into it, as specified by `{engine}.json`. + // Copies directory tree under `android/assets/scripting/sharedfiles/` into it, as specified by `SharedData.json` + // Copies directory tree under `android/assets/scripting/enginefiles/{engine}` into it, as specified by `{engine}.json`. // Returns `FileHandle()` for the temporary directory. val enginedir = getEngineLibraries(engine) val outdir = FileHandle.tempDirectory("unciv-${engine}_") @@ -50,10 +29,10 @@ object SourceManager { sourcedir.child(path).copyTo(target) } } - for (fp in shareddataFilelist) { - addfile(shareddataAssets, fp) + for (fp in ScriptingConstants.sharedfiles) { + addfile(ScriptingConstants.assetFolders.sharedfilesAssets, fp) } - for (fp in getEngineFilelist(engine)) { + for (fp in ScriptingConstants.engines[engine]!!.files) { addfile(enginedir, fp) } Runtime.getRuntime().addShutdownHook( diff --git a/core/src/com/unciv/scripting/utils/SyntaxHighlighter.kt b/core/src/com/unciv/scripting/utils/SyntaxHighlighter.kt new file mode 100644 index 0000000000000..9f6929962ca7a --- /dev/null +++ b/core/src/com/unciv/scripting/utils/SyntaxHighlighter.kt @@ -0,0 +1,16 @@ +package com.unciv.scripting.utils + + +interface SyntaxHighlighter { + fun cmlFromText(text: String): String { + // https://github.com/libgdx/libgdx/wiki/Color-Markup-Language + return text + } +} + +open class FunctionalSyntaxHighlighter() { + open val transformList: List<(String) -> String> = listOf() + fun cmlFromText(text: String): String { + return transformList.fold(text){ t: String, f: (String) -> String -> f(t) } + } +} diff --git a/core/src/com/unciv/scripting/utils/TokenizingJson.kt b/core/src/com/unciv/scripting/utils/TokenizingJson.kt index f54a5b1da9aa5..259209a7f8f91 100644 --- a/core/src/com/unciv/scripting/utils/TokenizingJson.kt +++ b/core/src/com/unciv/scripting/utils/TokenizingJson.kt @@ -18,7 +18,7 @@ import kotlinx.serialization.modules.SerializersModule object TokenizingJson { - // Json serialization that accepts `Any?`, and converts non-primitive values to string keys from `ScriptingObjectIndex`. + // Json serialization that accepts `Any?`, and converts non-primitive values to string keys from `ObjectTokenizer`. // class ObjectReifyingSerializer: KSerializer { // override val descriptor = SerialDescriptor("Reifying", delegateSerializer) @@ -44,7 +44,7 @@ object TokenizingJson { "Long" to serializer(), "Short" to serializer(), "String" to serializer() - // Only these types will be serialized. Everything else will be replaced with a string key from ScriptingObjectIndex. + // Only these types will be serialized. Everything else will be replaced with a string key from ObjectTokenizer. ).mapValues { (_, v) -> v as KSerializer } // private fun getDataTypeSerializer(value: T): KSerializer { @@ -60,7 +60,7 @@ object TokenizingJson { if (classname in dataTypeSerializers) { encoder.encodeSerializableValue(dataTypeSerializers[value!!::class.simpleName!!]!!, value!!) } else { - encoder.encodeString(ScriptingObjectIndex.getToken(value!!)) + encoder.encodeString(ObjectTokenizer.getToken(value!!)) } } } @@ -69,7 +69,7 @@ object TokenizingJson { if (decoder is JsonDecoder) { val jsonLiteral = (decoder as JsonDecoder).decodeJsonElement() val rawval: Any? = getJsonReal(jsonLiteral) - return ScriptingObjectIndex.getReal(rawval) + return ObjectTokenizer.getReal(rawval) } else { throw UnsupportedOperationException("Decoding is not supported by TokenizingSerializer for ${decoder::class.simpleName}.") } @@ -92,7 +92,7 @@ object TokenizingJson { if (value is JsonElement) { return value } - if (value is Map<*, *>) { + if (value is Map<*, *>) { //TODO: Decide what to do with the keys. return JsonObject( (value as Map).mapValues{ getJsonElement(it.value) } ) } if (value is Collection<*>) { @@ -104,13 +104,13 @@ object TokenizingJson { if (value is Int || value is Long || value is Float || value is Double) { return JsonPrimitive(value as Number) } - if (value is Boolean) { + if (value is Boolean) { //TODO: Arrays? return JsonPrimitive(value as Boolean) } if (value == null) { return JsonNull } - return JsonPrimitive(ScriptingObjectIndex.getToken(value)) + return JsonPrimitive(ObjectTokenizer.getToken(value)) } fun getJsonReal(value: JsonElement): Any? { @@ -126,7 +126,7 @@ object TokenizingJson { if (value is JsonPrimitive) { val v = value as JsonPrimitive if (v.isString) { - return ScriptingObjectIndex.getReal(v.content) + return ObjectTokenizer.getReal(v.content) } else { return v.content.toIntOrNull() ?: v.content.toDoubleOrNull() diff --git a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt index 2122618482473..f0d096eb17ad6 100644 --- a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt @@ -52,7 +52,7 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un backendsAdders.add("Launch new backend:".toLabel()).padRight(30f).padLeft(20f) for (backendtype in ScriptingBackendType.values()) { - var backendadder = backendtype.metadata.displayname.toTextButton() + var backendadder = backendtype.metadata.displayName.toTextButton() backendadder.onClick({ echo(scriptingState.spawnBackend(backendtype)) updateRunning() @@ -144,7 +144,7 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un runningList.clearChildren() var i = 0 for (backend in scriptingState.scriptingBackends) { - var button = backend.metadata.displayname.toTextButton() + var button = backend.metadata.displayName.toTextButton() val index = i runningList.add(button) if (i == scriptingState.activeBackend) { @@ -159,7 +159,7 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un val exc: Exception? = scriptingState.termBackend(index) updateRunning() if (exc != null) { - echo("Failed to stop ${backend.metadata.displayname} backend: ${exc.toString()}") + echo("Failed to stop ${backend.metadata.displayName} backend: ${exc.toString()}") } }) runningList.add(termbutton.surroundWithCircle(40f)).row() @@ -208,7 +208,7 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un echo(m) } //var minmatch = original //Checking against the current input would prevent autoinsertion from working for autocomplete backends that support getting results from the middle of the current input. - var minmatch = "" + var minmatch = original.slice(0..cursorpos-1) var chosenresult = results.matches.first({true}) for (l in original.length-1..chosenresult.length-1) { var longer = chosenresult.slice(0..l) @@ -241,6 +241,9 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un setText("") } +// fun clone(): ConsoleScreen { +// } + override fun resize(width: Int, height: Int) { if (stage.viewport.screenWidth != width || stage.viewport.screenHeight != height) { // Right. Actually resizing seems painful. game.consoleScreen = ConsoleScreen(scriptingState, closeAction) @@ -249,6 +252,7 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un } } } + enum class SetTextCursorMode() { End(), Unchanged(), From fdc171277d89f64369938de501b83b527a3e6494 Mon Sep 17 00:00:00 2001 From: will-ca Date: Thu, 11 Nov 2021 03:48:29 +0000 Subject: [PATCH 30/93] More cleanup. --- .../scripting/ScriptingEngineConstants.json | 2 +- build.gradle.kts | 1 - core/src/com/unciv/UncivGame.kt | 6 +- .../src/com/unciv/scripting/ScriptingState.kt | 64 ++++++++++++----- .../scripting/protocol/ScriptingProtocol.kt | 48 ++++++++----- .../unciv/ui/consolescreen/ConsoleScreen.kt | 71 +++++++++++++------ 6 files changed, 134 insertions(+), 58 deletions(-) diff --git a/android/assets/scripting/ScriptingEngineConstants.json b/android/assets/scripting/ScriptingEngineConstants.json index 903b25433c0cf..d4ad42a4f1557 100644 --- a/android/assets/scripting/ScriptingEngineConstants.json +++ b/android/assets/scripting/ScriptingEngineConstants.json @@ -22,7 +22,7 @@ syntaxHighlightingRegexStack: [ ] } - js: { + qjs: { files: [ unciv/ main.js diff --git a/build.gradle.kts b/build.gradle.kts index 24098c60320e5..f87d3f0380b23 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -20,7 +20,6 @@ buildscript { mavenLocal() mavenCentral() maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots/") } - //maven { url = uri("https://kotlin.bintray.com/kotlinx") } gradlePluginPortal() maven{ url = uri("https://jitpack.io") } // for the anuken packr } diff --git a/core/src/com/unciv/UncivGame.kt b/core/src/com/unciv/UncivGame.kt index 7028d1bcd0b4a..328d403d3c320 100644 --- a/core/src/com/unciv/UncivGame.kt +++ b/core/src/com/unciv/UncivGame.kt @@ -76,7 +76,7 @@ class UncivGame(parameters: UncivGameParameters) : Game() { val translations = Translations() - lateinit var scriptingState: ScriptingState // Could probably replace these with lazies. + lateinit var scriptingState: ScriptingState lateinit var consoleScreen: ConsoleScreen // Keep same ConsoleScreen() when possible, to avoid having to manually persist/restore history, input field, etc. override fun create() { @@ -206,6 +206,10 @@ class UncivGame(parameters: UncivGameParameters) : Game() { override fun resize(width: Int, height: Int) { screen.resize(width, height) + if (screen !== consoleScreen) { + // consoleScreen is usually persistent, so it needs to be resized even if not active. + consoleScreen.resize(width, height) + } } override fun render() = wrappedCrashHandlingRender() diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index 9d0f193b1122a..8b35aa0da479c 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -44,19 +44,31 @@ import kotlin.math.min ``` */ +fun ArrayList.clipIndexToBounds(index: Int, extendsize: Int = 0): Int { + return max(0, min(this.size-1+extendsize, index)) +} + +fun ArrayList.enforceValidIndex(index: Int) { + // Doing all checks with the same function and error message is probably easier to debug than letting an array access fail. + if (index < 0 || this.size <= index) { + throw IndexOutOfBoundsException("Index {index} is out of range of ArrayList().") + } +} + class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: ScriptingBackendType? = null){ - val scriptingBackends:ArrayList = ArrayList() + val scriptingBackends: ArrayList = ArrayList() - val outputHistory:ArrayList = ArrayList() - val commandHistory:ArrayList = ArrayList() //TODO: Private these + private val outputHistory: ArrayList = ArrayList() + private val commandHistory: ArrayList = ArrayList() - var activeBackend:Int = 0 + var activeBackend: Int = 0 - var maxOutputHistory:Int = 50 // Not implemented - var maxCommandHistory:Int = 50 // Not implemented + val maxOutputHistory: Int = 127 + val maxCommandHistory: Int = 255 - var activeCommandHistory:Int = 0 + var activeCommandHistory: Int = 0 + // Actually inverted, because history items are added to end of list and not start. 0 means nothing, 1 means most recent command at end of list. var civInfo: CivilizationInfo? @@ -81,18 +93,21 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr echo(spawnBackend(initialBackendType)) } } + + fun getOutputHistory() = outputHistory.toList() fun spawnBackend(backendtype: ScriptingBackendType): String { - var backend:ScriptingBackendBase = SpawnNamedScriptingBackend(backendtype, scriptingScope) + val backend:ScriptingBackendBase = SpawnNamedScriptingBackend(backendtype, scriptingScope) scriptingBackends.add(backend) activeBackend = scriptingBackends.size - 1 - var motd = backend.motd() + val motd = backend.motd() echo(motd) return motd } fun switchToBackend(index: Int) { - activeBackend = max(0, min(scriptingBackends.size - 1, index)) + scriptingBackends.enforceValidIndex(index) + activeBackend = index } fun switchToBackend(backend: ScriptingBackendBase) { @@ -104,15 +119,19 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr } throw IllegalArgumentException("Could not find scripting backend base: ${backend}") } + + fun switchToBackend(displayname: String) { + } fun termBackend(index: Int): Exception? { - if (!(0 <= index && index < scriptingBackends.size)) { - throw IndexOutOfBoundsException()// Maybe checking should be better done and unified. Also, I don't love the idea of an exposed method being able to trigger a crash, but I had this fail silently before, which would probably be worse. - } + scriptingBackends.enforceValidIndex(index) val result = scriptingBackends[index].terminate() if (result == null) { scriptingBackends.removeAt(index) - activeBackend = min(activeBackend, scriptingBackends.size - 1) + if (index < activeBackend) { + activeBackend -= 1 + } + activeBackend = scriptingBackends.clipIndexToBounds(activeBackend) } return result } @@ -127,9 +146,15 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr fun echo(text: String) { outputHistory.add(text) + while (outputHistory.size > maxOutputHistory) { + outputHistory.removeAt(0) + // If these are ArrayLists, performance will probably be `O(n)` relative ot maxOutputHistory. + // But premature optimization would be bad. + } } fun autocomplete(command: String, cursorPos: Int? = null): AutocompleteResults { + // Deliberately not calling `echo()` to add into history because I consider autocompletion a protocol/API level feature if (!(hasBackend())) { return AutocompleteResults(listOf(), false, "") } @@ -137,7 +162,7 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr } fun navigateHistory(increment: Int): String { - activeCommandHistory = max(0, min(commandHistory.size, activeCommandHistory + increment)) + activeCommandHistory = commandHistory.clipIndexToBounds(activeCommandHistory + increment, extendsize = 1) if (activeCommandHistory <= 0) { return "" } else { @@ -148,15 +173,20 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr fun exec(command: String): String { if (command.length > 0) { commandHistory.add(command) + while (commandHistory.size > maxCommandHistory) { + commandHistory.removeAt(0) + // No need to restrict activeCommandHistory to valid indices here because it gets set to zero anyway. + // Also O(n). + } } - var out:String + activeCommandHistory = 0 + var out: String if (hasBackend()) { out = getActiveBackend().exec(command) } else { out = "" } echo(out) - activeCommandHistory = 0 return out } } diff --git a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt index 3fec0ab3616b2..f3275dc698081 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt @@ -206,15 +206,27 @@ class ScriptingProtocol(val scope: Any) { return "${System.nanoTime()}-${Random.nextBits(30)}-${UUID.randomUUID().toString()}" } - val responseTypes = mapOf( + val responseTypes = mapOf( + // Deliberately repeating myself because I don't want to imply a hard specification for the name of response packets. + null to null, "motd" to "motd_response", "autocomplete" to "autocomplete_response", - "exec" to "exec_response" // TODO + "exec" to "exec_response", + "terminate" to "terminate_response", + "read" to "read_response", + "assign" to "assign_response", + "dir" to "dir_response", + "keys" to "keys_response", + "length" to "length_response", + "contains" to "contains_response", + "callable" to "callable_response", + "args" to "args_response", + "docstring" to "docstring_response" ) fun enforceIsResponse(original: ScriptingPacket, response: ScriptingPacket): ScriptingPacket { if (!( - ((response.action == null && original.action == null) || response.action == original.action.toString() + "_response") + (response.action == responseTypes[original.action]!!) && response.identifier == original.identifier )) { throw IllegalStateException("Scripting packet response does not match request ID and type: ${original}, ${response}") @@ -324,23 +336,14 @@ class ScriptingProtocol(val scope: Any) { "exception" to exception )) } - "length" -> { - action = "length_response" + "keys" -> { + action = "keys_response" try { val leaf = Reflection.resolveInstancePath( scope, TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) ) - try { - value = TokenizingJson.getJsonElement((leaf as Array<*>).size) - // Once/If I get Reflection working with properties/Transients/whatever, I could replace these casts with a single reflective call. - } catch (e: Exception) { - try { - value = TokenizingJson.getJsonElement((leaf as Map<*, *>).size) - } catch (e: Exception) { - value = TokenizingJson.getJsonElement((leaf as Collection<*>).size) - } - } + value = TokenizingJson.getJsonElement((leaf as Map).keys) } catch (e: Exception) { value = JsonNull exception = JsonPrimitive(e.toString()) @@ -350,14 +353,23 @@ class ScriptingProtocol(val scope: Any) { "exception" to exception )) } - "keys" -> { - action = "keys_response" + "length" -> { + action = "length_response" try { val leaf = Reflection.resolveInstancePath( scope, TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) ) - value = TokenizingJson.getJsonElement((leaf as Map).keys) + try { + value = TokenizingJson.getJsonElement((leaf as Array<*>).size) + // Once/If I get Reflection working with properties/Transients/whatever, I could replace these casts with a single reflective call. + } catch (e: Exception) { + try { + value = TokenizingJson.getJsonElement((leaf as Map<*, *>).size) + } catch (e: Exception) { + value = TokenizingJson.getJsonElement((leaf as Collection<*>).size) + } + } } catch (e: Exception) { value = JsonNull exception = JsonPrimitive(e.toString()) diff --git a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt index f0d096eb17ad6..93913edd00852 100644 --- a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt @@ -2,6 +2,8 @@ package com.unciv.ui.consolescreen import com.badlogic.gdx.Input import com.badlogic.gdx.graphics.Color +import com.badlogic.gdx.scenes.scene2d.Actor +import com.badlogic.gdx.scenes.scene2d.ui.Cell import com.badlogic.gdx.scenes.scene2d.ui.Image import com.badlogic.gdx.scenes.scene2d.ui.Label import com.badlogic.gdx.scenes.scene2d.ui.SplitPane @@ -15,6 +17,7 @@ import com.unciv.scripting.ScriptingState import com.unciv.ui.utils.* import com.unciv.ui.utils.AutoScrollPane as ScrollPane import kotlin.math.max +import kotlin.math.min class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Unit): CameraStageBaseScreen() { @@ -44,9 +47,15 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un private val layoutUpdators = ArrayList<() -> Unit>() private var isOpen = false - var input: String + var inputText: String get() = inputField.text set(value: String) { inputField.setText(value) } + + var cursorPos: Int + get() = inputField.getCursorPosition() + set(value: Int) { + inputField.setCursorPosition(max(0, min(inputText.length, value))) + } init { @@ -171,29 +180,37 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un printHistory.clearChildren() } - private fun setText(text: String, cursormode: SetTextCursorMode=SetTextCursorMode.End) { - val originaltext = inputField.text - val originalcursorpos = inputField.getCursorPosition() - inputField.setText(text) + fun setText(text: String, cursormode: SetTextCursorMode=SetTextCursorMode.End) { + val originaltext = inputText + val originalcursorpos = cursorPos + inputText = text when (cursormode) { - (SetTextCursorMode.End) -> { inputField.setCursorPosition(inputField.text.length) } + (SetTextCursorMode.End) -> { cursorPos = inputText.length } (SetTextCursorMode.Unchanged) -> {} - (SetTextCursorMode.Insert) -> { inputField.setCursorPosition(max(0, inputField.text.length-(originaltext.length-originalcursorpos))) } + (SetTextCursorMode.Insert) -> { cursorPos = inputText.length-(originaltext.length-originalcursorpos) } (SetTextCursorMode.SelectAll) -> { throw UnsupportedOperationException("NotImplemented.") } (SetTextCursorMode.SelectAfter) -> { throw UnsupportedOperationException("NotImplemented.") } } } + fun setScroll(x: Float, y: Float, animate: Boolean = true) { + printScroll.scrollTo(x, y, 1f, 1f) + if (!animate) { + printScroll.updateVisualScroll() + } + } + private fun echoHistory() { - for (hist in scriptingState.outputHistory) { + // Doesn't restore autocompletion. I guess that's by design. Autocompletion is a protocol/UI-level feature IMO, and not part of the emulated STDIN/STDOUT. Call `echo()` in `ScriptingState`'s `autocomplete` method if that's a problem. + for (hist in scriptingState.getOutputHistory()) { echo(hist) } } private fun autocomplete() { - val original = inputField.text - val cursorpos = inputField.getCursorPosition() - var results = scriptingState.autocomplete(input, cursorpos) + val original = inputText + val cursorpos = cursorPos + var results = scriptingState.autocomplete(inputText, cursorpos) if (results.isHelpText) { echo(results.helpText) return @@ -224,31 +241,45 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un } private fun navigateHistory(increment:Int) { - setText(scriptingState.navigateHistory(increment)) + setText(scriptingState.navigateHistory(increment), SetTextCursorMode.End) } private fun echo(text: String) { - var label = Label(text, skin) - var width = stage.width * 0.75f + val label = Label(text, skin) + val width = stage.width * 0.75f label.setWidth(width) label.setWrap(true) printHistory.add(label).left().bottom().width(width).padLeft(15f).row() - printScroll.scrollTo(0f,0f,1f,1f) + val cells = printHistory.getCells() + while (cells.size > scriptingState.maxOutputHistory && cells.size > 0) { + val cell = cells.first() + cell.getActor().remove() + cells.removeValue(cell, true) + //According to printHistory.getRows(), this isn't perfectly clean. The rows count still increases. + } + printHistory.invalidate() + setScroll(0f,0f) } private fun run() { - echo(scriptingState.exec(inputField.text)) + echo(scriptingState.exec(inputText)) setText("") } -// fun clone(): ConsoleScreen { -// } + fun clone(): ConsoleScreen { + return ConsoleScreen(scriptingState, closeAction).also { + it.inputText = inputText + it.cursorPos = cursorPos + it.setScroll(printScroll.getScrollX(), printScroll.getScrollY(), animate = false) + } + } override fun resize(width: Int, height: Int) { if (stage.viewport.screenWidth != width || stage.viewport.screenHeight != height) { // Right. Actually resizing seems painful. - game.consoleScreen = ConsoleScreen(scriptingState, closeAction) + game.consoleScreen = clone() if (isOpen) { - game.consoleScreen.openConsole() // If this leads to race conditions or some such due to occurring at the same time as other screens' resize methods, then probably close the ConsoleScreen() instead. + game.consoleScreen.openConsole() + // If this leads to race conditions or some such due to occurring at the same time as other screens' resize methods, then probably close the ConsoleScreen() instead. } } } From 13ff214c9939940fdc16aa13b6020176279bc6af Mon Sep 17 00:00:00 2001 From: will-ca Date: Thu, 11 Nov 2021 17:06:22 +0000 Subject: [PATCH 31/93] Cleanup. Docs. Key assignments. Script-side memory management for Kotlin/JVM instances. --- .../scripting/enginefiles/python/main.py | 103 ++++++++++++++++-- .../scripting/enginefiles/python/unciv/api.py | 2 +- .../python/unciv/autocompletion.py | 2 +- .../enginefiles/python/unciv/wrapping.py | 4 +- .../sharedfiles/ScriptAPIConstants.json | 2 +- core/src/com/unciv/UncivGame.kt | 4 - .../com/unciv/scripting/ScriptingBackend.kt | 32 +++--- .../com/unciv/scripting/ScriptingConstants.kt | 9 +- .../src/com/unciv/scripting/ScriptingScope.kt | 21 ++-- .../src/com/unciv/scripting/ScriptingState.kt | 8 +- .../scripting/protocol/ScriptingProtocol.kt | 54 ++++----- .../protocol/ScriptingReplManager.kt | 21 ++-- .../unciv/scripting/reflection/Reflection.kt | 16 ++- .../scripting/utils/InstanceFactories.kt | 10 ++ ...bjectTokenizer.kt => InstanceTokenizer.kt} | 18 +-- .../scripting/utils/StringifyException.kt | 4 + .../scripting/utils/SyntaxHighlighter.kt | 6 +- .../unciv/scripting/utils/TokenizingJson.kt | 26 +++-- 18 files changed, 228 insertions(+), 114 deletions(-) create mode 100644 core/src/com/unciv/scripting/utils/InstanceFactories.kt rename core/src/com/unciv/scripting/utils/{ObjectTokenizer.kt => InstanceTokenizer.kt} (74%) create mode 100644 core/src/com/unciv/scripting/utils/StringifyException.kt diff --git a/android/assets/scripting/enginefiles/python/main.py b/android/assets/scripting/enginefiles/python/main.py index 091cf57b3ce4a..65d029a57d58d 100644 --- a/android/assets/scripting/enginefiles/python/main.py +++ b/android/assets/scripting/enginefiles/python/main.py @@ -8,21 +8,44 @@ A **wrapper** is an object that stores a list of attribute names, keys, and function call parameters corresponding to a path in the Kotlin/JVM namespace. E.G.: `"civInfo.civilizations[0].population.setPopulation"` is a string representation of a wrapped path that begins with two attribute accesses, followed by one array index, and two more attribute names. -A wrapper object does not store any more values than that. When it is evaluated, it uses a simple IPC protocol to request an up-to-date real value from the game's Kotlin code. The `unciv.api.real()` function can be used to manually get a real Python value from a foreign object wrapper. +A wrapper object does not store any more values than that. When it is evaluated, it uses a simple IPC protocol to request an up-to-date real value from the game's Kotlin code. The `unciv.api.real()` function can be used to manually get a real Python value from a foreign instance wrapper. However, because the wrapper class implements many Magic Methods that automatically call its evaluation method, many programming idioms common in Python are possible with them even without manual evaluation. Comparisons, equality, arithmetic, concatenation, inplace operations and more are all supported. Accessing an attribute on a wrapper object returns a new wrapper object that has an additional name set to "Property" at the end of its path list. Performing an array or dictionary index returns a new wrapper with an additional "Key" element in its path list. +```python3 +print(civInfo) # Wrapper object. +print(civInfo.cities[0]) # Also a wrapper object, with two extra path elements. +print(civInfo.cities[0].isCapital) # Still a wrapper object, with another extra path element. +``` + Calling a wrapper object as a function or method also creates a new path list with an extra "Call" element at the end. But in this case, the new path list is immediately sent as a request to Kotlin/the JVM instead of being used in a new wrapper object, and the returned value is the naked result from the requested function call in the Kotlin/JVM namespace. -Likewise, assigning to an attribute or an index/key on a wrapper object sends an IPC request to assign to the Kotlin/JVM object at its path, instead of modifying the wrapper object. +```python3 +print(civInfo.cities[0].isCapital()) # Goes through four wrapper objects, but ultimately sends its path as a request on the function call, and returns a real value. +``` + +Likewise, assigning to an attribute or an index/key on a wrapper object sends an IPC request to assign to the Kotlin/JVM value at its path, instead of modifying the wrapper object. + +```python3 +civInfo.cities[0].name = "Metropolis" +# Uses IPC request to modify Kotlin/JVM object. + +civInfo.cities[0].cityConstructions.constructionQueue[0] = "Missile Cruiser" +# Uses IPC request to modify Kotlin/JVM container. +``` -When a Kotlin/JVM object implements a property for size or keys, the Python wrapper for it can also be iterated like a `tuple` or a `dict`, such as in `for` loops and list comprehensions. +When a Kotlin/JVM class implements a property for size or keys, Python wrappers for its instances can be iterated like a `tuple` or a `dict`, such as in `for` loops and iterable comprehensions. + +```python3 +print([real(city.name)+str(real(city.population.population)) for city in civInfo.cities]) +print({name: real(empire.cities and empire.cities[0]) for name, empire in gameInfo.ruleSet.nations.entries()}) +``` --- -A **token** is a string that has been generated by `ObjectTokenizer.kt` to represent a Kotlin object. +A **token** is a string that has been generated by `InstanceTokenizer.kt` to represent a Kotlin instance. The `unciv.api.isForeignToken()` function can be used to check whether a Python object is either a foreign token string or a wrapper that resolves to a foreign token string. @@ -30,9 +53,73 @@ However, if the value requested is an instance of a complicated Kotlin/JVM class, then the IPC protocol instead creates a unique string to identify it. -The original object is stored in the JVM in a mapping as a weak reference. The string doesn't have (m)any special properties as a Python object. But if it is sent back to Kotlin/the JVM at any point, then it will be parsed and substitued with the original object (provided the original object still exists). +```python3 +isForeignToken("Some random string.") +# False. + +isForeignToken(uncivGame.version) +# False. Version is stored as a simple string. + +isForeignToken(gameInfo.turns) +# False. Turn count is stored as a simple integer. + +isForeignToken(real(uncivGame)) +# True. Unserializable instances are turned into token strings on evaluation. + +isForeignToken(civInfo.getWorkerAutomation()) +# True. This method returns a complicated type that gets turned into a token string. + +isForeignToken(uncivGame) +# True. This is actually a wrapper, but `isForeignToken` returns True based on evaluated results. +``` + +The original instance is stored in the JVM in a mapping as a weak reference. The string doesn't have (m)any special properties as a Python object. But if it is sent back to Kotlin/the JVM at any point, then it will be parsed and substitued with the original instance (provided the original instance still exists). + +This is meant to allow Kotlin/JVM instances to be, E.G., used as function arguments and mapping keys from scripts. + +```python3 +civunits = civInfo.getCivUnits() +# List of token strings representing `MapUnit()` instances. + +unit = civunits[0] +# Single token string. + +civInfo.removeUnit(unit) +# Token string gets get transformed back into original `MapUnit()` when used as function argument. +``` + +--- + +Sometimes, you may want to access a path or call a method on an object that you have only as a token string— For example, an object returned from a foreign function or method call. + +Usually this would be impossible because you need a path in order to access foreign attributes. Without a valid path to an object, the wrapper code and the IPC protocol it uses have no way to identify where an object is or what to do with it. In fact, if the Kotlin/JVM code hasn't kept its own references to the object, then the object may not even exist anymore. + +To get around this, you can use the foreign token to assign the object it represents to a concrete path. + +The `apiHelpers.registeredInstances` helper object can be used for this: + +```python3 +token = civInfo.cities[0].getCenterTile() + +print(type(token)) +# . Cannot be used for attribute access. + +apiHelpers.registeredInstances["centertile"] = token + +print(type(apiHelpers.registeredInstances["centertile"])) +# . Is now a full wrapper with path, and can be used for full attribute, key, and method access. + +apiHelpers.registeredInstances["centertile"].baseTerrain + +apiHelpers.registeredInstances.remove("centertile") +# Delete the reference so it doesn't become a memory leak. +``` + +In order to use this technique properly, the assignment of an object to a concrete path should be done within the same REPL loop as the generation of the token used to assign it. This is because the Kotlin code responsible for generating tokens and managing the REPL loop keeps references to all returned objects within each REPL loop. Afterwards, these references are immediately cleared, so any objects that do not have references elsewhere in Kotlin/the JVM are liable to be garbage-collected. + +**It is very important that you delete concrete paths you have set after you are done with them.** Any objects held at paths you do not delete will continue to occupy system memory for the remaining run time of the application's lifespan. We can't rely on Python's garbage collection in this case because it doesn't control the Kotlin objects, nor can we rely on the JVM's garbage collector because it doesn't know whether Python code still needs the objects in question, so you will have to manage the memory yourself by keeping a reference as long as you need an object and deleting it to free up memory afterwards. -This is meant to allow Kotlin/JVM objects to be, E.G., used as function arguments and mapping keys from scripts. +For any complicated script in Python, it is suggested that you write a context manager class to automatically take care of saving and freeing each object for you where appropriate. --- @@ -61,8 +148,8 @@ foreignActionSender = unciv.ipc.ForeignActionSender() - apiScope = {}#{n: unciv.wrapping.ForeignObject(n, foreignrequester=foreignActionSender.GetForeignActionResponse) for n in ('civInfo', 'gameInfo', 'uncivGame', 'worldScreen', 'isInGame')} - # I wanna change the protocol to automatically generate these with a `dir()` on an empty `ForeignObject()` path in `.motd()`. + apiScope = {} + # TODO Use an empty/dummy module's `.__dict__`, so scripts have something in the module map that they can import. apiScope.update(unciv.api.Expose) diff --git a/android/assets/scripting/enginefiles/python/unciv/api.py b/android/assets/scripting/enginefiles/python/unciv/api.py index b0b12336ff39d..82f7e02f795fd 100644 --- a/android/assets/scripting/enginefiles/python/unciv/api.py +++ b/android/assets/scripting/enginefiles/python/unciv/api.py @@ -70,7 +70,7 @@ def real(obj): def isForeignToken(obj): """Return whether an object represents a token for a non-serializable foreign object.""" resolved = real(obj) - return isinstance(resolved, str) and resolved.startswith(_apiconstants['kotlinObjectTokenPrefix']) + return isinstance(resolved, str) and resolved.startswith(_apiconstants['kotlinInstanceTokenPrefix']) from . import wrapping diff --git a/android/assets/scripting/enginefiles/python/unciv/autocompletion.py b/android/assets/scripting/enginefiles/python/unciv/autocompletion.py index c4e86bc530525..fdfc5c3207533 100644 --- a/android/assets/scripting/enginefiles/python/unciv/autocompletion.py +++ b/android/assets/scripting/enginefiles/python/unciv/autocompletion.py @@ -31,7 +31,7 @@ def GetCommandComponents(self, command): _bdepth = 1 prefixsplit -= 1 while _bdepth and prefixsplit: - # Skip over whole blocks of open brackets. + # Skip over whole blocks of matched brackets. char = command[prefixsplit] if char == '[': _bdepth -= 1 diff --git a/android/assets/scripting/enginefiles/python/unciv/wrapping.py b/android/assets/scripting/enginefiles/python/unciv/wrapping.py index 58f27b113b77c..c36989cae02d7 100644 --- a/android/assets/scripting/enginefiles/python/unciv/wrapping.py +++ b/android/assets/scripting/enginefiles/python/unciv/wrapping.py @@ -87,13 +87,13 @@ def stringPathList(pathlist): _magicmeths = ( '__lt__', '__le__', - '__eq__', # Kinda undefined behaviour for comparision with Kotlin object tokens. Well, tokens are just strings that will always equal themselves, but multiple tokens can refer to the same Kotlin object. `ForeignObject()`s resolve to new tokens, that are currently uniquely generated in ObjectTokenizer.kt, on every `._getvalue_()`, so I think even the same `ForeignObject()` will never equal itself. + '__eq__', # Kinda undefined behaviour for comparision with Kotlin object tokens. Well, tokens are just strings that will always equal themselves, but multiple tokens can refer to the same Kotlin object. `ForeignObject()`s resolve to new tokens, that are currently uniquely generated in InstanceTokenizer.kt, on every `._getvalue_()`, so I think even the same `ForeignObject()` will never equal itself. '__ne__', '__ge__', '__gt__', '__not__', ('__bool__', 'truth'), -# @is # This could get messy. It's probably best to just not support identity comparison. What do you compare? JVM Kotlin value? Resolved Python value? Python data path? Token strings from ObjectTokenizer.kt— Which are currently randomly re-generated for multiple accesses to the same Kotlin object, and thus always unique, and which would require another protocol-level guarantee to not do that, in addition to being (kinda by design) procedurally indistinguishable from "real" resovled Python values? +# @is # This could get messy. It's probably best to just not support identity comparison. What do you compare? JVM Kotlin value? Resolved Python value? Python data path? Token strings from InstanceTokenizer.kt— Which are currently randomly re-generated for multiple accesses to the same Kotlin object, and thus always unique, and which would require another protocol-level guarantee to not do that, in addition to being (kinda by design) procedurally indistinguishable from "real" resovled Python values? # @is_not # Also, these aren't even magic methods. '__abs__', '__add__', diff --git a/android/assets/scripting/sharedfiles/ScriptAPIConstants.json b/android/assets/scripting/sharedfiles/ScriptAPIConstants.json index 351006a106428..cee3221fb4b3f 100644 --- a/android/assets/scripting/sharedfiles/ScriptAPIConstants.json +++ b/android/assets/scripting/sharedfiles/ScriptAPIConstants.json @@ -1,3 +1,3 @@ { - "kotlinObjectTokenPrefix": "_unciv-kt-obj@" + "kotlinInstanceTokenPrefix": "_unciv-kt-obj@" } diff --git a/core/src/com/unciv/UncivGame.kt b/core/src/com/unciv/UncivGame.kt index 328d403d3c320..bd8dea66985ed 100644 --- a/core/src/com/unciv/UncivGame.kt +++ b/core/src/com/unciv/UncivGame.kt @@ -25,13 +25,9 @@ import com.unciv.ui.worldscreen.WorldScreen import java.util.* import kotlin.concurrent.thread -import com.unciv.scripting.ScriptingConstants - class UncivGame(parameters: UncivGameParameters) : Game() { // we need this secondary constructor because Java code for iOS can't handle Kotlin lambda parameters constructor(version: String) : this(UncivGameParameters(version, null)) - - val ss by lazy { ScriptingConstants } val version = parameters.version private val crashReportSender = parameters.crashReportSender diff --git a/core/src/com/unciv/scripting/ScriptingBackend.kt b/core/src/com/unciv/scripting/ScriptingBackend.kt index 889d1563ff849..3feb937a66bcd 100644 --- a/core/src/com/unciv/scripting/ScriptingBackend.kt +++ b/core/src/com/unciv/scripting/ScriptingBackend.kt @@ -63,6 +63,8 @@ open class ScriptingBackendBase(val scriptingScope: ScriptingScope): ScriptingBa override fun new(scriptingScope: ScriptingScope) = ScriptingBackendBase(scriptingScope) override val displayName:String = "Dummy" } + // For the UI, a way is needed to list all available scripting backend types with 1. A readable display name and 2. A way to create new instances. + // So every ScriptngBackend has a Metadata:ScriptingBackend_metadata companion object, which is stored in the ScriptingBackendType enums. open val metadata get(): ScriptingBackend_metadata = this::class.companionObjectInstance as ScriptingBackend_metadata @@ -133,12 +135,12 @@ class HardcodedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacken } } "countcities" -> { - if (!(scriptingScope.isInGame)) { appendOut("Must be in-game for this command!"); return out } + if (!(scriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } appendOut(scriptingScope.civInfo!!.cities.size.toString()) } "locatebuildings" -> { var buildingcities:List = listOf() - if (!(scriptingScope.isInGame)) { appendOut("Must be in-game for this command!"); return out } + if (!(scriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } if (args.size > 1) { var searchfor = args.slice(1..args.size-1).joinToString(" ").trim(' ') buildingcities = scriptingScope.civInfo!!.cities @@ -154,7 +156,7 @@ class HardcodedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacken } "missingbuildings" -> { var buildingcities:List = listOf() - if (!(scriptingScope.isInGame)) { appendOut("Must be in-game for this command!"); return out } + if (!(scriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } if (args.size > 1) { var searchfor = args.slice(1..args.size-1).joinToString(" ").trim(' ') buildingcities = scriptingScope.civInfo!!.cities @@ -164,7 +166,7 @@ class HardcodedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacken appendOut(buildingcities.joinToString(", ")) } "listcities" -> { - if (!(scriptingScope.isInGame)) { appendOut("Must be in-game for this command!"); return out } + if (!(scriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } appendOut(scriptingScope.civInfo!!.cities .map { city -> city.name } .joinToString(", ") @@ -179,7 +181,7 @@ class HardcodedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacken appendOut("Cheats disabled.") } "godmode" -> { - if (!(scriptingScope.isInGame)) { appendOut("Must be in-game for this command!"); return out } + if (!(scriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } if (cheats) { var godmode = if (args.size > 1) args[1].toBoolean() else !(scriptingScope.gameInfo!!.gameParameters.godMode) scriptingScope.gameInfo!!.gameParameters.godMode = godmode @@ -189,7 +191,7 @@ class HardcodedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacken } } "godview" -> { - if (!(scriptingScope.isInGame)) { appendOut("Must be in-game for this command!"); return out } + if (!(scriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } if (cheats) { var godview = if (args.size > 1) args[1].toBoolean() else !(scriptingScope.uncivGame!!.viewEntireMapForDebug) scriptingScope.uncivGame!!.viewEntireMapForDebug = godview @@ -199,7 +201,7 @@ class HardcodedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacken } } "inspectpath" -> { - if (!(scriptingScope.isInGame)) { appendOut("Must be in-game for this command!"); return out } + if (!(scriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } if (cheats) { val detailed = args.size > 1 && args[1] == "detailed" val startindex = if (detailed) 2 else 1 @@ -239,7 +241,7 @@ class HardcodedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacken } } "simulatetoturn" -> { - if (!(scriptingScope.isInGame)) { appendOut("Must be in-game for this command!"); return out } + if (!(scriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } if (cheats) { var numturn = 0 if (args.size > 1) { @@ -256,7 +258,7 @@ class HardcodedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacken } } "supercharge" -> { - if (!(scriptingScope.isInGame)) { appendOut("Must be in-game for this command!"); return out } + if (!(scriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } if (cheats) { var supercharge = if (args.size > 1) args[1].toBoolean() else !(scriptingScope.uncivGame!!.superchargedForDebug) scriptingScope.uncivGame!!.superchargedForDebug = supercharge @@ -371,7 +373,7 @@ class ReflectiveScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacke abstract class EnvironmentedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackendBase(scriptingScope) { companion object Metadata: EnvironmentedScriptBackend_metadata() { - // Need this here, or else won't compile. + // Need full metadata companion here, or else won't compile. // Ideally would be able to just declare that subclasses must define a companion of the correct type, but ah well. override val displayName = "" override fun new(scriptingScope: ScriptingScope) = throw UnsupportedOperationException("Base scripting backend class not meant to be instantiated.") @@ -379,10 +381,10 @@ abstract class EnvironmentedScriptingBackend(scriptingScope: ScriptingScope): Sc } override val metadata - // Since the companion object type is different, we have to define a new getter for the subclass instance companion getter. + // Since the companion object type is different, we have to define a new getter for the subclass instance companion getter to get its new members. get() = this::class.companionObjectInstance as EnvironmentedScriptBackend_metadata - val folderHandle: FileHandle by lazy { SourceManager.setupInterpreterEnvironment(this.metadata.engine) } + val folderHandle: FileHandle by lazy { SourceManager.setupInterpreterEnvironment(metadata.engine) } // This requires the overridden values for `engine`, so setting it in the constructor causes a null error... May be fixed since moving `engine` to the companions. // Also, BlackboxScriptingBackend inherits from this, but not all subclasses of BlackboxScriptingBackend might need it. So as long as it's not accessed, it won't be intialized. @@ -392,7 +394,7 @@ abstract class EnvironmentedScriptingBackend(scriptingScope: ScriptingScope): Sc abstract class BlackboxScriptingBackend(scriptingScope: ScriptingScope): EnvironmentedScriptingBackend(scriptingScope) { companion object Metadata: EnvironmentedScriptBackend_metadata() { - // Need this here, or else won't compile. + // Need full metadata companion here, or else won't compile. // Ideally would be able to just declare that subclasses must define a companion of the correct type, but ah well. override val displayName = "" override fun new(scriptingScope: ScriptingScope) = throw UnsupportedOperationException("Base scripting backend class not meant to be instantiated.") @@ -409,7 +411,7 @@ abstract class BlackboxScriptingBackend(scriptingScope: ScriptingScope): Environ try { return replManager.motd() } catch (e: Exception) { - return "No MOTD for ${this.metadata.engine} backend: ${e}\n" + return "No MOTD for ${metadata.engine} backend: ${e}\n" } } @@ -446,7 +448,7 @@ abstract class SubprocessScriptingBackend(scriptingScope: ScriptingScope): Black override val blackbox by lazy { SubprocessBlackbox(processCmd) } override fun motd(): String { - return "\n\nWelcome to the Unciv '${displayName}' API. This backend relies on running the system `${processCmd.firstOrNull()}` command as a subprocess.\n\nIf you do not have an interactive REPL below, then please make sure the below command is valid on your system:\n\n${processCmd.joinToString(" ")}\n\n${super.motd()}\n" + return "\n\nWelcome to the Unciv '${metadata.displayName}' API. This backend relies on running the system `${processCmd.firstOrNull()}` command as a subprocess.\n\nIf you do not have an interactive REPL below, then please make sure the below command is valid on your system:\n\n${processCmd.joinToString(" ")}\n\n${super.motd()}\n" } } diff --git a/core/src/com/unciv/scripting/ScriptingConstants.kt b/core/src/com/unciv/scripting/ScriptingConstants.kt index c0128d1460e45..c18578e1ff2cd 100644 --- a/core/src/com/unciv/scripting/ScriptingConstants.kt +++ b/core/src/com/unciv/scripting/ScriptingConstants.kt @@ -12,7 +12,7 @@ val ScriptingConstants: _ScriptingConstantsClasses.ScriptingConstantsClass = Jso object _ScriptingConstantsClasses{ // Need to define classes to deserialize the JSONs into, but really this whole file should be one singleton. - // It would be better slightly with KotlinX, I think, since then I could at least use data classes and don't have to initialize all the properties. + // It would be slightly better with KotlinX, I think, since then I could at least use data classes and don't have to initialize all the properties. class ScriptingConstantsClass() { var engines = HashMap() @@ -24,10 +24,9 @@ object _ScriptingConstantsClasses{ get() = field.toList() as ArrayList set(value) = throw UnsupportedOperationException("This property is supposed to be constant.") - val apiConstants = JsonParser().getFromJson(ScriptingAPIConstants::class.java, assetFolders.sharedfilesAssets.child("ScriptAPIConstants.json")) + val assetFolders = ScriptingAssetFolders - val assetFolders - get() = ScriptingAssetFolders + val apiConstants = JsonParser().getFromJson(ScriptingAPIConstants::class.java, assetFolders.sharedfilesAssets.child("ScriptAPIConstants.json")) } class ScriptingEngineConfig(){ @@ -41,7 +40,7 @@ object _ScriptingConstantsClasses{ } class ScriptingAPIConstants() { - var kotlinObjectTokenPrefix = "" + var kotlinInstanceTokenPrefix = "" } object ScriptingAssetFolders { diff --git a/core/src/com/unciv/scripting/ScriptingScope.kt b/core/src/com/unciv/scripting/ScriptingScope.kt index ce1d0a46e1bb9..1e0d6d777af95 100644 --- a/core/src/com/unciv/scripting/ScriptingScope.kt +++ b/core/src/com/unciv/scripting/ScriptingScope.kt @@ -3,25 +3,30 @@ package com.unciv.scripting import com.unciv.logic.GameInfo import com.unciv.logic.civilization.CivilizationInfo import com.unciv.UncivGame +import com.unciv.scripting.utils.InstanceFactories import com.unciv.ui.worldscreen.WorldScreen + class ScriptingScope( var civInfo: CivilizationInfo?, var gameInfo: GameInfo?, var uncivGame: UncivGame?, var worldScreen: WorldScreen? ) { - val isInGame: Boolean - get() = (civInfo != null && gameInfo != null && uncivGame != null) - //val kObjectHelpers //TODO - // val registeredObjects: Set - // val __registeredObjectsForRepl - // fun Vector - // fun MapUnit + val apiHelpers = ApiHelpers(this) + + class ApiHelpers(val scriptingScope: ScriptingScope) { + val isInGame: Boolean + get() = (scriptingScope.civInfo != null && scriptingScope.gameInfo != null && scriptingScope.uncivGame != null) + val Factories = InstanceFactories + val registeredInstances = mutableMapOf() + fun unchanged(obj: Any?) = obj //Debug/dev identity function for both Kotlin and scripts. Check if value survives serialization, force something to be added to ScriptingProtocol.instanceSaver, etc. + fun printLn(msg: Any?) = println(msg) + fun toString(obj: Any?) = obj.toString() + } // Holds references to all internal game data that the console has access to. - // Mostly `.civInfo`/.`gameInfo`, but could be cool to E.G. allow loading and making saves through CLI/API too. // Also where to put any `PlayerAPI`, `CheatAPI`, `ModAPI`, etc. // For `LuaScriptingBackend`, `UpyScriptingBackend`, `QjsScriptingBackend`, etc, the hierarchy of data under this class definition should probably directly mirror the wrappers in the namespace exposed to the scripting language. // `WorldScreen` gives access to `UnitTable.selectedUnit`, `MapHolder.selectedTile`, etc. Useful for contextual operations. diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index 8b35aa0da479c..87770d3ff59cc 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -36,10 +36,10 @@ import kotlin.math.min MainMenuScreen(): consoleScreen scriptingState // Same as for worldScreen. - ObjectTokenizer() // Holds WeakRefs used by ScriptingProtocol. Unserializable objects get strings as placeholders, and then turned back into into objects if seen again. + InstanceTokenizer() // Holds WeakRefs used by ScriptingProtocol. Unserializable objects get strings as placeholders, and then turned back into into objects if seen again. Reflection() // Used by some hard-coded scripting backends, and essential to dynamic bindings in ScriptingProtocol(). SourceManager() // Source of the folderHandler and setupInterpreterEnvironment() above. - TokenizingJson() // Serializer and functions that use ObjectTokenizer. + TokenizingJson() // Serializer and functions that use InstanceTokenizer. ``` */ @@ -64,8 +64,8 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr var activeBackend: Int = 0 - val maxOutputHistory: Int = 127 - val maxCommandHistory: Int = 255 + val maxOutputHistory: Int = 511 + val maxCommandHistory: Int = 511 var activeCommandHistory: Int = 0 // Actually inverted, because history items are added to end of list and not start. 0 means nothing, 1 means most recent command at end of list. diff --git a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt index f3275dc698081..74e4b6a209ee8 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt @@ -1,8 +1,9 @@ package com.unciv.scripting.protocol import com.unciv.scripting.AutocompleteResults -import com.unciv.scripting.ScriptingBackend +//import com.unciv.scripting.ScriptingBackend import com.unciv.scripting.reflection.Reflection +import com.unciv.scripting.utils.stringifyException import com.unciv.scripting.utils.TokenizingJson import kotlin.random.Random import kotlin.reflect.KCallable @@ -164,7 +165,7 @@ import java.util.UUID ``` //'Exception' //Indicates that this packet is associated with an error. - //Not implemented. Currently Kotlin-side exceptions while processing a request are communicated to the script interpreter with an error message at an expected place in the data field of the response, and exceptions in the script interpreter while executing a command are stringified and returned to the Kotlin side the same as any REPL output. + //Not implemented. Currently Kotlin-side exceptions while processing a request are communicated to the script interpreter with an error message at an expected place in the data field of the response, and exceptions in the script interpreter while executing a command are stringified and returned to the Kotlin side the same as any REPL output. Miight be useful for a modding API, though. ``` --- @@ -194,7 +195,11 @@ data class ScriptingPacket( } -class ScriptingProtocol(val scope: Any) { +class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = null) { + + // Adds all returned and potentially tokenized objects in responses to `instanceSaver` to save them from garbage collection. + // `instanceSaver` should not be usd as long-term storage. + // Whatever's using this protocol object should clear `instanceSaver` as soon as whatever's running at the other end of the protocol has had a chance to create references elsewhere (E.G. ScriptingScope.apiHelpers) for any objects that it needs. enum class KnownFlag(val value: String) { PassMic("PassMic") @@ -208,7 +213,6 @@ class ScriptingProtocol(val scope: Any) { val responseTypes = mapOf( // Deliberately repeating myself because I don't want to imply a hard specification for the name of response packets. - null to null, "motd" to "motd_response", "autocomplete" to "autocomplete_response", "exec" to "exec_response", @@ -278,9 +282,17 @@ class ScriptingProtocol(val scope: Any) { else RuntimeException((packet.data as JsonPrimitive).content) } + + fun trySaveObject(obj: Any?): Any? { + if (instanceSaver != null) { + instanceSaver.add(obj) + } + return obj + } fun makeActionResponse(packet: ScriptingPacket): ScriptingPacket { - var action: String? = null +// var action: String? = null + val action = ScriptingProtocol.responseTypes[packet.action]!! var data: JsonElement? = null var flags = mutableListOf() var value: JsonElement = JsonNull @@ -288,18 +300,18 @@ class ScriptingProtocol(val scope: Any) { when (packet.action) { // There's a lot of repetition here, because I don't want to enforce any specification on what form the request and response data fields for actions must take. // I prefer to try to keep the code for each response type independent enough to be readable on its own. + // This is kinda the reference (and only) implementation of the protocol spec. So the serialization and such can be and is handled with functions, but the actual structure and logic of each response should be hardcoded manually IMO. "read" -> { - action = "read_response" try { - value = TokenizingJson.getJsonElement( + value = TokenizingJson.getJsonElement(trySaveObject( Reflection.resolveInstancePath( scope, TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) ) - ) + )) } catch (e: Exception) { value = JsonNull - exception = JsonPrimitive(e.toString()) + exception = JsonPrimitive(stringifyException(e)) } data = JsonObject(mapOf( "value" to value, @@ -307,7 +319,6 @@ class ScriptingProtocol(val scope: Any) { )) } "assign" -> { - action = "assign_response" try { Reflection.setInstancePath( scope, @@ -315,12 +326,11 @@ class ScriptingProtocol(val scope: Any) { TokenizingJson.getJsonReal((packet.data as JsonObject)["value"]!!) ) } catch (e: Exception) { - exception = JsonPrimitive(e.toString()) + exception = JsonPrimitive(stringifyException(e)) } data = exception } "dir" -> { - action = "dir_response" try { val leaf = Reflection.resolveInstancePath( scope, @@ -329,7 +339,7 @@ class ScriptingProtocol(val scope: Any) { value = TokenizingJson.getJsonElement(leaf!!::class.members.map{it.name}) } catch (e: Exception) { value = JsonNull - exception = JsonPrimitive(e.toString()) + exception = JsonPrimitive(stringifyException(e)) } data = JsonObject(mapOf( "value" to value, @@ -337,7 +347,6 @@ class ScriptingProtocol(val scope: Any) { )) } "keys" -> { - action = "keys_response" try { val leaf = Reflection.resolveInstancePath( scope, @@ -346,7 +355,7 @@ class ScriptingProtocol(val scope: Any) { value = TokenizingJson.getJsonElement((leaf as Map).keys) } catch (e: Exception) { value = JsonNull - exception = JsonPrimitive(e.toString()) + exception = JsonPrimitive(stringifyException(e)) } data = JsonObject(mapOf( "value" to value, @@ -354,7 +363,6 @@ class ScriptingProtocol(val scope: Any) { )) } "length" -> { - action = "length_response" try { val leaf = Reflection.resolveInstancePath( scope, @@ -372,7 +380,7 @@ class ScriptingProtocol(val scope: Any) { } } catch (e: Exception) { value = JsonNull - exception = JsonPrimitive(e.toString()) + exception = JsonPrimitive(stringifyException(e)) } data = JsonObject(mapOf( "value" to value, @@ -380,7 +388,6 @@ class ScriptingProtocol(val scope: Any) { )) } "contains" -> { - action = "contains_response" try { val leaf = Reflection.resolveInstancePath( scope, @@ -394,7 +401,7 @@ class ScriptingProtocol(val scope: Any) { } } catch (e: Exception) { value = JsonNull - exception = JsonPrimitive(e.toString()) + exception = JsonPrimitive(stringifyException(e)) } data = JsonObject(mapOf( "value" to value, @@ -402,7 +409,6 @@ class ScriptingProtocol(val scope: Any) { )) } "callable" -> { - action = "callable_response" try { val leaf = Reflection.resolveInstancePath( scope, @@ -411,7 +417,7 @@ class ScriptingProtocol(val scope: Any) { value = TokenizingJson.getJsonElement(leaf is KCallable<*>) } catch (e: Exception) { value = JsonNull - exception = JsonPrimitive(e.toString()) + exception = JsonPrimitive(stringifyException(e)) } data = JsonObject(mapOf( "value" to value, @@ -419,7 +425,6 @@ class ScriptingProtocol(val scope: Any) { )) } "args" -> { - action = "args_response" try { val leaf = Reflection.resolveInstancePath( scope, @@ -428,7 +433,7 @@ class ScriptingProtocol(val scope: Any) { value = TokenizingJson.getJsonElement((leaf as KCallable<*>).parameters.map { listOf(it.name.toString(), it.type.toString()) }) } catch (e: Exception) { value = JsonNull - exception = JsonPrimitive(e.toString()) + exception = JsonPrimitive(stringifyException(e)) } data = JsonObject(mapOf( "value" to value, @@ -436,7 +441,6 @@ class ScriptingProtocol(val scope: Any) { )) } "docstring" -> { - action = "docstring_response" try { val leaf = Reflection.resolveInstancePath( scope, @@ -445,7 +449,7 @@ class ScriptingProtocol(val scope: Any) { value = TokenizingJson.getJsonElement(leaf.toString()) } catch (e: Exception) { value = JsonNull - exception = JsonPrimitive(e.toString()) + exception = JsonPrimitive(stringifyException(e)) } data = JsonObject(mapOf( "value" to value, diff --git a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt index 06fee8deaf24d..d206e52b8e0c3 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt @@ -42,15 +42,14 @@ import com.unciv.scripting.utils.Blackbox class ScriptingReplManager(val scriptingScope: ScriptingScope, val blackbox: Blackbox): ScriptingBackend { - val scriptingProtocol = ScriptingProtocol(scriptingScope) + val instanceSaver = mutableListOf() + // ScriptingProtocol puts references to pre-tokenized returned objects in here. + // Should be cleared here at the end of each REPL execution. + // This makes sure a single script execution doesn't get its tokenized Kotlin/JVM objects garbage collected, and has a chance to save them elsewhere (E.G. ScriptingScope.apiHelpers) if it needs them later. + // Should preserve each instance, not just each value, so should be List and not Set. + // To test in Python console backend: x = apiHelpers.Factories.Vector2(1,2); civInfo.endTurn(); print(apiHelpers.toString(x)) - fun whileEval() { - return - } - - fun runCode(code: String) { - blackbox.write(code) - } + val scriptingProtocol = ScriptingProtocol(scriptingScope, instanceSaver = instanceSaver) fun getRequestResponse(packetToSend: ScriptingPacket, enforceValidity: Boolean = true, execLoop: () -> Unit = fun(){}): ScriptingPacket { blackbox.write(packetToSend.toJson() + "\n") @@ -59,11 +58,12 @@ class ScriptingReplManager(val scriptingScope: ScriptingScope, val blackbox: Bla if (enforceValidity) { ScriptingProtocol.enforceIsResponse(packetToSend, response) } + instanceSaver.clear() // Clear saved references to objects in response, now that the script has had a chance to save them elsewhere. return response } fun foreignExecLoop() { - // Lists to requests for values from the black box, and replies to them, during script execution. + // Listens to requests for values from the black box, and replies to them, during script execution. // Terminates loop after receiving a request with a the 'PassMic' flag. while (true) { val request = ScriptingPacket.fromJson(blackbox.read(block=true)) @@ -105,9 +105,6 @@ class ScriptingReplManager(val scriptingScope: ScriptingScope, val blackbox: Bla execLoop = { foreignExecLoop() } ) ) - runCode(command) - whileEval() - return blackbox.readAll(block=true).joinToString("\n") } } diff --git a/core/src/com/unciv/scripting/reflection/Reflection.kt b/core/src/com/unciv/scripting/reflection/Reflection.kt index 1b104409183cf..96f32d2aa6357 100644 --- a/core/src/com/unciv/scripting/reflection/Reflection.kt +++ b/core/src/com/unciv/scripting/reflection/Reflection.kt @@ -39,6 +39,14 @@ object Reflection { .first { it.name == propertyName } as KMutableProperty1 property.set(instance, value) } + + fun setInstanceItem(instance: Any, keyOrIndex: Any, value: Any?): Unit { + if (keyOrIndex is Int) { + (instance as MutableList)[keyOrIndex] = value + } else{ + (instance as MutableMap)[keyOrIndex] = value + } + } enum class PathElementType() { @@ -52,7 +60,7 @@ object Reflection { val type: PathElementType, val name: String, val doEval: Boolean = false, - //For key and index accesses, and function calls, evaluate `name` instead of using `params`. + //For key and index accesses, and function calls, evaluate `name` instead of using `params` for arguments/key. //Default should be false, so deserialized JSON path lists are configured correctly in ScriptingProtocol.kt. val params: List<@Serializable(with=TokenizingJson.TokenizingSerializer::class) Any?> = listOf() // val params: List<@Contextual Any?> = listOf() @@ -244,13 +252,13 @@ object Reflection { setInstanceProperty(leafobj!!, leafelement.name, value) } PathElementType.Key -> { - throw UnsupportedOperationException("Keys not implemented.") - leafobj = readInstanceItem( + setInstanceItem( leafobj!!, if (leafelement.doEval) evalKotlinString(instance, leafelement.name)!! else - leafelement.name + leafelement.params[0]!!, + value ) } PathElementType.Call -> { diff --git a/core/src/com/unciv/scripting/utils/InstanceFactories.kt b/core/src/com/unciv/scripting/utils/InstanceFactories.kt new file mode 100644 index 0000000000000..70b6ad626f0e3 --- /dev/null +++ b/core/src/com/unciv/scripting/utils/InstanceFactories.kt @@ -0,0 +1,10 @@ +package com.unciv.scripting.utils + +import com.badlogic.gdx.math.Vector2 + + +object InstanceFactories { + // For use in ScriptingScope. Allows bound scripts to make new instances of Kotlin/JVM classes. + fun Vector2(x: Float, y: Float) = com.badlogic.gdx.math.Vector2(x, y) + fun MapUnit() = "NotImplemented" +} diff --git a/core/src/com/unciv/scripting/utils/ObjectTokenizer.kt b/core/src/com/unciv/scripting/utils/InstanceTokenizer.kt similarity index 74% rename from core/src/com/unciv/scripting/utils/ObjectTokenizer.kt rename to core/src/com/unciv/scripting/utils/InstanceTokenizer.kt index 40b38a8571642..1d7b5d2e0224c 100644 --- a/core/src/com/unciv/scripting/utils/ObjectTokenizer.kt +++ b/core/src/com/unciv/scripting/utils/InstanceTokenizer.kt @@ -6,15 +6,15 @@ import java.lang.ref.WeakReference import java.util.UUID -object ObjectTokenizer { +object InstanceTokenizer { - private val objs = mutableMapOf>() + private val instances = mutableMapOf>() private val tokenPrefix - get() = ScriptingConstants.apiConstants.kotlinObjectTokenPrefix + get() = ScriptingConstants.apiConstants.kotlinInstanceTokenPrefix private val tokenMaxLength = 100 - private fun tokenFromObject(value: Any?): String { + private fun tokenFromInstance(value: Any?): String { //Try to keep this human-informing, but don't parse it to extracting information. var stringified = value.toString() if (stringified.length > tokenMaxLength) { @@ -30,27 +30,27 @@ object ObjectTokenizer { fun clean(): Unit { val badtokens = mutableListOf() - for ((t, o) in objs) { + for ((t, o) in instances) { if (o.get() == null) { badtokens.add(t) } } for (t in badtokens) { - objs.remove(t) + instances.remove(t) } } fun getToken(obj: Any?): String { clean() - val token = tokenFromObject(obj) - objs[token] = WeakReference(obj) + val token = tokenFromInstance(obj) + instances[token] = WeakReference(obj) return token } fun getReal(token: Any?): Any? { clean() if (isToken(token)) { - return objs[token]!!.get() + return instances[token]!!.get() } else { return token } diff --git a/core/src/com/unciv/scripting/utils/StringifyException.kt b/core/src/com/unciv/scripting/utils/StringifyException.kt new file mode 100644 index 0000000000000..86af0f44d41ac --- /dev/null +++ b/core/src/com/unciv/scripting/utils/StringifyException.kt @@ -0,0 +1,4 @@ +package com.unciv.scripting.utils + + +fun stringifyException(exception: Exception): String = listOf(*exception.getStackTrace(), exception.toString()).joinToString("\n") diff --git a/core/src/com/unciv/scripting/utils/SyntaxHighlighter.kt b/core/src/com/unciv/scripting/utils/SyntaxHighlighter.kt index 9f6929962ca7a..a11bca97dfa1e 100644 --- a/core/src/com/unciv/scripting/utils/SyntaxHighlighter.kt +++ b/core/src/com/unciv/scripting/utils/SyntaxHighlighter.kt @@ -2,15 +2,15 @@ package com.unciv.scripting.utils interface SyntaxHighlighter { + // TODO: Implement/use these. fun cmlFromText(text: String): String { // https://github.com/libgdx/libgdx/wiki/Color-Markup-Language return text } } -open class FunctionalSyntaxHighlighter() { - open val transformList: List<(String) -> String> = listOf() - fun cmlFromText(text: String): String { +class FunctionalSyntaxHighlighter(val transformList: List<(String) -> String>): SyntaxHighlighter { + override fun cmlFromText(text: String): String { return transformList.fold(text){ t: String, f: (String) -> String -> f(t) } } } diff --git a/core/src/com/unciv/scripting/utils/TokenizingJson.kt b/core/src/com/unciv/scripting/utils/TokenizingJson.kt index 259209a7f8f91..a41b33f6ddd70 100644 --- a/core/src/com/unciv/scripting/utils/TokenizingJson.kt +++ b/core/src/com/unciv/scripting/utils/TokenizingJson.kt @@ -18,12 +18,7 @@ import kotlinx.serialization.modules.SerializersModule object TokenizingJson { - // Json serialization that accepts `Any?`, and converts non-primitive values to string keys from `ObjectTokenizer`. - -// class ObjectReifyingSerializer: KSerializer { -// override val descriptor = SerialDescriptor("Reifying", delegateSerializer) -// } - + // Json serialization that accepts `Any?`, and converts non-primitive values to string keys from `InstanceTokenizer`. object TokenizingSerializer: KSerializer { @@ -44,7 +39,7 @@ object TokenizingJson { "Long" to serializer(), "Short" to serializer(), "String" to serializer() - // Only these types will be serialized. Everything else will be replaced with a string key from ObjectTokenizer. + // Only these types will be serialized. Everything else will be replaced with a string key from InstanceTokenizer. ).mapValues { (_, v) -> v as KSerializer } // private fun getDataTypeSerializer(value: T): KSerializer { @@ -60,7 +55,7 @@ object TokenizingJson { if (classname in dataTypeSerializers) { encoder.encodeSerializableValue(dataTypeSerializers[value!!::class.simpleName!!]!!, value!!) } else { - encoder.encodeString(ObjectTokenizer.getToken(value!!)) + encoder.encodeString(InstanceTokenizer.getToken(value!!)) } } } @@ -69,7 +64,7 @@ object TokenizingJson { if (decoder is JsonDecoder) { val jsonLiteral = (decoder as JsonDecoder).decodeJsonElement() val rawval: Any? = getJsonReal(jsonLiteral) - return ObjectTokenizer.getReal(rawval) + return InstanceTokenizer.getReal(rawval) } else { throw UnsupportedOperationException("Decoding is not supported by TokenizingSerializer for ${decoder::class.simpleName}.") } @@ -95,9 +90,16 @@ object TokenizingJson { if (value is Map<*, *>) { //TODO: Decide what to do with the keys. return JsonObject( (value as Map).mapValues{ getJsonElement(it.value) } ) } - if (value is Collection<*>) { + if (value is Iterable<*>) { + // Apparently ::class.java.isArray can be used to check for primitive arrays, but it breaks return JsonArray(value.map{ getJsonElement(it) }) } + if (value is Sequence<*>) { + var v = (value as Sequence).toList() + println(v) + println(v[0]!!::class.qualifiedName) + return getJsonElement(v) + } if (value is String) { return JsonPrimitive(value as String) } @@ -110,7 +112,7 @@ object TokenizingJson { if (value == null) { return JsonNull } - return JsonPrimitive(ObjectTokenizer.getToken(value)) + return JsonPrimitive(InstanceTokenizer.getToken(value)) } fun getJsonReal(value: JsonElement): Any? { @@ -126,7 +128,7 @@ object TokenizingJson { if (value is JsonPrimitive) { val v = value as JsonPrimitive if (v.isString) { - return ObjectTokenizer.getReal(v.content) + return InstanceTokenizer.getReal(v.content) } else { return v.content.toIntOrNull() ?: v.content.toDoubleOrNull() From ad6770d5a3022d2097d1c6db61c6d3e1cc7ba907 Mon Sep 17 00:00:00 2001 From: will-ca Date: Fri, 12 Nov 2021 08:49:42 +0000 Subject: [PATCH 32/93] More docs, fixes, finishing features, cleanup. --- .../scripting/enginefiles/python/main.py | 29 +- .../python/unciv/autocompletion.py | 8 +- .../enginefiles/python/unciv/wrapping.py | 12 +- .../src/com/unciv/scripting/ScriptingScope.kt | 4 +- .../src/com/unciv/scripting/ScriptingState.kt | 62 ++-- .../scripting/protocol/ScriptingProtocol.kt | 337 +++++++++++------- .../protocol/ScriptingReplManager.kt | 41 +-- .../unciv/scripting/reflection/Reflection.kt | 35 +- .../unciv/scripting/utils/InstanceRegistry.kt | 40 +++ .../unciv/scripting/utils/TokenizingJson.kt | 78 ++-- 10 files changed, 413 insertions(+), 233 deletions(-) create mode 100644 core/src/com/unciv/scripting/utils/InstanceRegistry.kt diff --git a/android/assets/scripting/enginefiles/python/main.py b/android/assets/scripting/enginefiles/python/main.py index 65d029a57d58d..79c99ba503749 100644 --- a/android/assets/scripting/enginefiles/python/main.py +++ b/android/assets/scripting/enginefiles/python/main.py @@ -88,6 +88,10 @@ # Token string gets get transformed back into original `MapUnit()` when used as function argument. ``` +The rules for which classes are serialized as JSON values and which are serialized as token strings may be a little bit fuzzy and variable, as they are designed to maximise use cases. + +In general, Kotlin/JVM instances that *can* be cast into a type compatible with a JSON type will be serialized as such— Unless they are of classes defined within the Unciv packages themselves, in which case they will always be tokenized. The exemption for certain classes prevents everything that inherits from iterable interfaces— Like `Building()`, which inherits from `Stats:Iterable`— From being stripped down into JSON arrays when having having references to and members of their instances is much more useful. + --- Sometimes, you may want to access a path or call a method on an object that you have only as a token string— For example, an object returned from a foreign function or method call. @@ -100,18 +104,21 @@ ```python3 token = civInfo.cities[0].getCenterTile() +# Token string representing a `TileInfo()` instance. print(type(token)) # . Cannot be used for attribute access. apiHelpers.registeredInstances["centertile"] = token +# Token string gets transformed back into `TileInfo()` in Kotlin/JVM assignment. print(type(apiHelpers.registeredInstances["centertile"])) # . Is now a full wrapper with path, and can be used for full attribute, key, and method access. -apiHelpers.registeredInstances["centertile"].baseTerrain +print(apiHelpers.registeredInstances["centertile"].baseTerrain) +# Successful attribute access. -apiHelpers.registeredInstances.remove("centertile") +del apiHelpers.registeredInstances["centertile"] # Delete the reference so it doesn't become a memory leak. ``` @@ -119,7 +126,23 @@ **It is very important that you delete concrete paths you have set after you are done with them.** Any objects held at paths you do not delete will continue to occupy system memory for the remaining run time of the application's lifespan. We can't rely on Python's garbage collection in this case because it doesn't control the Kotlin objects, nor can we rely on the JVM's garbage collector because it doesn't know whether Python code still needs the objects in question, so you will have to manage the memory yourself by keeping a reference as long as you need an object and deleting it to free up memory afterwards. -For any complicated script in Python, it is suggested that you write a context manager class to automatically take care of saving and freeing each object for you where appropriate. +For any complicated script in Python, it is suggested that you write a context manager class to automatically take care of saving and freeing each object where appropriate. + +It is also recommended that all scripts create a separate mapping with a unique and identifiable key in `apiHelpers.registeredInstances`, instead of assigning directly to the top level. + +```python3 +apiHelpers.registeredInstances["myCoolScript"] = {} + +memalloc = apiHelpers.registeredInstances["myCoolScript"] + +memalloc["capitaltile"] = civInfo.cities[0].getCenterTile() + +worldScreen.mapHolder.setCenterPosition(memalloc["capitaltile"].position, True, True) + +del memalloc["capitaltile"] + +del apiHelpers.registeredInstances["myCoolScript"] +``` --- diff --git a/android/assets/scripting/enginefiles/python/unciv/autocompletion.py b/android/assets/scripting/enginefiles/python/unciv/autocompletion.py index fdfc5c3207533..acc4084f4bbaf 100644 --- a/android/assets/scripting/enginefiles/python/unciv/autocompletion.py +++ b/android/assets/scripting/enginefiles/python/unciv/autocompletion.py @@ -31,7 +31,7 @@ def GetCommandComponents(self, command): _bdepth = 1 prefixsplit -= 1 while _bdepth and prefixsplit: - # Skip over whole blocks of matched brackets. + # Skip over whole blocks of matched brackets. char = command[prefixsplit] if char == '[': _bdepth -= 1 @@ -56,11 +56,11 @@ def GetAutocomplete(self, command): class PyAutocompleteManager(AutocompleteManager): - """Advanced autocompleter. Returns keys when accessing mappings. Implements API that returns docstrings as help text for callables.""" + """Advanced autocompleter. Returns keys when accessing mappings. Implements API that returns docstrings as help text for callables. Adds opening round and square brackets to autocomplete matches to show callables and mappings.""" def Evaled(self, path): - return eval(path, self.scope, self.scope) - #Seems safe. Well, I'm checking before calling here that there's no closing brackets that could mean a function call. Let's check again, I guess. assert ')' not in path, f"Closing brackets not currently allowed in autocomplete eval: {path}" + return eval(path, self.scope, self.scope) + #Seems safe. Well, I'm already checking before calling here that there's no closing brackets that could mean a function call. Let's check again, I guess. def GetAutocomplete(self, command, cursorpos=None): try: if cursorpos is None: diff --git a/android/assets/scripting/enginefiles/python/unciv/wrapping.py b/android/assets/scripting/enginefiles/python/unciv/wrapping.py index c36989cae02d7..a401da7ae83e2 100644 --- a/android/assets/scripting/enginefiles/python/unciv/wrapping.py +++ b/android/assets/scripting/enginefiles/python/unciv/wrapping.py @@ -87,7 +87,7 @@ def stringPathList(pathlist): _magicmeths = ( '__lt__', '__le__', - '__eq__', # Kinda undefined behaviour for comparision with Kotlin object tokens. Well, tokens are just strings that will always equal themselves, but multiple tokens can refer to the same Kotlin object. `ForeignObject()`s resolve to new tokens, that are currently uniquely generated in InstanceTokenizer.kt, on every `._getvalue_()`, so I think even the same `ForeignObject()` will never equal itself. + '__eq__', # Kinda undefined behaviour for comparison with Kotlin object tokens. Well, tokens are just strings that will always equal themselves, but multiple tokens can refer to the same Kotlin object. `ForeignObject()`s resolve to new tokens, that are currently uniquely generated in InstanceTokenizer.kt, on every `._getvalue_()`, so I think even the same `ForeignObject()` will never equal itself. '__ne__', '__ge__', '__gt__', @@ -265,6 +265,16 @@ def __setitem__(self, key, value): 'assign_response', foreignErrmsgChecker) @ForeignRequestMethod + def __delitem__(self, key): + return ({ + 'action': 'delete', + 'data': { + 'path': self[key]._getpath_() + } + }, + 'delete_response', + foreignErrmsgChecker) + @ForeignRequestMethod def __len__(self): return ({ 'action': 'length', diff --git a/core/src/com/unciv/scripting/ScriptingScope.kt b/core/src/com/unciv/scripting/ScriptingScope.kt index 1e0d6d777af95..f43fbeeb38ad9 100644 --- a/core/src/com/unciv/scripting/ScriptingScope.kt +++ b/core/src/com/unciv/scripting/ScriptingScope.kt @@ -4,6 +4,7 @@ import com.unciv.logic.GameInfo import com.unciv.logic.civilization.CivilizationInfo import com.unciv.UncivGame import com.unciv.scripting.utils.InstanceFactories +import com.unciv.scripting.utils.InstanceRegistry import com.unciv.ui.worldscreen.WorldScreen @@ -20,7 +21,8 @@ class ScriptingScope( val isInGame: Boolean get() = (scriptingScope.civInfo != null && scriptingScope.gameInfo != null && scriptingScope.uncivGame != null) val Factories = InstanceFactories - val registeredInstances = mutableMapOf() +// val registeredInstances = mutableMapOf() + val registeredInstances = InstanceRegistry() fun unchanged(obj: Any?) = obj //Debug/dev identity function for both Kotlin and scripts. Check if value survives serialization, force something to be added to ScriptingProtocol.instanceSaver, etc. fun printLn(msg: Any?) = println(msg) fun toString(obj: Any?) = obj.toString() diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index 87770d3ff59cc..60b69c231f4b6 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -9,39 +9,39 @@ import kotlin.math.max import kotlin.math.min /* - - The major classes involved in the scripting API are structured as follows. UpperCamelCase() and parentheses means a new instantiation of a class. lowerCamelCase means a reference to an already-existing instance. An asterisk at the start of an item means zero or multiple instances of that class may be held. A question mark at the start of an item means that it may not exist in all implementations of the parent base class/interface. A question mark at the end of an item means that it is nullable, or otherwise may not be available in all states. - - ``` - UncivGame(): - ScriptingState(): // Persistent per UncivGame(). - ScriptingScope(): - civInfo? // These are set by WorldScreen init, and unset by MainMenuScreen. - gameInfo? - uncivGame - worldScreen? - *ScriptingBackend(): + +The major classes involved in the scripting API are structured as follows. `UpperCamelCase()` and parentheses means a new instantiation of a class. `lowerCamelCase` means a reference to an already-existing instance. An asterisk at the start of an item means zero or multiple instances of that class may be held. A question mark at the start of an item means that it may not exist in all implementations of the parent base class/interface. A question mark at the end of an item means that it is nullable, or otherwise may not be available in all states. + +``` +UncivGame(): + ScriptingState(): // Persistent per UncivGame(). + ScriptingScope(): + civInfo? // These are set by WorldScreen init, and unset by MainMenuScreen. + gameInfo? + uncivGame + worldScreen? + *ScriptingBackend(): + scriptingScope + ?ScriptingReplManager(): + Blackbox() // Common interface to wrap foreign interpreter with pipes, STDIN/STDOUT, queues, sockets, embedding, JNI, etc. scriptingScope - ?ScriptingReplManager(): - Blackbox() // Common interface to wrap foreign interpreter with pipes, STDIN/STDOUT, queues, sockets, embedding, JNI, etc. + ScriptingProtocol(): scriptingScope - ScriptingProtocol(): - scriptingScope - ?folderHandler: setupInterpreterEnvironment() // If used, a temporary directory with file structure copied from engine and shared folders in `assets/scripting`. - ConsoleScreen(): // Persistent as long as window isn't resized. Recreates itself and restores most of its state from scriptingState if resized. - scriptingState - WorldScreen(): - consoleScreen - scriptingState // ScriptingState has getters and setters that wrap scriptingScope, which WorldScreen uses to update game info. - MainMenuScreen(): - consoleScreen - scriptingState // Same as for worldScreen. - InstanceTokenizer() // Holds WeakRefs used by ScriptingProtocol. Unserializable objects get strings as placeholders, and then turned back into into objects if seen again. - Reflection() // Used by some hard-coded scripting backends, and essential to dynamic bindings in ScriptingProtocol(). - SourceManager() // Source of the folderHandler and setupInterpreterEnvironment() above. - TokenizingJson() // Serializer and functions that use InstanceTokenizer. - - ``` + ?folderHandler: setupInterpreterEnvironment() // If used, a temporary directory with file structure copied from engine and shared folders in `assets/scripting`. + ConsoleScreen(): // Persistent as long as window isn't resized. Recreates itself and restores most of its state from scriptingState if resized. + scriptingState +WorldScreen(): + consoleScreen + scriptingState // ScriptingState has getters and setters that wrap scriptingScope, which WorldScreen uses to update game info. +MainMenuScreen(): + consoleScreen + scriptingState // Same as for worldScreen. +InstanceTokenizer() // Holds WeakRefs used by ScriptingProtocol. Unserializable objects get strings as placeholders, and then turned back into into objects if seen again. +Reflection() // Used by some hard-coded scripting backends, and essential to dynamic bindings in ScriptingProtocol(). +SourceManager() // Source of the folderHandler and setupInterpreterEnvironment() above. +TokenizingJson() // Serializer and functions that use InstanceTokenizer. +``` + */ fun ArrayList.clipIndexToBounds(index: Int, extendsize: Int = 0): Int { diff --git a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt index 74e4b6a209ee8..2b85956cec486 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt @@ -20,157 +20,210 @@ import kotlinx.serialization.json.decodeFromJsonElement import java.util.UUID /* - A single IPC action consists of one request packet and one response packet. - A request packet should always be followed by a response packet if it has an action. - If a request packet has a null action, then it should not be followed by a response. This is to let flags be sent without generating useless responses. +A single IPC action consists of one request packet and one response packet. +A request packet should always be followed by a response packet if it has an action. +If a request packet has a null action, then it should not be followed by a response. This is to let flags be sent without generating useless responses. - Responses do not have to be sent in the same order as their corresponding requests. New requests can be sent out while old ones are left "open"— E.G., if creating a response requires requesting new information. - - (So far, I think all the requests and responses follow a "stack" model, though. If request B is sent out before response A is received, then response B still gets received before response A, like parentheses. The time two requests remain open can be completely nested subsets or supersets, or they can be completely separated in series, but they don't partially overlap.) - - (That said, none of these are hard requirements. If you want to do something fancy with coroutines or whatever and dispatch to multiple open request handlers, and you can make it both stable and language-aggnostic, go right ahead.) +Responses do not have to be sent in the same order as their corresponding requests. New requests can be sent out while old ones are left "open"— E.G., if creating a response requires requesting new information. - Both the Kotlin side and the script interpreter can send and receive packets, but not necessarily at all times. +(So far, I think all the requests and responses follow a "stack" model, though. If request B is sent out before response A is received, then response B gets received before response A, like parentheses. The time two requests remain open can be completely nested subsets or supersets, or they can be completely separated in series, but they don't currently ever partially overlap.) - (The current loop is described in a comment in ScriptingReplManager.kt. Kotlin initiates a scripting exec, during which the script interpreter can request values from Kotlin, and at the end of which the script interpreter sends its STDOUT response to the Kotlin side.) +(That said, none of these are hard requirements. If you want to do something fancy with coroutines or whatever and dispatch to multiple open request handlers, and you can make it both stable and language-agnostic, go right ahead.) - A single packet is a JSON string of the form: +Both the Kotlin side and the script interpreter can send and receive packets, but not necessarily at all times. - ``` - { - "action": String?, - "identifier": String?, - "data": Any?, - "flags": Collection - } - ``` +(The current loop is described in a comment in ScriptingReplManager.kt. Kotlin initiates a scripting exec, during which the script interpreter can request values from Kotlin, and at the end of which the script interpreter sends its STDOUT response to the Kotlin side.) - Identifiers should be set to a unique value in each request. - Each response should have the same identifier as its corresponding request. - Upon receiving a response, both its action and identifier should be checked to match the relevant request. - - --- - - The data field is allowed to represent any hierarchy, of objects of any types. - If it must represent objects that are not possible or not useful to serialize, then unique identifying token strings should be generated and sent in their place. - If those strings are received at any hierarchical depth in the data field of any later packets, then they are to be substituted with their original objects in all uses of the information from those packets. - If the original object of a received token string no longer exists, then an exception should be thrown, and handled as would any exception at the point where the object is to be accessed. - - (Caveats in practice: The scripting API design is highly asymmetric in that script interpreter needs a lot of access to the Kotlin side's state, but the Kotlin side should rarely or never need the script interpreter's state, so the script interpreter doesn't have to bother implementing its own arbitrary object serialization. Requests sent by the Kotlin side also all have very simple response formats because of this, while access to and use of complicated Kotlin-side objects is always initiated by a request from the script interpreter while in the execution loop of its REPL, so the Kotlin side bothers implementing arbitrary object tokenization only when receiving requests and not when receiving responses. Exceptions from reifying invalid received tokens on the Kotlin side should be handled as would any other exceptions at their code paths, but because such tokens are only used on the Kotlin side when preparing a response to a received request from the scripting side, that currently means sending a response packet that is marked in some way as representing an exception and then carrying on as normal.) - - --- +A single packet is a JSON string of the form: - Some action types, data formats, and expected response data formats for packets sent from the Kotlin side to the script interpreter include: - - ``` - 'motd': null -> - 'motd_response': String - ``` - - ``` - 'autocomplete': {'command': String, 'cursorpos': Int} -> - 'autocomplete_response': Collection or String - //List of matches, or help text to print. - ``` - - ``` - 'exec': String -> - 'exec_response': String - //REPL print. - ``` - - ``` - 'terminate': null -> - 'terminate_response': String? - //Error message or null. - ``` - - The above are basically a mirror of ScriptingBackendBase, so the same interface can be implemented in the scripting language. - - --- +``` +{ + "action": String?, + "identifier": String?, + "data": Any?, + "flags": Collection +} +``` - Some action types, data formats, and expected response types and formats for packets sent from the script interpreter to the Kotlin side include: - - ``` - 'read': {'path': List<{'type':String, 'name':String, 'params':List}>} -> - 'read_reponse': {'value': Any?, 'exception': String?} - //Attribute/property access, by list of `PathElement` properties. - ``` - - ``` - //'call': {'path': List<{'type':String, 'name':String, 'params':List}>, 'args': Collection, 'kwargs': Map} -> - //'call_response': {'value': Any?, 'exception': String?} - //Method/function call. - //Deprecated and removed. Instead, use `"action":"read"` with a "path" that has `"type":"call"` element(s) as per `PathElement`. - ``` - - ``` - 'assign': {'path': List<{'type':String, 'name':String, 'params':List}>, 'value': Any} -> - 'assign_response': String? - //Error message or null. - ``` - - ``` - 'dir': {'path': List<{'type':String, 'name':String, 'params':List}>} -> - 'dir_response': {'value': Collection, 'exception': String?} - //Names of all members/properties/attributes/methods. - ``` - - ``` - 'keys': {'path': List<{'type':String, 'name':String, 'params':List}>} -> - 'keys_response': {'value': Collection, 'exception': String?} - ``` - - ``` - 'length': {'path': List<{'type':String, 'name':String, 'params':List}>} -> - 'length_response': {'value': Int?, 'exception': String?} - ``` +Identifiers should be set to a unique value in each request. +Each response should have the same identifier as its corresponding request. +Upon receiving a response, both its action and identifier should be checked to match the relevant request. + +--- + +The data field is allowed to represent any hierarchy, of instances of any types. + +If it must represent instances that are not possible or not useful to serialize as JSON hierarchies, then unique identifying token strings should be generated and sent in the places of those instances. + +If those strings are received at any hierarchical depth in the data field of any later packets, then they are to be substituted with their original instances in all uses of the information from those packets. +If the original instance of a received token string no longer exists, then an exception should be thrown, and handled as would any exception at the point where the instance is to be accessed. + +Example instance requested by script interpreter: + +``` +SomeKotlinInstance@M3mAdDr +``` + +Example response packet to send script interpreter this instance: + +``` +{ + "action": "read_response", + "identifier": "ABC001", + "data": { + "value": "_someStringifiedTokenForSomeKotlinInstance", + "exception": null + }, + "flags": [] +} +``` + +Example subsequent request packet from script interpreter using the token string: + +``` +{ + "action": "assign", + "identifier": "CDE002", + "data": { + "path": [{"type": "Property", "name": "someProperty", "params": []}], + "value": [5, "ActualStringValue", "_someStringifiedTokenForSomeKotlinInstance"] + }, + "flags": [] +} +``` + +Equivalent assignment operation resulting from this request packet: + +``` +someProperty = listOf(5, "ActualStringValue", SomeKotlinInstance@M3mAdDr) +``` + +(Caveats in practice: The scripting API design is highly asymmetric in that script interpreter needs a lot of access to the Kotlin side's state, but the Kotlin side should rarely or never need the script interpreter's state, so the script interpreter doesn't have to bother implementing its own arbitrary object tokenization. Requests sent by the Kotlin side also all have very simple response formats because of this, while access to and use of complicated Kotlin-side instances is always initiated by a request from the script interpreter while in the execution loop of its REPL, so the Kotlin side bothers implementing arbitrary instance tokenization only when receiving requests and not when receiving responses. Exceptions from reifying invalid received tokens on the Kotlin side should be handled as would any other exceptions at their code paths, but because such tokens are only used on the Kotlin side when preparing a response to a received request from the scripting side, that currently means sending a response packet that is marked in some way as representing an exception and then carrying on as normal.) + +--- + +Some action types, data formats, and expected response data formats for packets sent from the Kotlin side to the script interpreter include: + + ``` + 'motd': null -> + 'motd_response': String + ``` + + ``` + 'autocomplete': {'command': String, 'cursorpos': Int} -> + 'autocomplete_response': Collection or String + //List of matches, or help text to print. + ``` - ``` - 'contains': {'path': List<{'type':String, 'name':String, 'params':List}>, 'value': Any?} -> - 'contains_response': {'value': Boolean?, 'exception': String?} - ``` + ``` + 'exec': String -> + 'exec_response': String + //REPL print. + ``` - ``` - 'callable': {'path': List<{'type':String, 'name':String, 'params':List}>} -> - 'callable_response': {'value': Boolean?, 'exception': String?} - //Used by Python autocompleter to add opening bracket to methods and function suggestions. Quite useful for exploring API at a glance. - ``` - - ``` - 'args': 'path': List<{'type':String, 'name':String, 'params':List}>} -> - 'args_response': {'value': List>?, 'exception': String?} - //Names and types of arguments accepted by a function. - //Currently just used by Python autocompleter to generate help text. - //Could also be used to control functions in scripting environment. If so, then names of types should be standardized. - ``` + ``` + 'terminate': null -> + 'terminate_response': String? + //Error message or null. + ``` - ``` - 'docstring': {'path': List<{'type':String, 'name':String, 'params':List}>} -> - 'docstring_response': {'value': String?, 'exception': String?} - //Used by Python wrappers and autocompleter to get help text showing arguments and types for callables. Useful for exploring API without having to browse code. - ``` +The above are basically a mirror of ScriptingBackend, so the same interface can be implemented in the scripting language. + +--- + +Some action types, data formats, and expected response types and formats for packets sent from the script interpreter to the Kotlin side include: + + ``` + 'read': {'path': List<{'type':String, 'name':String, 'params':List}>} -> + 'read_reponse': {'value': Any?, 'exception': String?} + //Attribute/property access, by list of `PathElement` properties. + ``` - The paths fields in some of the data fields mirror PathElement. + ``` + //'call': {'path': List<{'type':String, 'name':String, 'params':List}>, 'args': Collection, 'kwargs': Map} -> + //'call_response': {'value': Any?, 'exception': String?} + //Method/function call. + //Deprecated and removed. Instead, use `"action":"read"` with a "path" that has `"type":"call"` element(s) as per `PathElement`. + ``` + + ``` + 'assign': {'path': List<{'type':String, 'name':String, 'params':List}>, 'value': Any} -> + 'assign_response': String? + //Error message or null. + ``` - --- + ``` + 'delete': {'path': List<{'type':String, 'name':String, 'params':List}>} -> + 'delete_response': String? + //Error message or null. + //Only meaningful and implemented for MutableMap() keys and MutableList() indices. + ``` - Flags are string values for communicating extra information that doesn't need a separate packet or response. Depending on the flag and action, they may be contextual to the packet, or they may not. I think I see them mostly as a way to semantically separate meta-communication about the protocol from actual requests for actions: - ``` - 'PassMic' - //Indicates that the sending side will now begin listening instead, and the receiving side should send the next request. - //Sent by Kotlin side at start of script engine startup/MOTD, autocompletion, and execution to allow script to request values, and should be sent by script interpreter immediately before sending response with results of execution. - ``` + ``` + 'dir': {'path': List<{'type':String, 'name':String, 'params':List}>} -> + 'dir_response': {'value': Collection, 'exception': String?} + //Names of all members/properties/attributes/methods. + ``` - ``` - //'Exception' - //Indicates that this packet is associated with an error. - //Not implemented. Currently Kotlin-side exceptions while processing a request are communicated to the script interpreter with an error message at an expected place in the data field of the response, and exceptions in the script interpreter while executing a command are stringified and returned to the Kotlin side the same as any REPL output. Miight be useful for a modding API, though. - ``` + ``` + 'keys': {'path': List<{'type':String, 'name':String, 'params':List}>} -> + 'keys_response': {'value': Collection, 'exception': String?} + //Keys of Map-interfaced instances. Used by Python bindings for iteration and autocomplete. + ``` - --- + ``` + 'length': {'path': List<{'type':String, 'name':String, 'params':List}>} -> + 'length_response': {'value': Int?, 'exception': String?} + //Used by Python bindings for length and also for iteration. + ``` + + ``` + 'contains': {'path': List<{'type':String, 'name':String, 'params':List}>, 'value': Any?} -> + 'contains_response': {'value': Boolean?, 'exception': String?} + //Doing this through an IPC call instead of in the script interpreter should let tokenized instances be checked for properly. + ``` + + ``` + 'callable': {'path': List<{'type':String, 'name':String, 'params':List}>} -> + 'callable_response': {'value': Boolean?, 'exception': String?} + //Used by Python autocompleter to add opening bracket to methods and function suggestions. Quite useful for exploring API at a glance. + ``` - Thus, at the IPC level, all foreign backends will actually use the same language, which is this JSON-based protocol. Differences between Python, JS, Lua, etc. will all be down to how they interpret the "exec", "autocomplete", and "motd" actions differently, which each high-level scripting language is free to implement as works best for it. + ``` + 'args': 'path': List<{'type':String, 'name':String, 'params':List}>} -> + 'args_response': {'value': List>?, 'exception': String?} + //Names and types of arguments accepted by a function. + //Currently just used by Python autocompleter to generate help text. + //Could also be used to control functions in scripting environment. If so, then names of types should be standardized. + ``` + + ``` + 'docstring': {'path': List<{'type':String, 'name':String, 'params':List}>} -> + 'docstring_response': {'value': String?, 'exception': String?} + //Used by Python wrappers and autocompleter to get help text showing arguments and types for callables. Useful for exploring API without having to browse code. + ``` + +The path elements in some of the data fields mirror PathElement. + +--- + +Flags are string values for communicating extra information that doesn't need a separate packet or response. Depending on the flag and action, they may be contextual to the packet, or they may not. I think I see them mostly as a way to semantically separate meta-communication about the protocol from actual requests for actions: + + ``` + 'PassMic' + //Indicates that the sending side will now begin listening instead, and the receiving side should send the next request. + //Sent by Kotlin side at start of script engine startup/MOTD, autocompletion, and execution to allow script to request values, and should be sent by script interpreter immediately before sending response with results of execution. + ``` + + ``` + //'Exception' + //Indicates that this packet is associated with an error. + //Not implemented. Currently Kotlin-side exceptions while processing a request are communicated to the script interpreter with an error message at an expected place in the data field of the response, and exceptions in the script interpreter while executing a command are stringified and returned to the Kotlin side the same as any REPL output. Miight be useful for a modding API, though. + ``` + +--- + +Thus, at the IPC level, all foreign backends will actually use the same language, which is this JSON-based protocol. Differences between Python, JS, Lua, etc. will all be down to how they interpret the "exec", "autocomplete", and "motd" requests differently, which each high-level scripting language is free to implement as works best for it. */ @@ -197,9 +250,9 @@ data class ScriptingPacket( class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = null) { - // Adds all returned and potentially tokenized objects in responses to `instanceSaver` to save them from garbage collection. + // Adds all returned and potentially tokenized instances in responses to `instanceSaver` to save them from garbage collection. // `instanceSaver` should not be usd as long-term storage. - // Whatever's using this protocol object should clear `instanceSaver` as soon as whatever's running at the other end of the protocol has had a chance to create references elsewhere (E.G. ScriptingScope.apiHelpers) for any objects that it needs. + // Whatever's using this protocol instance should clear `instanceSaver` as soon as whatever's running at the other end of the protocol has had a chance to create references elsewhere (E.G. ScriptingScope.apiHelpers) for any instances that it needs. enum class KnownFlag(val value: String) { PassMic("PassMic") @@ -219,6 +272,7 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = "terminate" to "terminate_response", "read" to "read_response", "assign" to "assign_response", + "delete" to "delete_response", "dir" to "dir_response", "keys" to "keys_response", "length" to "length_response", @@ -330,6 +384,17 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = } data = exception } + "delete" -> { + try { + Reflection.removeInstancePath( + scope, + TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) + ) + } catch (e: Exception) { + exception = JsonPrimitive(stringifyException(e)) + } + data = exception + } "dir" -> { try { val leaf = Reflection.resolveInstancePath( diff --git a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt index d206e52b8e0c3..978375d8e0965 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt @@ -9,31 +9,32 @@ import com.unciv.scripting.utils.Blackbox /* - 1. A scripted action is initiated from the Kotlin side, by sending a command string to the script interpreter. - 2. While the script interpreter is running, it has a chance to request values from the Kotlin side by sending back packets encoding attribute/property, key, and call stacks. - 3. When the Kotlin side receives a request for a value, it uses reflection to access the requested property or call the requested method, and it sends the result to the script interpreter. - 4. When the script interpreter finishes running, it sends a special packet to the Kotlin side. It then sends the REPL output of the command to the Kotlin side. - 5. When the Kotlin interpreter receives the packet marking the end of the command run, it stops listening for value requests packets. It then receives the commnad result as the next value, and passes it back to the display/handler. +1. A scripted action is initiated from the Kotlin side, by sending a command string to the script interpreter. + 1.a. While the script interpreter is running, it has a chance to request values from the Kotlin side by sending back packets encoding attribute/property, key, and call, and assignment stacks. + 1.b. When the Kotlin side receives a request for a value, it uses reflection to access the requested property or call the requested method, and it sends the result to the script interpreter. + 1.c. When the script interpreter finishes running, it sends a special packet to the Kotlin side communicating that the script interpreter has no more requests to make. The script interpreter then sends the REPL output of the command to the Kotlin side. +2. When the Kotlin interpreter receives the packet marking the end of the command run, it stops listening for value requests packets. It then receives the commnad result as the next value, and passes it back to the console screen or script handler. - ``` - fun ExecuteCommand(command): - SendToInterpreter(command:String) - while True: - packet:Packet = ReceiveFromInterpreter().parsed() - if isPropertyrequest(packet): - SendToInterpreter(ResolvePacket(scriptingScope, packet)) - else if isCommandEndPacket(packet): - break - PrintToConsole(ReceiveFromInterpreter():String) - ``` +From Kotlin: +``` +fun ExecuteCommand(command:String): + SendToInterpreter(command) + while True: + packet:Packet = ReceiveFromInterpreter().parsed() + if isPropertyRequest(packet): + SendToInterpreter(ResolvePacket(scriptingScope, packet)) + else if isCommandEndPacket(packet): + break + PrintToConsole(ReceiveFromInterpreter().parsed().data:String) +``` - The "packets" should probably all be encoded as strings, probably JSON. The technique used to connect the script interpreter to the Kotlin code shouldn't matter, as long as it's wrapped up in and implements the `Blackbox` interface. IPC/embedding based on pipes, STDIN/STDOUT, sockets, queues, embedding, JNI, etc. should all be interchangeable. +The "packets" should probably all be encoded as strings, probably JSON. The technique used to connect the script interpreter to the Kotlin code shouldn't matter, as long as it's wrapped up in and implements the `Blackbox` interface. IPC/embedding based on pipes, STDIN/STDOUT, sockets, queues, embedding, JNI, etc. should all be interchangeable and equally functional. - I'm not sure if there'd be much point to or a good technique for letting the script interpreter run constantly and initiate actions on its own, instead of waiting for commands from the Kotlin side. +I'm not sure if there'd be much point to or a good technique for letting the script interpreter run constantly and initiate actions on its own, instead of waiting for commands from the Kotlin side. - You would presumably have to interrupt the main Kotlin-side thread anyway in order to safely run any actions initiated by the script interpreter— Which means that you may as well just register a handler to call the script interpreter at that point. +You would presumably have to interrupt the main Kotlin-side thread anyway in order to safely run any actions initiated by the script interpreter— Which means that you may as well just register a handler to call the script interpreter at that point. - Plus, letting the script interpreter run completely in parallel would probably introduce potential for all sorts of issues with no-deterministic synchronicity, and performance issues Calling the script interpreter from +Plus, letting the script interpreter run completely in parallel would probably introduce potential for all sorts of issues with non-deterministic synchronicity, and performance issues Calling the script interpreter from the Kotlin side means that the state of the Kotlin side is more predictable at the moment of script execution. */ //interface ScriptingReplManager { diff --git a/core/src/com/unciv/scripting/reflection/Reflection.kt b/core/src/com/unciv/scripting/reflection/Reflection.kt index 96f32d2aa6357..5538ec263db9d 100644 --- a/core/src/com/unciv/scripting/reflection/Reflection.kt +++ b/core/src/com/unciv/scripting/reflection/Reflection.kt @@ -43,10 +43,18 @@ object Reflection { fun setInstanceItem(instance: Any, keyOrIndex: Any, value: Any?): Unit { if (keyOrIndex is Int) { (instance as MutableList)[keyOrIndex] = value - } else{ + } else { (instance as MutableMap)[keyOrIndex] = value } } + + fun removeInstanceItem(instance: Any, keyOrIndex: Any): Unit { + if (keyOrIndex is Int) { + (instance as MutableList).removeAt(keyOrIndex) + } else { + (instance as MutableMap).remove(keyOrIndex) + } + } enum class PathElementType() { @@ -269,4 +277,29 @@ object Reflection { } } } + + fun removeInstancePath(instance: Any, path: List): Unit { + val leafobj = resolveInstancePath(instance, path.slice(0..path.size-2)) + val leafelement = path[path.size - 1] + when (leafelement.type) { + PathElementType.Property -> { + throw UnsupportedOperationException("Cannot remove instance property.") + } + PathElementType.Key -> { + removeInstanceItem( + leafobj!!, + if (leafelement.doEval) + evalKotlinString(instance, leafelement.name)!! + else + leafelement.params[0]!! + ) + } + PathElementType.Call -> { + throw UnsupportedOperationException("Cannot remove function call.") + } + else -> { + throw UnsupportedOperationException("Unknown path element type: ${leafelement.type}") + } + } + } } diff --git a/core/src/com/unciv/scripting/utils/InstanceRegistry.kt b/core/src/com/unciv/scripting/utils/InstanceRegistry.kt new file mode 100644 index 0000000000000..b9159af290740 --- /dev/null +++ b/core/src/com/unciv/scripting/utils/InstanceRegistry.kt @@ -0,0 +1,40 @@ +package com.unciv.scripting.utils + + +class InstanceRegistry(): MutableMap { + // Namespace in ScriptingScope().apiHelpers, for scripts to do their own memory management by keeping references to objects alive. Wraps a MutableMap<>(). + // Currently all it does is throw an exception on an assignment colliding with an existing key, and reads and removals for non-existent keys.. + // Was going to have functions named to fit creating and freeing fields, but so far Python's item assignment and deletion syntaxes are plenty clean + private val backingMap = mutableMapOf() + override val entries + get() = backingMap.entries + override val keys + get() = backingMap.keys + override val values + get() = backingMap.values + override val size + get() = backingMap.size + override fun containsKey(key: String) = backingMap.containsKey(key) + override fun containsValue(value: Any?) = backingMap.containsValue(value) + override fun get(key: String): Any? { + if (key !in this) { + throw NoSuchElementException("\"${key}\" not in ${this}.") + } + return backingMap.get(key) + } + override fun isEmpty() = backingMap.isEmpty() + override fun clear() = backingMap.clear() + override fun put(key: String, value: Any?): Any? { + if (key in this) { + throw IllegalArgumentException("\"${key}\" already in ${this}.") + } + return backingMap.put(key, value) + } + override fun putAll(from: Map) = backingMap.putAll(from) + override fun remove(key: String): Any? { + if (key !in this) { + throw NoSuchElementException("\"${key}\" not in ${this}.") + } + return backingMap.remove(key) + } +} diff --git a/core/src/com/unciv/scripting/utils/TokenizingJson.kt b/core/src/com/unciv/scripting/utils/TokenizingJson.kt index a41b33f6ddd70..80416e40cce09 100644 --- a/core/src/com/unciv/scripting/utils/TokenizingJson.kt +++ b/core/src/com/unciv/scripting/utils/TokenizingJson.kt @@ -29,6 +29,7 @@ object TokenizingJson { @Suppress("UNCHECKED_CAST") private val dataTypeSerializers: Map> = //Tried replacing this with a generic `fun serialize` and `serializer`. The change in signature prevents `serialize` from being overridden correctly, I guess. + //Also tried generic `fun getSerializer(v: T) = serialize() as KSerializer`. mapOf( "Boolean" to serializer(), "Byte" to serializer(), @@ -41,18 +42,14 @@ object TokenizingJson { "String" to serializer() // Only these types will be serialized. Everything else will be replaced with a string key from InstanceTokenizer. ).mapValues { (_, v) -> v as KSerializer } - -// private fun getDataTypeSerializer(value: T): KSerializer { -// return serializer() as KSerializer -// //Right. T needs to be known at compile time. -// } override fun serialize(encoder: Encoder, value: Any?) { + // Hum. I tested this, but I'm not sure it's actually used anywhere since I think PathElements (which use it for params) are only ever received and not sent. if (value == null) { encoder.encodeNull() } else { val classname = value!!::class.simpleName!! - if (classname in dataTypeSerializers) { + if (classname in dataTypeSerializers && !isTokenizationMandatory(value)) { encoder.encodeSerializableValue(dataTypeSerializers[value!!::class.simpleName!!]!!, value!!) } else { encoder.encodeString(InstanceTokenizer.getToken(value!!)) @@ -70,11 +67,6 @@ object TokenizingJson { } } } - -// val serializersModule = SerializersModule { -// contextual(TokenizingSerializer); -// contextual(DynamicLookupSerializer()) -// } val json = Json { explicitNulls = true; //Disable these if it becomes a problem. @@ -82,35 +74,49 @@ object TokenizingJson { // serializersModule = serializersModule } + private fun isTokenizationMandatory(value: Any?): Boolean { +// // Forbid some objects from being serialized as normal JSON values. +// // com.unciv.models.ruleset.Building(), for example, and resumably all other types that inherit from Stats(), implement Iterable<*> and thus get serialized as JSON Arrays by default even though it's probably better to tokenize them. +// // Python example: civInfo.cities[0].cityConstructions.getBuildableBuildings() +// // (Should return list of Building tokens, not list of lists of tokens for seemingly unrelated stats.) + if (value == null) { + return false + } + val qualname = value::class.qualifiedName + return qualname != null && qualname.startsWith("com.unciv") + } + - fun getJsonElement(value: T?): JsonElement { + fun getJsonElement(value: Any?): JsonElement { if (value is JsonElement) { return value } - if (value is Map<*, *>) { //TODO: Decide what to do with the keys. - return JsonObject( (value as Map).mapValues{ getJsonElement(it.value) } ) - } - if (value is Iterable<*>) { - // Apparently ::class.java.isArray can be used to check for primitive arrays, but it breaks - return JsonArray(value.map{ getJsonElement(it) }) - } - if (value is Sequence<*>) { - var v = (value as Sequence).toList() - println(v) - println(v[0]!!::class.qualifiedName) - return getJsonElement(v) - } - if (value is String) { - return JsonPrimitive(value as String) - } - if (value is Int || value is Long || value is Float || value is Double) { - return JsonPrimitive(value as Number) - } - if (value is Boolean) { //TODO: Arrays? - return JsonPrimitive(value as Boolean) - } - if (value == null) { - return JsonNull + if (!isTokenizationMandatory(value)) { + if (value is Map<*, *>) { //TODO: Decide what to do with the keys. + return JsonObject( (value as Map).mapValues{ getJsonElement(it.value) } ) + } + if (value is Iterable<*>) { + // Apparently ::class.java.isArray can be used to check for primitive arrays, but it breaks + return JsonArray(value.map{ getJsonElement(it) }) + } + if (value is Sequence<*>) { + var v = (value as Sequence).toList() + println(v) + println(v[0]!!::class.qualifiedName) + return getJsonElement(v) + } + if (value is String) { + return JsonPrimitive(value as String) + } + if (value is Int || value is Long || value is Float || value is Double) { + return JsonPrimitive(value as Number) + } + if (value is Boolean) { //TODO: Arrays? + return JsonPrimitive(value as Boolean) + } + if (value == null) { + return JsonNull + } } return JsonPrimitive(InstanceTokenizer.getToken(value)) } From d6642e926f701ce18133e377f6e358b4fcf2b979 Mon Sep 17 00:00:00 2001 From: will-ca Date: Fri, 12 Nov 2021 10:03:13 +0000 Subject: [PATCH 33/93] Organized Python modules, made API available via `import`. --- .../scripting/ScriptingEngineConstants.json | 14 +- .../scripting/enginefiles/python/main.py | 103 ++++--------- .../scripting/enginefiles/python/unciv/api.py | 77 ---------- .../python/{unciv => unciv_lib}/__init__.py | 0 .../enginefiles/python/unciv_lib/api.py | 137 ++++++++++++++++++ .../{unciv => unciv_lib}/autocompletion.py | 0 .../python/{unciv => unciv_lib}/ipc.py | 17 +-- .../python/{unciv => unciv_lib}/utils.py | 0 .../python/{unciv => unciv_lib}/wrapping.py | 2 +- .../scripting/protocol/ScriptingProtocol.kt | 6 + 10 files changed, 183 insertions(+), 173 deletions(-) delete mode 100644 android/assets/scripting/enginefiles/python/unciv/api.py rename android/assets/scripting/enginefiles/python/{unciv => unciv_lib}/__init__.py (100%) create mode 100644 android/assets/scripting/enginefiles/python/unciv_lib/api.py rename android/assets/scripting/enginefiles/python/{unciv => unciv_lib}/autocompletion.py (100%) rename android/assets/scripting/enginefiles/python/{unciv => unciv_lib}/ipc.py (92%) rename android/assets/scripting/enginefiles/python/{unciv => unciv_lib}/utils.py (100%) rename android/assets/scripting/enginefiles/python/{unciv => unciv_lib}/wrapping.py (98%) diff --git a/android/assets/scripting/ScriptingEngineConstants.json b/android/assets/scripting/ScriptingEngineConstants.json index d4ad42a4f1557..90bba4bb0ff8e 100644 --- a/android/assets/scripting/ScriptingEngineConstants.json +++ b/android/assets/scripting/ScriptingEngineConstants.json @@ -2,13 +2,13 @@ engines: { python: { files: [ - unciv/ - unciv/__init__.py - unciv/api.py - unciv/autocompletion.py - unciv/ipc.py - unciv/utils.py - unciv/wrapping.py + unciv_lib/ + unciv_lib/__init__.py + unciv_lib/api.py + unciv_lib/autocompletion.py + unciv_lib/ipc.py + unciv_lib/utils.py + unciv_lib/wrapping.py main.py ] syntaxHighlightingRegexStack: [ diff --git a/android/assets/scripting/enginefiles/python/main.py b/android/assets/scripting/enginefiles/python/main.py index 79c99ba503749..ccfec51303779 100644 --- a/android/assets/scripting/enginefiles/python/main.py +++ b/android/assets/scripting/enginefiles/python/main.py @@ -8,7 +8,7 @@ A **wrapper** is an object that stores a list of attribute names, keys, and function call parameters corresponding to a path in the Kotlin/JVM namespace. E.G.: `"civInfo.civilizations[0].population.setPopulation"` is a string representation of a wrapped path that begins with two attribute accesses, followed by one array index, and two more attribute names. -A wrapper object does not store any more values than that. When it is evaluated, it uses a simple IPC protocol to request an up-to-date real value from the game's Kotlin code. The `unciv.api.real()` function can be used to manually get a real Python value from a foreign instance wrapper. +A wrapper object does not store any more values than that. When it is evaluated, it uses a simple IPC protocol to request an up-to-date real value from the game's Kotlin code. The `unciv_lib.api.real()` function can be used to manually get a real Python value from a foreign instance wrapper. However, because the wrapper class implements many Magic Methods that automatically call its evaluation method, many programming idioms common in Python are possible with them even without manual evaluation. Comparisons, equality, arithmetic, concatenation, inplace operations and more are all supported. @@ -47,7 +47,7 @@ A **token** is a string that has been generated by `InstanceTokenizer.kt` to represent a Kotlin instance. -The `unciv.api.isForeignToken()` function can be used to check whether a Python object is either a foreign token string or a wrapper that resolves to a foreign token string. +The `unciv_lib.api.isForeignToken()` function can be used to check whether a Python object is either a foreign token string or a wrapper that resolves to a foreign token string. When a Kotlin/JVM path requested by a script resolves to an immutable primative like a number or boolean, or something that is otherwise practical to serialize, then the value returned to Python is usually a real object. @@ -146,106 +146,57 @@ --- -Some of this may not yet be fully implemented. +The top-level namespace of the API can be accessed as a module in any script imported from it. + +This is useful when loading external scripts. + +```python3 +import unciv + +def printCivilizations(): + for civ in unciv.gameInfo.civilizations: + print(f"{unciv.real(civ.nation.name)}: {len(civ.cities)} cities") +``` + +--- The Python-specific behaviour is also not meant as a hard standard, in that it doesn't have to be copied exactly in any other languages. Some other design may be more suited for ECMAScript, Lua, and other possible backends. If implementing another language, I think some attempt should still be made to keep a similar API and feature parity, though. """ try: - - # This is all a very messy code structure, but it's literally the most surface-facing part of the entire scripting API on which nothing depends and failures are easy to debug, and since it's making changes to `sys`, I'm not sure how I feel about hiding parts of it in a module. - # Otherwise, I would clean it up a bit and move it into `api.py`. - import sys, json + import sys, types stdout = sys.stdout - import unciv - - try: - unciv.wrapping.ForeignObject.__doc__ = (unciv.wrapping.ForeignObject.__doc__ or "") + "\n\n\n---\n\n" + __doc__ - except: - pass + import unciv_lib - foreignActionSender = unciv.ipc.ForeignActionSender() + uncivModule = types.ModuleType(name='unciv', doc=__doc__) - apiScope = {} - # TODO Use an empty/dummy module's `.__dict__`, so scripts have something in the module map that they can import. + sys.modules['unciv'] = uncivModule - apiScope.update(unciv.api.Expose) + apiScope = uncivModule.__dict__ + # None of this will work on Upy. - apiScope['help'] = lambda thing=None: print(__doc__) if thing is None else help(thing) + apiScope.update(unciv_lib.api.Expose) + apiScope['help'] = lambda *a, **kw: print(__doc__) if thing is None else help(*a, **kw) - class fsdebug: - pass - fsdebug = fsdebug() - fsdebug.__dict__ = apiScope + foreignAutocompleter = unciv_lib.autocompletion.PyAutocompleteManager(apiScope, **unciv_lib.api.autocompleterkwargs) - foreignAutocompleter = unciv.autocompletion.PyAutocompleteManager(apiScope, **unciv.api._autocompleterkwargs) - - class ForeignUncivActionReplReceiver(unciv.ipc.ForeignActionReceiver): - def populateApiScope(self): - names = dir(unciv.wrapping.ForeignObject((), foreignrequester=foreignActionSender.GetForeignActionResponse)) - for n in names: - if n not in self.scope: - self.scope[n] = unciv.wrapping.ForeignObject(n, foreignrequester=foreignActionSender.GetForeignActionResponse) - def passMic(self): - """Send a 'PassMic' packet.""" - foreignActionSender.SendForeignAction({'action':None, 'identifier': None, 'data':None, 'flags':('PassMic',)}) - @unciv.ipc.receiverMethod('motd', 'motd_response') - def EvalForeignMotd(self, packet): - self.populateApiScope() - self.passMic() - return f""" -sys.implementation == {str(sys.implementation)} - -Press [TAB] at any time to trigger autocompletion at the current cursor position, or display help text for an empty function call. - -""" - @unciv.ipc.receiverMethod('autocomplete', 'autocomplete_response') - def EvalForeignAutocomplete(self, packet): - assert 'PassMic' in packet.flags, f"Expected 'PassMic' in packet flags: {packet}" - res = foreignAutocompleter.GetAutocomplete(packet.data["command"], packet.data["cursorpos"]) - self.passMic() - return res - @unciv.ipc.receiverMethod('exec', 'exec_response') - def EvalForeignExec(self, packet): - line = packet.data - assert 'PassMic' in packet.flags, f"Expected 'PassMic' in packet flags: {packet}" - with unciv.ipc.FakeStdout() as fakeout: - print(f">>> {str(line)}") - try: - try: - code = compile(line, 'STDIN', 'eval') - except SyntaxError: - exec(compile(line, 'STDIN', 'exec'), self.scope, self.scope) - else: - print(eval(code, self.scope, self.scope)) - except Exception as e: - print(unciv.utils.formatException(e)) - finally: - self.passMic() - return fakeout.getvalue() - - @unciv.ipc.receiverMethod('terminate', 'terminate_response') - def EvalForeignTerminate(self, packet): - return None - - foreignActionReceiver = ForeignUncivActionReplReceiver(scope=apiScope) + foreignActionReceiver = unciv_lib.api.UncivReplTransciever(scope=apiScope, autocompleter=foreignAutocompleter) foreignActionReceiver.ForeignREPL() - # Disable this to run manually with `python3 -i main.py` for debug. raise RuntimeError("No REPL. Did you forget to uncomment a line in `main.py`?") except Exception as e: # try: -# import unciv.utils -# exc = unciv.utils.formatException(e) +# import unciv_lib.utils +# exc = unciv_lib.utils.formatException(e) # # Disable this. A single line with undefined format is more likely to be printed than multiple. # except: # exc = repr(e) diff --git a/android/assets/scripting/enginefiles/python/unciv/api.py b/android/assets/scripting/enginefiles/python/unciv/api.py deleted file mode 100644 index 82f7e02f795fd..0000000000000 --- a/android/assets/scripting/enginefiles/python/unciv/api.py +++ /dev/null @@ -1,77 +0,0 @@ -import json, os, builtins - -from . import ipc - -_enginedir = os.path.dirname(__file__) - -def _readlibfile(fp): - try: - # In an Unciv scripting backend, `SourceManager.kt` uses LibGDX to merge `sharedfiles/` with `enginedaata/{engine}/` in a temporary directory. - with open(os.path.join(_enginedir, "..", fp)) as file: - return file.read() - except: - # For debug with standalone Python, `sharedfiles` has to be accessed manually. - with open(os.path.join(_enginedir, "../../../sharedfiles", fp)) as file: - return file.read() - -_apiconstants = json.loads(_readlibfile("ScriptAPIConstants.json")) - - -Expose = {} - -def expose(name=None): - def _expose(obj): - Expose[name or obj.__name__] = obj - return obj - return _expose - - -def _get_keys(obj): - try: - return obj.keys() - except (AttributeError, ipc.ForeignError): - return () - -def _get_doc(obj): - try: - if isinstance(obj, wrapping.ForeignObject): - doc = f"\n\n{str(obj._docstring_() or wrapping.stringPathList(obj._getpath_()))}\n\nArguments:\n" - doc += "\n".join(f"\t{argname}: {argtype}" for argname, argtype in obj._args_()) - return doc - else: - return obj.__doc__ - except AttributeError: - return None - - -@expose() -def callable(obj): - if isinstance(obj, wrapping.ForeignObject): - return obj._callable_(raise_exceptions=False) - else: - return builtins.callable(obj) - - -_autocompleterkwargs = { - 'get_keys': _get_keys, - 'get_doc': _get_doc, - 'check_callable': callable -} - - -@expose() -def real(obj): - """Evaluate a foreign object wrapper into a real Python value, or return a value unchanged if it is not a foreign object wrapper.""" - if isinstance(obj, wrapping.ForeignObject): - return obj._getvalue_() - return obj - -@expose() -def isForeignToken(obj): - """Return whether an object represents a token for a non-serializable foreign object.""" - resolved = real(obj) - return isinstance(resolved, str) and resolved.startswith(_apiconstants['kotlinInstanceTokenPrefix']) - - -from . import wrapping -# Should only need it at run time anyway, so makes a circular import more predictable. Basically, this whole file gets prepended to `wrapping` under the `api` name. diff --git a/android/assets/scripting/enginefiles/python/unciv/__init__.py b/android/assets/scripting/enginefiles/python/unciv_lib/__init__.py similarity index 100% rename from android/assets/scripting/enginefiles/python/unciv/__init__.py rename to android/assets/scripting/enginefiles/python/unciv_lib/__init__.py diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/api.py b/android/assets/scripting/enginefiles/python/unciv_lib/api.py new file mode 100644 index 0000000000000..5cd89e6ff35f6 --- /dev/null +++ b/android/assets/scripting/enginefiles/python/unciv_lib/api.py @@ -0,0 +1,137 @@ +import json, os, builtins, sys + +from . import ipc, utils + +enginedir = os.path.dirname(__file__) + +def readlibfile(fp): + """Return the text contents of a file that should be available""" + try: + # In an Unciv scripting backend, `SourceManager.kt` uses LibGDX to merge `sharedfiles/` with `enginedaata/{engine}/` in a temporary directory. + with open(os.path.join(enginedir, "..", fp)) as file: + return file.read() + except OSError: + # For debug with standalone Python, `sharedfiles` has to be accessed manually. + with open(os.path.join(enginedir, "../../../sharedfiles", fp)) as file: + return file.read() + +apiconstants = json.loads(readlibfile("ScriptAPIConstants.json")) + + +Expose = {} + +def expose(name=None): + """Returns a decorator that adds objects to a mapping of names to expose in the Unciv Python scripting API.""" + def _expose(obj): + Expose[name or obj.__name__] = obj + return obj + return _expose + + +def get_keys(obj): + """Get keys of object. Fail silently if it has no keys. Used to let PyAutocompleter work with ForeignObject.""" + try: + return obj.keys() + except (AttributeError, ipc.ForeignError): + return () + +def get_doc(obj): + """Get docstring of object. Fail silently if it has none, or generate one if it's a ForeignObject(). Used for PyAutocompleter.""" + try: + if isinstance(obj, wrapping.ForeignObject): + doc = f"\n\n{str(obj._docstring_() or wrapping.stringPathList(obj._getpath_()))}\n\nArguments:\n" + doc += "\n".join(f"\t{argname}: {argtype}" for argname, argtype in obj._args_()) + return doc + else: + return obj.__doc__ + except AttributeError: + return None + + +@expose() +def callable(obj): + """Return whether or not an object is callable. Used to let PyAutocompleter work with ForeignObject""" + if isinstance(obj, wrapping.ForeignObject): + return obj._callable_(raise_exceptions=False) + else: + return builtins.callable(obj) + + +autocompleterkwargs = { + 'get_keys': get_keys, + 'get_doc': get_doc, + 'check_callable': callable +} + + +@expose() +def real(obj): + """Evaluate a foreign object wrapper into a real Python value, or return a value unchanged if it is not a foreign object wrapper.""" + if isinstance(obj, wrapping.ForeignObject): + return obj._getvalue_() + return obj + +@expose() +def isForeignToken(obj): + """Return whether an object represents a token for a non-serializable foreign object.""" + resolved = real(obj) + return isinstance(resolved, str) and resolved.startswith(apiconstants['kotlinInstanceTokenPrefix']) + + +class UncivReplTransciever(ipc.ForeignActionReceiver, ipc.ForeignActionSender): + """Class that implements the Unciv IPC and scripting protocol by receiving and responding to its packets.""" + def __init__(self, *args, autocompleter=None, **kwargs): + ipc.ForeignActionReceiver.__init__(self, *args, **kwargs) + self.autocompleter = autocompleter + def populateApiScope(self): + """Use dir() on a foreign object wrapper with an empty path to populate the execution scope with all available names.""" + names = dir(wrapping.ForeignObject((), foreignrequester=self.GetForeignActionResponse)) + for n in names: + if n not in self.scope: + self.scope[n] = wrapping.ForeignObject(n, foreignrequester=self.GetForeignActionResponse) + def passMic(self): + """Send a 'PassMic' packet.""" + self.SendForeignAction({'action':None, 'identifier': None, 'data':None, 'flags':('PassMic',)}) + @ipc.receiverMethod('motd', 'motd_response') + def EvalForeignMotd(self, packet): + """Populate the exeuction scope, and then reply to a MOTD request.""" + self.populateApiScope() + self.passMic() + return f""" +sys.implementation == {str(sys.implementation)} + +Press [TAB] at any time to trigger autocompletion at the current cursor position, or display help text for an empty function call. + +""" + @ipc.receiverMethod('autocomplete', 'autocomplete_response') + def EvalForeignAutocomplete(self, packet): + assert 'PassMic' in packet.flags, f"Expected 'PassMic' in packet flags: {packet}" + res = self.autocompleter.GetAutocomplete(packet.data["command"], packet.data["cursorpos"]) if self.autocompleter else "No autocompleter set." + self.passMic() + return res + @ipc.receiverMethod('exec', 'exec_response') + def EvalForeignExec(self, packet): + line = packet.data + assert 'PassMic' in packet.flags, f"Expected 'PassMic' in packet flags: {packet}" + with ipc.FakeStdout() as fakeout: + print(f">>> {str(line)}") + try: + try: + code = compile(line, 'STDIN', 'eval') + except SyntaxError: + exec(compile(line, 'STDIN', 'exec'), self.scope, self.scope) + else: + print(eval(code, self.scope, self.scope)) + except Exception as e: + print(utils.formatException(e)) + finally: + self.passMic() + return fakeout.getvalue() + + @ipc.receiverMethod('terminate', 'terminate_response') + def EvalForeignTerminate(self, packet): + return None + + +from . import wrapping +# Should only need it at run time anyway, so import at end makes a circular import more predictable. Basically, this whole file gets prepended to `wrapping` under the `api` name. diff --git a/android/assets/scripting/enginefiles/python/unciv/autocompletion.py b/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py similarity index 100% rename from android/assets/scripting/enginefiles/python/unciv/autocompletion.py rename to android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py diff --git a/android/assets/scripting/enginefiles/python/unciv/ipc.py b/android/assets/scripting/enginefiles/python/unciv_lib/ipc.py similarity index 92% rename from android/assets/scripting/enginefiles/python/unciv/ipc.py rename to android/assets/scripting/enginefiles/python/unciv_lib/ipc.py index 0819d5d8a2352..42345d11b0af5 100644 --- a/android/assets/scripting/enginefiles/python/unciv/ipc.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/ipc.py @@ -6,8 +6,8 @@ ## return eval(path, scope, scope) # raise NotImplementedError() - class IpcJsonEncoder(json.JSONEncoder): + """JSONEncoder that lets classes define a special ._ipcjson_() method to control how they'll be serialized. Used by ForeignObject to send its resolved value.""" def default(self, obj): if hasattr(obj.__class__, '_ipcjson_'): return obj._ipcjson_() @@ -15,12 +15,14 @@ def default(self, obj): def MakeUniqueId(): + """Return a string that should never repeat or collide. Used for IPC packet identity fields.""" return f"{time.time_ns()}-{random.getrandbits(30)}" class ForeignError(RuntimeError): pass class ForeignPacket: + """Class for IPC packet conforming to spec in ScriptingProtocol.kt.""" def __init__(self, action, identifier, data, flags=()): self.action = action self.identifier = identifier @@ -30,6 +32,7 @@ def __repr__(self): return self.__class__.__name__+"(**"+str(self.as_dict())+")" @classmethod def deserialized(cls, serialized): + """Return a packet object from a JSON string.""" return cls(**json.loads(serialized)) def enforce_type(self, expect_action=None, expect_identifier=None): if expect_action is not None and self.action != expect_action: @@ -47,8 +50,6 @@ def as_dict(self): def serialized(self): return json.dumps(self.as_dict(), cls=IpcJsonEncoder) -# Flags: 'FinishEval', 'BeginIteration', 'StopIteration' - class ForeignActionManager: def __init__(self, sender=None, receiver=None): @@ -64,20 +65,11 @@ def sender(self, message): print(message, file=stdout) def receiver(self): return sys.stdin.readline() -# def EncodeForeignAction(self, action, identifier=None, **data): -# return ForeignPacket( -# action = action, -# identifier = identifier, -# data = data -# ).serialized() class ForeignActionSender(ForeignActionManager): -# def MakeUniqueID(self): -# return 4 # Chosen by fair dice roll. Guaranteed to be random. def SendForeignAction(self, actionparams): self.sender(ForeignPacket(**actionparams).serialized()) - def GetForeignActionResponse(self, actionparams, responsetype): identifier = MakeUniqueId() self.SendForeignAction({**actionparams, 'identifier': identifier}) @@ -125,6 +117,7 @@ def ForeignREPL(self): class FakeStdout: + """Context manager that returns a StringIO and sets sys.stdout to it on entrance, then restores it to its original value on exit.""" def __init__(self): self.stdout = sys.stdout def __enter__(self): diff --git a/android/assets/scripting/enginefiles/python/unciv/utils.py b/android/assets/scripting/enginefiles/python/unciv_lib/utils.py similarity index 100% rename from android/assets/scripting/enginefiles/python/unciv/utils.py rename to android/assets/scripting/enginefiles/python/unciv_lib/utils.py diff --git a/android/assets/scripting/enginefiles/python/unciv/wrapping.py b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py similarity index 98% rename from android/assets/scripting/enginefiles/python/unciv/wrapping.py rename to android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py index a401da7ae83e2..ca0822bb3854f 100644 --- a/android/assets/scripting/enginefiles/python/unciv/wrapping.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py @@ -5,7 +5,7 @@ class ForeignRequestMethod: - """Decorator for methods that return values from foreign requests.""" + """Decorator and descriptor protocol implementation for methods of ForeignObject subclasses that return values from foreign requests.""" def __init__(self, func): self.func = func try: diff --git a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt index 2b85956cec486..2b4e22168d829 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt @@ -221,6 +221,12 @@ Flags are string values for communicating extra information that doesn't need a //Not implemented. Currently Kotlin-side exceptions while processing a request are communicated to the script interpreter with an error message at an expected place in the data field of the response, and exceptions in the script interpreter while executing a command are stringified and returned to the Kotlin side the same as any REPL output. Miight be useful for a modding API, though. ``` + ``` +// 'BeginIteration' +// 'StopIteration' +// //Not implemented. Probably needed if iteration over non-sized objects is needed. Probably not worth the trouble. + ``` + --- Thus, at the IPC level, all foreign backends will actually use the same language, which is this JSON-based protocol. Differences between Python, JS, Lua, etc. will all be down to how they interpret the "exec", "autocomplete", and "motd" requests differently, which each high-level scripting language is free to implement as works best for it. From b466f775f6a43187fbbc609a071ca00854268ee4 Mon Sep 17 00:00:00 2001 From: will-ca Date: Fri, 12 Nov 2021 10:44:43 +0000 Subject: [PATCH 34/93] Add raw string REPL back in to re-enable JS and Lua backends. --- .../com/unciv/scripting/ScriptingBackend.kt | 18 +++++++--- .../protocol/ScriptingReplManager.kt | 33 +++++++++++++++++-- .../src/com/unciv/scripting/utils/Blackbox.kt | 2 -- 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/core/src/com/unciv/scripting/ScriptingBackend.kt b/core/src/com/unciv/scripting/ScriptingBackend.kt index 3feb937a66bcd..28a13301aa864 100644 --- a/core/src/com/unciv/scripting/ScriptingBackend.kt +++ b/core/src/com/unciv/scripting/ScriptingBackend.kt @@ -5,10 +5,11 @@ import com.badlogic.gdx.files.FileHandle import com.unciv.scripting.reflection.Reflection import com.unciv.scripting.protocol.ScriptingReplManager +import com.unciv.scripting.protocol.ScriptingProtocolReplManager +import com.unciv.scripting.protocol.ScriptingRawReplManager import com.unciv.scripting.protocol.SubprocessBlackbox import com.unciv.scripting.utils.ApiSpecGenerator import com.unciv.scripting.utils.Blackbox -import com.unciv.scripting.utils.DummyBlackbox import com.unciv.scripting.utils.SourceManager import com.unciv.scripting.utils.SyntaxHighlighter import kotlin.reflect.full.* @@ -403,8 +404,8 @@ abstract class BlackboxScriptingBackend(scriptingScope: ScriptingScope): Environ abstract val blackbox: Blackbox - val replManager: ScriptingReplManager by lazy { ScriptingReplManager(scriptingScope, blackbox) } - // Was originally a method that could be called by subclasses' constructors. This seems cleaner. Subclasses don't even have to define any functions this way. And the liberal use of `lazy` should naturally make sure the properties will always be initialized in the right order. + abstract val replManager: ScriptingReplManager + // Should be lazy. Was originally a method that could be called by subclasses' constructors. This seems cleaner. Subclasses don't even have to define any functions this way. And the liberal use of `lazy` should naturally make sure the properties will always be initialized in the right order. // Downside: Potential latency on first command, or possibly depending on `motd()` for immediate initialization. override fun motd(): String { @@ -447,13 +448,22 @@ abstract class SubprocessScriptingBackend(scriptingScope: ScriptingScope): Black override val blackbox by lazy { SubprocessBlackbox(processCmd) } + override val replManager: ScriptingReplManager by lazy { ScriptingRawReplManager(scriptingScope, blackbox) } + override fun motd(): String { return "\n\nWelcome to the Unciv '${metadata.displayName}' API. This backend relies on running the system `${processCmd.firstOrNull()}` command as a subprocess.\n\nIf you do not have an interactive REPL below, then please make sure the below command is valid on your system:\n\n${processCmd.joinToString(" ")}\n\n${super.motd()}\n" } } -class SpyScriptingBackend(scriptingScope: ScriptingScope): SubprocessScriptingBackend(scriptingScope) { +abstract class ProtocolSubprocessScriptingBackend(scriptingScope: ScriptingScope): SubprocessScriptingBackend(scriptingScope) { + + override val replManager by lazy { ScriptingProtocolReplManager(scriptingScope, blackbox) } + +} + + +class SpyScriptingBackend(scriptingScope: ScriptingScope): ProtocolSubprocessScriptingBackend(scriptingScope) { companion object Metadata: EnvironmentedScriptBackend_metadata() { override fun new(scriptingScope: ScriptingScope) = SpyScriptingBackend(scriptingScope) diff --git a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt index 978375d8e0965..a61fc8d6ca452 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt @@ -37,11 +37,38 @@ You would presumably have to interrupt the main Kotlin-side thread anyway in ord Plus, letting the script interpreter run completely in parallel would probably introduce potential for all sorts of issues with non-deterministic synchronicity, and performance issues Calling the script interpreter from the Kotlin side means that the state of the Kotlin side is more predictable at the moment of script execution. */ -//interface ScriptingReplManager { -//} TODO +abstract class ScriptingReplManager(val scriptingScope: ScriptingScope, val blackbox: Blackbox): ScriptingBackend { +} + + +class ScriptingRawReplManager(scriptingScope: ScriptingScope, blackbox: Blackbox): ScriptingReplManager(scriptingScope, blackbox) { + // REPL manager that sends and receives only raw code and print strings to a black box. Allows interacting with an external script interpreter, but not suitable for exposing internal API in external scripts. + + override fun motd(): String { + return "${exec("motd()\n")}\n" + } + + override fun autocomplete(command: String, cursorPos: Int?): AutocompleteResults { + return AutocompleteResults() + } + + override fun exec(command: String): String { + if (!blackbox.readyForWrite) { + throw IllegalStateException("REPL not ready: ${blackbox}") + } else { + blackbox.write(command) + return blackbox.readAll(block=true).joinToString("\n") + } + } + + override fun terminate(): Exception? { + return blackbox.stop() + } +} -class ScriptingReplManager(val scriptingScope: ScriptingScope, val blackbox: Blackbox): ScriptingBackend { +class ScriptingProtocolReplManager(scriptingScope: ScriptingScope, blackbox: Blackbox): ScriptingReplManager(scriptingScope, blackbox) { + // REPL manager that uses the IPC protocol defined in ScriptingProtocol to communicate with a black box. Suitable for presenting arbitrary access to Kotlin/JVM state to scripting APIs. See file-level comment. val instanceSaver = mutableListOf() // ScriptingProtocol puts references to pre-tokenized returned objects in here. diff --git a/core/src/com/unciv/scripting/utils/Blackbox.kt b/core/src/com/unciv/scripting/utils/Blackbox.kt index 3e35f423da61d..ded54a43ca9d2 100644 --- a/core/src/com/unciv/scripting/utils/Blackbox.kt +++ b/core/src/com/unciv/scripting/utils/Blackbox.kt @@ -36,5 +36,3 @@ interface Blackbox { } -class DummyBlackbox(): Blackbox { -} From 1993c7f02edddfa4cad0775f08a2c16574b9dc9a Mon Sep 17 00:00:00 2001 From: will-ca Date: Sat, 13 Nov 2021 02:53:57 +0000 Subject: [PATCH 35/93] Convert existing doc comments to valid KDoc. Also fix QJS eating CPU on exit. --- .gitignore | 3 + .../assets/scripting/enginefiles/lua/main.lua | 5 +- .../scripting/enginefiles/python/main.py | 33 +- .../enginefiles/python/unciv_lib/api.py | 7 +- .../enginefiles/python/unciv_lib/wrapping.py | 14 +- .../assets/scripting/enginefiles/qjs/main.js | 34 ++- core/Module.md | 285 ++++++++++++++++++ .../com/unciv/scripting/ScriptingBackend.kt | 123 ++++---- .../src/com/unciv/scripting/ScriptingScope.kt | 19 +- .../src/com/unciv/scripting/ScriptingState.kt | 43 +-- .../scripting/protocol/ScriptingProtocol.kt | 244 +-------------- .../protocol/ScriptingReplManager.kt | 80 ++--- .../unciv/scripting/reflection/Reflection.kt | 28 +- .../src/com/unciv/scripting/utils/Blackbox.kt | 38 ++- .../scripting/utils/InstanceFactories.kt | 4 +- .../unciv/scripting/utils/InstanceRegistry.kt | 9 +- .../unciv/scripting/utils/SourceManager.kt | 14 +- .../unciv/scripting/utils/TokenizingJson.kt | 26 +- 18 files changed, 547 insertions(+), 462 deletions(-) create mode 100644 core/Module.md diff --git a/.gitignore b/.gitignore index a0af9db6286e1..9ec1b037bacc6 100644 --- a/.gitignore +++ b/.gitignore @@ -149,3 +149,6 @@ GameSettings.json # Python *.pyc __pycache__ + +# Geany +.geany diff --git a/android/assets/scripting/enginefiles/lua/main.lua b/android/assets/scripting/enginefiles/lua/main.lua index 9d344968f8f4c..a21f02f41865f 100644 --- a/android/assets/scripting/enginefiles/lua/main.lua +++ b/android/assets/scripting/enginefiles/lua/main.lua @@ -2,10 +2,11 @@ io.stdout:setvbuf('full') -function motd () - return "\n\nWelcome to the Lua Unciv CLI. Currently, this backend relies on launching the system `lua` command.\n\nRunning ".._VERSION..".\n\n" +function motd () + return "\nRunning ".._VERSION..".\n\nThis backend is HIGHLY EXPERIMENTAL. It does not implement any API bindings yet, and it may not be stable. Use it at your own risk!\n\n" end + while true do _in = io.stdin:read() io.stdout:write("> ".._in.."\n") diff --git a/android/assets/scripting/enginefiles/python/main.py b/android/assets/scripting/enginefiles/python/main.py index ccfec51303779..1a6d1857c0596 100644 --- a/android/assets/scripting/enginefiles/python/main.py +++ b/android/assets/scripting/enginefiles/python/main.py @@ -30,7 +30,7 @@ ```python3 civInfo.cities[0].name = "Metropolis" -# Uses IPC request to modify Kotlin/JVM object. +# Uses IPC request to modify Kotlin/JVM property. civInfo.cities[0].cityConstructions.constructionQueue[0] = "Missile Cruiser" # Uses IPC request to modify Kotlin/JVM container. @@ -73,7 +73,7 @@ # True. This is actually a wrapper, but `isForeignToken` returns True based on evaluated results. ``` -The original instance is stored in the JVM in a mapping as a weak reference. The string doesn't have (m)any special properties as a Python object. But if it is sent back to Kotlin/the JVM at any point, then it will be parsed and substitued with the original instance (provided the original instance still exists). +The original instance is stored in the JVM in a mapping as a weak reference. The string doesn't have any special properties as a Python object. But if it is sent back to Kotlin/the JVM at any point, then it will be parsed and substituted with the original instance (provided the original instance still exists). This is meant to allow Kotlin/JVM instances to be, E.G., used as function arguments and mapping keys from scripts. @@ -164,35 +164,36 @@ def printCivilizations(): """ + try: - + import sys, types stdout = sys.stdout - + import unciv_lib - - + + uncivModule = types.ModuleType(name='unciv', doc=__doc__) - + sys.modules['unciv'] = uncivModule - + apiScope = uncivModule.__dict__ # None of this will work on Upy. - + apiScope.update(unciv_lib.api.Expose) - + apiScope['help'] = lambda *a, **kw: print(__doc__) if thing is None else help(*a, **kw) - - + + foreignAutocompleter = unciv_lib.autocompletion.PyAutocompleteManager(apiScope, **unciv_lib.api.autocompleterkwargs) - + foreignActionReceiver = unciv_lib.api.UncivReplTransciever(scope=apiScope, autocompleter=foreignAutocompleter) - + foreignActionReceiver.ForeignREPL() - + raise RuntimeError("No REPL. Did you forget to uncomment a line in `main.py`?") - + except Exception as e: # try: # import unciv_lib.utils diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/api.py b/android/assets/scripting/enginefiles/python/unciv_lib/api.py index 5cd89e6ff35f6..fd09d8d790e26 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/api.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/api.py @@ -2,6 +2,7 @@ from . import ipc, utils + enginedir = os.path.dirname(__file__) def readlibfile(fp): @@ -14,7 +15,7 @@ def readlibfile(fp): # For debug with standalone Python, `sharedfiles` has to be accessed manually. with open(os.path.join(enginedir, "../../../sharedfiles", fp)) as file: return file.read() - + apiconstants = json.loads(readlibfile("ScriptAPIConstants.json")) @@ -46,7 +47,7 @@ def get_doc(obj): return obj.__doc__ except AttributeError: return None - + @expose() def callable(obj): @@ -127,7 +128,7 @@ def EvalForeignExec(self, packet): finally: self.passMic() return fakeout.getvalue() - + @ipc.receiverMethod('terminate', 'terminate_response') def EvalForeignTerminate(self, packet): return None diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py index ca0822bb3854f..9a1ec3fe03807 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py @@ -54,16 +54,16 @@ def _reversedop(a, b, *args, **kwargs): def dummyForeignRequester(actionparams, responsetype): return actionparams, responsetype - - + + def foreignValueParser(packet, *, raise_exceptions=True): - """Value parse that reads a foreign request packet fitting a common structure.""" + """Value parser that reads a foreign request packet fitting a common structure.""" if packet.data["exception"] is not None and raise_exceptions: raise ipc.ForeignError(packet.data["exception"]) return packet.data["value"] def foreignErrmsgChecker(packet): - """Value parse that processes a foreign request packet fitting a simple structure.""" + """Value parser that processes a foreign request packet fitting a simple structure.""" if packet.data is not None: raise ipc.ForeignError(packet.data) @@ -160,7 +160,7 @@ def alreadyhas(name): @ResolveForOperators class ForeignObject: - """Wrapper for a foreign object.""" + """Wrapper for a foreign object Implements ScriptingProtocol.""" def __init__(self, path, foreignrequester=dummyForeignRequester): object.__setattr__(self, '_path', (makePathElement(name=path),) if isinstance(path, str) else tuple(path)) object.__setattr__(self, '_foreignrequester', foreignrequester) @@ -283,7 +283,7 @@ def __len__(self): } }, 'length_response', - foreignValueParser) + foreignValueParser) @ForeignRequestMethod def __contains__(self, item): return ({ @@ -304,7 +304,7 @@ def keys(self): } }, 'keys_response', - foreignValueParser) + foreignValueParser) def values(self): return (self[k] for k in self.keys()) def entries(self): diff --git a/android/assets/scripting/enginefiles/qjs/main.js b/android/assets/scripting/enginefiles/qjs/main.js index d21112d4a89ae..5c55ef32b7df9 100644 --- a/android/assets/scripting/enginefiles/qjs/main.js +++ b/android/assets/scripting/enginefiles/qjs/main.js @@ -1,18 +1,26 @@ -//import("std"); - function motd() { - return "\n\nWelcome to the QuickJS Unciv CLI. Currently, this backend relies on launching the system `qjs` command.\n\n" + return "\nThis backend is HIGHLY EXPERIMENTAL. It does not implement any API bindings yet, and it may not be stable. Use it at your own risk!\n\n" } -while (true) { - let line = std.in.getline(); - let out = `qjs > ${line}\n`; - try { - out += String(eval(line)); - } catch (e) { - out += String(e) + +try { + while (true) { + let line = std.in.getline(); + if (line === null) { + // std.in.getline() returns null in case of IOError, broken pipes. So this check prevents it from eating 100% CPU in a loop, which could previously happen if you closed Unciv with the window button. + throw Error("Null on STDIN.") + } + let out = `qjs > ${line}\n`; + try { + out += String(eval(line)); + } catch (e) { + out += String(e) + } + out += "\n" + std.out.puts(out) + std.out.flush() } - out += "\n" - std.out.puts(out) - std.out.flush() +} catch (e) { +} finally { + std.exit() } diff --git a/core/Module.md b/core/Module.md new file mode 100644 index 0000000000000..a09827a64f589 --- /dev/null +++ b/core/Module.md @@ -0,0 +1,285 @@ + +# Package com.unciv.scripting + +## Class Overview + +The major classes involved in the scripting API are structured as follows. `UpperCamelCase()` and parentheses means a new instantiation of a class. `lowerCamelCase` means a reference to an already-existing instance. An asterisk at the start of an item means zero or multiple instances of that class may be held. A question mark at the start of an item means that it may not exist in all implementations of the parent base class/interface. A question mark at the end of an item means that it is nullable, or otherwise may not be available in all states. + +``` +UncivGame(): + ScriptingState(): // Persistent per UncivGame(). + ScriptingScope(): + civInfo? // These are set by WorldScreen init, and unset by MainMenuScreen. + gameInfo? + uncivGame + worldScreen? + *ScriptingBackend(): + scriptingScope + ?ScriptingReplManager(): + Blackbox() // Common interface to wrap foreign interpreter with pipes, STDIN/STDOUT, queues, sockets, embedding, JNI, etc. + scriptingScope + ScriptingProtocol(): + scriptingScope + ?folderHandler: setupInterpreterEnvironment() // If used, a temporary directory with file structure copied from engine and shared folders in `assets/scripting`. + ConsoleScreen(): // Persistent as long as window isn't resized. Recreates itself and restores most of its state from scriptingState if resized. + scriptingState +WorldScreen(): + consoleScreen + scriptingState // ScriptingState has getters and setters that wrap scriptingScope, which WorldScreen uses to update game info. +MainMenuScreen(): + consoleScreen + scriptingState // Same as for worldScreen. +InstanceTokenizer() // Holds WeakRefs used by ScriptingProtocol. Unserializable objects get strings as placeholders, and then turned back into into objects if seen again. +Reflection() // Used by some hard-coded scripting backends, and essential to dynamic bindings in ScriptingProtocol(). +SourceManager() // Source of the folderHandler and setupInterpreterEnvironment() above. +TokenizingJson() // Serializer and functions that use InstanceTokenizer. +``` + + +# Package com.unciv.scripting.protocol + +## REPL Loop + +1. A scripted action is initiated from the Kotlin side, by sending a command string to the script interpreter. + 1.a. While the script interpreter is running, it has a chance to request values from the Kotlin side by sending back packets encoding attribute/property, key, and call, and assignment stacks. + 1.b. When the Kotlin side receives a request for a value, it uses reflection to access the requested property or call the requested method, and it sends the result to the script interpreter. + 1.c. When the script interpreter finishes running, it sends a special packet to the Kotlin side communicating that the script interpreter has no more requests to make. The script interpreter then sends the REPL output of the command to the Kotlin side. +2. When the Kotlin interpreter receives the packet marking the end of the command run, it stops listening for value requests packets. It then receives the commnad result as the next value, and passes it back to the console screen or script handler. + +From Kotlin: +``` +fun ExecuteCommand(command:String): + SendToInterpreter(command) + while True: + packet:Packet = ReceiveFromInterpreter().parsed() + if isPropertyRequest(packet): + SendToInterpreter(ResolvePacket(scriptingScope, packet)) + else if isCommandEndPacket(packet): + break + PrintToConsole(ReceiveFromInterpreter().parsed().data:String) +``` + +The "packets" should probably all be encoded as strings, probably JSON. The technique used to connect the script interpreter to the Kotlin code shouldn't matter, as long as it's wrapped up in and implements the `Blackbox` interface. IPC/embedding based on pipes, STDIN/STDOUT, sockets, queues, embedding, JNI, etc. should all be interchangeable and equally functional. + +I'm not sure if there'd be much point to or a good technique for letting the script interpreter run constantly and initiate actions on its own, instead of waiting for commands from the Kotlin side. + +You would presumably have to interrupt the main Kotlin-side thread anyway in order to safely run any actions initiated by the script interpreter— Which means that you may as well just register a handler to call the script interpreter at that point. + +Plus, letting the script interpreter run completely in parallel would probably introduce potential for all sorts of issues with non-deterministic synchronicity, and performance issues Calling the script interpreter from the Kotlin side means that the state of the Kotlin side is more predictable at the moment of script execution. + +## IPC Protocol + +A single IPC action consists of one request packet and one response packet. +A request packet should always be followed by a response packet if it has an action. +If a request packet has a null action, then it should not be followed by a response. This is to let flags be sent without generating useless responses. + +Responses do not have to be sent in the same order as their corresponding requests. New requests can be sent out while old ones are left "open"— E.G., if creating a response requires requesting new information. + +(So far, I think all the requests and responses follow a "stack" model, though. If request B is sent out before response A is received, then response B gets received before response A, like parentheses. The time two requests remain open can be completely nested subsets or supersets, or they can be completely separated in series, but they don't currently ever partially overlap.) + +(That said, none of these are hard requirements. If you want to do something fancy with coroutines or whatever and dispatch to multiple open request handlers, and you can make it both stable and language-agnostic, go right ahead.) + +Both the Kotlin side and the script interpreter can send and receive packets, but not necessarily at all times. + +(The current loop is described in a comment in ScriptingReplManager.kt. Kotlin initiates a scripting exec, during which the script interpreter can request values from Kotlin, and at the end of which the script interpreter sends its STDOUT response to the Kotlin side.) + +A single packet is a JSON string of the form: + +``` +{ + "action": String?, + "identifier": String?, + "data": Any?, + "flags": Collection +} +``` + +Identifiers should be set to a unique value in each request. +Each response should have the same identifier as its corresponding request. +Upon receiving a response, both its action and identifier should be checked to match the relevant request. + +--- + +The data field is allowed to represent any hierarchy, of instances of any types. + +If it must represent instances that are not possible or not useful to serialize as JSON hierarchies, then unique identifying token strings should be generated and sent in the places of those instances. + +If those strings are received at any hierarchical depth in the data field of any later packets, then they are to be substituted with their original instances in all uses of the information from those packets. +If the original instance of a received token string no longer exists, then an exception should be thrown, and handled as would any exception at the point where the instance is to be accessed. + +Example Kotlin-side instance requested by script interpreter: + +``` +SomeKotlinInstance@M3mAdDr +``` + +Example response packet to send script interpreter this instance: + +``` +{ + "action": "read_response", + "identifier": "ABC001", + "data": { + "value": "_someStringifiedTokenForSomeKotlinInstance", + "exception": null + }, + "flags": [] +} +``` + +Example subsequent request packet from script interpreter using the token string: + +``` +{ + "action": "assign", + "identifier": "CDE002", + "data": { + "path": [{"type": "Property", "name": "someProperty", "params": []}], + "value": [5, "ActualStringValue", "_someStringifiedTokenForSomeKotlinInstance"] + }, + "flags": [] +} +``` + +Equivalent Kotlin-side assignment operation resulting from this request packet: + +``` +someProperty = listOf(5, "ActualStringValue", SomeKotlinInstance@M3mAdDr) +``` + +(Caveats in practice: The scripting API design is highly asymmetric in that script interpreter needs a lot of access to the Kotlin side's state, but the Kotlin side should rarely or never need the script interpreter's state, so the script interpreter doesn't have to bother implementing its own arbitrary object tokenization. Requests sent by the Kotlin side also all have very simple response formats because of this, while access to and use of complicated Kotlin-side instances is always initiated by a request from the script interpreter while in the execution loop of its REPL, so the Kotlin side bothers implementing arbitrary instance tokenization only when receiving requests and not when receiving responses. Exceptions from reifying invalid received tokens on the Kotlin side should be handled as would any other exceptions at their code paths, but because such tokens are only used on the Kotlin side when preparing a response to a received request from the scripting side, that currently means sending a response packet that is marked in some way as representing an exception and then carrying on as normal.) + +--- + +Some action types, data formats, and expected response data formats for packets sent from the Kotlin side to the script interpreter include: + + ``` + 'motd': null -> + 'motd_response': String + ``` + + ``` + 'autocomplete': {'command': String, 'cursorpos': Int} -> + 'autocomplete_response': Collection or String + //List of matches, or help text to print. + ``` + + ``` + 'exec': String -> + 'exec_response': String + //REPL print. + ``` + + ``` + 'terminate': null -> + 'terminate_response': String? + //Error message or null. + ``` + +The above are basically a mirror of ScriptingBackend, so the same interface can be implemented in the scripting language. + +--- + +Some action types, data formats, and expected response types and formats for packets sent from the script interpreter to the Kotlin side include: + + ``` + 'read': {'path': List<{'type':String, 'name':String, 'params':List}>} -> + 'read_reponse': {'value': Any?, 'exception': String?} + //Attribute/property access, by list of `PathElement` properties. + ``` + + ``` + //'call': {'path': List<{'type':String, 'name':String, 'params':List}>, 'args': Collection, 'kwargs': Map} -> + //'call_response': {'value': Any?, 'exception': String?} + //Method/function call. + //Deprecated and removed. Instead, use `"action":"read"` with a "path" that has `"type":"Call"` element(s) as per `PathElement`. + ``` + + ``` + 'assign': {'path': List<{'type':String, 'name':String, 'params':List}>, 'value': Any} -> + 'assign_response': String? + //Error message or null. + ``` + + ``` + 'delete': {'path': List<{'type':String, 'name':String, 'params':List}>} -> + 'delete_response': String? + //Error message or null. + //Only meaningful and implemented for MutableMap() keys and MutableList() indices. + ``` + + ``` + 'dir': {'path': List<{'type':String, 'name':String, 'params':List}>} -> + 'dir_response': {'value': Collection, 'exception': String?} + //Names of all members/properties/attributes/methods. + ``` + + ``` + 'keys': {'path': List<{'type':String, 'name':String, 'params':List}>} -> + 'keys_response': {'value': Collection, 'exception': String?} + //Keys of Map-interfaced instances. Used by Python bindings for iteration and autocomplete. + ``` + + ``` + 'length': {'path': List<{'type':String, 'name':String, 'params':List}>} -> + 'length_response': {'value': Int?, 'exception': String?} + //Used by Python bindings for length and also for iteration. + ``` + + ``` + 'contains': {'path': List<{'type':String, 'name':String, 'params':List}>, 'value': Any?} -> + 'contains_response': {'value': Boolean?, 'exception': String?} + //Doing this through an IPC call instead of in the script interpreter should let tokenized instances be checked for properly. + ``` + + ``` + 'callable': {'path': List<{'type':String, 'name':String, 'params':List}>} -> + 'callable_response': {'value': Boolean?, 'exception': String?} + //Used by Python autocompleter to add opening bracket to methods and function suggestions. Quite useful for exploring API at a glance. + ``` + + ``` + 'args': 'path': List<{'type':String, 'name':String, 'params':List}>} -> + 'args_response': {'value': List>?, 'exception': String?} + //Names and types of arguments accepted by a function. + //Currently just used by Python autocompleter to generate help text. + //Could also be used to control functions in scripting environment. If so, then names of types should be standardized. + ``` + + ``` + 'docstring': {'path': List<{'type':String, 'name':String, 'params':List}>} -> + 'docstring_response': {'value': String?, 'exception': String?} + //Used by Python wrappers and autocompleter to get help text showing arguments and types for callables. Useful for exploring API without having to browse code. + ``` + +The path elements in some of the data fields mirror PathElement. + +--- + +Flags are string values for communicating extra information that doesn't need a separate packet or response. Depending on the flag and action, they may be contextual to the packet, or they may not. I think I see them mostly as a way to semantically separate meta-communication about the protocol from actual requests for actions: + + ``` + 'PassMic' + //Indicates that the sending side will now begin listening instead, and the receiving side should send the next request. + //Sent by Kotlin side at start of script engine startup/MOTD, autocompletion, and execution to allow script to request values, and should be sent by script interpreter immediately before sending response with results of execution. + ``` + + ``` + //'Exception' + //Indicates that this packet is associated with an error. + //Not implemented. Currently Kotlin-side exceptions while processing a request are communicated to the script interpreter with an error message at an expected place in the data field of the response, and exceptions in the script interpreter while executing a command are stringified and returned to the Kotlin side the same as any REPL output. Miight be useful for a modding API, though. + ``` + + ``` + //'BeginIteration' + //'StopIteration' + //Not implemented. Probably needed if iteration over non-sized objects is needed. Probably not worth the trouble. + ``` + +--- + +Thus, at the IPC level, all foreign backends will actually use the same language, which is this JSON-based protocol. Differences between Python, JS, Lua, etc. will all be down to how they interpret the "exec", "autocomplete", and "motd" requests differently, which each high-level scripting language is free to implement as works best for it. + +## Python Binding Implementation + +A description of how this REPL loop and IPC protocol are used to build scripting langauage bindings is in the module-level docstring of `/android/assets/scripting/enginefiles/python/main.py`. diff --git a/core/src/com/unciv/scripting/ScriptingBackend.kt b/core/src/com/unciv/scripting/ScriptingBackend.kt index 28a13301aa864..b18d885e0e31b 100644 --- a/core/src/com/unciv/scripting/ScriptingBackend.kt +++ b/core/src/com/unciv/scripting/ScriptingBackend.kt @@ -16,10 +16,13 @@ import kotlin.reflect.full.* import java.util.* -data class AutocompleteResults(val matches:List = listOf(), val isHelpText:Boolean = false, val helpText:String = "") +data class AutocompleteResults(val matches: List = listOf(), val isHelpText: Boolean = false, val helpText: String = "") abstract class ScriptingBackend_metadata { + /** + * @return A new instance of the parent class of which this object is a companion. + */ abstract fun new(scriptingScope: ScriptingScope): ScriptingBackendBase abstract val displayName: String val syntaxHighlighting: SyntaxHighlighter? = null @@ -35,41 +38,54 @@ abstract class EnvironmentedScriptBackend_metadata: ScriptingBackend_metadata() interface ScriptingBackend { + /** + * @return Message to print on launch. Should be called exactly once per instance, and prior to calling any of the other methods defined here. + */ fun motd(): String { - // Message to print on launch. Should be called exactly once per instance, and prior to calling any of the other methods defined here. return "\n\nWelcome to the Unciv CLI!\nYou are currently running the dummy backend, which will echo all commands but never do anything.\n" } + /** + * @return AutocompleteResults object that represents either a List of full autocompletion matches or a help string to print. + */ fun autocomplete(command: String, cursorPos: Int? = null): AutocompleteResults { - // Return either an AutocompleteResults object that represents either a List of full autocompletion matches or a help string to print. return AutocompleteResults(listOf(command+"_autocomplete")) } + /** + * @param command Code to execute + * @return REPL printout. + */ fun exec(command: String): String { - // Execute code and return output. return command } + /** + * @return `null` on successful termination, an `Exception()` otherwise. + */ fun terminate(): Exception? { - // Return `null` on successful termination, an `Exception()` otherwise. return null } - + } open class ScriptingBackendBase(val scriptingScope: ScriptingScope): ScriptingBackend { + /** + * For the UI, a way is needed to list all available scripting backend types with 1. A readable display name and 2. A way to create new instances. + * So every ScriptngBackend has a Metadata:ScriptingBackend_metadata companion object, which is stored in the ScriptingBackendType enums. + */ companion object Metadata: ScriptingBackend_metadata() { override fun new(scriptingScope: ScriptingScope) = ScriptingBackendBase(scriptingScope) - override val displayName:String = "Dummy" + override val displayName: String = "Dummy" } - // For the UI, a way is needed to list all available scripting backend types with 1. A readable display name and 2. A way to create new instances. - // So every ScriptngBackend has a Metadata:ScriptingBackend_metadata companion object, which is stored in the ScriptingBackendType enums. + /** + * Let the companion object of the correct subclass be accessed in subclass instances. + */ open val metadata get(): ScriptingBackend_metadata = this::class.companionObjectInstance as ScriptingBackend_metadata - // Let the companion object of the correct subclass be accessed in subclass instances. } @@ -78,7 +94,7 @@ class HardcodedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacken companion object Metadata: ScriptingBackend_metadata() { override fun new(scriptingScope: ScriptingScope) = HardcodedScriptingBackend(scriptingScope) - override val displayName:String = "Hardcoded" + override val displayName: String = "Hardcoded" } val commandshelp = mapOf( @@ -99,7 +115,7 @@ class HardcodedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacken "supercharge" to "supercharge [true|false] - Massively boost all empire growth stats.\n\tRun with no arguments to toggle. (Requires cheats.)" ) - var cheats:Boolean = false + var cheats: Boolean = false override fun motd(): String { return "\n\nWelcome to the hardcoded demo CLI backend.\n\nPlease run \"help\" or press [TAB] to see a list of available commands.\nPress [TAB] at any time to see help for currently typed command.\n\nPlease note that the available commands are meant as a DEMO for the CLI.\n" @@ -140,7 +156,7 @@ class HardcodedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacken appendOut(scriptingScope.civInfo!!.cities.size.toString()) } "locatebuildings" -> { - var buildingcities:List = listOf() + var buildingcities: List = listOf() if (!(scriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } if (args.size > 1) { var searchfor = args.slice(1..args.size-1).joinToString(" ").trim(' ') @@ -156,7 +172,7 @@ class HardcodedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacken appendOut(buildingcities.joinToString(", ")) } "missingbuildings" -> { - var buildingcities:List = listOf() + var buildingcities: List = listOf() if (!(scriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } if (args.size > 1) { var searchfor = args.slice(1..args.size-1).joinToString(" ").trim(' ') @@ -281,7 +297,7 @@ class ReflectiveScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacke companion object Metadata: ScriptingBackend_metadata() { override fun new(scriptingScope: ScriptingScope) = ReflectiveScriptingBackend(scriptingScope) - override val displayName:String = "Reflective" + override val displayName: String = "Reflective" } private val commandparams = mapOf("get" to 1, "set" to 2, "typeof" to 1) //showprivates? @@ -300,7 +316,7 @@ class ReflectiveScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacke override fun motd(): String { return "\n\nWelcome to the reflective Unciv CLI backend.\n\nCommands you enter will be parsed as a path consisting of property reads, key and index accesses, function calls, and string, numeric, boolean, and null literals.\nKeys, indices, and function arguments are parsed recursively.\nProperties can be both read from and written to.\n\nExamples:\n${examples.map({"> ${it}"}).joinToString("\n")}\n\nPress [TAB] at any time to trigger autocompletion for all known leaf names at the currently entered path.\n" } - + override fun autocomplete(command: String, cursorPos: Int?): AutocompleteResults { try { var comm = commandparams.keys.find{ command.startsWith(it+" ") } @@ -330,7 +346,7 @@ class ReflectiveScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacke return AutocompleteResults(listOf(), true, "Could not get autocompletion: ${e}") } } - + override fun exec(command: String): String{ var parts = command.split(' ', limit=2) var out = "\n> ${command}\n" @@ -372,7 +388,7 @@ class ReflectiveScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacke abstract class EnvironmentedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackendBase(scriptingScope) { - + companion object Metadata: EnvironmentedScriptBackend_metadata() { // Need full metadata companion here, or else won't compile. // Ideally would be able to just declare that subclasses must define a companion of the correct type, but ah well. @@ -384,16 +400,16 @@ abstract class EnvironmentedScriptingBackend(scriptingScope: ScriptingScope): Sc override val metadata // Since the companion object type is different, we have to define a new getter for the subclass instance companion getter to get its new members. get() = this::class.companionObjectInstance as EnvironmentedScriptBackend_metadata - + val folderHandle: FileHandle by lazy { SourceManager.setupInterpreterEnvironment(metadata.engine) } // This requires the overridden values for `engine`, so setting it in the constructor causes a null error... May be fixed since moving `engine` to the companions. // Also, BlackboxScriptingBackend inherits from this, but not all subclasses of BlackboxScriptingBackend might need it. So as long as it's not accessed, it won't be intialized. - + } abstract class BlackboxScriptingBackend(scriptingScope: ScriptingScope): EnvironmentedScriptingBackend(scriptingScope) { - + companion object Metadata: EnvironmentedScriptBackend_metadata() { // Need full metadata companion here, or else won't compile. // Ideally would be able to just declare that subclasses must define a companion of the correct type, but ah well. @@ -401,13 +417,13 @@ abstract class BlackboxScriptingBackend(scriptingScope: ScriptingScope): Environ override fun new(scriptingScope: ScriptingScope) = throw UnsupportedOperationException("Base scripting backend class not meant to be instantiated.") override val engine = "" } - + abstract val blackbox: Blackbox - + abstract val replManager: ScriptingReplManager - // Should be lazy. Was originally a method that could be called by subclasses' constructors. This seems cleaner. Subclasses don't even have to define any functions this way. And the liberal use of `lazy` should naturally make sure the properties will always be initialized in the right order. + // Should be lazy in implementations. Was originally a method that could be called by subclasses' constructors. This seems cleaner. Subclasses don't even have to define any functions this way. And the liberal use of `lazy` should naturally make sure the properties will always be initialized in the right order. // Downside: Potential latency on first command, or possibly depending on `motd()` for immediate initialization. - + override fun motd(): String { try { return replManager.motd() @@ -415,7 +431,7 @@ abstract class BlackboxScriptingBackend(scriptingScope: ScriptingScope): Environ return "No MOTD for ${metadata.engine} backend: ${e}\n" } } - + override fun autocomplete(command: String, cursorPos: Int?): AutocompleteResults { try { return replManager.autocomplete(command, cursorPos) @@ -423,7 +439,7 @@ abstract class BlackboxScriptingBackend(scriptingScope: ScriptingScope): Environ return AutocompleteResults(isHelpText = true, helpText = "Autocomplete error: ${e}") } } - + override fun exec(command: String): String { try { return replManager.exec("${command}\n") @@ -431,7 +447,7 @@ abstract class BlackboxScriptingBackend(scriptingScope: ScriptingScope): Environ return "${e}" } } - + override fun terminate(): Exception? { try { return replManager.terminate() @@ -443,13 +459,13 @@ abstract class BlackboxScriptingBackend(scriptingScope: ScriptingScope): Environ abstract class SubprocessScriptingBackend(scriptingScope: ScriptingScope): BlackboxScriptingBackend(scriptingScope) { - + abstract val processCmd: Array - + override val blackbox by lazy { SubprocessBlackbox(processCmd) } - + override val replManager: ScriptingReplManager by lazy { ScriptingRawReplManager(scriptingScope, blackbox) } - + override fun motd(): String { return "\n\nWelcome to the Unciv '${metadata.displayName}' API. This backend relies on running the system `${processCmd.firstOrNull()}` command as a subprocess.\n\nIf you do not have an interactive REPL below, then please make sure the below command is valid on your system:\n\n${processCmd.joinToString(" ")}\n\n${super.motd()}\n" } @@ -457,9 +473,9 @@ abstract class SubprocessScriptingBackend(scriptingScope: ScriptingScope): Black abstract class ProtocolSubprocessScriptingBackend(scriptingScope: ScriptingScope): SubprocessScriptingBackend(scriptingScope) { - + override val replManager by lazy { ScriptingProtocolReplManager(scriptingScope, blackbox) } - + } @@ -467,12 +483,12 @@ class SpyScriptingBackend(scriptingScope: ScriptingScope): ProtocolSubprocessScr companion object Metadata: EnvironmentedScriptBackend_metadata() { override fun new(scriptingScope: ScriptingScope) = SpyScriptingBackend(scriptingScope) - override val displayName:String = "System Python" + override val displayName: String = "System Python" override val engine = "python" } - + override val processCmd = arrayOf("python3", "-u", "-X", "utf8", folderHandle.child("main.py").toString()) - + } @@ -480,12 +496,12 @@ class SqjsScriptingBackend(scriptingScope: ScriptingScope): SubprocessScriptingB companion object Metadata: EnvironmentedScriptBackend_metadata() { override fun new(scriptingScope: ScriptingScope) = SqjsScriptingBackend(scriptingScope) - override val displayName:String = "System QuickJS" + override val displayName: String = "System QuickJS" override val engine = "qjs" } - + override val processCmd = arrayOf("qjs", "--std", "--script", folderHandle.child("main.js").toString()) - + } @@ -493,12 +509,12 @@ class SluaScriptingBackend(scriptingScope: ScriptingScope): SubprocessScriptingB companion object Metadata: EnvironmentedScriptBackend_metadata() { override fun new(scriptingScope: ScriptingScope) = SluaScriptingBackend(scriptingScope) - override val displayName:String = "System Lua" + override val displayName: String = "System Lua" override val engine = "lua" } - + override val processCmd = arrayOf("lua", folderHandle.child("main.lua").toString()) - + } @@ -508,26 +524,26 @@ class DevToolsScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackend companion object Metadata: ScriptingBackend_metadata() { override fun new(scriptingScope: ScriptingScope) = DevToolsScriptingBackend(scriptingScope) - override val displayName:String = "DevTools" + override val displayName: String = "DevTools" } - + val commands = listOf( "PrintFlatApiDefs", "PrintClassApiDefs", "WriteOutApiFile ", "WriteOutApiFile android/assets/scripting/sharedfiles/ScriptAPI.json" ) - + override fun motd() = """ - + You have launched the DevTools CLI backend." This tool is meant to help update code files. - + Available commands: """.trimIndent()+"\n"+commands.map{ "> ${it}" }.joinToString("\n")+"\n\n" - + override fun autocomplete(command: String, cursorPos: Int?) = AutocompleteResults(commands.filter{ it.startsWith(command) }) - + override fun exec(command: String): String { val commv = command.split(' ', limit=2) var out = "> ${command}\n" @@ -543,7 +559,7 @@ class DevToolsScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackend out += "Unknown command: ${commv[0]}\n" } } - + } catch (e: Exception) { out += e.toString() } @@ -560,11 +576,12 @@ enum class ScriptingBackendType(val metadata: ScriptingBackend_metadata) { SystemPython(SpyScriptingBackend), SystemQuickJS(SqjsScriptingBackend), SystemLua(SluaScriptingBackend), - DevTools(DevToolsScriptingBackend), +// DevTools(DevToolsScriptingBackend), //For running ApiSpecGenerator. Comment in releases. Uncomment if needed. } -fun SpawnNamedScriptingBackend(backendtype:ScriptingBackendType, scriptingScope: ScriptingScope): ScriptingBackendBase { +fun SpawnNamedScriptingBackend(backendtype: ScriptingBackendType, scriptingScope: ScriptingScope): ScriptingBackendBase { + // Seems unnecessary? return backendtype.metadata.new(scriptingScope) } diff --git a/core/src/com/unciv/scripting/ScriptingScope.kt b/core/src/com/unciv/scripting/ScriptingScope.kt index f43fbeeb38ad9..0469fb1d92b09 100644 --- a/core/src/com/unciv/scripting/ScriptingScope.kt +++ b/core/src/com/unciv/scripting/ScriptingScope.kt @@ -8,13 +8,22 @@ import com.unciv.scripting.utils.InstanceRegistry import com.unciv.ui.worldscreen.WorldScreen +/** + * Holds references to all internal game data that the console has access to. + * + * Also where to put any future `PlayerAPI`, `CheatAPI`, `ModAPI`, etc. + * + * For `LuaScriptingBackend`, `UpyScriptingBackend`, `QjsScriptingBackend`, etc, the hierarchy of data under this class definition should probably directly mirror the wrappers in the namespace exposed to the scripting language. + * + * `WorldScreen` gives access to `UnitTable.selectedUnit`, `MapHolder.selectedTile`, etc. Useful for contextual operations. + */ class ScriptingScope( var civInfo: CivilizationInfo?, var gameInfo: GameInfo?, var uncivGame: UncivGame?, var worldScreen: WorldScreen? ) { - + val apiHelpers = ApiHelpers(this) class ApiHelpers(val scriptingScope: ScriptingScope) { @@ -27,11 +36,5 @@ class ScriptingScope( fun printLn(msg: Any?) = println(msg) fun toString(obj: Any?) = obj.toString() } - - // Holds references to all internal game data that the console has access to. - // Also where to put any `PlayerAPI`, `CheatAPI`, `ModAPI`, etc. - // For `LuaScriptingBackend`, `UpyScriptingBackend`, `QjsScriptingBackend`, etc, the hierarchy of data under this class definition should probably directly mirror the wrappers in the namespace exposed to the scripting language. - // `WorldScreen` gives access to `UnitTable.selectedUnit`, `MapHolder.selectedTile`, etc. Useful for contextual operations. - - // + } diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index 60b69c231f4b6..e3ca30c7c92a4 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -8,41 +8,6 @@ import kotlin.collections.ArrayList import kotlin.math.max import kotlin.math.min -/* - -The major classes involved in the scripting API are structured as follows. `UpperCamelCase()` and parentheses means a new instantiation of a class. `lowerCamelCase` means a reference to an already-existing instance. An asterisk at the start of an item means zero or multiple instances of that class may be held. A question mark at the start of an item means that it may not exist in all implementations of the parent base class/interface. A question mark at the end of an item means that it is nullable, or otherwise may not be available in all states. - -``` -UncivGame(): - ScriptingState(): // Persistent per UncivGame(). - ScriptingScope(): - civInfo? // These are set by WorldScreen init, and unset by MainMenuScreen. - gameInfo? - uncivGame - worldScreen? - *ScriptingBackend(): - scriptingScope - ?ScriptingReplManager(): - Blackbox() // Common interface to wrap foreign interpreter with pipes, STDIN/STDOUT, queues, sockets, embedding, JNI, etc. - scriptingScope - ScriptingProtocol(): - scriptingScope - ?folderHandler: setupInterpreterEnvironment() // If used, a temporary directory with file structure copied from engine and shared folders in `assets/scripting`. - ConsoleScreen(): // Persistent as long as window isn't resized. Recreates itself and restores most of its state from scriptingState if resized. - scriptingState -WorldScreen(): - consoleScreen - scriptingState // ScriptingState has getters and setters that wrap scriptingScope, which WorldScreen uses to update game info. -MainMenuScreen(): - consoleScreen - scriptingState // Same as for worldScreen. -InstanceTokenizer() // Holds WeakRefs used by ScriptingProtocol. Unserializable objects get strings as placeholders, and then turned back into into objects if seen again. -Reflection() // Used by some hard-coded scripting backends, and essential to dynamic bindings in ScriptingProtocol(). -SourceManager() // Source of the folderHandler and setupInterpreterEnvironment() above. -TokenizingJson() // Serializer and functions that use InstanceTokenizer. -``` - -*/ fun ArrayList.clipIndexToBounds(index: Int, extendsize: Int = 0): Int { return max(0, min(this.size-1+extendsize, index)) @@ -93,11 +58,11 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr echo(spawnBackend(initialBackendType)) } } - + fun getOutputHistory() = outputHistory.toList() fun spawnBackend(backendtype: ScriptingBackendType): String { - val backend:ScriptingBackendBase = SpawnNamedScriptingBackend(backendtype, scriptingScope) + val backend: ScriptingBackendBase = SpawnNamedScriptingBackend(backendtype, scriptingScope) scriptingBackends.add(backend) activeBackend = scriptingBackends.size - 1 val motd = backend.motd() @@ -109,7 +74,7 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr scriptingBackends.enforceValidIndex(index) activeBackend = index } - + fun switchToBackend(backend: ScriptingBackendBase) { for ((i, b) in scriptingBackends.withIndex()) { // TODO: Apparently there's a bunch of extensions like `.withIndex()`, `.indices`, and `.lastIndex` that I can use to replace a lot of stuff currently done with `.size`. @@ -119,7 +84,7 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr } throw IllegalArgumentException("Could not find scripting backend base: ${backend}") } - + fun switchToBackend(displayname: String) { } diff --git a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt index 2b4e22168d829..05a387cd5b290 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt @@ -1,7 +1,6 @@ package com.unciv.scripting.protocol import com.unciv.scripting.AutocompleteResults -//import com.unciv.scripting.ScriptingBackend import com.unciv.scripting.reflection.Reflection import com.unciv.scripting.utils.stringifyException import com.unciv.scripting.utils.TokenizingJson @@ -19,219 +18,8 @@ import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.decodeFromJsonElement import java.util.UUID -/* -A single IPC action consists of one request packet and one response packet. -A request packet should always be followed by a response packet if it has an action. -If a request packet has a null action, then it should not be followed by a response. This is to let flags be sent without generating useless responses. -Responses do not have to be sent in the same order as their corresponding requests. New requests can be sent out while old ones are left "open"— E.G., if creating a response requires requesting new information. - -(So far, I think all the requests and responses follow a "stack" model, though. If request B is sent out before response A is received, then response B gets received before response A, like parentheses. The time two requests remain open can be completely nested subsets or supersets, or they can be completely separated in series, but they don't currently ever partially overlap.) - -(That said, none of these are hard requirements. If you want to do something fancy with coroutines or whatever and dispatch to multiple open request handlers, and you can make it both stable and language-agnostic, go right ahead.) - -Both the Kotlin side and the script interpreter can send and receive packets, but not necessarily at all times. - -(The current loop is described in a comment in ScriptingReplManager.kt. Kotlin initiates a scripting exec, during which the script interpreter can request values from Kotlin, and at the end of which the script interpreter sends its STDOUT response to the Kotlin side.) - -A single packet is a JSON string of the form: - -``` -{ - "action": String?, - "identifier": String?, - "data": Any?, - "flags": Collection -} -``` - -Identifiers should be set to a unique value in each request. -Each response should have the same identifier as its corresponding request. -Upon receiving a response, both its action and identifier should be checked to match the relevant request. - ---- - -The data field is allowed to represent any hierarchy, of instances of any types. - -If it must represent instances that are not possible or not useful to serialize as JSON hierarchies, then unique identifying token strings should be generated and sent in the places of those instances. - -If those strings are received at any hierarchical depth in the data field of any later packets, then they are to be substituted with their original instances in all uses of the information from those packets. -If the original instance of a received token string no longer exists, then an exception should be thrown, and handled as would any exception at the point where the instance is to be accessed. - -Example instance requested by script interpreter: - -``` -SomeKotlinInstance@M3mAdDr -``` - -Example response packet to send script interpreter this instance: - -``` -{ - "action": "read_response", - "identifier": "ABC001", - "data": { - "value": "_someStringifiedTokenForSomeKotlinInstance", - "exception": null - }, - "flags": [] -} -``` - -Example subsequent request packet from script interpreter using the token string: - -``` -{ - "action": "assign", - "identifier": "CDE002", - "data": { - "path": [{"type": "Property", "name": "someProperty", "params": []}], - "value": [5, "ActualStringValue", "_someStringifiedTokenForSomeKotlinInstance"] - }, - "flags": [] -} -``` - -Equivalent assignment operation resulting from this request packet: - -``` -someProperty = listOf(5, "ActualStringValue", SomeKotlinInstance@M3mAdDr) -``` - -(Caveats in practice: The scripting API design is highly asymmetric in that script interpreter needs a lot of access to the Kotlin side's state, but the Kotlin side should rarely or never need the script interpreter's state, so the script interpreter doesn't have to bother implementing its own arbitrary object tokenization. Requests sent by the Kotlin side also all have very simple response formats because of this, while access to and use of complicated Kotlin-side instances is always initiated by a request from the script interpreter while in the execution loop of its REPL, so the Kotlin side bothers implementing arbitrary instance tokenization only when receiving requests and not when receiving responses. Exceptions from reifying invalid received tokens on the Kotlin side should be handled as would any other exceptions at their code paths, but because such tokens are only used on the Kotlin side when preparing a response to a received request from the scripting side, that currently means sending a response packet that is marked in some way as representing an exception and then carrying on as normal.) - ---- - -Some action types, data formats, and expected response data formats for packets sent from the Kotlin side to the script interpreter include: - - ``` - 'motd': null -> - 'motd_response': String - ``` - - ``` - 'autocomplete': {'command': String, 'cursorpos': Int} -> - 'autocomplete_response': Collection or String - //List of matches, or help text to print. - ``` - - ``` - 'exec': String -> - 'exec_response': String - //REPL print. - ``` - - ``` - 'terminate': null -> - 'terminate_response': String? - //Error message or null. - ``` - -The above are basically a mirror of ScriptingBackend, so the same interface can be implemented in the scripting language. - ---- - -Some action types, data formats, and expected response types and formats for packets sent from the script interpreter to the Kotlin side include: - - ``` - 'read': {'path': List<{'type':String, 'name':String, 'params':List}>} -> - 'read_reponse': {'value': Any?, 'exception': String?} - //Attribute/property access, by list of `PathElement` properties. - ``` - - ``` - //'call': {'path': List<{'type':String, 'name':String, 'params':List}>, 'args': Collection, 'kwargs': Map} -> - //'call_response': {'value': Any?, 'exception': String?} - //Method/function call. - //Deprecated and removed. Instead, use `"action":"read"` with a "path" that has `"type":"call"` element(s) as per `PathElement`. - ``` - - ``` - 'assign': {'path': List<{'type':String, 'name':String, 'params':List}>, 'value': Any} -> - 'assign_response': String? - //Error message or null. - ``` - - ``` - 'delete': {'path': List<{'type':String, 'name':String, 'params':List}>} -> - 'delete_response': String? - //Error message or null. - //Only meaningful and implemented for MutableMap() keys and MutableList() indices. - ``` - - ``` - 'dir': {'path': List<{'type':String, 'name':String, 'params':List}>} -> - 'dir_response': {'value': Collection, 'exception': String?} - //Names of all members/properties/attributes/methods. - ``` - - ``` - 'keys': {'path': List<{'type':String, 'name':String, 'params':List}>} -> - 'keys_response': {'value': Collection, 'exception': String?} - //Keys of Map-interfaced instances. Used by Python bindings for iteration and autocomplete. - ``` - - ``` - 'length': {'path': List<{'type':String, 'name':String, 'params':List}>} -> - 'length_response': {'value': Int?, 'exception': String?} - //Used by Python bindings for length and also for iteration. - ``` - - ``` - 'contains': {'path': List<{'type':String, 'name':String, 'params':List}>, 'value': Any?} -> - 'contains_response': {'value': Boolean?, 'exception': String?} - //Doing this through an IPC call instead of in the script interpreter should let tokenized instances be checked for properly. - ``` - - ``` - 'callable': {'path': List<{'type':String, 'name':String, 'params':List}>} -> - 'callable_response': {'value': Boolean?, 'exception': String?} - //Used by Python autocompleter to add opening bracket to methods and function suggestions. Quite useful for exploring API at a glance. - ``` - - ``` - 'args': 'path': List<{'type':String, 'name':String, 'params':List}>} -> - 'args_response': {'value': List>?, 'exception': String?} - //Names and types of arguments accepted by a function. - //Currently just used by Python autocompleter to generate help text. - //Could also be used to control functions in scripting environment. If so, then names of types should be standardized. - ``` - - ``` - 'docstring': {'path': List<{'type':String, 'name':String, 'params':List}>} -> - 'docstring_response': {'value': String?, 'exception': String?} - //Used by Python wrappers and autocompleter to get help text showing arguments and types for callables. Useful for exploring API without having to browse code. - ``` - -The path elements in some of the data fields mirror PathElement. - ---- - -Flags are string values for communicating extra information that doesn't need a separate packet or response. Depending on the flag and action, they may be contextual to the packet, or they may not. I think I see them mostly as a way to semantically separate meta-communication about the protocol from actual requests for actions: - - ``` - 'PassMic' - //Indicates that the sending side will now begin listening instead, and the receiving side should send the next request. - //Sent by Kotlin side at start of script engine startup/MOTD, autocompletion, and execution to allow script to request values, and should be sent by script interpreter immediately before sending response with results of execution. - ``` - - ``` - //'Exception' - //Indicates that this packet is associated with an error. - //Not implemented. Currently Kotlin-side exceptions while processing a request are communicated to the script interpreter with an error message at an expected place in the data field of the response, and exceptions in the script interpreter while executing a command are stringified and returned to the Kotlin side the same as any REPL output. Miight be useful for a modding API, though. - ``` - - ``` -// 'BeginIteration' -// 'StopIteration' -// //Not implemented. Probably needed if iteration over non-sized objects is needed. Probably not worth the trouble. - ``` - ---- - -Thus, at the IPC level, all foreign backends will actually use the same language, which is this JSON-based protocol. Differences between Python, JS, Lua, etc. will all be down to how they interpret the "exec", "autocomplete", and "motd" requests differently, which each high-level scripting language is free to implement as works best for it. - -*/ +// See Module.md a description of the protocol. @Serializable @@ -246,30 +34,30 @@ data class ScriptingPacket( companion object { fun fromJson(string: String): ScriptingPacket = TokenizingJson.json.decodeFromString(string) } - + fun toJson() = TokenizingJson.json.encodeToString(this) - + fun hasFlag(flag: ScriptingProtocol.KnownFlag) = flag.value in flags - + } class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = null) { - + // Adds all returned and potentially tokenized instances in responses to `instanceSaver` to save them from garbage collection. // `instanceSaver` should not be usd as long-term storage. // Whatever's using this protocol instance should clear `instanceSaver` as soon as whatever's running at the other end of the protocol has had a chance to create references elsewhere (E.G. ScriptingScope.apiHelpers) for any instances that it needs. - + enum class KnownFlag(val value: String) { PassMic("PassMic") } companion object { - + fun makeUniqueId(): String { return "${System.nanoTime()}-${Random.nextBits(30)}-${UUID.randomUUID().toString()}" } - + val responseTypes = mapOf( // Deliberately repeating myself because I don't want to imply a hard specification for the name of response packets. "motd" to "motd_response", @@ -287,7 +75,7 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = "args" to "args_response", "docstring" to "docstring_response" ) - + fun enforceIsResponse(original: ScriptingPacket, response: ScriptingPacket): ScriptingPacket { if (!( (response.action == responseTypes[original.action]!!) @@ -297,7 +85,7 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = } return response } - + } object makeActionRequests { @@ -324,25 +112,25 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = makeUniqueId() ) } - + object parseActionResponses { fun motd(packet: ScriptingPacket): String = (packet.data as JsonPrimitive).content - + fun autocomplete(packet: ScriptingPacket): AutocompleteResults = - if (packet.data is JsonArray) + if (packet.data is JsonArray) AutocompleteResults((packet.data as List).map{ (it as JsonPrimitive).content }) else AutocompleteResults(listOf(), true, (packet.data as JsonPrimitive).content) - + fun exec(packet: ScriptingPacket): String = (packet.data as JsonPrimitive).content - + fun terminate(packet: ScriptingPacket): Exception? = if (packet.data == JsonNull || packet.data == null) null else RuntimeException((packet.data as JsonPrimitive).content) } - + fun trySaveObject(obj: Any?): Any? { if (instanceSaver != null) { instanceSaver.add(obj) diff --git a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt index a61fc8d6ca452..e8a5d2c05ae7e 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt @@ -8,50 +8,23 @@ import com.unciv.scripting.protocol.ScriptingProtocol import com.unciv.scripting.utils.Blackbox -/* -1. A scripted action is initiated from the Kotlin side, by sending a command string to the script interpreter. - 1.a. While the script interpreter is running, it has a chance to request values from the Kotlin side by sending back packets encoding attribute/property, key, and call, and assignment stacks. - 1.b. When the Kotlin side receives a request for a value, it uses reflection to access the requested property or call the requested method, and it sends the result to the script interpreter. - 1.c. When the script interpreter finishes running, it sends a special packet to the Kotlin side communicating that the script interpreter has no more requests to make. The script interpreter then sends the REPL output of the command to the Kotlin side. -2. When the Kotlin interpreter receives the packet marking the end of the command run, it stops listening for value requests packets. It then receives the commnad result as the next value, and passes it back to the console screen or script handler. - -From Kotlin: -``` -fun ExecuteCommand(command:String): - SendToInterpreter(command) - while True: - packet:Packet = ReceiveFromInterpreter().parsed() - if isPropertyRequest(packet): - SendToInterpreter(ResolvePacket(scriptingScope, packet)) - else if isCommandEndPacket(packet): - break - PrintToConsole(ReceiveFromInterpreter().parsed().data:String) -``` - -The "packets" should probably all be encoded as strings, probably JSON. The technique used to connect the script interpreter to the Kotlin code shouldn't matter, as long as it's wrapped up in and implements the `Blackbox` interface. IPC/embedding based on pipes, STDIN/STDOUT, sockets, queues, embedding, JNI, etc. should all be interchangeable and equally functional. - -I'm not sure if there'd be much point to or a good technique for letting the script interpreter run constantly and initiate actions on its own, instead of waiting for commands from the Kotlin side. - -You would presumably have to interrupt the main Kotlin-side thread anyway in order to safely run any actions initiated by the script interpreter— Which means that you may as well just register a handler to call the script interpreter at that point. - -Plus, letting the script interpreter run completely in parallel would probably introduce potential for all sorts of issues with non-deterministic synchronicity, and performance issues Calling the script interpreter from the Kotlin side means that the state of the Kotlin side is more predictable at the moment of script execution. -*/ - abstract class ScriptingReplManager(val scriptingScope: ScriptingScope, val blackbox: Blackbox): ScriptingBackend { } +/** + * REPL manager that sends and receives only raw code and prints raw strings with a black box. Allows interacting with an external script interpreter, but not suitable for exposing Kotlin-side API in external scripts. + */ class ScriptingRawReplManager(scriptingScope: ScriptingScope, blackbox: Blackbox): ScriptingReplManager(scriptingScope, blackbox) { - // REPL manager that sends and receives only raw code and print strings to a black box. Allows interacting with an external script interpreter, but not suitable for exposing internal API in external scripts. - + override fun motd(): String { return "${exec("motd()\n")}\n" } - + override fun autocomplete(command: String, cursorPos: Int?): AutocompleteResults { return AutocompleteResults() } - + override fun exec(command: String): String { if (!blackbox.readyForWrite) { throw IllegalStateException("REPL not ready: ${blackbox}") @@ -60,25 +33,30 @@ class ScriptingRawReplManager(scriptingScope: ScriptingScope, blackbox: Blackbox return blackbox.readAll(block=true).joinToString("\n") } } - + override fun terminate(): Exception? { return blackbox.stop() } } +/** + * REPL manager that uses the IPC protocol defined in ScriptingProtocol.kt to communicate with a black box. Suitable for presenting arbitrary access to Kotlin/JVM state to scripting APIs. See Module.md for a description of the REPL loop. + */ class ScriptingProtocolReplManager(scriptingScope: ScriptingScope, blackbox: Blackbox): ScriptingReplManager(scriptingScope, blackbox) { - // REPL manager that uses the IPC protocol defined in ScriptingProtocol to communicate with a black box. Suitable for presenting arbitrary access to Kotlin/JVM state to scripting APIs. See file-level comment. - + + /** + * ScriptingProtocol puts references to pre-tokenized returned objects in here. + * Should be cleared here at the end of each REPL execution. + * + * This makes sure a single script execution doesn't get its tokenized Kotlin/JVM objects garbage collected, and has a chance to save them elsewhere (E.G. ScriptingScope.apiHelpers) if it needs them later. + * Should preserve each instance, not just each value, so should be List and not Set. + * To test in Python console backend: x = apiHelpers.Factories.Vector2(1,2); civInfo.endTurn(); print(apiHelpers.toString(x)) + */ val instanceSaver = mutableListOf() - // ScriptingProtocol puts references to pre-tokenized returned objects in here. - // Should be cleared here at the end of each REPL execution. - // This makes sure a single script execution doesn't get its tokenized Kotlin/JVM objects garbage collected, and has a chance to save them elsewhere (E.G. ScriptingScope.apiHelpers) if it needs them later. - // Should preserve each instance, not just each value, so should be List and not Set. - // To test in Python console backend: x = apiHelpers.Factories.Vector2(1,2); civInfo.endTurn(); print(apiHelpers.toString(x)) - + val scriptingProtocol = ScriptingProtocol(scriptingScope, instanceSaver = instanceSaver) - + fun getRequestResponse(packetToSend: ScriptingPacket, enforceValidity: Boolean = true, execLoop: () -> Unit = fun(){}): ScriptingPacket { blackbox.write(packetToSend.toJson() + "\n") execLoop() @@ -89,10 +67,12 @@ class ScriptingProtocolReplManager(scriptingScope: ScriptingScope, blackbox: Bla instanceSaver.clear() // Clear saved references to objects in response, now that the script has had a chance to save them elsewhere. return response } - + + /** + * Listens to requests for values from the black box, and replies to them, during script execution. + * Terminates loop after receiving a request with a the 'PassMic' flag. + */ fun foreignExecLoop() { - // Listens to requests for values from the black box, and replies to them, during script execution. - // Terminates loop after receiving a request with a the 'PassMic' flag. while (true) { val request = ScriptingPacket.fromJson(blackbox.read(block=true)) if (request.action != null) { @@ -104,7 +84,7 @@ class ScriptingProtocolReplManager(scriptingScope: ScriptingScope, blackbox: Bla } } } - + override fun motd(): String { return ScriptingProtocol.parseActionResponses.motd( getRequestResponse( @@ -113,7 +93,7 @@ class ScriptingProtocolReplManager(scriptingScope: ScriptingScope, blackbox: Bla ) ) } - + override fun autocomplete(command: String, cursorPos: Int?): AutocompleteResults { return ScriptingProtocol.parseActionResponses.autocomplete( getRequestResponse( @@ -122,7 +102,7 @@ class ScriptingProtocolReplManager(scriptingScope: ScriptingScope, blackbox: Bla ) ) } - + override fun exec(command: String): String { if (!blackbox.readyForWrite) { throw IllegalStateException("REPL not ready: ${blackbox}") @@ -135,7 +115,7 @@ class ScriptingProtocolReplManager(scriptingScope: ScriptingScope, blackbox: Bla ) } } - + override fun terminate(): Exception? { try { val msg = ScriptingProtocol.parseActionResponses.terminate( diff --git a/core/src/com/unciv/scripting/reflection/Reflection.kt b/core/src/com/unciv/scripting/reflection/Reflection.kt index 5538ec263db9d..dbbce6b5cc29f 100644 --- a/core/src/com/unciv/scripting/reflection/Reflection.kt +++ b/core/src/com/unciv/scripting/reflection/Reflection.kt @@ -18,7 +18,7 @@ object Reflection { .first { it.name == propertyName } as KProperty1 return property.get(instance) as R? } - + fun readInstanceMethod(instance: Any, methodName: String): KCallable { val method = instance::class.members .first { it.name == methodName } as KCallable @@ -39,7 +39,7 @@ object Reflection { .first { it.name == propertyName } as KMutableProperty1 property.set(instance, value) } - + fun setInstanceItem(instance: Any, keyOrIndex: Any, value: Any?): Unit { if (keyOrIndex is Int) { (instance as MutableList)[keyOrIndex] = value @@ -47,7 +47,7 @@ object Reflection { (instance as MutableMap)[keyOrIndex] = value } } - + fun removeInstanceItem(instance: Any, keyOrIndex: Any): Unit { if (keyOrIndex is Int) { (instance as MutableList).removeAt(keyOrIndex) @@ -67,9 +67,11 @@ object Reflection { data class PathElement( val type: PathElementType, val name: String, + /** + * For key and index accesses, and function calls, whether to evaluate `name` instead of using `params` for arguments/key. + * Default should be false, so deserialized JSON path lists are configured correctly in ScriptingProtocol.kt. + */ val doEval: Boolean = false, - //For key and index accesses, and function calls, evaluate `name` instead of using `params` for arguments/key. - //Default should be false, so deserialized JSON path lists are configured correctly in ScriptingProtocol.kt. val params: List<@Serializable(with=TokenizingJson.TokenizingSerializer::class) Any?> = listOf() // val params: List<@Contextual Any?> = listOf() ) @@ -86,7 +88,7 @@ object Reflection { ) fun parseKotlinPath(code: String): List { - var path:MutableList = ArrayList() + var path: MutableList = ArrayList() var curr_type = PathElementType.Property var curr_name = ArrayList() var curr_brackets = "" @@ -157,21 +159,21 @@ object Reflection { fun stringifyKotlinPath() { } - + private val closingbrackets = null - + data class OpenBracket( val char: Char, var offset: Int ) - + //class OpenBracketIterator() { //} - - + + //fun getOpenBracketStack() { //} - + fun splitToplevelExprs(code: String, delimiters: String = ","): List { return code.split(',').map{ it.trim(' ') } var segs = ArrayList() @@ -277,7 +279,7 @@ object Reflection { } } } - + fun removeInstancePath(instance: Any, path: List): Unit { val leafobj = resolveInstancePath(instance, path.slice(0..path.size-2)) val leafelement = path[path.size - 1] diff --git a/core/src/com/unciv/scripting/utils/Blackbox.kt b/core/src/com/unciv/scripting/utils/Blackbox.kt index ded54a43ca9d2..891a77529f6cb 100644 --- a/core/src/com/unciv/scripting/utils/Blackbox.kt +++ b/core/src/com/unciv/scripting/utils/Blackbox.kt @@ -4,21 +4,39 @@ package com.unciv.scripting.utils interface Blackbox { fun start() { } - - fun stop(): Exception? = null // Return null on success, or return an Exception() on failure. Because there might be normal situations where a "black box" isn't viable to cleanly shut down, I'm thinking that letting exceptions be returned will let those situations be distinguished from actual errors. E.G.: Making an invalid API call to a requests library should still throw the Exception as usual, but making the right call only to find out that the network's down or a process is frozen would be a more "normal" and uncontrollable situation, so in that case the exception should be a return value instead of thrown. - + + /** + * Try to shut down this black box. + * + * Because there might be normal situations where a "black box" isn't viable to cleanly shut down, I'm thinking that letting exceptions be returned will let those situations be distinguished from actual errors. + * + * E.G.: Making an invalid API call to a requests library should still throw the Exception as usual, but making the right call only to find out that the network's down or a process is frozen would be a more "normal" and uncontrollable situation, so in that case the exception should be a return value instead of being thrown. + * + * @return null on success, or an Exception() on failure. + */ + fun stop(): Exception? = null + val isAlive: Boolean get() = false - val readyForRead: Int // Return approximate number of items ready to be read. Should try to always return 0 correctly, but may return 1 if a greater number of items are available. + /** + * Return approximate number of items ready to be read. Should try to always return 0 correctly, but may return 1 if a greater number of items are available. + */ + val readyForRead: Int get() = 0 - + val readyForWrite: Boolean get() = false - + fun read(block: Boolean = true): String = ""// Return a single string if either blocking or ready to read, or throw an IllegalStateException() otherwise. - - fun readAll(block: Boolean = true, limit: Int = 0): List { // Read out all lines up to a limit if greater than zero, returning an empty list if none are available and no blocking is allowed + + /** + * Read out all lines up to a limit if given a limit greater than zero. + * + * @param block Whether to wait until at least one line is available before returning. + * @return Empty list if no lines are available and blocking is disabled. List of at least one string if blocking is allowed. + */ + fun readAll(block: Boolean = true, limit: Int = 0): List { val lines = ArrayList() var i = 0 if (block) { @@ -31,8 +49,8 @@ interface Blackbox { } return lines } - + fun write(string: String) { } - + } diff --git a/core/src/com/unciv/scripting/utils/InstanceFactories.kt b/core/src/com/unciv/scripting/utils/InstanceFactories.kt index 70b6ad626f0e3..13521e333014b 100644 --- a/core/src/com/unciv/scripting/utils/InstanceFactories.kt +++ b/core/src/com/unciv/scripting/utils/InstanceFactories.kt @@ -3,8 +3,10 @@ package com.unciv.scripting.utils import com.badlogic.gdx.math.Vector2 +/** + * For use in ScriptingScope. Allows bound scripts to make new instances of Kotlin/JVM classes. + */ object InstanceFactories { - // For use in ScriptingScope. Allows bound scripts to make new instances of Kotlin/JVM classes. fun Vector2(x: Float, y: Float) = com.badlogic.gdx.math.Vector2(x, y) fun MapUnit() = "NotImplemented" } diff --git a/core/src/com/unciv/scripting/utils/InstanceRegistry.kt b/core/src/com/unciv/scripting/utils/InstanceRegistry.kt index b9159af290740..34422958f7447 100644 --- a/core/src/com/unciv/scripting/utils/InstanceRegistry.kt +++ b/core/src/com/unciv/scripting/utils/InstanceRegistry.kt @@ -1,10 +1,13 @@ package com.unciv.scripting.utils +/** + * Namespace in ScriptingScope().apiHelpers, for scripts to do their own memory management by keeping references to objects alive. Wraps a MutableMap<>(). + * + * Currently all it does is throw an exception on an assignment colliding with an existing key, and reads and removals for non-existent keys.. + * Was going to have functions named to fit creating and freeing fields, but so far Python's item assignment and deletion syntaxes are plenty clean + */ class InstanceRegistry(): MutableMap { - // Namespace in ScriptingScope().apiHelpers, for scripts to do their own memory management by keeping references to objects alive. Wraps a MutableMap<>(). - // Currently all it does is throw an exception on an assignment colliding with an existing key, and reads and removals for non-existent keys.. - // Was going to have functions named to fit creating and freeing fields, but so far Python's item assignment and deletion syntaxes are plenty clean private val backingMap = mutableMapOf() override val entries get() = backingMap.entries diff --git a/core/src/com/unciv/scripting/utils/SourceManager.kt b/core/src/com/unciv/scripting/utils/SourceManager.kt index 2c604ed0784df..a8c43f8fd0cca 100644 --- a/core/src/com/unciv/scripting/utils/SourceManager.kt +++ b/core/src/com/unciv/scripting/utils/SourceManager.kt @@ -14,11 +14,17 @@ object SourceManager { return ScriptingConstants.assetFolders.enginefilesAssets.child("${engine}/") } + /** + * Set up a directory tree with all known libraries and files for a scripting engine/language type. + * + * Creates temporary directory. + * Copies directory tree under `android/assets/scripting/sharedfiles/` into it, as specified in `ScriptingEngineConstants.json` + * Copies directory tree under `android/assets/scripting/enginefiles/{engine}` into it, as specified in `ScriptingEngineConstants.json`. + * + * @param engine Name of the engine type, as defined in scripting constants. + * @return `FileHandle()` for the temporary directory. + */ fun setupInterpreterEnvironment(engine: String): FileHandle { - // Creates temporary directory. - // Copies directory tree under `android/assets/scripting/sharedfiles/` into it, as specified by `SharedData.json` - // Copies directory tree under `android/assets/scripting/enginefiles/{engine}` into it, as specified by `{engine}.json`. - // Returns `FileHandle()` for the temporary directory. val enginedir = getEngineLibraries(engine) val outdir = FileHandle.tempDirectory("unciv-${engine}_") fun addfile(sourcedir: FileHandle, path: String) { diff --git a/core/src/com/unciv/scripting/utils/TokenizingJson.kt b/core/src/com/unciv/scripting/utils/TokenizingJson.kt index 80416e40cce09..3d5f08ea4c863 100644 --- a/core/src/com/unciv/scripting/utils/TokenizingJson.kt +++ b/core/src/com/unciv/scripting/utils/TokenizingJson.kt @@ -16,14 +16,16 @@ import kotlinx.serialization.modules.SerializersModule +/** + * Json serialization that accepts `Any?`, and converts non-primitive values to string keys from `InstanceTokenizer`. + */ object TokenizingJson { - // Json serialization that accepts `Any?`, and converts non-primitive values to string keys from `InstanceTokenizer`. object TokenizingSerializer: KSerializer { - + // Adapted from https://stackoverflow.com/a/66158603/12260302 - + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Any?") @Suppress("UNCHECKED_CAST") @@ -73,7 +75,7 @@ object TokenizingJson { encodeDefaults = true; // serializersModule = serializersModule } - + private fun isTokenizationMandatory(value: Any?): Boolean { // // Forbid some objects from being serialized as normal JSON values. // // com.unciv.models.ruleset.Building(), for example, and resumably all other types that inherit from Stats(), implement Iterable<*> and thus get serialized as JSON Arrays by default even though it's probably better to tokenize them. @@ -85,18 +87,18 @@ object TokenizingJson { val qualname = value::class.qualifiedName return qualname != null && qualname.startsWith("com.unciv") } - - + + fun getJsonElement(value: Any?): JsonElement { if (value is JsonElement) { return value } if (!isTokenizationMandatory(value)) { - if (value is Map<*, *>) { //TODO: Decide what to do with the keys. + if (value is Map<*, *>) { //TODO: Decide what to do with non-string keys... I guess I could tokenize them? return JsonObject( (value as Map).mapValues{ getJsonElement(it.value) } ) } if (value is Iterable<*>) { - // Apparently ::class.java.isArray can be used to check for primitive arrays, but it breaks + // Apparently ::class.java.isArray can be used to check for primitive arrays, but it breaks return JsonArray(value.map{ getJsonElement(it) }) } if (value is Sequence<*>) { @@ -105,13 +107,13 @@ object TokenizingJson { println(v[0]!!::class.qualifiedName) return getJsonElement(v) } - if (value is String) { + if (value is String) { return JsonPrimitive(value as String) } - if (value is Int || value is Long || value is Float || value is Double) { + if (value is Int || value is Long || value is Float || value is Double) { return JsonPrimitive(value as Number) } - if (value is Boolean) { //TODO: Arrays? + if (value is Boolean) { //TODO: Arrays and primitive arrays? return JsonPrimitive(value as Boolean) } if (value == null) { @@ -120,7 +122,7 @@ object TokenizingJson { } return JsonPrimitive(InstanceTokenizer.getToken(value)) } - + fun getJsonReal(value: JsonElement): Any? { if (value is JsonNull || value == JsonNull) { return null From 7d227ef1df1c4831a2c51401ff14c0fe7c16d36f Mon Sep 17 00:00:00 2001 From: will-ca Date: Sat, 13 Nov 2021 03:15:46 +0000 Subject: [PATCH 36/93] Move Python doc into Markdown file too. --- .../enginefiles/python/PythonScripting.md | 162 +++++++++++++++++ .../scripting/enginefiles/python/main.py | 170 +----------------- core/Module.md | 2 +- 3 files changed, 168 insertions(+), 166 deletions(-) create mode 100644 android/assets/scripting/enginefiles/python/PythonScripting.md diff --git a/android/assets/scripting/enginefiles/python/PythonScripting.md b/android/assets/scripting/enginefiles/python/PythonScripting.md new file mode 100644 index 0000000000000..18247e51f6f45 --- /dev/null +++ b/android/assets/scripting/enginefiles/python/PythonScripting.md @@ -0,0 +1,162 @@ +There are basically two types of Python objects in this API: + + * Wrappers. + * Tokens. + +--- + +A **wrapper** is an object that stores a list of attribute names, keys, and function call parameters corresponding to a path in the Kotlin/JVM namespace. E.G.: `"civInfo.civilizations[0].population.setPopulation"` is a string representation of a wrapped path that begins with two attribute accesses, followed by one array index, and two more attribute names. + +A wrapper object does not store any more values than that. When it is evaluated, it uses a simple IPC protocol to request an up-to-date real value from the game's Kotlin code. The `unciv_lib.api.real()` function can be used to manually get a real Python value from a foreign instance wrapper. + +However, because the wrapper class implements many Magic Methods that automatically call its evaluation method, many programming idioms common in Python are possible with them even without manual evaluation. Comparisons, equality, arithmetic, concatenation, inplace operations and more are all supported. + +Accessing an attribute on a wrapper object returns a new wrapper object that has an additional name set to "Property" at the end of its path list. Performing an array or dictionary index returns a new wrapper with an additional "Key" element in its path list. + +```python3 +print(civInfo) # Wrapper object. +print(civInfo.cities[0]) # Also a wrapper object, with two extra path elements. +print(civInfo.cities[0].isCapital) # Still a wrapper object, with another extra path element. +``` + +Calling a wrapper object as a function or method also creates a new path list with an extra "Call" element at the end. But in this case, the new path list is immediately sent as a request to Kotlin/the JVM instead of being used in a new wrapper object, and the returned value is the naked result from the requested function call in the Kotlin/JVM namespace. + +```python3 +print(civInfo.cities[0].isCapital()) # Goes through four wrapper objects, but ultimately sends its path as a request on the function call, and returns a real value. +``` + +Likewise, assigning to an attribute or an index/key on a wrapper object sends an IPC request to assign to the Kotlin/JVM value at its path, instead of modifying the wrapper object. + +```python3 +civInfo.cities[0].name = "Metropolis" +# Uses IPC request to modify Kotlin/JVM property. + +civInfo.cities[0].cityConstructions.constructionQueue[0] = "Missile Cruiser" +# Uses IPC request to modify Kotlin/JVM container. +``` + +When a Kotlin/JVM class implements a property for size or keys, Python wrappers for its instances can be iterated like a `tuple` or a `dict`, such as in `for` loops and iterable comprehensions. + +```python3 +print([real(city.name)+str(real(city.population.population)) for city in civInfo.cities]) +print({name: real(empire.cities and empire.cities[0]) for name, empire in gameInfo.ruleSet.nations.entries()}) +``` + +--- + +A **token** is a string that has been generated by `InstanceTokenizer.kt` to represent a Kotlin instance. + +The `unciv_lib.api.isForeignToken()` function can be used to check whether a Python object is either a foreign token string or a wrapper that resolves to a foreign token string. + +When a Kotlin/JVM path requested by a script resolves to an immutable primative like a number or boolean, or something that is otherwise practical to serialize, then the value returned to Python is usually a real object. + +However, if the value requested is an instance of a complicated Kotlin/JVM class, then the IPC protocol instead creates a unique string to identify it. + +```python3 +isForeignToken("Some random string.") +# False. + +isForeignToken(uncivGame.version) +# False. Version is stored as a simple string. + +isForeignToken(gameInfo.turns) +# False. Turn count is stored as a simple integer. + +isForeignToken(real(uncivGame)) +# True. Unserializable instances are turned into token strings on evaluation. + +isForeignToken(civInfo.getWorkerAutomation()) +# True. This method returns a complicated type that gets turned into a token string. + +isForeignToken(uncivGame) +# True. This is actually a wrapper, but `isForeignToken` returns True based on evaluated results. +``` + +The original instance is stored in the JVM in a mapping as a weak reference. The string doesn't have any special properties as a Python object. But if it is sent back to Kotlin/the JVM at any point, then it will be parsed and substituted with the original instance (provided the original instance still exists). + +This is meant to allow Kotlin/JVM instances to be, E.G., used as function arguments and mapping keys from scripts. + +```python3 +civunits = civInfo.getCivUnits() +# List of token strings representing `MapUnit()` instances. + +unit = civunits[0] +# Single token string. + +civInfo.removeUnit(unit) +# Token string gets get transformed back into original `MapUnit()` when used as function argument. +``` + +The rules for which classes are serialized as JSON values and which are serialized as token strings may be a little bit fuzzy and variable, as they are designed to maximise use cases. + +In general, Kotlin/JVM instances that *can* be cast into a type compatible with a JSON type will be serialized as such— Unless they are of classes defined within the Unciv packages themselves, in which case they will always be tokenized. The exemption for certain classes prevents everything that inherits from iterable interfaces— Like `Building()`, which inherits from `Stats:Iterable`— From being stripped down into JSON arrays when having having references to and members of their instances is much more useful. + +--- + +Sometimes, you may want to access a path or call a method on an object that you have only as a token string— For example, an object returned from a foreign function or method call. + +Usually this would be impossible because you need a path in order to access foreign attributes. Without a valid path to an object, the wrapper code and the IPC protocol it uses have no way to identify where an object is or what to do with it. In fact, if the Kotlin/JVM code hasn't kept its own references to the object, then the object may not even exist anymore. + +To get around this, you can use the foreign token to assign the object it represents to a concrete path. + +The `apiHelpers.registeredInstances` helper object can be used for this: + +```python3 +token = civInfo.cities[0].getCenterTile() +# Token string representing a `TileInfo()` instance. + +print(type(token)) +# . Cannot be used for attribute access. + +apiHelpers.registeredInstances["centertile"] = token +# Token string gets transformed back into `TileInfo()` in Kotlin/JVM assignment. + +print(type(apiHelpers.registeredInstances["centertile"])) +# . Is now a full wrapper with path, and can be used for full attribute, key, and method access. + +print(apiHelpers.registeredInstances["centertile"].baseTerrain) +# Successful attribute access. + +del apiHelpers.registeredInstances["centertile"] +# Delete the reference so it doesn't become a memory leak. +``` + +In order to use this technique properly, the assignment of an object to a concrete path should be done within the same REPL loop as the generation of the token used to assign it. This is because the Kotlin code responsible for generating tokens and managing the REPL loop keeps references to all returned objects within each REPL loop. Afterwards, these references are immediately cleared, so any objects that do not have references elsewhere in Kotlin/the JVM are liable to be garbage-collected. + +**It is very important that you delete concrete paths you have set after you are done with them.** Any objects held at paths you do not delete will continue to occupy system memory for the remaining run time of the application's lifespan. We can't rely on Python's garbage collection in this case because it doesn't control the Kotlin objects, nor can we rely on the JVM's garbage collector because it doesn't know whether Python code still needs the objects in question, so you will have to manage the memory yourself by keeping a reference as long as you need an object and deleting it to free up memory afterwards. + +For any complicated script in Python, it is suggested that you write a context manager class to automatically take care of saving and freeing each object where appropriate. + +It is also recommended that all scripts create a separate mapping with a unique and identifiable key in `apiHelpers.registeredInstances`, instead of assigning directly to the top level. + +```python3 +apiHelpers.registeredInstances["myCoolScript"] = {} + +memalloc = apiHelpers.registeredInstances["myCoolScript"] + +memalloc["capitaltile"] = civInfo.cities[0].getCenterTile() + +worldScreen.mapHolder.setCenterPosition(memalloc["capitaltile"].position, True, True) + +del memalloc["capitaltile"] + +del apiHelpers.registeredInstances["myCoolScript"] +``` + +--- + +The top-level namespace of the API can be accessed as a module in any script imported from it. + +This is useful when loading external scripts. + +```python3 +import unciv + +def printCivilizations(): + for civ in unciv.gameInfo.civilizations: + print(f"{unciv.real(civ.nation.name)}: {len(civ.cities)} cities") +``` + +--- + +The Python-specific behaviour is also not meant as a hard standard, in that it doesn't have to be copied exactly in any other languages. Some other design may be more suited for ECMAScript, Lua, and other possible backends. If implementing another language, I think some attempt should still be made to keep a similar API and feature parity, though. diff --git a/android/assets/scripting/enginefiles/python/main.py b/android/assets/scripting/enginefiles/python/main.py index 1a6d1857c0596..c294a0fab6bf9 100644 --- a/android/assets/scripting/enginefiles/python/main.py +++ b/android/assets/scripting/enginefiles/python/main.py @@ -1,168 +1,8 @@ -""" -There are basically two types of Python objects in this API: - - * Wrappers. - * Tokens. - ---- - -A **wrapper** is an object that stores a list of attribute names, keys, and function call parameters corresponding to a path in the Kotlin/JVM namespace. E.G.: `"civInfo.civilizations[0].population.setPopulation"` is a string representation of a wrapped path that begins with two attribute accesses, followed by one array index, and two more attribute names. - -A wrapper object does not store any more values than that. When it is evaluated, it uses a simple IPC protocol to request an up-to-date real value from the game's Kotlin code. The `unciv_lib.api.real()` function can be used to manually get a real Python value from a foreign instance wrapper. - -However, because the wrapper class implements many Magic Methods that automatically call its evaluation method, many programming idioms common in Python are possible with them even without manual evaluation. Comparisons, equality, arithmetic, concatenation, inplace operations and more are all supported. - -Accessing an attribute on a wrapper object returns a new wrapper object that has an additional name set to "Property" at the end of its path list. Performing an array or dictionary index returns a new wrapper with an additional "Key" element in its path list. - -```python3 -print(civInfo) # Wrapper object. -print(civInfo.cities[0]) # Also a wrapper object, with two extra path elements. -print(civInfo.cities[0].isCapital) # Still a wrapper object, with another extra path element. -``` - -Calling a wrapper object as a function or method also creates a new path list with an extra "Call" element at the end. But in this case, the new path list is immediately sent as a request to Kotlin/the JVM instead of being used in a new wrapper object, and the returned value is the naked result from the requested function call in the Kotlin/JVM namespace. - -```python3 -print(civInfo.cities[0].isCapital()) # Goes through four wrapper objects, but ultimately sends its path as a request on the function call, and returns a real value. -``` - -Likewise, assigning to an attribute or an index/key on a wrapper object sends an IPC request to assign to the Kotlin/JVM value at its path, instead of modifying the wrapper object. - -```python3 -civInfo.cities[0].name = "Metropolis" -# Uses IPC request to modify Kotlin/JVM property. - -civInfo.cities[0].cityConstructions.constructionQueue[0] = "Missile Cruiser" -# Uses IPC request to modify Kotlin/JVM container. -``` - -When a Kotlin/JVM class implements a property for size or keys, Python wrappers for its instances can be iterated like a `tuple` or a `dict`, such as in `for` loops and iterable comprehensions. - -```python3 -print([real(city.name)+str(real(city.population.population)) for city in civInfo.cities]) -print({name: real(empire.cities and empire.cities[0]) for name, empire in gameInfo.ruleSet.nations.entries()}) -``` - ---- - -A **token** is a string that has been generated by `InstanceTokenizer.kt` to represent a Kotlin instance. - -The `unciv_lib.api.isForeignToken()` function can be used to check whether a Python object is either a foreign token string or a wrapper that resolves to a foreign token string. - -When a Kotlin/JVM path requested by a script resolves to an immutable primative like a number or boolean, or something that is otherwise practical to serialize, then the value returned to Python is usually a real object. - -However, if the value requested is an instance of a complicated Kotlin/JVM class, then the IPC protocol instead creates a unique string to identify it. - -```python3 -isForeignToken("Some random string.") -# False. - -isForeignToken(uncivGame.version) -# False. Version is stored as a simple string. - -isForeignToken(gameInfo.turns) -# False. Turn count is stored as a simple integer. - -isForeignToken(real(uncivGame)) -# True. Unserializable instances are turned into token strings on evaluation. - -isForeignToken(civInfo.getWorkerAutomation()) -# True. This method returns a complicated type that gets turned into a token string. - -isForeignToken(uncivGame) -# True. This is actually a wrapper, but `isForeignToken` returns True based on evaluated results. -``` - -The original instance is stored in the JVM in a mapping as a weak reference. The string doesn't have any special properties as a Python object. But if it is sent back to Kotlin/the JVM at any point, then it will be parsed and substituted with the original instance (provided the original instance still exists). - -This is meant to allow Kotlin/JVM instances to be, E.G., used as function arguments and mapping keys from scripts. - -```python3 -civunits = civInfo.getCivUnits() -# List of token strings representing `MapUnit()` instances. - -unit = civunits[0] -# Single token string. - -civInfo.removeUnit(unit) -# Token string gets get transformed back into original `MapUnit()` when used as function argument. -``` - -The rules for which classes are serialized as JSON values and which are serialized as token strings may be a little bit fuzzy and variable, as they are designed to maximise use cases. - -In general, Kotlin/JVM instances that *can* be cast into a type compatible with a JSON type will be serialized as such— Unless they are of classes defined within the Unciv packages themselves, in which case they will always be tokenized. The exemption for certain classes prevents everything that inherits from iterable interfaces— Like `Building()`, which inherits from `Stats:Iterable`— From being stripped down into JSON arrays when having having references to and members of their instances is much more useful. - ---- - -Sometimes, you may want to access a path or call a method on an object that you have only as a token string— For example, an object returned from a foreign function or method call. - -Usually this would be impossible because you need a path in order to access foreign attributes. Without a valid path to an object, the wrapper code and the IPC protocol it uses have no way to identify where an object is or what to do with it. In fact, if the Kotlin/JVM code hasn't kept its own references to the object, then the object may not even exist anymore. - -To get around this, you can use the foreign token to assign the object it represents to a concrete path. - -The `apiHelpers.registeredInstances` helper object can be used for this: - -```python3 -token = civInfo.cities[0].getCenterTile() -# Token string representing a `TileInfo()` instance. - -print(type(token)) -# . Cannot be used for attribute access. - -apiHelpers.registeredInstances["centertile"] = token -# Token string gets transformed back into `TileInfo()` in Kotlin/JVM assignment. - -print(type(apiHelpers.registeredInstances["centertile"])) -# . Is now a full wrapper with path, and can be used for full attribute, key, and method access. - -print(apiHelpers.registeredInstances["centertile"].baseTerrain) -# Successful attribute access. - -del apiHelpers.registeredInstances["centertile"] -# Delete the reference so it doesn't become a memory leak. -``` - -In order to use this technique properly, the assignment of an object to a concrete path should be done within the same REPL loop as the generation of the token used to assign it. This is because the Kotlin code responsible for generating tokens and managing the REPL loop keeps references to all returned objects within each REPL loop. Afterwards, these references are immediately cleared, so any objects that do not have references elsewhere in Kotlin/the JVM are liable to be garbage-collected. - -**It is very important that you delete concrete paths you have set after you are done with them.** Any objects held at paths you do not delete will continue to occupy system memory for the remaining run time of the application's lifespan. We can't rely on Python's garbage collection in this case because it doesn't control the Kotlin objects, nor can we rely on the JVM's garbage collector because it doesn't know whether Python code still needs the objects in question, so you will have to manage the memory yourself by keeping a reference as long as you need an object and deleting it to free up memory afterwards. - -For any complicated script in Python, it is suggested that you write a context manager class to automatically take care of saving and freeing each object where appropriate. - -It is also recommended that all scripts create a separate mapping with a unique and identifiable key in `apiHelpers.registeredInstances`, instead of assigning directly to the top level. - -```python3 -apiHelpers.registeredInstances["myCoolScript"] = {} - -memalloc = apiHelpers.registeredInstances["myCoolScript"] - -memalloc["capitaltile"] = civInfo.cities[0].getCenterTile() - -worldScreen.mapHolder.setCenterPosition(memalloc["capitaltile"].position, True, True) - -del memalloc["capitaltile"] - -del apiHelpers.registeredInstances["myCoolScript"] -``` - ---- - -The top-level namespace of the API can be accessed as a module in any script imported from it. - -This is useful when loading external scripts. - -```python3 -import unciv - -def printCivilizations(): - for civ in unciv.gameInfo.civilizations: - print(f"{unciv.real(civ.nation.name)}: {len(civ.cities)} cities") -``` - ---- - -The Python-specific behaviour is also not meant as a hard standard, in that it doesn't have to be copied exactly in any other languages. Some other design may be more suited for ECMAScript, Lua, and other possible backends. If implementing another language, I think some attempt should still be made to keep a similar API and feature parity, though. - -""" +try: + with open("PythonScripting.md", 'r') as f: + __doc__ = f.read() +except: + pass try: diff --git a/core/Module.md b/core/Module.md index a09827a64f589..961442f80327d 100644 --- a/core/Module.md +++ b/core/Module.md @@ -282,4 +282,4 @@ Thus, at the IPC level, all foreign backends will actually use the same language ## Python Binding Implementation -A description of how this REPL loop and IPC protocol are used to build scripting langauage bindings is in the module-level docstring of `/android/assets/scripting/enginefiles/python/main.py`. +A description of how this REPL loop and IPC protocol are used to build scripting langauage bindings [is at `/android/assets/scripting/enginefiles/python/PythonScripting.md`](../android/assets/scripting/enginefiles/python/PythonScripting.md). From c9b0b6a8a65c11cc944cb69398fe2ccb6edba755 Mon Sep 17 00:00:00 2001 From: will-ca Date: Sat, 13 Nov 2021 03:08:02 +0000 Subject: [PATCH 37/93] Cool, Table of Contents in Markdown. --- core/Module.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/core/Module.md b/core/Module.md index 961442f80327d..89c9fd50ab565 100644 --- a/core/Module.md +++ b/core/Module.md @@ -1,3 +1,13 @@ +# Package com.unciv + +## Table of Contents + +* [Package `com.unciv.scripting`](#package-comuncivscripting) + * [Class Overview](#class-overview) +* [Package `com.unciv.scripting.protocol`](#package-comuncivscriptingprotocol) + * [REPL Loop](#repl-loop) + * [IPC Protocol](#ipc-protocol) + * [Python Binding Implementation](#python-binding-implementation) # Package com.unciv.scripting From d5c8d51df58b97f5daf6b63ff57e8f5f2094cd99 Mon Sep 17 00:00:00 2001 From: will-ca Date: Sat, 13 Nov 2021 04:33:48 +0000 Subject: [PATCH 38/93] Small doc and Python fixes. --- .../scripting/ScriptingEngineConstants.json | 1 + .../enginefiles/python/PythonScripting.md | 19 +++++++++++++++---- .../scripting/enginefiles/python/main.py | 12 ++++++++---- .../enginefiles/python/unciv_lib/api.py | 1 + .../enginefiles/python/unciv_lib/wrapping.py | 1 + core/Module.md | 8 ++++---- .../src/com/unciv/scripting/ScriptingState.kt | 12 ++++++++++-- .../protocol/ScriptingReplManager.kt | 1 + 8 files changed, 41 insertions(+), 14 deletions(-) diff --git a/android/assets/scripting/ScriptingEngineConstants.json b/android/assets/scripting/ScriptingEngineConstants.json index 90bba4bb0ff8e..30da917cf2f08 100644 --- a/android/assets/scripting/ScriptingEngineConstants.json +++ b/android/assets/scripting/ScriptingEngineConstants.json @@ -9,6 +9,7 @@ unciv_lib/ipc.py unciv_lib/utils.py unciv_lib/wrapping.py + PythonScripting.md main.py ] syntaxHighlightingRegexStack: [ diff --git a/android/assets/scripting/enginefiles/python/PythonScripting.md b/android/assets/scripting/enginefiles/python/PythonScripting.md index 18247e51f6f45..93e1d3256b976 100644 --- a/android/assets/scripting/enginefiles/python/PythonScripting.md +++ b/android/assets/scripting/enginefiles/python/PythonScripting.md @@ -89,7 +89,7 @@ civInfo.removeUnit(unit) The rules for which classes are serialized as JSON values and which are serialized as token strings may be a little bit fuzzy and variable, as they are designed to maximise use cases. -In general, Kotlin/JVM instances that *can* be cast into a type compatible with a JSON type will be serialized as such— Unless they are of classes defined within the Unciv packages themselves, in which case they will always be tokenized. The exemption for certain classes prevents everything that inherits from iterable interfaces— Like `Building()`, which inherits from `Stats:Iterable`— From being stripped down into JSON arrays when having having references to and members of their instances is much more useful. +In general, Kotlin/JVM instances that *can* be cast into a type compatible with a JSON type will be serialized as such— Unless they are of classes defined within the Unciv packages themselves, in which case they will always be tokenized. The exemption for certain classes prevents everything that inherits from iterable interfaces— Like `Building()`, which inherits from `Stats:Iterable`— From being stripped down into JSON arrays when having access to their members and instances is much more useful. --- @@ -112,7 +112,7 @@ apiHelpers.registeredInstances["centertile"] = token # Token string gets transformed back into `TileInfo()` in Kotlin/JVM assignment. print(type(apiHelpers.registeredInstances["centertile"])) -# . Is now a full wrapper with path, and can be used for full attribute, key, and method access. +# . A full wrapper with path, that can be used for full attribute, key, and method access. print(apiHelpers.registeredInstances["centertile"].baseTerrain) # Successful attribute access. @@ -147,9 +147,13 @@ del apiHelpers.registeredInstances["myCoolScript"] The top-level namespace of the API can be accessed as a module in any script imported from it. -This is useful when loading external scripts. +This is useful when writing modules that are meant to be imported from the main Unciv Python namespace. + +In a file in your `PYTHONPATH`/`sys.path`: ```python3 +#MyCoolModule.py + import unciv def printCivilizations(): @@ -157,6 +161,13 @@ def printCivilizations(): print(f"{unciv.real(civ.nation.name)}: {len(civ.cities)} cities") ``` +In Unciv: + +```python3 +>>> import MyCoolModule +>>> MyCoolModule.printCivilizations() +``` + --- -The Python-specific behaviour is also not meant as a hard standard, in that it doesn't have to be copied exactly in any other languages. Some other design may be more suited for ECMAScript, Lua, and other possible backends. If implementing another language, I think some attempt should still be made to keep a similar API and feature parity, though. +The Python-specific behaviour is not meant as a hard standard, in that it doesn't have to be copied exactly in any other languages. Some other design may be more suited for ECMAScript, Lua, and other possible backends. If implementing another language, I think some attempt should still be made to keep a similar API and feature equivalence, though. diff --git a/android/assets/scripting/enginefiles/python/main.py b/android/assets/scripting/enginefiles/python/main.py index c294a0fab6bf9..87ef430ed55ac 100644 --- a/android/assets/scripting/enginefiles/python/main.py +++ b/android/assets/scripting/enginefiles/python/main.py @@ -1,8 +1,12 @@ try: - with open("PythonScripting.md", 'r') as f: + import os + with open(os.path.join(os.path.dirname(__file__), "PythonScripting.md"), 'r') as f: __doc__ = f.read() -except: - pass +except Exception as e: + try: + __doc__ = f"{repr(e)}" + except: + pass try: @@ -23,7 +27,7 @@ apiScope.update(unciv_lib.api.Expose) - apiScope['help'] = lambda *a, **kw: print(__doc__) if thing is None else help(*a, **kw) + apiScope['help'] = lambda thing=None: print(__doc__) if thing is None else print(unciv_lib.api.get_doc(thing)) if isinstance(thing, unciv_lib.wrapping.ForeignObject) else help(thing) foreignAutocompleter = unciv_lib.autocompletion.PyAutocompleteManager(apiScope, **unciv_lib.api.autocompleterkwargs) diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/api.py b/android/assets/scripting/enginefiles/python/unciv_lib/api.py index fd09d8d790e26..a27c08fce18a7 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/api.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/api.py @@ -41,6 +41,7 @@ def get_doc(obj): try: if isinstance(obj, wrapping.ForeignObject): doc = f"\n\n{str(obj._docstring_() or wrapping.stringPathList(obj._getpath_()))}\n\nArguments:\n" + # TODO: This should proably be in ForeignObject.__getattr__/__getattribute__. doc += "\n".join(f"\t{argname}: {argtype}" for argname, argtype in obj._args_()) return doc else: diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py index 9a1ec3fe03807..e9eea134c1365 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py @@ -172,6 +172,7 @@ def _getpath_(self): return tuple(self._path) #return ''.join(self._path) def __getattr__(self, name): + # TODO: Shouldn't I calling get_doc or _docstring_ here? return self.__class__((*self._path, makePathElement(name=name)), self._foreignrequester) def __getitem__(self, key): return self.__class__((*self._path, makePathElement(ttype='Key', params=(key,))), self._foreignrequester) diff --git a/core/Module.md b/core/Module.md index 89c9fd50ab565..29ef602b15526 100644 --- a/core/Module.md +++ b/core/Module.md @@ -51,9 +51,9 @@ TokenizingJson() // Serializer and functions that use InstanceTokenizer. ## REPL Loop 1. A scripted action is initiated from the Kotlin side, by sending a command string to the script interpreter. - 1.a. While the script interpreter is running, it has a chance to request values from the Kotlin side by sending back packets encoding attribute/property, key, and call, and assignment stacks. - 1.b. When the Kotlin side receives a request for a value, it uses reflection to access the requested property or call the requested method, and it sends the result to the script interpreter. - 1.c. When the script interpreter finishes running, it sends a special packet to the Kotlin side communicating that the script interpreter has no more requests to make. The script interpreter then sends the REPL output of the command to the Kotlin side. + 1. While the script interpreter is running, it has a chance to request values from the Kotlin side by sending back packets encoding attribute/property, key, and call, and assignment stacks. + 2. When the Kotlin side receives a request for a value, it uses reflection to access the requested property or call the requested method, and it sends the result to the script interpreter. + 3. When the script interpreter finishes running, it sends a special packet to the Kotlin side communicating that the script interpreter has no more requests to make. The script interpreter then sends the REPL output of the command to the Kotlin side. 2. When the Kotlin interpreter receives the packet marking the end of the command run, it stops listening for value requests packets. It then receives the commnad result as the next value, and passes it back to the console screen or script handler. From Kotlin: @@ -151,7 +151,7 @@ Example subsequent request packet from script interpreter using the token string } ``` -Equivalent Kotlin-side assignment operation resulting from this request packet: +Equivalent Kotlin-side assignment operation resulting from this later request packet: ``` someProperty = listOf(5, "ActualStringValue", SomeKotlinInstance@M3mAdDr) diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index e3ca30c7c92a4..df18217eb926f 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -13,8 +13,16 @@ fun ArrayList.clipIndexToBounds(index: Int, extendsize: Int = 0): Int { return max(0, min(this.size-1+extendsize, index)) } + +/** + * Make sure an index is valid for this array. + * + * Doing all checks with the same function and error message is probably easier to debug than letting an array access fail at the point of access. + * + * @param index Index to check. + * @throws IndexOutOfBoundsException if given invalid index. + */ fun ArrayList.enforceValidIndex(index: Int) { - // Doing all checks with the same function and error message is probably easier to debug than letting an array access fail. if (index < 0 || this.size <= index) { throw IndexOutOfBoundsException("Index {index} is out of range of ArrayList().") } @@ -35,7 +43,7 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr var activeCommandHistory: Int = 0 // Actually inverted, because history items are added to end of list and not start. 0 means nothing, 1 means most recent command at end of list. - + //Wrapper for property of scriptingScope. var civInfo: CivilizationInfo? get() = scriptingScope.civInfo set(value) { scriptingScope.civInfo = value } diff --git a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt index e8a5d2c05ae7e..f710b19486822 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt @@ -9,6 +9,7 @@ import com.unciv.scripting.utils.Blackbox abstract class ScriptingReplManager(val scriptingScope: ScriptingScope, val blackbox: Blackbox): ScriptingBackend { + //Thus, separate partly as a semantic distinction. ScriptingBackend is designed mostly to interact with ScriptingState and (indirectly, through ScriptingState) ConsoleScreen by presenting a clean interface to shallower classes in the call stack. This class is designed to do the opposite, and keep all the code for wrapping the interfaces of the deeper and more complicated ScriptingProtocol and Blackbox classes in one place. } From d614eafa517c06cfc4f27eaf9545adb912affd1a Mon Sep 17 00:00:00 2001 From: will-ca Date: Sat, 13 Nov 2021 17:52:32 +0000 Subject: [PATCH 39/93] Cleanup and docs. Python examples. Separate Python `unciv` module and helper functions from REPL namespace. Restrict license for now (will probably switch to MPL later). --- android/assets/scripting/LICENSE | 1 + .../scripting/ScriptingEngineConstants.json | 7 ++ .../enginefiles/python/PythonScripting.md | 16 ++++ .../scripting/enginefiles/python/main.py | 26 ++++--- .../enginefiles/python/unciv_lib/api.py | 22 ++++-- .../python/unciv_lib/autocompletion.py | 6 +- .../enginefiles/python/unciv_lib/wrapping.py | 3 +- .../enginefiles/python/unciv_pyhelpers.py | 11 +++ .../unciv_scripting_examples/EndTimes.py | 73 +++++++++++++++++++ .../unciv_scripting_examples/ExternalPipe.py | 30 ++++++++ .../unciv_scripting_examples/Merfolk.py | 29 ++++++++ .../unciv_scripting_examples/PlayerMacros.py | 66 +++++++++++++++++ .../unciv_scripting_examples/__init__.py | 3 + core/Module.md | 35 +++++++-- core/src/com/unciv/scripting/LICENSE | 1 + .../src/com/unciv/scripting/ScriptingScope.kt | 3 + .../src/com/unciv/scripting/ScriptingState.kt | 2 + .../scripting/protocol/ScriptingProtocol.kt | 5 +- .../protocol/ScriptingReplManager.kt | 2 +- 19 files changed, 305 insertions(+), 36 deletions(-) create mode 100644 android/assets/scripting/LICENSE create mode 100644 android/assets/scripting/enginefiles/python/unciv_pyhelpers.py create mode 100644 android/assets/scripting/enginefiles/python/unciv_scripting_examples/EndTimes.py create mode 100644 android/assets/scripting/enginefiles/python/unciv_scripting_examples/ExternalPipe.py create mode 100644 android/assets/scripting/enginefiles/python/unciv_scripting_examples/Merfolk.py create mode 100644 android/assets/scripting/enginefiles/python/unciv_scripting_examples/PlayerMacros.py create mode 100644 android/assets/scripting/enginefiles/python/unciv_scripting_examples/__init__.py create mode 100644 core/src/com/unciv/scripting/LICENSE diff --git a/android/assets/scripting/LICENSE b/android/assets/scripting/LICENSE new file mode 100644 index 0000000000000..9c59f04b1059b --- /dev/null +++ b/android/assets/scripting/LICENSE @@ -0,0 +1 @@ +Copyright 2021 will-ca. All rights reserved (for now). diff --git a/android/assets/scripting/ScriptingEngineConstants.json b/android/assets/scripting/ScriptingEngineConstants.json index 30da917cf2f08..662b565cd58b4 100644 --- a/android/assets/scripting/ScriptingEngineConstants.json +++ b/android/assets/scripting/ScriptingEngineConstants.json @@ -10,7 +10,14 @@ unciv_lib/utils.py unciv_lib/wrapping.py PythonScripting.md + unciv_pyhelpers.py main.py + unciv_scripting_examples/ + unciv_scripting_examples/__init__.py + unciv_scripting_examples/EndTimes.py + unciv_scripting_examples/Merfolk.py + unciv_scripting_examples/ExternalPipe.py + unciv_scripting_examples/PlayerMacros.py ] syntaxHighlightingRegexStack: [ ] diff --git a/android/assets/scripting/enginefiles/python/PythonScripting.md b/android/assets/scripting/enginefiles/python/PythonScripting.md index 93e1d3256b976..f602c20615c36 100644 --- a/android/assets/scripting/enginefiles/python/PythonScripting.md +++ b/android/assets/scripting/enginefiles/python/PythonScripting.md @@ -1,3 +1,7 @@ +The Python API described by this document is built on the [IPC protocols and execution model described in `/core/Module.md`](../../../../../core/Module.md#package-comuncivscriptingprotocol). + +--- + There are basically two types of Python objects in this API: * Wrappers. @@ -42,6 +46,18 @@ print([real(city.name)+str(real(city.population.population)) for city in civInfo print({name: real(empire.cities and empire.cities[0]) for name, empire in gameInfo.ruleSet.nations.entries()}) ``` +In the Python implementation of the IPC protocol, wrapper objects are automatically serialized as their evaluated values when used in IPC requests and responses. + +```python3 +somePythonVariable = gameInfo.turns +# Assigns a wrapper object to somePythonVariable. Does not evaluate real value for `gameInfo.turns`. + +civInfo.tech.freeTechs = real(gameInfo.turns) +# Explicitly evaluate gameInfo.turns before + + + + --- A **token** is a string that has been generated by `InstanceTokenizer.kt` to represent a Kotlin instance. diff --git a/android/assets/scripting/enginefiles/python/main.py b/android/assets/scripting/enginefiles/python/main.py index 87ef430ed55ac..4e25383671d2f 100644 --- a/android/assets/scripting/enginefiles/python/main.py +++ b/android/assets/scripting/enginefiles/python/main.py @@ -21,28 +21,30 @@ uncivModule = types.ModuleType(name='unciv', doc=__doc__) sys.modules['unciv'] = uncivModule - - apiScope = uncivModule.__dict__ + # Let the entire API be imported from external scripts. # None of this will work on Upy. - apiScope.update(unciv_lib.api.Expose) + uncivModule.help = lambda thing=None: print(__doc__) if thing is None else print(unciv_lib.api.get_doc(thing)) if isinstance(thing, unciv_lib.wrapping.ForeignObject) else help(thing) + + replScope = {} - apiScope['help'] = lambda thing=None: print(__doc__) if thing is None else print(unciv_lib.api.get_doc(thing)) if isinstance(thing, unciv_lib.wrapping.ForeignObject) else help(thing) + exec('from unciv_pyhelpers import *', replScope, replScope) + # TODO: This, and the scope update in UncivReplTransceiver, should probably be a default exec in Kotlin-side game options instead. - foreignAutocompleter = unciv_lib.autocompletion.PyAutocompleteManager(apiScope, **unciv_lib.api.autocompleterkwargs) + foreignAutocompleter = unciv_lib.autocompletion.PyAutocompleteManager(replScope, **unciv_lib.api.autocompleterkwargs) - foreignActionReceiver = unciv_lib.api.UncivReplTransciever(scope=apiScope, autocompleter=foreignAutocompleter) + foreignActionReceiver = unciv_lib.api.UncivReplTransceiver(scope=replScope, apiscope=uncivModule.__dict__, autocompleter=foreignAutocompleter) foreignActionReceiver.ForeignREPL() raise RuntimeError("No REPL. Did you forget to uncomment a line in `main.py`?") except Exception as e: -# try: -# import unciv_lib.utils -# exc = unciv_lib.utils.formatException(e) -# # Disable this. A single line with undefined format is more likely to be printed than multiple. -# except: -# exc = repr(e) + # try: + # import unciv_lib.utils + # exc = unciv_lib.utils.formatException(e) + # Disable this. A single line with undefined format is more likely to be printed than multiple. + # except: + # exc = repr(e) print(f"Fatal error in Python interepreter: {repr(e)}", file=stdout, flush=True) diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/api.py b/android/assets/scripting/enginefiles/python/unciv_lib/api.py index a27c08fce18a7..2fcf286eedab8 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/api.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/api.py @@ -37,7 +37,7 @@ def get_keys(obj): return () def get_doc(obj): - """Get docstring of object. Fail silently if it has none, or generate one if it's a ForeignObject(). Used for PyAutocompleter.""" + """Get docstring of object. Fail silently if it has none, or get one through its IPC methods if it's a ForeignObject(). Used for PyAutocompleter.""" try: if isinstance(obj, wrapping.ForeignObject): doc = f"\n\n{str(obj._docstring_() or wrapping.stringPathList(obj._getpath_()))}\n\nArguments:\n" @@ -52,7 +52,7 @@ def get_doc(obj): @expose() def callable(obj): - """Return whether or not an object is callable. Used to let PyAutocompleter work with ForeignObject""" + """Return whether or not an object is callable. Used to let PyAutocompleter work with ForeignObject by calling the latters' IPC callability method.""" if isinstance(obj, wrapping.ForeignObject): return obj._callable_(raise_exceptions=False) else: @@ -68,7 +68,7 @@ def callable(obj): @expose() def real(obj): - """Evaluate a foreign object wrapper into a real Python value, or return a value unchanged if it is not a foreign object wrapper.""" + """Evaluate a foreign object wrapper into a real Python value, or return a value unchanged if not given a foreign object wrapper.""" if isinstance(obj, wrapping.ForeignObject): return obj._getvalue_() return obj @@ -80,19 +80,23 @@ def isForeignToken(obj): return isinstance(resolved, str) and resolved.startswith(apiconstants['kotlinInstanceTokenPrefix']) -class UncivReplTransciever(ipc.ForeignActionReceiver, ipc.ForeignActionSender): +class UncivReplTransceiver(ipc.ForeignActionReceiver, ipc.ForeignActionSender): """Class that implements the Unciv IPC and scripting protocol by receiving and responding to its packets.""" - def __init__(self, *args, autocompleter=None, **kwargs): + def __init__(self, *args, apiscope=None, autocompleter=None, **kwargs): ipc.ForeignActionReceiver.__init__(self, *args, **kwargs) self.autocompleter = autocompleter + self.apiscope = {} if apiscope is None else apiscope def populateApiScope(self): """Use dir() on a foreign object wrapper with an empty path to populate the execution scope with all available names.""" names = dir(wrapping.ForeignObject((), foreignrequester=self.GetForeignActionResponse)) for n in names: - if n not in self.scope: - self.scope[n] = wrapping.ForeignObject(n, foreignrequester=self.GetForeignActionResponse) + if n not in self.apiscope: + self.apiscope[n] = wrapping.ForeignObject(n, foreignrequester=self.GetForeignActionResponse) + self.scope.update({**self.apiscope, **self.scope}) + # TODO: Replace this update with Kotlin-side init? def passMic(self): """Send a 'PassMic' packet.""" + #TODO: This should use ForeignPacket(), no? self.SendForeignAction({'action':None, 'identifier': None, 'data':None, 'flags':('PassMic',)}) @ipc.receiverMethod('motd', 'motd_response') def EvalForeignMotd(self, packet): @@ -102,6 +106,10 @@ def EvalForeignMotd(self, packet): return f""" sys.implementation == {str(sys.implementation)} +Current imports: + from unciv import * + from unciv_pyhelpers import * + Press [TAB] at any time to trigger autocompletion at the current cursor position, or display help text for an empty function call. """ diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py b/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py index acc4084f4bbaf..6996c44eae98d 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py @@ -105,14 +105,14 @@ def GetAutocomplete(self, command, cursorpos=None): + working_base + working_dot + ( - f"{a}[" + f"{a}[" if self.get_keys(get_a(a)) else f"{a}(" if self.check_callable(get_a(a)) else a ) + suffix - for a in attrs + for a in sorted(attrs) if a.startswith(working_leaf) ]) except Exception as e: @@ -137,4 +137,4 @@ def GetAutocomplete(self, command, cursorpos=None): else: matches = [*self.scope.keys()]#, *keyword.kwlist] return tuple([prefix+m+suffix for m in matches]) - + diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py index e9eea134c1365..00712c0e391b9 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py @@ -34,7 +34,6 @@ def _resolvingfunction(*arguments, **keywords): if not allowforeigntokens: # Forbid foreign token strings from being manipulated through this operation. for l in (args, kwargs.values()): - #TODO: Test this. for o in l: if api.isForeignToken(o): raise TypeError(f"Not allowed to call `{op.__name__}()` function with foreign object token: {o}") @@ -172,7 +171,7 @@ def _getpath_(self): return tuple(self._path) #return ''.join(self._path) def __getattr__(self, name): - # TODO: Shouldn't I calling get_doc or _docstring_ here? + # TODO: Shouldn't I special-casing get_doc or _docstring_ here? Wait, no, I think I thought it would be accessed on the class. return self.__class__((*self._path, makePathElement(name=name)), self._foreignrequester) def __getitem__(self, key): return self.__class__((*self._path, makePathElement(ttype='Key', params=(key,))), self._foreignrequester) diff --git a/android/assets/scripting/enginefiles/python/unciv_pyhelpers.py b/android/assets/scripting/enginefiles/python/unciv_pyhelpers.py new file mode 100644 index 0000000000000..166825f654c65 --- /dev/null +++ b/android/assets/scripting/enginefiles/python/unciv_pyhelpers.py @@ -0,0 +1,11 @@ +""" +Python helpers for handling wrapped Unciv objects. + +Copied from a dictionary in unciv_lib.api. +""" + +import unciv_lib.api + +globals().update(unciv_lib.api.Expose) + +del unciv_lib diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EndTimes.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EndTimes.py new file mode 100644 index 0000000000000..d2ea4c3d2f231 --- /dev/null +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EndTimes.py @@ -0,0 +1,73 @@ +""" +Example and developmental test case for potential future modding API. + +Call onNewTurn() on every new turn. + +Call onUnitMove(worldScreen.bottomUnitTable.selectedUnit) every time a unit moves. +""" + +import unciv + + +# What does it say about me that my go-to for a developmental test case is to implement an apocalypse mod? +# Then again, randomly flipping data values is naturally the easiest test case to code regardless of lore. +# But randomness is entropy, so random changes to the world around you are by definition apocalyptic. +# ...At which point, the lore writes itself. + +turnNotifications = [] + + +def gaussianTileSelector(focus): + def _gaussianTileSelector(tile): + return True + return _gaussianTileSelector + + +def scatterFallout(focus, improvementtype, maxdistance, tileselector=lambda t: True): + pass + +def spawnNewDisasters(naturalwonder, *falloutparams): + pass + +def spreadFalloutType(improvementtype, tilepermitter=lambda t: True): + pass + + + + +def eruptVolcanoes(): + pass + +def damageBurningUnits(): + pass + +def damageBurningCities(): + pass + +def depopulateBurningCities(): + # Gods.. This sounds kinda... bad, when I write it down. + # Meh. I'll comfort myself by telling myself I go to war less than most Civ players. + pass + +def landAlienInvaders(): + pass + +def erodeMountains(): + pass + +def erodeShorelines(): + pass + + +def onNewTurn(): + """""" + spawnNewDisasters('Krakatoa') + spawnNewDisasters('Barringer Crater') + + + +def ambushUnit(unit): + pass + +def onUnitMove(unit): + pass diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/ExternalPipe.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/ExternalPipe.py new file mode 100644 index 0000000000000..e27a16e1066d2 --- /dev/null +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/ExternalPipe.py @@ -0,0 +1,30 @@ +""" +Demo for using the scripting API to get and return data from the main Unciv process's STDIN and STDOUT. + +Could potentially be useful for "AI/ML" applications and the like. + + +It's a bit weird, because it means that every call basically goes through two different REPLs and at least six back and forth IPC packets: + +Kotlin/JVM:Unciv +—> CPython:EmulatedREPL +—> Kotlin/JVM:Unciv +—> ExternalProcess:REPLOrAutomation +—> Kotlin/JVM:Unciv +—> CPython:EmulatedREPL +—> Kotlin/JVM:Unciv + +But I think letting the script have control of the process's STDIN and STDOUT is preferable to hardcoding something into the Kotlin/JVM Unciv code. +""" + +def readUncivSdtOn(): + pass + +def printUncivStdOut(): + pass + +def evalFromUncivStdIn(): + pass + +def execFromUncivStdIn(): + pass diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Merfolk.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Merfolk.py new file mode 100644 index 0000000000000..aeb4c8f2f825f --- /dev/null +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Merfolk.py @@ -0,0 +1,29 @@ +""" +Example and developmental test case for potential future modding API. + +Adds peaceful, playful Merpeople communities. + +But they won't hesitate to defend themselves if you bully them or others! + + +Call onGameStart() **once** at start of game. + +Call onNewTurn() on every new turn. + +Call onUnitMove(worldScreen.bottomUnitTable.selectedUnit) every time a unit moves. +""" + + +# Have them be like rubberbanding mobile city states in the early game, popping up in shorelines, lakes, rivers, and land to give gifts to the weakest players. +# Instantly declare war on nuclear weapon use, foreign capital city capture, or city state capture. +# Wage war by spawning units in water tiles, poisoning the water supply/charming the populace into resistance in landlocked cities. +# Stay at war until capital puppeted in case of nuclear weapon use, all foreign cities liberated in case of foreign capital/CS capture. +# Fight against mechanized barbarians (synergy with EndTimes.py alien invaders). +# In late game, cast spells to destroy volcanoes/craters (synergy with EndTimes.py natural disasters). +# Migratory cities. +# Seed the oceans with basic and luxury resources. +# If you really piss them off, flood/destroy your capital and displace its population. + + +def onGameStart(): + pass diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/PlayerMacros.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/PlayerMacros.py new file mode 100644 index 0000000000000..eb0f7dc713f45 --- /dev/null +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/PlayerMacros.py @@ -0,0 +1,66 @@ +""" +Automations to quickly do repititive tasks. + +Currently enables actions that break game rules to be done. + +IMO, that should be changed at the Kotlin side, by adding some kind of `PlayerAPI` class that unifies what the GUI is allowed to do with what the CLI is allowed to do. +""" + +from unciv import * +from unciv_pyhelpers import * + +def gatherBiggestCities(cities, ratio, stat='production'): + """Return the biggest cities from given cities that collectively comprise at least a given ratio of the total stats of all the cities. E.G: `gatherBiggestCities(civInfo.cities, 0.25, 'production')` will return a list of cities that together produce 25% of your empire's production.""" + assert 0 < ratio <= 1 + getstat = lambda c: getattr(c.cityStats.currentCityStats, stat) + total = sum(getstat(c) for c in cities) + inorder = sorted(cities, key=getstat, reverse=True) + currsum = 0 + threshold = total*ratio + selected = [] + for c in cities: + if currsum >= threshold: + break + selected.append(c) + currsum += getstat(c) + assert sum(getstat(c) for c in cities) >= threshold + print(f"Chose {len(selected)}/{len(inorder)} cities, representing {str(currsum/total*100)[:5]}% of total {total} {stat}.") + return selected + +def clearCitiesProduction(cities): + """Clear given cities of all queued and current production, and return the iterable of cities.""" + for i, city in enumerate(cities): + city.cityConstructions.constructionQueue.clear() + print(f"Cleared production from {i+1} cities.") + return cities + +def addCitiesProduction(cities, queue=()): + """Add given construction items to the construction queues of given cities, and return the iterable of cities.""" + for i, city in enumerate(cities): + for build in queue: + city.cityConstructions.addToQueue(build) + print(f"Set {i+1} cities to build {queue}.") + return cities + +def clearCitiesSpecialists(cities): + for city in cities: + city.population.specialistAllocations.clear() + return cities + +def focusCitiesFood(cities): + for city in cities: + city.population.autoAssignPopulation(999) + return cities + +def buildCitiesQueue(cities, queue): + for city in cities: + apiHelpers.registeredObjects["x"] = city.cityConstructions.getBuildableBuildings() + +#import os, sys; sys.path.append(os.path.join(os.getcwd(), "../..")); from democomms import * + +#from unciv_scripting_examples.PlayerMacros import * +#[real(c.name) for c in addCitiesProduction(clearCitiesProduction(gatherBiggestCities(civInfo.cities, 0.5)), ('Missile Cruiser', 'Nuclear Submarine', 'Mobile SAM'))] + +#focusCitiesFood(clearCitiesSpecialists(civInfo.cities)) + +#apiHelpers.toString(worldScreen.bottomUnitTable.selectedCity.cityConstructions.getBuildableBuildings()) diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/__init__.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/__init__.py new file mode 100644 index 0000000000000..09a2bf8ee9e73 --- /dev/null +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/__init__.py @@ -0,0 +1,3 @@ +__all__ = ["EndTimes", "ExternalPipe", "Merfolk", "PlayerMacros"] + +from . import EndTimes, ExternalPipe, Merfolk, PlayerMacros diff --git a/core/Module.md b/core/Module.md index 29ef602b15526..6ac7732b3a9df 100644 --- a/core/Module.md +++ b/core/Module.md @@ -50,9 +50,11 @@ TokenizingJson() // Serializer and functions that use InstanceTokenizer. ## REPL Loop +*Implemented by `class ScriptingProtocolReplManager(){}`.* + 1. A scripted action is initiated from the Kotlin side, by sending a command string to the script interpreter. 1. While the script interpreter is running, it has a chance to request values from the Kotlin side by sending back packets encoding attribute/property, key, and call, and assignment stacks. - 2. When the Kotlin side receives a request for a value, it uses reflection to access the requested property or call the requested method, and it sends the result to the script interpreter. + 2. When the Kotlin side receives a request for a value, it uses reflection to access the requested property, call the requested method, or assign to the requested property, and it sends the result to the script interpreter. No changes to gameInfo state should happen during this loop except for what is specifically requested by the running script. 3. When the script interpreter finishes running, it sends a special packet to the Kotlin side communicating that the script interpreter has no more requests to make. The script interpreter then sends the REPL output of the command to the Kotlin side. 2. When the Kotlin interpreter receives the packet marking the end of the command run, it stops listening for value requests packets. It then receives the commnad result as the next value, and passes it back to the console screen or script handler. @@ -60,12 +62,17 @@ From Kotlin: ``` fun ExecuteCommand(command:String): SendToInterpreter(command) + LockGameInfo() while True: packet:Packet = ReceiveFromInterpreter().parsed() if isPropertyRequest(packet): - SendToInterpreter(ResolvePacket(scriptingScope, packet)) + UnlockGameInfo() + response:Packet = ResolvePacket(scriptingScope, packet) + LockGameInfo() + SendToInterpreter(response) else if isCommandEndPacket(packet): break + UnlockGameInfo() PrintToConsole(ReceiveFromInterpreter().parsed().data:String) ``` @@ -79,8 +86,10 @@ Plus, letting the script interpreter run completely in parallel would probably i ## IPC Protocol -A single IPC action consists of one request packet and one response packet. -A request packet should always be followed by a response packet if it has an action. +*Implemented by `ScriptingProtocol.kt`, `ipc.py`, and `wrapping.py`.* + +A single IPC action consists of one request packet and one response packet.\ +A request packet should always be followed by a response packet if it has an action.\ If a request packet has a null action, then it should not be followed by a response. This is to let flags be sent without generating useless responses. Responses do not have to be sent in the same order as their corresponding requests. New requests can be sent out while old ones are left "open"— E.G., if creating a response requires requesting new information. @@ -91,10 +100,12 @@ Responses do not have to be sent in the same order as their corresponding reques Both the Kotlin side and the script interpreter can send and receive packets, but not necessarily at all times. -(The current loop is described in a comment in ScriptingReplManager.kt. Kotlin initiates a scripting exec, during which the script interpreter can request values from Kotlin, and at the end of which the script interpreter sends its STDOUT response to the Kotlin side.) +(The current loop is described in the section above. Kotlin initiates a scripting exec, during which the script interpreter can request values from Kotlin, and at the end of which the script interpreter sends its STDOUT response to the Kotlin side.) A single packet is a JSON string of the form: +*Implemented by `data class ScriptingPacket(){}` and `class ForeignPacket().`* + ``` { "action": String?, @@ -104,17 +115,19 @@ A single packet is a JSON string of the form: } ``` -Identifiers should be set to a unique value in each request. -Each response should have the same identifier as its corresponding request. +Identifiers should be set to a unique value in each request.\ +Each response should have the same identifier as its corresponding request.\ Upon receiving a response, both its action and identifier should be checked to match the relevant request. --- +*Implemented by `object InstanceTokenizer{}` and `object TokenizingJson{}`.* + The data field is allowed to represent any hierarchy, of instances of any types. If it must represent instances that are not possible or not useful to serialize as JSON hierarchies, then unique identifying token strings should be generated and sent in the places of those instances. -If those strings are received at any hierarchical depth in the data field of any later packets, then they are to be substituted with their original instances in all uses of the information from those packets. +If those strings are received at any hierarchical depth in the data field of any later packets, then they are to be substituted with their original instances in all uses of the information from those packets.\ If the original instance of a received token string no longer exists, then an exception should be thrown, and handled as would any exception at the point where the instance is to be accessed. Example Kotlin-side instance requested by script interpreter: @@ -163,6 +176,8 @@ someProperty = listOf(5, "ActualStringValue", SomeKotlinInstance@M3mAdDr) Some action types, data formats, and expected response data formats for packets sent from the Kotlin side to the script interpreter include: +*Implemented by `class ScriptingProtocol(){}` and `class UncivReplTransceiver()`* + ``` 'motd': null -> 'motd_response': String @@ -192,6 +207,8 @@ The above are basically a mirror of ScriptingBackend, so the same interface can Some action types, data formats, and expected response types and formats for packets sent from the script interpreter to the Kotlin side include: +*Implemented by `class ScriptingProtocol(){}` and `class ForeignObject()`* + ``` 'read': {'path': List<{'type':String, 'name':String, 'params':List}>} -> 'read_reponse': {'value': Any?, 'exception': String?} @@ -268,6 +285,8 @@ The path elements in some of the data fields mirror PathElement. Flags are string values for communicating extra information that doesn't need a separate packet or response. Depending on the flag and action, they may be contextual to the packet, or they may not. I think I see them mostly as a way to semantically separate meta-communication about the protocol from actual requests for actions: +*Implemented by `enum class KnownFlag(){}` and `class UncivReplTransceiver()`.* + ``` 'PassMic' //Indicates that the sending side will now begin listening instead, and the receiving side should send the next request. diff --git a/core/src/com/unciv/scripting/LICENSE b/core/src/com/unciv/scripting/LICENSE new file mode 100644 index 0000000000000..9c59f04b1059b --- /dev/null +++ b/core/src/com/unciv/scripting/LICENSE @@ -0,0 +1 @@ +Copyright 2021 will-ca. All rights reserved (for now). diff --git a/core/src/com/unciv/scripting/ScriptingScope.kt b/core/src/com/unciv/scripting/ScriptingScope.kt index 0469fb1d92b09..d537c92a3a28d 100644 --- a/core/src/com/unciv/scripting/ScriptingScope.kt +++ b/core/src/com/unciv/scripting/ScriptingScope.kt @@ -22,6 +22,7 @@ class ScriptingScope( var gameInfo: GameInfo?, var uncivGame: UncivGame?, var worldScreen: WorldScreen? + //val _availableNames = listOf("civInfo", "gameInfo", "uncivGame", "worldScreen", "apiHelpers") ) { val apiHelpers = ApiHelpers(this) @@ -34,6 +35,8 @@ class ScriptingScope( val registeredInstances = InstanceRegistry() fun unchanged(obj: Any?) = obj //Debug/dev identity function for both Kotlin and scripts. Check if value survives serialization, force something to be added to ScriptingProtocol.instanceSaver, etc. fun printLn(msg: Any?) = println(msg) + //fun readLine + //Return a line from the main game process's STDIN. fun toString(obj: Any?) = obj.toString() } diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index df18217eb926f..45c5eceaabd8a 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -14,6 +14,8 @@ fun ArrayList.clipIndexToBounds(index: Int, extendsize: Int = 0): Int { } +// TODO: Replace Exception types with Throwable? Wait, no. Apparently that just includes "serious problems that a reasonable application should not try to catch." + /** * Make sure an index is valid for this array. * diff --git a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt index 05a387cd5b290..065dd4ef0eb25 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt @@ -19,7 +19,7 @@ import kotlinx.serialization.json.decodeFromJsonElement import java.util.UUID -// See Module.md a description of the protocol. +// See Module.md for a description of the protocol. @Serializable @@ -139,10 +139,9 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = } fun makeActionResponse(packet: ScriptingPacket): ScriptingPacket { -// var action: String? = null val action = ScriptingProtocol.responseTypes[packet.action]!! var data: JsonElement? = null - var flags = mutableListOf() + val flags = mutableListOf() var value: JsonElement = JsonNull var exception: JsonElement = JsonNull when (packet.action) { diff --git a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt index f710b19486822..1d6a795e34dfb 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt @@ -42,7 +42,7 @@ class ScriptingRawReplManager(scriptingScope: ScriptingScope, blackbox: Blackbox /** - * REPL manager that uses the IPC protocol defined in ScriptingProtocol.kt to communicate with a black box. Suitable for presenting arbitrary access to Kotlin/JVM state to scripting APIs. See Module.md for a description of the REPL loop. + * REPL manager that uses the IPC protocol defined in ScriptingProtocol.kt to communicate with a black box. Suitable for presenting arbitrary access to Kotlin/JVM state to scripting APIs. See Module.md for a detailed description of the REPL loop. */ class ScriptingProtocolReplManager(scriptingScope: ScriptingScope, blackbox: Blackbox): ScriptingReplManager(scriptingScope, blackbox) { From 60c9a6439b534bab99483c9f67c571d4aeb19545 Mon Sep 17 00:00:00 2001 From: will-ca Date: Sun, 14 Nov 2021 07:24:29 +0000 Subject: [PATCH 40/93] In-place Python operators. Docs. --- .../enginefiles/python/PythonScripting.md | 55 ++++++++++++++-- .../enginefiles/python/unciv_lib/wrapping.py | 65 ++++++++++++------- .../unciv_scripting_examples/PlayerMacros.py | 2 +- core/Module.md | 10 +-- .../unciv/scripting/utils/ApiSpecGenerator.kt | 17 +++-- 5 files changed, 112 insertions(+), 37 deletions(-) diff --git a/android/assets/scripting/enginefiles/python/PythonScripting.md b/android/assets/scripting/enginefiles/python/PythonScripting.md index f602c20615c36..de6db5365c606 100644 --- a/android/assets/scripting/enginefiles/python/PythonScripting.md +++ b/android/assets/scripting/enginefiles/python/PythonScripting.md @@ -13,7 +13,7 @@ A **wrapper** is an object that stores a list of attribute names, keys, and func A wrapper object does not store any more values than that. When it is evaluated, it uses a simple IPC protocol to request an up-to-date real value from the game's Kotlin code. The `unciv_lib.api.real()` function can be used to manually get a real Python value from a foreign instance wrapper. -However, because the wrapper class implements many Magic Methods that automatically call its evaluation method, many programming idioms common in Python are possible with them even without manual evaluation. Comparisons, equality, arithmetic, concatenation, inplace operations and more are all supported. +However, because the wrapper class implements many Magic Methods that automatically call its evaluation method, many programming idioms common in Python are possible with them even without manual evaluation. Comparisons, equality, arithmetic, concatenation, in-place operations and more are all supported. Accessing an attribute on a wrapper object returns a new wrapper object that has an additional name set to "Property" at the end of its path list. Performing an array or dictionary index returns a new wrapper with an additional "Key" element in its path list. @@ -53,10 +53,34 @@ somePythonVariable = gameInfo.turns # Assigns a wrapper object to somePythonVariable. Does not evaluate real value for `gameInfo.turns`. civInfo.tech.freeTechs = real(gameInfo.turns) -# Explicitly evaluate gameInfo.turns before +# Explicitly evaluate gameInfo.turns before using it in IPC assignment. +# 1. Makes IPC request for gameInfo.turns. +# 2. Receives IPC response for gameInfo.turns as integer. +# 3. Makes IPC request to assign resulting integer to civInfo.tech.freeTechs. +civInfo.techs.freeTechs = gameInfo.turns +# Does the same thing as above, because the gameInfo.turns wrapper object is resolved at the point of serialization. +``` + +The magic methods implemented on wrapper objects automatically evaluate wrappers into real Python values if they are used in Python-space operations. + +```python3 +gameInfo.turns + 5 +5 + gameInfo.turns +x = 5 +x += gameInfo.turns +# All works, because the gameInfo.turns wrapper is automatically resolved into an integer when it's added. +``` + +In-place operations on wrapper objects are implemented by performing the operation using Python semantics and then making an IPC request to assign the result in Kotlin/the JVM. + +```python3 +gameInfo.turns += 5 +# Equivalent to: +gameInfo.turns = real(gameInfo.turns) + real(5) +``` --- @@ -139,6 +163,27 @@ del apiHelpers.registeredInstances["centertile"] In order to use this technique properly, the assignment of an object to a concrete path should be done within the same REPL loop as the generation of the token used to assign it. This is because the Kotlin code responsible for generating tokens and managing the REPL loop keeps references to all returned objects within each REPL loop. Afterwards, these references are immediately cleared, so any objects that do not have references elsewhere in Kotlin/the JVM are liable to be garbage-collected. +```python3 +>>> apiHelpers.registeredInstances["x"] = apiHelpers.Factories.Vector2(1,2) +>>> worldScreen.mapHolder.setCenterPosition(apiHelpers.registeredInstances["x"], True, True) +# Works, because the instance creation and token-based assignment in Kotlin are done in the same REPL execution. + +>>> x = apiHelpers.Factories.Vector2(1,2); civInfo.endTurn(); apiHelpers.registeredInstances["x"] = x +>>> worldScreen.mapHolder.setCenterPosition(apiHelpers.registeredInstances["x"], True, True) +# Also works. +``` + +```python3 +>>> x = apiHelpers.Factories.Vector2(1,2) +>>> apiHelpers.registeredInstances["x"] = x +>>> worldScreen.mapHolder.setCenterPosition(apiHelpers.registeredInstances["x"], True, True) +# May not work, because the created instance has no reference in Kotlin between the two script executions and can be garbage-collected. + +>>> x = apiHelpers.Factories.Vector2(1,2) +>>> worldScreen.mapHolder.setCenterPosition(x, True, True) +# Also may not work. +``` + **It is very important that you delete concrete paths you have set after you are done with them.** Any objects held at paths you do not delete will continue to occupy system memory for the remaining run time of the application's lifespan. We can't rely on Python's garbage collection in this case because it doesn't control the Kotlin objects, nor can we rely on the JVM's garbage collector because it doesn't know whether Python code still needs the objects in question, so you will have to manage the memory yourself by keeping a reference as long as you need an object and deleting it to free up memory afterwards. For any complicated script in Python, it is suggested that you write a context manager class to automatically take care of saving and freeing each object where appropriate. @@ -161,7 +206,8 @@ del apiHelpers.registeredInstances["myCoolScript"] --- -The top-level namespace of the API can be accessed as a module in any script imported from it. +The top-level namespace of the API can be imported as the `unciv` module in any script started running in the same interpreter as it.\ +Further tools can be imported as `unciv_pyhelpers`. This is useful when writing modules that are meant to be imported from the main Unciv Python namespace. @@ -171,10 +217,11 @@ In a file in your `PYTHONPATH`/`sys.path`: #MyCoolModule.py import unciv +import unciv_pyhelpers def printCivilizations(): for civ in unciv.gameInfo.civilizations: - print(f"{unciv.real(civ.nation.name)}: {len(civ.cities)} cities") + print(f"{unciv_pyhelpers.real(civ.nation.name)}: {len(civ.cities)} cities") ``` In Unciv: diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py index 00712c0e391b9..9ec96d1449c5f 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py @@ -50,6 +50,13 @@ def _reversedop(a, b, *args, **kwargs): _reversedop.__doc__ = f"{func.__doc__ or name + ' operator.'}\n\nReversed version." return _reversedop +def InplaceMethod(func): + """Return a wrapped a function that calls ._setvalue_() on its first self argument with its original result.""" + def _inplacemethod(self, *args, **kwargs): + self._setvalue_(func(self, *args, **kwargs)) + return self + return _inplacemethod + def dummyForeignRequester(actionparams, responsetype): return actionparams, responsetype @@ -138,6 +145,22 @@ def stringPathList(pathlist): '__ror__', ) +_imagicmethods = ( + '__iadd__', + '__isub__', + '__imul__', + '__imatmul__', + '__itruediv__', + '__ifloordiv__', + '__imod__', + '__ipow__', + '__ilshift__', + '__irshift__', + '__iand__', + '__ixor__', + '__ior__' +) + def ResolveForOperators(cls): """Decorator. Adds missing magic methods to a class, which resolve their arguments with `api.real(a)`.""" def alreadyhas(name): @@ -154,6 +177,11 @@ def alreadyhas(name): normalname = rmeth.replace('__r', '__', 1) if not alreadyhas(rmeth) and hasattr(cls, normalname): setattr(cls, rmeth, ReversedMethod(getattr(cls, normalname))) + for imeth in _imagicmethods: + normalname = imeth.replace('__i', '__', 1) + if not alreadyhas(imeth) and hasattr(cls, normalname): + normalfunc = getattr(cls, normalname) + setattr(cls, imeth, InplaceMethod(normalfunc)) return cls @@ -182,6 +210,10 @@ def __iter__(self): return iter(self.keys()) except: return (self[i] for i in range(0, len(self))) + def __setattr__(self, name, value): + return getattr(self, name)._setvalue_(value) + def __setitem__(self, key, value): + return self[key]._setvalue_(value) @ForeignRequestMethod def _getvalue_(self): return ({ @@ -193,6 +225,17 @@ def _getvalue_(self): 'read_response', foreignValueParser) @ForeignRequestMethod + def _setvalue_(self, value): + return ({ + 'action': 'assign', + 'data': { + 'path': self._getpath_(), + 'value': value + } + }, + 'assign_response', + foreignErrmsgChecker) + @ForeignRequestMethod def _callable_(self, *, raise_exceptions=True): return ({ 'action': 'callable', @@ -233,17 +276,6 @@ def __dir__(self): 'dir_response', foreignValueParser) @ForeignRequestMethod - def __setattr__(self, name, value): - return ({ - 'action': 'assign', - 'data': { - 'path': getattr(self, name)._getpath_(), - 'value': value - } - }, - 'assign_response', - foreignErrmsgChecker) - @ForeignRequestMethod def __call__(self, *args): return ({ 'action': 'read', @@ -254,17 +286,6 @@ def __call__(self, *args): 'read_response', foreignValueParser) @ForeignRequestMethod - def __setitem__(self, key, value): - return ({ - 'action': 'assign', - 'data': { - 'path': self[key]._getpath_(), - 'value': value - } - }, - 'assign_response', - foreignErrmsgChecker) - @ForeignRequestMethod def __delitem__(self, key): return ({ 'action': 'delete', diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/PlayerMacros.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/PlayerMacros.py index eb0f7dc713f45..d5bd929463976 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/PlayerMacros.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/PlayerMacros.py @@ -1,5 +1,5 @@ """ -Automations to quickly do repititive tasks. +Example automations to quickly do repititive tasks. Currently enables actions that break game rules to be done. diff --git a/core/Module.md b/core/Module.md index 6ac7732b3a9df..aca8f8c4bd0f6 100644 --- a/core/Module.md +++ b/core/Module.md @@ -56,7 +56,7 @@ TokenizingJson() // Serializer and functions that use InstanceTokenizer. 1. While the script interpreter is running, it has a chance to request values from the Kotlin side by sending back packets encoding attribute/property, key, and call, and assignment stacks. 2. When the Kotlin side receives a request for a value, it uses reflection to access the requested property, call the requested method, or assign to the requested property, and it sends the result to the script interpreter. No changes to gameInfo state should happen during this loop except for what is specifically requested by the running script. 3. When the script interpreter finishes running, it sends a special packet to the Kotlin side communicating that the script interpreter has no more requests to make. The script interpreter then sends the REPL output of the command to the Kotlin side. -2. When the Kotlin interpreter receives the packet marking the end of the command run, it stops listening for value requests packets. It then receives the commnad result as the next value, and passes it back to the console screen or script handler. +2. When the Kotlin interpreter receives the packet marking the end of the command run, it stops listening for value requests packets. It then receives the command result as the next value, and passes it back to the console screen or script handler. From Kotlin: ``` @@ -174,7 +174,7 @@ someProperty = listOf(5, "ActualStringValue", SomeKotlinInstance@M3mAdDr) --- -Some action types, data formats, and expected response data formats for packets sent from the Kotlin side to the script interpreter include: +Some action types, data formats, and expected response types and data formats for packets sent from the Kotlin side to the script interpreter include: *Implemented by `class ScriptingProtocol(){}` and `class UncivReplTransceiver()`* @@ -205,7 +205,7 @@ The above are basically a mirror of ScriptingBackend, so the same interface can --- -Some action types, data formats, and expected response types and formats for packets sent from the script interpreter to the Kotlin side include: +Some action types, data formats, and expected response types and data formats for packets sent from the script interpreter to the Kotlin side include: *Implemented by `class ScriptingProtocol(){}` and `class ForeignObject()`* @@ -307,8 +307,8 @@ Flags are string values for communicating extra information that doesn't need a --- -Thus, at the IPC level, all foreign backends will actually use the same language, which is this JSON-based protocol. Differences between Python, JS, Lua, etc. will all be down to how they interpret the "exec", "autocomplete", and "motd" requests differently, which each high-level scripting language is free to implement as works best for it. +Thus, at the IPC level, all foreign backends will actually use the same language, which is this JSON-based protocol. Differences between Python, JS, Lua, etc. will all be down to how they interpret the "exec", "autocomplete", and "motd" requests differently, and how they use and expose the Kotlin/JVM-access request types differently, which each high-level scripting language is free to implement as works best for it. ## Python Binding Implementation -A description of how this REPL loop and IPC protocol are used to build scripting langauage bindings [is at `/android/assets/scripting/enginefiles/python/PythonScripting.md`](../android/assets/scripting/enginefiles/python/PythonScripting.md). +A description of how this REPL loop and IPC protocol are used to build a scripting langauage binding [is at `/android/assets/scripting/enginefiles/python/PythonScripting.md`](../android/assets/scripting/enginefiles/python/PythonScripting.md). diff --git a/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt b/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt index 3dac52f71470a..342b4069d8b48 100644 --- a/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt +++ b/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt @@ -11,6 +11,9 @@ import com.badlogic.gdx.utils.Json // Automatically running this should probably be a part of the build process. // Probably do whatever is done with `TranslationFileWriter`. +@Retention(AnnotationRetention.RUNTIME) +annotation class ExposeScriptingApi + data class ApiSpecDef( var path: String, var isIterable: Boolean, @@ -18,7 +21,7 @@ data class ApiSpecDef( var isCallable: Boolean, var callableArgs: List, // These are the basic values that are needed to implement a scripting API. - + var _type: String? = null, //_docstring: String? = null, var _repeatedReferenceTo: String? = null, @@ -44,7 +47,7 @@ fun makeMemberSpecDef(member: KCallable<*>): ApiSpecDef { }*/ //val tmp: Collection = kclass.members.filter{ it.name != null }.map{ it.name!! } //submembers.addAll(tmp) - //Using a straight `.map` + //Using a straight `.map` return ApiSpecDef( path = member.name, isIterable = "iterator" in submembers, @@ -60,7 +63,7 @@ class ApiSpecGenerator(val scriptingScope: ScriptingScope) { fun isUncivClass(cls: KClass<*>): Boolean { return cls.qualifiedName!!.startsWith("com.unciv") } - + fun getAllUncivClasses(): Set> { val searchclasses = mutableListOf>(scriptingScope::class) val encounteredclasses = mutableSetOf>() @@ -85,11 +88,15 @@ class ApiSpecGenerator(val scriptingScope: ScriptingScope) { } return encounteredclasses } - + + fun generateRootsApi(): List { + return listOf() + } + fun generateFlatApi(): List { return scriptingScope::class.members.map{ it.name } } - + fun generateClassApi(): Map> { // Provide options for the scripting languages. This function val classes = getAllUncivClasses() From 3b3f3a498fc5c0e7ad99c671877ef39c2aed99c7 Mon Sep 17 00:00:00 2001 From: will-ca Date: Tue, 16 Nov 2021 05:27:43 +0000 Subject: [PATCH 41/93] Mostly random notes/placeholders. --- .../scripting/ScriptingEngineConstants.json | 1 + .../enginefiles/python/PythonScripting.md | 8 +- .../scripting/enginefiles/python/main.py | 4 + .../enginefiles/python/unciv_lib/api.py | 1 + .../python/unciv_lib/autocompletion.py | 2 +- .../enginefiles/python/unciv_lib/wrapping.py | 4 +- .../MapEditingMacros.py | 98 +++++++++++++++++++ .../unciv_scripting_examples/PlayerMacros.py | 2 +- .../python/unciv_scripting_examples/Tests.py | 1 + .../src/com/unciv/scripting/ScriptingScope.kt | 11 ++- .../src/com/unciv/scripting/ScriptingState.kt | 2 +- .../unciv/scripting/utils/ApiSpecGenerator.kt | 1 + .../scripting/utils/InstanceFactories.kt | 3 +- .../scripting/utils/ScriptingApiEnums.kt | 14 +++ 14 files changed, 139 insertions(+), 13 deletions(-) create mode 100644 android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py create mode 100644 android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py create mode 100644 core/src/com/unciv/scripting/utils/ScriptingApiEnums.kt diff --git a/android/assets/scripting/ScriptingEngineConstants.json b/android/assets/scripting/ScriptingEngineConstants.json index 662b565cd58b4..edb12eeb8e5e6 100644 --- a/android/assets/scripting/ScriptingEngineConstants.json +++ b/android/assets/scripting/ScriptingEngineConstants.json @@ -18,6 +18,7 @@ unciv_scripting_examples/Merfolk.py unciv_scripting_examples/ExternalPipe.py unciv_scripting_examples/PlayerMacros.py + unciv_scripting_examples/Tests.py ] syntaxHighlightingRegexStack: [ ] diff --git a/android/assets/scripting/enginefiles/python/PythonScripting.md b/android/assets/scripting/enginefiles/python/PythonScripting.md index de6db5365c606..972048ef319a9 100644 --- a/android/assets/scripting/enginefiles/python/PythonScripting.md +++ b/android/assets/scripting/enginefiles/python/PythonScripting.md @@ -11,7 +11,7 @@ There are basically two types of Python objects in this API: A **wrapper** is an object that stores a list of attribute names, keys, and function call parameters corresponding to a path in the Kotlin/JVM namespace. E.G.: `"civInfo.civilizations[0].population.setPopulation"` is a string representation of a wrapped path that begins with two attribute accesses, followed by one array index, and two more attribute names. -A wrapper object does not store any more values than that. When it is evaluated, it uses a simple IPC protocol to request an up-to-date real value from the game's Kotlin code. The `unciv_lib.api.real()` function can be used to manually get a real Python value from a foreign instance wrapper. +A wrapper object does not store any more values than that. When it is evaluated, it uses a simple IPC protocol to request an up-to-date real value from the game's Kotlin code. The `unciv_lib.api.real()`/`unciv_pyhelpers.real()` function can be used to manually get a real Python value from a foreign instance wrapper. However, because the wrapper class implements many Magic Methods that automatically call its evaluation method, many programming idioms common in Python are possible with them even without manual evaluation. Comparisons, equality, arithmetic, concatenation, in-place operations and more are all supported. @@ -46,7 +46,7 @@ print([real(city.name)+str(real(city.population.population)) for city in civInfo print({name: real(empire.cities and empire.cities[0]) for name, empire in gameInfo.ruleSet.nations.entries()}) ``` -In the Python implementation of the IPC protocol, wrapper objects are automatically serialized as their evaluated values when used in IPC requests and responses. +In the Python implementation of the IPC protocol, wrapper objects are automatically evaluated and serialized as their resolved values when used in IPC requests and responses. ```python3 somePythonVariable = gameInfo.turns @@ -62,14 +62,14 @@ civInfo.techs.freeTechs = gameInfo.turns # Does the same thing as above, because the gameInfo.turns wrapper object is resolved at the point of serialization. ``` -The magic methods implemented on wrapper objects automatically evaluate wrappers into real Python values if they are used in Python-space operations. +The magic methods implemented on wrapper objects also automatically evaluate wrappers into real Python values if they are used in Python-space operations. ```python3 gameInfo.turns + 5 5 + gameInfo.turns x = 5 x += gameInfo.turns -# All works, because the gameInfo.turns wrapper is automatically resolved into an integer when it's added. +# All works, because the gameInfo.turns wrapper automatically sends and receives IPC packets to resolve into its real value as an integer when it's added. ``` In-place operations on wrapper objects are implemented by performing the operation using Python semantics and then making an IPC request to assign the result in Kotlin/the JVM. diff --git a/android/assets/scripting/enginefiles/python/main.py b/android/assets/scripting/enginefiles/python/main.py index 4e25383671d2f..7d39fea2988ad 100644 --- a/android/assets/scripting/enginefiles/python/main.py +++ b/android/assets/scripting/enginefiles/python/main.py @@ -1,3 +1,7 @@ +# This should never be used to run untrusted code. AFAICT, Python is basically impossible to sandbox, short of running it in a VM. +# Example: https://lwn.net/Articles/574215/ +# Even if Python's sandboxed, the full reflective access on the Kotlin/JVM side isn't. + try: import os with open(os.path.join(os.path.dirname(__file__), "PythonScripting.md"), 'r') as f: diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/api.py b/android/assets/scripting/enginefiles/python/unciv_lib/api.py index 2fcf286eedab8..0a20e07a06f2c 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/api.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/api.py @@ -126,6 +126,7 @@ def EvalForeignExec(self, packet): with ipc.FakeStdout() as fakeout: print(f">>> {str(line)}") try: + # TODO: See if you can use signals to catch infinite loops/excess run duration. try: code = compile(line, 'STDIN', 'eval') except SyntaxError: diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py b/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py index 6996c44eae98d..70efbebc763ec 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py @@ -111,7 +111,7 @@ def GetAutocomplete(self, command, cursorpos=None): if self.check_callable(get_a(a)) else a ) - + suffix + + suffix#TODO: Merge end of matches with beginnnings of suffixes if there's overlap. for a in sorted(attrs) if a.startswith(working_leaf) ]) diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py index 9ec96d1449c5f..56c15242a3d1f 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py @@ -98,7 +98,7 @@ def stringPathList(pathlist): '__ge__', '__gt__', '__not__', - ('__bool__', 'truth'), + ('__bool__', 'truth'),#TODO: Allow foreign tokens for this, and other unary operators. # @is # This could get messy. It's probably best to just not support identity comparison. What do you compare? JVM Kotlin value? Resolved Python value? Python data path? Token strings from InstanceTokenizer.kt— Which are currently randomly re-generated for multiple accesses to the same Kotlin object, and thus always unique, and which would require another protocol-level guarantee to not do that, in addition to being (kinda by design) procedurally indistinguishable from "real" resovled Python values? # @is_not # Also, these aren't even magic methods. '__abs__', @@ -202,7 +202,7 @@ def __getattr__(self, name): # TODO: Shouldn't I special-casing get_doc or _docstring_ here? Wait, no, I think I thought it would be accessed on the class. return self.__class__((*self._path, makePathElement(name=name)), self._foreignrequester) def __getitem__(self, key): - return self.__class__((*self._path, makePathElement(ttype='Key', params=(key,))), self._foreignrequester) + return self.__class__((*self._path, makePathElement(ttype='Key', params=(key,))), self._foreignrequester) #TODO: Should negative indexing from end be supported? # def __hash__(self): # return hash(stringPathList(self._getpath_())) def __iter__(self): diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py new file mode 100644 index 0000000000000..a6a2673de3a6e --- /dev/null +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py @@ -0,0 +1,98 @@ +""" +Example scripted map editor tools. + +Shows benefits of using the scripting language: +User modifiability, rapid development, idiomatic and expressive syntax, and dynamic access to system libraries. + +These example features below would be a nightmare to implement and maintain in the Kotlin code. +But in Python, they can be drafted up fairly quickly, and it doesn't even matter if they break, because they can't really interfere with anything else. + +And most importantly, they can be made and distributed by the user themselves as external files in their interpreter's library path. This lets very obscure and niche features be implemented and used by those who want them, without adding anything to the maintenance burden for the core Unciv codebase. +""" + +# If you modify this file, please add any new functions to the build tests. + +_defaultterrainsequence = () + +#[(setattr(t, "baseTerrain", "Mountain") if real(t) else None) for r in gameInfo.tileMap.tileMatrix for t in r] +#Apparently there's no distinction between base terrain types and terrain features. +#[([setattr(t, "baseTerrain", "Grassland" if t.position.x % 2 else "Coast"), setattr(t, "naturalWonder", None if t.position.y%3 else "Krakatoa")] if real(t) else None) for r in gameInfo.tileMap.tileMatrix for t in r] + + +def hexCoordToRectCoord(vector): + pass + +def rectCoordToHexCoord(vector): + pass + + +def terrainAsString(tileInfo): + s = real(t.baseTerrain) + if len(t.terrainFeatures): + s += "/" + ",".join() + return s + +def terrainFromString(terrainstring): + return baseterrain, tuple(terrainfeatures) + +def setTerrain(tileInfo, terrainstring): + pass + + +def checkValidValue(value, checktype="TileType"): + if value not in unciv.mapEditorScreen.ruleset.whatever: + raise Exception() + + +def spreadResources(resourcetype="Horses", mode="random", restrictfrom=(), restrictto=None): + if mode == "random": + pass + elif mode in ("jittered", "grid", "clustered", "bluenoise"): + raise NotImplementedError(f"") + else: + raise TypeError() + +def makeMandelbrot(): + pass + +def graph2D(expr="sin(x/5)*5", north="Ocean", south="Desert"): + pass + +def graph3D(expr="sqrt(x**2+y**2)/5%5*len(terrains)", terrains=_defaultterrainsequence) + pass + +def loadImageHeightmap(imagepath=None, tiletypes("Ocean", "Coast")): + import PIL + pass + +def dilateTileTypes(tiletypes=("Coast", "Flood Plains"), chance=1.0, forbidreplace=("Ocean", "Mountain"), dilateas=("Desert/Flood Plains", "Coast"), iterations=1): + # .terrainFeatures and .baseTerrain + pass + +def erodeTileType(tiletypes=("Mountains", "Plains/Hill", "Grassland/Hill")): + pass + +def floodFillSelected(start=None, fillas=None, *, alsopropagateto=()): + pass + + +def _computeTerrainColours(terraintypes) + global _ + +def requireComputedColours(terraintypes, allowcompute=False): + global _ + if not _ or not all(t in _ for t in terraintypes): + if allow_compute_colours: + pass + else: + print(f"This function requires the average colour to be computed for the following terrain types:\n{terraintypes}\n\nDoing so may take a long time. The interface will be unresponsive during the process.\nPass `allow_compute_colours=True` to this function, or run `_computeTerrainColours({terraintypes})`, in order to compute the required colours.") + + +def loadImageColours(imagepath=None, allowedterrains=(), allow_compute_colours=False, visualspace=True): + global _ + requireComputedColours(allowedterrains, allow_compute_colours) + +def makeImageFromTerrainColours(allowedterrains, allow_compute_colours, visualspace=True): + requireComputedColours + pass# Make a PIL image, but don't mess with the user's filesystem. + #I guess inversing the output from this using loadImageColours could be a unit test. diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/PlayerMacros.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/PlayerMacros.py index d5bd929463976..94c76a6dbd046 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/PlayerMacros.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/PlayerMacros.py @@ -1,5 +1,5 @@ """ -Example automations to quickly do repititive tasks. +Example automations to quickly do repetitive in-game tasks. Currently enables actions that break game rules to be done. diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py new file mode 100644 index 0000000000000..8b137891791fe --- /dev/null +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py @@ -0,0 +1 @@ + diff --git a/core/src/com/unciv/scripting/ScriptingScope.kt b/core/src/com/unciv/scripting/ScriptingScope.kt index d537c92a3a28d..91b6583a86747 100644 --- a/core/src/com/unciv/scripting/ScriptingScope.kt +++ b/core/src/com/unciv/scripting/ScriptingScope.kt @@ -1,8 +1,9 @@ package com.unciv.scripting +import com.unciv.UncivGame import com.unciv.logic.GameInfo import com.unciv.logic.civilization.CivilizationInfo -import com.unciv.UncivGame +import com.unciv.scripting.utils.ScriptingApiEnums import com.unciv.scripting.utils.InstanceFactories import com.unciv.scripting.utils.InstanceRegistry import com.unciv.ui.worldscreen.WorldScreen @@ -22,7 +23,8 @@ class ScriptingScope( var gameInfo: GameInfo?, var uncivGame: UncivGame?, var worldScreen: WorldScreen? - //val _availableNames = listOf("civInfo", "gameInfo", "uncivGame", "worldScreen", "apiHelpers") + //mapEditorScreen + //val _availableNames = listOf("civInfo", "gameInfo", "uncivGame", "worldScreen", "apiHelpers") // Nope. Annotate instead. ) { val apiHelpers = ApiHelpers(this) @@ -31,13 +33,16 @@ class ScriptingScope( val isInGame: Boolean get() = (scriptingScope.civInfo != null && scriptingScope.gameInfo != null && scriptingScope.uncivGame != null) val Factories = InstanceFactories -// val registeredInstances = mutableMapOf() + val Enums = ScriptingApiEnums val registeredInstances = InstanceRegistry() fun unchanged(obj: Any?) = obj //Debug/dev identity function for both Kotlin and scripts. Check if value survives serialization, force something to be added to ScriptingProtocol.instanceSaver, etc. fun printLn(msg: Any?) = println(msg) //fun readLine //Return a line from the main game process's STDIN. fun toString(obj: Any?) = obj.toString() + //fun typeOf(obj: Any?) = obj::class.simpleName + //fun typeOfQualified(obj: Any?) = obj::class.qualifiedName } } +//worldScreen.bottomUnitTable.selectedCity.cityConstructions.purchaseConstruction("Missionary", -1, False, apiHelpers.Enums.Stat.statsUsableToBuy[4]) diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index 45c5eceaabd8a..06d72ec599379 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -147,7 +147,7 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr fun exec(command: String): String { if (command.length > 0) { - commandHistory.add(command) + commandHistory.add(command) // TODO: Also don't add duplicates. while (commandHistory.size > maxCommandHistory) { commandHistory.removeAt(0) // No need to restrict activeCommandHistory to valid indices here because it gets set to zero anyway. diff --git a/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt b/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt index 342b4069d8b48..596b473eef926 100644 --- a/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt +++ b/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt @@ -13,6 +13,7 @@ import com.badlogic.gdx.utils.Json @Retention(AnnotationRetention.RUNTIME) annotation class ExposeScriptingApi +// Eventually use this to whitelist safe API accessible members for security. data class ApiSpecDef( var path: String, diff --git a/core/src/com/unciv/scripting/utils/InstanceFactories.kt b/core/src/com/unciv/scripting/utils/InstanceFactories.kt index 13521e333014b..70bdecd69132c 100644 --- a/core/src/com/unciv/scripting/utils/InstanceFactories.kt +++ b/core/src/com/unciv/scripting/utils/InstanceFactories.kt @@ -4,9 +4,10 @@ import com.badlogic.gdx.math.Vector2 /** - * For use in ScriptingScope. Allows bound scripts to make new instances of Kotlin/JVM classes. + * For use in ScriptingScope. Allows interpreted scripts to make new instances of Kotlin/JVM classes. */ object InstanceFactories { fun Vector2(x: Float, y: Float) = com.badlogic.gdx.math.Vector2(x, y) fun MapUnit() = "NotImplemented" } + diff --git a/core/src/com/unciv/scripting/utils/ScriptingApiEnums.kt b/core/src/com/unciv/scripting/utils/ScriptingApiEnums.kt new file mode 100644 index 0000000000000..a429c32c04048 --- /dev/null +++ b/core/src/com/unciv/scripting/utils/ScriptingApiEnums.kt @@ -0,0 +1,14 @@ +package com.unciv.scripting.utils + +import com.unciv.models.stats.Stat + + +inline fun > enumToMap() = enumValues().associateBy{ it.name } + + +/** + * For use in ScriptingScope. Allows interpreted scripts to access Unciv Enum constants. + */ +object ScriptingApiEnums { + val Stat = enumToMap() +} From d4ac1d16ad85a485e00a53e77174158c31f8a158 Mon Sep 17 00:00:00 2001 From: will-ca Date: Wed, 17 Nov 2021 04:41:20 +0000 Subject: [PATCH 42/93] KDocs for most things. --- .../enginefiles/python/PythonScripting.md | 46 +++++++- .../enginefiles/python/unciv_lib/ipc.py | 4 +- .../enginefiles/python/unciv_lib/wrapping.py | 2 +- .../assets/scripting/enginefiles/qjs/main.js | 15 +++ core/Module.md | 20 ++-- .../com/unciv/scripting/ScriptingBackend.kt | 39 ++++-- .../com/unciv/scripting/ScriptingConstants.kt | 63 ++++++++-- .../src/com/unciv/scripting/ScriptingScope.kt | 8 +- .../src/com/unciv/scripting/ScriptingState.kt | 17 ++- .../scripting/protocol/ScriptingProtocol.kt | 111 ++++++++++++++++-- .../protocol/ScriptingReplManager.kt | 4 + .../scripting/protocol/SubprocessBlackbox.kt | 49 ++++++-- .../unciv/scripting/reflection/Reflection.kt | 2 +- .../unciv/scripting/utils/ApiSpecGenerator.kt | 8 +- .../src/com/unciv/scripting/utils/Blackbox.kt | 26 +++- .../unciv/scripting/utils/InstanceRegistry.kt | 3 +- .../scripting/utils/InstanceTokenizer.kt | 61 +++++++++- .../unciv/scripting/utils/SourceManager.kt | 14 ++- .../scripting/utils/StringifyException.kt | 4 + .../unciv/scripting/utils/TokenizingJson.kt | 50 ++++++-- 20 files changed, 455 insertions(+), 91 deletions(-) diff --git a/android/assets/scripting/enginefiles/python/PythonScripting.md b/android/assets/scripting/enginefiles/python/PythonScripting.md index 972048ef319a9..d1cea8b96a5ea 100644 --- a/android/assets/scripting/enginefiles/python/PythonScripting.md +++ b/android/assets/scripting/enginefiles/python/PythonScripting.md @@ -1,7 +1,11 @@ + + The Python API described by this document is built on the [IPC protocols and execution model described in `/core/Module.md`](../../../../../core/Module.md#package-comuncivscriptingprotocol). --- +## Overview + There are basically two types of Python objects in this API: * Wrappers. @@ -9,6 +13,8 @@ There are basically two types of Python objects in this API: --- +## Foreign Object Wrappers + A **wrapper** is an object that stores a list of attribute names, keys, and function call parameters corresponding to a path in the Kotlin/JVM namespace. E.G.: `"civInfo.civilizations[0].population.setPopulation"` is a string representation of a wrapped path that begins with two attribute accesses, followed by one array index, and two more attribute names. A wrapper object does not store any more values than that. When it is evaluated, it uses a simple IPC protocol to request an up-to-date real value from the game's Kotlin code. The `unciv_lib.api.real()`/`unciv_pyhelpers.real()` function can be used to manually get a real Python value from a foreign instance wrapper. @@ -84,9 +90,11 @@ gameInfo.turns = real(gameInfo.turns) + real(5) --- +## Foreign Object Tokens + A **token** is a string that has been generated by `InstanceTokenizer.kt` to represent a Kotlin instance. -The `unciv_lib.api.isForeignToken()` function can be used to check whether a Python object is either a foreign token string or a wrapper that resolves to a foreign token string. +The `unciv_lib.api.isForeignToken()`/`unciv_pyhelpers.api.isForeignToken()` function can be used to check whether a Python object is either a foreign token string or a wrapper that resolves to a foreign token string. When a Kotlin/JVM path requested by a script resolves to an immutable primative like a number or boolean, or something that is otherwise practical to serialize, then the value returned to Python is usually a real object. @@ -133,6 +141,8 @@ In general, Kotlin/JVM instances that *can* be cast into a type compatible with --- +## Assigning Tokens to Paths to Get Wrappers + Sometimes, you may want to access a path or call a method on an object that you have only as a token string— For example, an object returned from a foreign function or method call. Usually this would be impossible because you need a path in order to access foreign attributes. Without a valid path to an object, the wrapper code and the IPC protocol it uses have no way to identify where an object is or what to do with it. In fact, if the Kotlin/JVM code hasn't kept its own references to the object, then the object may not even exist anymore. @@ -161,7 +171,7 @@ del apiHelpers.registeredInstances["centertile"] # Delete the reference so it doesn't become a memory leak. ``` -In order to use this technique properly, the assignment of an object to a concrete path should be done within the same REPL loop as the generation of the token used to assign it. This is because the Kotlin code responsible for generating tokens and managing the REPL loop keeps references to all returned objects within each REPL loop. Afterwards, these references are immediately cleared, so any objects that do not have references elsewhere in Kotlin/the JVM are liable to be garbage-collected. +In order to use this technique properly, the assignment of an object to a concrete path should be done within the same REPL loop as the generation of the token used to assign it. This is because the Kotlin code responsible for generating tokens and managing the REPL loop keeps references to all returned objects within each REPL loop. Afterwards, these references are immediately cleared, so any objects that do not have references elsewhere in Kotlin/the JVM are liable to be garbage-collected in between REPL loops. ```python3 >>> apiHelpers.registeredInstances["x"] = apiHelpers.Factories.Vector2(1,2) @@ -177,7 +187,7 @@ In order to use this technique properly, the assignment of an object to a concre >>> x = apiHelpers.Factories.Vector2(1,2) >>> apiHelpers.registeredInstances["x"] = x >>> worldScreen.mapHolder.setCenterPosition(apiHelpers.registeredInstances["x"], True, True) -# May not work, because the created instance has no reference in Kotlin between the two script executions and can be garbage-collected. +# May not work, because the created instance has no reference in Kotlin between the first two script executions and can be garbage-collected. >>> x = apiHelpers.Factories.Vector2(1,2) >>> worldScreen.mapHolder.setCenterPosition(x, True, True) @@ -204,8 +214,36 @@ del memalloc["capitaltile"] del apiHelpers.registeredInstances["myCoolScript"] ``` +```python3 +apiHelpers.registeredInstances["myCoolScript"] = {} + +memalloc = apiHelpers.registeredInstances["myCoolScript"] + +class MyForeignContextManager: + def __init__(self, *tokens): + self.tokens = tokens + self.memallocKeys = set() + def __enter__(self): + for token in self.tokens: + key = f"{random.random()}_{time.time_ns()}" + memalloc[key] = token + self.memallocKeys.add(key) + return tuple(memalloc[k] for k in self.memallocKeys) + def __exit__(self, *exc): + for key in self.memallocKeys: + del memalloc[key] + self.memallocKeys.clear() + +with MyForeignContextManager(apiHelpers.MapUnit(), ) as mapUnit, : + mapUnit + +del apiHelpers.registeredInstances["myCoolScript"] +``` + --- +## API Modules + The top-level namespace of the API can be imported as the `unciv` module in any script started running in the same interpreter as it.\ Further tools can be imported as `unciv_pyhelpers`. @@ -233,4 +271,6 @@ In Unciv: --- +## Other Languages + The Python-specific behaviour is not meant as a hard standard, in that it doesn't have to be copied exactly in any other languages. Some other design may be more suited for ECMAScript, Lua, and other possible backends. If implementing another language, I think some attempt should still be made to keep a similar API and feature equivalence, though. diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/ipc.py b/android/assets/scripting/enginefiles/python/unciv_lib/ipc.py index 42345d11b0af5..3d867702a112a 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/ipc.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/ipc.py @@ -22,7 +22,7 @@ class ForeignError(RuntimeError): pass class ForeignPacket: - """Class for IPC packet conforming to spec in ScriptingProtocol.kt.""" + """Class for IPC packet conforming to specification in Module.md and ScriptingProtocol.kt.""" def __init__(self, action, identifier, data, flags=()): self.action = action self.identifier = identifier @@ -139,7 +139,7 @@ def __exit__(self, *exc): # pass #class ForeignActionBindingReceiver(ForeignActionReceiver): -# # This is nice to have, but basically useless, right? In the current model, there shouldn't be any circumstances where the Kotlin code explicitly changes or is even aware of the state of the scripting interpreter, since the Kotlin side has to deal with any number of languages, all the data lives on the Kotlin side and it's thus the scripting interpreter's job to request what it needs, and all communication is through standardized requests for either Kotlin-side reflection or a handful of REPL functions and raw code eval on the scripting side +# # This is nice to have, but basically useless, right? In the current model, there shouldn't be any circumstances where the Kotlin code explicitly changes or is even aware of the state of the script interpreter, since the Kotlin side has to deal with any number of languages, all the data lives on the Kotlin side and it's thus the script interpreter's job to request what it needs, and all communication is through standardized requests for either Kotlin-side reflection or a handful of REPL functions and raw code eval on the scripting side # def foreignCallEvaluator(func): # def _foreignCallEvaluator(self, *args, **kwargs): # try: diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py index 56c15242a3d1f..6b07263e2e654 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py @@ -98,7 +98,7 @@ def stringPathList(pathlist): '__ge__', '__gt__', '__not__', - ('__bool__', 'truth'),#TODO: Allow foreign tokens for this, and other unary operators. + ('__bool__', 'truth'),#TODO: Allow foreign tokens for this, and other unary operators. # Wait, no. That wouldn't make any sense. # @is # This could get messy. It's probably best to just not support identity comparison. What do you compare? JVM Kotlin value? Resolved Python value? Python data path? Token strings from InstanceTokenizer.kt— Which are currently randomly re-generated for multiple accesses to the same Kotlin object, and thus always unique, and which would require another protocol-level guarantee to not do that, in addition to being (kinda by design) procedurally indistinguishable from "real" resovled Python values? # @is_not # Also, these aren't even magic methods. '__abs__', diff --git a/android/assets/scripting/enginefiles/qjs/main.js b/android/assets/scripting/enginefiles/qjs/main.js index 5c55ef32b7df9..ce6a06fbe324f 100644 --- a/android/assets/scripting/enginefiles/qjs/main.js +++ b/android/assets/scripting/enginefiles/qjs/main.js @@ -3,6 +3,21 @@ function motd() { } +` +Due to the basic design of Python, any Python environment that you lock down enough to be safe will also be nearly useless. + +So, I'm thinking that the two or three different backend languages can be specialized for different uses. + +With the lightweight nature and easy sandboxing of JS and Lua, plus the ready availability of JS engines on Android and the easy embedability of Lua, JS and Lua are well-suited for running mods. + +Python should be disabled for downloaded mods, I think. CPython's big and porous, PyPy has a sandbox but it's complicated, and MicroPython's just a smaller and less compatible reimplementation of CPython— At which point, you as well just use JS/Lua. + +Instead, CPython can be the favoured interpreter for developer tools and user script macros. Debug inspection, map editor tools, prototype features and research projects, player-written automation, etc. Because it wouldn't need to be sandboxed in these types of uses, this would let Python's massive standard library and high extensibility shine. Numpy, Cython modules, C extensions and CTypes, PIL, Tensorflow, etc would all be possible to use, as would the user's filesystem and their own modules. + +So JS and Lua can be made highly portable/lightweight, and safely sandboxed to run mods. Meanwhile, CPython, if it's installed on the user's system, can be used as a richer scripting environment for developer/modder tools and user customization. +` + + try { while (true) { let line = std.in.getline(); diff --git a/core/Module.md b/core/Module.md index aca8f8c4bd0f6..63ebc649ed982 100644 --- a/core/Module.md +++ b/core/Module.md @@ -15,7 +15,7 @@ The major classes involved in the scripting API are structured as follows. `UpperCamelCase()` and parentheses means a new instantiation of a class. `lowerCamelCase` means a reference to an already-existing instance. An asterisk at the start of an item means zero or multiple instances of that class may be held. A question mark at the start of an item means that it may not exist in all implementations of the parent base class/interface. A question mark at the end of an item means that it is nullable, or otherwise may not be available in all states. -``` +```JS UncivGame(): ScriptingState(): // Persistent per UncivGame(). ScriptingScope(): @@ -59,7 +59,7 @@ TokenizingJson() // Serializer and functions that use InstanceTokenizer. 2. When the Kotlin interpreter receives the packet marking the end of the command run, it stops listening for value requests packets. It then receives the command result as the next value, and passes it back to the console screen or script handler. From Kotlin: -``` +```Python fun ExecuteCommand(command:String): SendToInterpreter(command) LockGameInfo() @@ -104,9 +104,9 @@ Both the Kotlin side and the script interpreter can send and receive packets, bu A single packet is a JSON string of the form: -*Implemented by `data class ScriptingPacket(){}` and `class ForeignPacket().`* +*Implemented by `data class ScriptingPacket(){}` and `class ForeignPacket()`.* -``` +```JS { "action": String?, "identifier": String?, @@ -132,13 +132,13 @@ If the original instance of a received token string no longer exists, then an ex Example Kotlin-side instance requested by script interpreter: -``` +```Kotlin SomeKotlinInstance@M3mAdDr ``` Example response packet to send script interpreter this instance: -``` +```JSON { "action": "read_response", "identifier": "ABC001", @@ -152,7 +152,7 @@ Example response packet to send script interpreter this instance: Example subsequent request packet from script interpreter using the token string: -``` +```JSON { "action": "assign", "identifier": "CDE002", @@ -166,7 +166,7 @@ Example subsequent request packet from script interpreter using the token string Equivalent Kotlin-side assignment operation resulting from this later request packet: -``` +```Kotlin someProperty = listOf(5, "ActualStringValue", SomeKotlinInstance@M3mAdDr) ``` @@ -176,7 +176,7 @@ someProperty = listOf(5, "ActualStringValue", SomeKotlinInstance@M3mAdDr) Some action types, data formats, and expected response types and data formats for packets sent from the Kotlin side to the script interpreter include: -*Implemented by `class ScriptingProtocol(){}` and `class UncivReplTransceiver()`* +*Implemented by `class ScriptingProtocol(){}` and `class UncivReplTransceiver()`.* ``` 'motd': null -> @@ -207,7 +207,7 @@ The above are basically a mirror of ScriptingBackend, so the same interface can Some action types, data formats, and expected response types and data formats for packets sent from the script interpreter to the Kotlin side include: -*Implemented by `class ScriptingProtocol(){}` and `class ForeignObject()`* +*Implemented by `class ScriptingProtocol(){}` and `class ForeignObject()`.* ``` 'read': {'path': List<{'type':String, 'name':String, 'params':List}>} -> diff --git a/core/src/com/unciv/scripting/ScriptingBackend.kt b/core/src/com/unciv/scripting/ScriptingBackend.kt index b18d885e0e31b..82485e48ff88a 100644 --- a/core/src/com/unciv/scripting/ScriptingBackend.kt +++ b/core/src/com/unciv/scripting/ScriptingBackend.kt @@ -16,9 +16,22 @@ import kotlin.reflect.full.* import java.util.* +/** + * Data class representing an autocompletion result. + * + * @property matches List of valid matches. Each match should be a full input string after applying autocompletion (I.E. Don't truncate to the cursor position). + * @property helpText String to print out instead of showing autocomplete matches. + */ +//TODO: Probably replace isHelpText with a nullable helpText. data class AutocompleteResults(val matches: List = listOf(), val isHelpText: Boolean = false, val helpText: String = "") +/** + * Base class for required companion objects of ScriptingBackend implementations. + * + * Subtypes (or specifically, companions of subtypes) of ScriptingBackend are organized in an Enum. + * Companion objects allow new instances of the correct subtype to be created directly from the Enum constants. + */ abstract class ScriptingBackend_metadata { /** * @return A new instance of the parent class of which this object is a companion. @@ -27,15 +40,19 @@ abstract class ScriptingBackend_metadata { abstract val displayName: String val syntaxHighlighting: SyntaxHighlighter? = null // Putting the syntax highlighters here makes the most sense semantically as they should be singletons. - // But it'd also be nice to let subclasses define ways of generating new their syntax highlighters based on their other parameters. (E.G.: Read a JSON of REGEXs, based on `EnvironmentedScriptingBackend().engine`. - // Hm. I think the solution in that case is to subclass `ScriptingBackend_metadata`, and put those properties in the companion, which I will now do. + // But it'd also be nice to let subclasses define ways of generating new their syntax highlighters based on their other parameters. (E.G.: Read a JSON of REGEXs, based on EnvironmentedScriptingBackend().engine. + // Hm. I think the solution in that case is to subclass ScriptingBackend_metadata, and put those properties in the companion, which I will now do. } abstract class EnvironmentedScriptBackend_metadata: ScriptingBackend_metadata() { abstract val engine: String + //Why did I put this here? There was probably a reason, because it was a lot of trouble. } +/** + * Interface for a single object that parses, interprets, and executes scripts. + */ interface ScriptingBackend { /** @@ -46,7 +63,9 @@ interface ScriptingBackend { } /** - * @return AutocompleteResults object that represents either a List of full autocompletion matches or a help string to print. + * @param command Current input to run autocomplete on. + * @param cursorPos Active cursor position in the current command input. + * @return AutocompleteResults object that represents either a List of full autocompletion matches or a help string to print for the current input. */ fun autocomplete(command: String, cursorPos: Int? = null): AutocompleteResults { return AutocompleteResults(listOf(command+"_autocomplete")) @@ -57,11 +76,12 @@ interface ScriptingBackend { * @return REPL printout. */ fun exec(command: String): String { + //TODO: To support modding (and more specifically, error catching in mod development and use), this (and everything that parallels/implements it) should eventually support returning a Boolean to flag the REPL printout as an error message, in addition to the return command } /** - * @return `null` on successful termination, an `Exception()` otherwise. + * @return null on successful termination, an Exception() otherwise. */ fun terminate(): Exception? { return null @@ -89,7 +109,9 @@ open class ScriptingBackendBase(val scriptingScope: ScriptingScope): ScriptingBa } +//Test, reference, example, and backup +//Has class HardcodedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackendBase(scriptingScope) { companion object Metadata: ScriptingBackend_metadata() { @@ -402,7 +424,7 @@ abstract class EnvironmentedScriptingBackend(scriptingScope: ScriptingScope): Sc get() = this::class.companionObjectInstance as EnvironmentedScriptBackend_metadata val folderHandle: FileHandle by lazy { SourceManager.setupInterpreterEnvironment(metadata.engine) } - // This requires the overridden values for `engine`, so setting it in the constructor causes a null error... May be fixed since moving `engine` to the companions. + // This requires the overridden values for engine, so setting it in the constructor causes a null error... May be fixed since moving engine to the companions. // Also, BlackboxScriptingBackend inherits from this, but not all subclasses of BlackboxScriptingBackend might need it. So as long as it's not accessed, it won't be intialized. } @@ -421,14 +443,15 @@ abstract class BlackboxScriptingBackend(scriptingScope: ScriptingScope): Environ abstract val blackbox: Blackbox abstract val replManager: ScriptingReplManager - // Should be lazy in implementations. Was originally a method that could be called by subclasses' constructors. This seems cleaner. Subclasses don't even have to define any functions this way. And the liberal use of `lazy` should naturally make sure the properties will always be initialized in the right order. - // Downside: Potential latency on first command, or possibly depending on `motd()` for immediate initialization. + // Should be lazy in implementations. Was originally a method that could be called by subclasses' constructors. This seems cleaner. Subclasses don't even have to define any functions this way. And the liberal use of lazy should naturally make sure the properties will always be initialized in the right order. + // Downside: Potential latency on first command, or possibly depending on motd() for immediate initialization. override fun motd(): String { try { return replManager.motd() } catch (e: Exception) { return "No MOTD for ${metadata.engine} backend: ${e}\n" + //TODO: Can't you access companion properties directly from instances? } } @@ -467,7 +490,7 @@ abstract class SubprocessScriptingBackend(scriptingScope: ScriptingScope): Black override val replManager: ScriptingReplManager by lazy { ScriptingRawReplManager(scriptingScope, blackbox) } override fun motd(): String { - return "\n\nWelcome to the Unciv '${metadata.displayName}' API. This backend relies on running the system `${processCmd.firstOrNull()}` command as a subprocess.\n\nIf you do not have an interactive REPL below, then please make sure the below command is valid on your system:\n\n${processCmd.joinToString(" ")}\n\n${super.motd()}\n" + return "\n\nWelcome to the Unciv '${metadata.displayName}' API. This backend relies on running the system ${processCmd.firstOrNull()} command as a subprocess.\n\nIf you do not have an interactive REPL below, then please make sure the below command is valid on your system:\n\n${processCmd.joinToString(" ")}\n\n${super.motd()}\n" } } diff --git a/core/src/com/unciv/scripting/ScriptingConstants.kt b/core/src/com/unciv/scripting/ScriptingConstants.kt index c18578e1ff2cd..8bff9ee5a9584 100644 --- a/core/src/com/unciv/scripting/ScriptingConstants.kt +++ b/core/src/com/unciv/scripting/ScriptingConstants.kt @@ -10,42 +10,91 @@ val ScriptingConstants: _ScriptingConstantsClasses.ScriptingConstantsClass = Jso ) +/** + * Class defining the structure of ScriptingConstants. + */ object _ScriptingConstantsClasses{ // Need to define classes to deserialize the JSONs into, but really this whole file should be one singleton. - // It would be slightly better with KotlinX, I think, since then I could at least use data classes and don't have to initialize all the properties. + // It would be slightly better with KotlinX, I think, since then I could at least use data classes and don't have to initialize all the properties with mutable values. LibGDX instantiates with no constructor arguments, and then assigns to properties/fields/whatever. + /** + * Constant values for scripting API. + * + * Mostly mirrors assets/scripting/ScriptingEngineConstants.json. + * Also includes folder handles, and additional constants shared with script interpreter engines. + */ class ScriptingConstantsClass() { + /** + * Map of parameters for each script interpreter engine type. + */ var engines = HashMap() - // Really, these should be `val: Map<>`s in a data class constructor, not `var: HashMap<>()` in a regular class body. But GDX doesn't seem to like parsing .JSON into those, so instead let's override the accessors. + // Really, these should be val:Map<>s in a data class constructor, not var:HashMap<>() in a regular class body. But GDX doesn't seem to like parsing .JSON into those, so instead let's override the accessors. get() = field.toMap() as HashMap set(value) = throw UnsupportedOperationException("This property is supposed to be constant.") - + + /** + * List of filepaths that are shared by all engine types, starting from assets/scripting/sharedfiles. + * + * Used by SourceManager. + * Required because internal directories can't be identified or traversed on Desktop, as all assets are put on the classpath. + */ var sharedfiles = ArrayList() get() = field.toList() as ArrayList set(value) = throw UnsupportedOperationException("This property is supposed to be constant.") - + val assetFolders = ScriptingAssetFolders - + val apiConstants = JsonParser().getFromJson(ScriptingAPIConstants::class.java, assetFolders.sharedfilesAssets.child("ScriptAPIConstants.json")) } + /** + * Configuration values for a single script interpreter engine type, as specified in assets/scripting/ScriptingEngineConstants.json. + */ class ScriptingEngineConfig(){ // Not sure if these should be called "engines" or "languages". "Language" better reflects the actual distinction between the files and (not implemented) syntax highlighting REGEXs for each, but "engine" is less ambiguous with the the translation stuff. + /** + * Filepath strings, starting from the root folder of the engine at assets/scripting/enginefiles/{engine}, that should be copied when constructing the environment of a particular engine type. + * + * Used by SourceManager. + * Required because internal directories can't be identified or traversed on Desktop, as all assets are put on the classpath. + */ var files = ArrayList() // https://github.com/libgdx/libgdx/issues/4074 // https://github.com/libgdx/libgdx/wiki/Reading-and-writing-JSON - // Apparently identifying and listing internal directories doesn't work on Desktop, as all assets are put on the classpath. - // So all the files for each engine engine have to be manually listed in a .JSON instead. + /** + * Stack of Regular Expression operations that can be used to apply LibGDX Color Markup Language syntax highlighting to the output of an engine type. + * Not yet implemented/used. + */ var syntaxHighlightingRegexStack = ArrayList() } + /** + * Constant values mirroring assets/scripting/sharedfiles/ScriptingAPIConstants.json. + * + * Separate file and class from other constants because these have to be shared with each engine's interpreters. + */ class ScriptingAPIConstants() { + /** + * Prefix used to generate and identify string tokens from InstanceTokenizer. + */ var kotlinInstanceTokenPrefix = "" } + /** + * File handles for internal scripting asset folders. + */ object ScriptingAssetFolders { + /** + * File handle for base internal scripting asset folder. + */ val scriptingAssets = Gdx.files.internal("scripting/") + /** + * File handle for internal folder holding each engine's asset directory. + */ val enginefilesAssets = scriptingAssets.child("enginefiles/") + /** + * File handle for internal asset folder holding files shared across each engine. + */ val sharedfilesAssets = scriptingAssets.child("sharedfiles/") } } diff --git a/core/src/com/unciv/scripting/ScriptingScope.kt b/core/src/com/unciv/scripting/ScriptingScope.kt index 91b6583a86747..9c6b95fd88fe0 100644 --- a/core/src/com/unciv/scripting/ScriptingScope.kt +++ b/core/src/com/unciv/scripting/ScriptingScope.kt @@ -10,13 +10,13 @@ import com.unciv.ui.worldscreen.WorldScreen /** - * Holds references to all internal game data that the console has access to. + * Holds references to all internal game data that scripting backends have access to. * - * Also where to put any future `PlayerAPI`, `CheatAPI`, `ModAPI`, etc. + * Also where to put any future PlayerAPI, CheatAPI, ModAPI, etc. * - * For `LuaScriptingBackend`, `UpyScriptingBackend`, `QjsScriptingBackend`, etc, the hierarchy of data under this class definition should probably directly mirror the wrappers in the namespace exposed to the scripting language. + * For LuaScriptingBackend, UpyScriptingBackend, QjsScriptingBackend, etc, the hierarchy of data under this class definition should probably directly mirror the wrappers in the namespace exposed to the scripting language. * - * `WorldScreen` gives access to `UnitTable.selectedUnit`, `MapHolder.selectedTile`, etc. Useful for contextual operations. + * WorldScreen gives access to UnitTable.selectedUnit, MapHolder.selectedTile, etc. Useful for contextual operations. */ class ScriptingScope( var civInfo: CivilizationInfo?, diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index 06d72ec599379..ad0b437a633c7 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -30,6 +30,16 @@ fun ArrayList.enforceValidIndex(index: Int) { } } + +/** + * Self-contained instance of scripting API use. + * + * Abstracts available scope, running backends, command history + * Should be unique per isolated use of scripting. E.G. One for the [~]-key console screen, one for each mod/all mods per save file (or whatever works best), etc. + * + * @property scriptingScope ScriptingScope instance at the root of all scripting API. + */ +//TODO: Probably deprecate/remove initialBackendType. class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: ScriptingBackendType? = null){ val scriptingBackends: ArrayList = ArrayList() @@ -87,7 +97,7 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr fun switchToBackend(backend: ScriptingBackendBase) { for ((i, b) in scriptingBackends.withIndex()) { - // TODO: Apparently there's a bunch of extensions like `.withIndex()`, `.indices`, and `.lastIndex` that I can use to replace a lot of stuff currently done with `.size`. + // TODO: Apparently there's a bunch of extensions like .withIndex(), .indices, and .lastIndex that I can use to replace a lot of stuff currently done with .size. if (b == backend) { switchToBackend(index = i) } @@ -96,6 +106,7 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr } fun switchToBackend(displayname: String) { + //TODO } fun termBackend(index: Int): Exception? { @@ -123,13 +134,13 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr outputHistory.add(text) while (outputHistory.size > maxOutputHistory) { outputHistory.removeAt(0) - // If these are ArrayLists, performance will probably be `O(n)` relative ot maxOutputHistory. + // If these are ArrayLists, performance will probably be O(n) relative to maxOutputHistory. // But premature optimization would be bad. } } fun autocomplete(command: String, cursorPos: Int? = null): AutocompleteResults { - // Deliberately not calling `echo()` to add into history because I consider autocompletion a protocol/API level feature + // Deliberately not calling echo() to add into history because I consider autocompletion a protocol/API/UI level feature if (!(hasBackend())) { return AutocompleteResults(listOf(), false, "") } diff --git a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt index 065dd4ef0eb25..bafa57561d0c4 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt @@ -20,8 +20,18 @@ import java.util.UUID // See Module.md for a description of the protocol. +// Please update the specifications there if you add, remove, or change any action types or packet structures here. + +/** + * Implementation of IPC packet specified in Module.md. + * + * @property action String or null specifying request or response type of packet. + * @property identifier Randomly generated String, or null, that should be shared by and unique to each request-response pair. + * @property data Arbitratry hierarchy of containers and values. + * @property flags Collection of Strings that communicate extra information in this packet. Can be used with null action. + */ @Serializable data class ScriptingPacket( var action: String?, @@ -32,32 +42,70 @@ data class ScriptingPacket( ) { companion object { + /** + * Parse a JSON string into a ScriptingPacket instance. + + * Uses automatic Kotlin object tokenization by InstanceTokenizer through TokenizingJson. + + * @param string Valid JSON string. + * @return ScriptingPacket instance with properties and data field hierarchy of JSON string. + */ fun fromJson(string: String): ScriptingPacket = TokenizingJson.json.decodeFromString(string) } + /** + * Encode this packet into a JSON string. + + * Uses automatic Kotlin object tokenization by InstanceTokenizer through TokenizingJson. + + * @return Valid JSON string. + */ fun toJson() = TokenizingJson.json.encodeToString(this) + /** + * @param flag Flag type to check for. + * @return Whether the given flag is present in this packet's flags field. + */ fun hasFlag(flag: ScriptingProtocol.KnownFlag) = flag.value in flags } +/** + * Implementation of IPC communication protocol specified in Module.md. + * + * Does not handle transmission or reception. Only creates responses and requests. + * Agnostic to Unciv types. Protocol spec should be generic enough to apply to all Kotlin objects. + * Uses automatic Kotlin/JVM object tokenization by InstanceTokenizer through TokenizingJson. + * + * @property scope Kotlin/JVM object that represents the hierarchical root of actions that require recursively resolving a property path through reflection. + * @property instanceSaver Mutable list in which to save response values, to prevent created instances from being garbage collected before the other end of the IPC can save or use them. List, not Set, to preserve instance identity and not value. Not automatically cleared. Should be manually cleared when not needed. + */ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = null) { - // Adds all returned and potentially tokenized instances in responses to `instanceSaver` to save them from garbage collection. - // `instanceSaver` should not be usd as long-term storage. - // Whatever's using this protocol instance should clear `instanceSaver` as soon as whatever's running at the other end of the protocol has had a chance to create references elsewhere (E.G. ScriptingScope.apiHelpers) for any instances that it needs. - + /** + * Enum class of valid items for the flag field in scripting packets. + * + * The flag field requires strings, so currently the .value property is usually used when constructing and working with scripting packets. + * + * @property value Serialized string value of this flag. + */ enum class KnownFlag(val value: String) { - PassMic("PassMic") + PassMic("PassMic")//TODO: Would getting rid of the string value work? I think I may have decided that having KotlinX Serialization implicitly coerce enums to/from strings would be worse than explicitly accessing the string even if raw enums do work. } companion object { + /** + * @return Unique, never-repeating ID string. + */ fun makeUniqueId(): String { return "${System.nanoTime()}-${Random.nextBits(30)}-${UUID.randomUUID().toString()}" } + /** + * Map of valid request action types to their required response action types. + */ val responseTypes = mapOf( // Deliberately repeating myself because I don't want to imply a hard specification for the name of response packets. "motd" to "motd_response", @@ -76,18 +124,36 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = "docstring" to "docstring_response" ) - fun enforceIsResponse(original: ScriptingPacket, response: ScriptingPacket): ScriptingPacket { + /** + * Ensure that a response packet is a valid response for a request packet. + * + * This function only checks the action type and unique identifier metadata of the packets. + * It should catch desynchronization arising from E.G. race conditions, or if one side either forgets to send an expected packet or erroneously sends an unexpected packet. + * But as the data field of each packet is variable, and defined more by convention/convergence than by specification, it will not catch if the data contained in the request or response is malformed or invalid. That task is left up to the sender and receiver of the packet. + * + * @param request Original packet. Sets action type and identifier. + * @param response Response packet. Should have matching action type and same identifier as request. + * @throws IllegalStateException If response and request mismatched. + */ + fun enforceIsResponse(request: ScriptingPacket, response: ScriptingPacket): ScriptingPacket { if (!( - (response.action == responseTypes[original.action]!!) - && response.identifier == original.identifier + (response.action == responseTypes[request.action]!!) + && response.identifier == request.identifier )) { - throw IllegalStateException("Scripting packet response does not match request ID and type: ${original}, ${response}") + throw IllegalStateException("Scripting packet response does not match request ID and type: ${request}, ${response}") } return response } } + + /** + * Functions to generate requests to send to a script interpreter. + * + * Implements the specifications on packet field and structure in Module.md. + * Function names and call arguments parallel ScriptingBackend. + */ object makeActionRequests { fun motd() = ScriptingPacket( "motd", @@ -113,6 +179,12 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = ) } + /** + * Functions to parse a response packet received after a request packet sent to a scripting interpreter. + * + * Implements the specifications on packet field and structure in Module.md. + * Function names and return types parallel ScriptingBackend. + */ object parseActionResponses { fun motd(packet: ScriptingPacket): String = (packet.data as JsonPrimitive).content @@ -131,13 +203,28 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = RuntimeException((packet.data as JsonPrimitive).content) } - fun trySaveObject(obj: Any?): Any? { + /** + * Save an instance in the mutable list cache that prevents generated responses from being garbage-collected before the other end of the protocol can use them. + * + * @param obj Instance to save. + * @return Same instance as given, unchanged. Allows this function to be chained, or used to pass through an anonymous instance. + */ + fun trySaveInstance(obj: Any?): Any? { if (instanceSaver != null) { instanceSaver.add(obj) - } + }//TODO: I should use this in more of the request types. return obj } + /** + * Return a valid response packet for a request packet from a script interpreter. + * + * This is what allows scripts to access Kotlin/JVM properties, methods, keys, etc. + * Implements the specifications on action and response types, structures, and behaviours in Module.md. + * + * @param packet Packet to respond to. + * @return Response packet. + */ fun makeActionResponse(packet: ScriptingPacket): ScriptingPacket { val action = ScriptingProtocol.responseTypes[packet.action]!! var data: JsonElement? = null @@ -150,7 +237,7 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = // This is kinda the reference (and only) implementation of the protocol spec. So the serialization and such can be and is handled with functions, but the actual structure and logic of each response should be hardcoded manually IMO. "read" -> { try { - value = TokenizingJson.getJsonElement(trySaveObject( + value = TokenizingJson.getJsonElement(trySaveInstance( Reflection.resolveInstancePath( scope, TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) diff --git a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt index 1d6a795e34dfb..5d2230e3de585 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt @@ -9,6 +9,8 @@ import com.unciv.scripting.utils.Blackbox abstract class ScriptingReplManager(val scriptingScope: ScriptingScope, val blackbox: Blackbox): ScriptingBackend { + // + //Thus, separate partly as a semantic distinction. ScriptingBackend is designed mostly to interact with ScriptingState and (indirectly, through ScriptingState) ConsoleScreen by presenting a clean interface to shallower classes in the call stack. This class is designed to do the opposite, and keep all the code for wrapping the interfaces of the deeper and more complicated ScriptingProtocol and Blackbox classes in one place. } @@ -58,7 +60,9 @@ class ScriptingProtocolReplManager(scriptingScope: ScriptingScope, blackbox: Bla val scriptingProtocol = ScriptingProtocol(scriptingScope, instanceSaver = instanceSaver) + //TODO: Doc fun getRequestResponse(packetToSend: ScriptingPacket, enforceValidity: Boolean = true, execLoop: () -> Unit = fun(){}): ScriptingPacket { + // Please update the specifications in Module.md if you change the basic structure of this REPL loop. blackbox.write(packetToSend.toJson() + "\n") execLoop() val response = ScriptingPacket.fromJson(blackbox.read(block=true)) diff --git a/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt b/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt index 918ff754e7bf9..c0e6ad67572bf 100644 --- a/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt +++ b/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt @@ -4,32 +4,57 @@ import com.unciv.scripting.utils.Blackbox import java.io.* +/** + * Blackbox that launches and wraps a child process, allowing interacting with it using a common interface. + * + * @property processCmd String Array of the command to run to start the child process. + */ class SubprocessBlackbox(val processCmd: Array): Blackbox { + /** + * The wrapped process. + */ var process: java.lang.Process? = null - + + /** + * STDOUT of the wrapped process, or null. + */ var inStream: BufferedReader? = null + /** + * STDIN of the wrapped process, or null. + */ var outStream: BufferedWriter? = null - + + /** + * Null, or error message string if launching the process produced an exception. + */ var processLaunchFail: String? = null - + override val isAlive: Boolean get() = process != null && process!!.isAlive() - + override val readyForWrite: Boolean get() = isAlive - + override val readyForRead: Int get() = if (isAlive && inStream!!.ready()) 1 else 0 - + init { start() } - + override fun toString(): String { return "${this::class.simpleName}(process=${process}).apply{ inStream=${inStream}; outStream=${outStream}; processLaunchFail=${processLaunchFail} }" } - + + /** + * Launch the child process. + * + * Set the inStream and outStream to readers and writers for its STDOUT and STDIN respectively if successful. + * Set processLauchFail to the exception raised if launching produces an exception. + * + * @throws RuntimeException if the process is already running. + */ override fun start() { if (isAlive) { throw RuntimeException("Process is already running: ${process}") @@ -44,7 +69,7 @@ class SubprocessBlackbox(val processCmd: Array): Blackbox { inStream = BufferedReader(InputStreamReader(process!!.getInputStream())) outStream = BufferedWriter(OutputStreamWriter(process!!.getOutputStream())) } - + override fun stop(): Exception? { try { if (isAlive) { @@ -64,7 +89,7 @@ class SubprocessBlackbox(val processCmd: Array): Blackbox { } return null } - + override fun read(block: Boolean): String { if (block || readyForRead > 0) { return inStream!!.readLine() @@ -72,10 +97,10 @@ class SubprocessBlackbox(val processCmd: Array): Blackbox { throw IllegalStateException("Empty STDOUT for ${process}.") } } - + override fun write(string: String) { outStream!!.write(string) outStream!!.flush() } -} +} diff --git a/core/src/com/unciv/scripting/reflection/Reflection.kt b/core/src/com/unciv/scripting/reflection/Reflection.kt index dbbce6b5cc29f..075861eed90c5 100644 --- a/core/src/com/unciv/scripting/reflection/Reflection.kt +++ b/core/src/com/unciv/scripting/reflection/Reflection.kt @@ -68,7 +68,7 @@ object Reflection { val type: PathElementType, val name: String, /** - * For key and index accesses, and function calls, whether to evaluate `name` instead of using `params` for arguments/key. + * For key and index accesses, and function calls, whether to evaluate name instead of using params for arguments/key. * Default should be false, so deserialized JSON path lists are configured correctly in ScriptingProtocol.kt. */ val doEval: Boolean = false, diff --git a/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt b/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt index 596b473eef926..5c806ca716f4d 100644 --- a/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt +++ b/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt @@ -9,7 +9,7 @@ import com.badlogic.gdx.utils.Json // Automatically running this should probably be a part of the build process. -// Probably do whatever is done with `TranslationFileWriter`. +// Probably do whatever is done with TranslationFileWriter. @Retention(AnnotationRetention.RUNTIME) annotation class ExposeScriptingApi @@ -48,7 +48,7 @@ fun makeMemberSpecDef(member: KCallable<*>): ApiSpecDef { }*/ //val tmp: Collection = kclass.members.filter{ it.name != null }.map{ it.name!! } //submembers.addAll(tmp) - //Using a straight `.map` + //Using a straight .map return ApiSpecDef( path = member.name, isIterable = "iterator" in submembers, @@ -79,7 +79,7 @@ class ApiSpecGenerator(val scriptingScope: ScriptingScope) { println("Skipping property ${m.name} in ${cls.qualifiedName} because of ${e}") continue } - //kclass.members //Directly accessing kclass.members gets a `KotlinInternalReflectionError`, but iterating through `searchclasses` seems to work just fine. + //kclass.members //Directly accessing kclass.members gets a KotlinInternalReflectionError, but iterating through searchclasses seems to work just fine. if (isUncivClass(kclass!!) && kclass!! !in encounteredclasses) { encounteredclasses.add(kclass!!) searchclasses.add(kclass!!) @@ -101,7 +101,7 @@ class ApiSpecGenerator(val scriptingScope: ScriptingScope) { fun generateClassApi(): Map> { // Provide options for the scripting languages. This function val classes = getAllUncivClasses() - var c = 0 // Test count. Something like 5,400, IIRC. For now, it's easier to just dynamically generate the API using Python's magic methods and the reflective tools in ScriptingProtocol. JS has proxies too, but other languages may not be so dynamic. // TBF I think some of those might have been GDX/Kotlin/JVM classes, which I should filter oout by `.qualifiedName`. + var c = 0 // Test count. Something like 5,400, IIRC. For now, it's easier to just dynamically generate the API using Python's magic methods and the reflective tools in ScriptingProtocol. JS has proxies too, but other languages may not be so dynamic. // TBF I think some of those might have been GDX/Kotlin/JVM classes, which I should filter oout by .qualifiedName. val output = mutableMapOf>( *classes.map{ it.qualifiedName!! to it.members.map{ c += 1; makeMemberSpecDef(it) } diff --git a/core/src/com/unciv/scripting/utils/Blackbox.kt b/core/src/com/unciv/scripting/utils/Blackbox.kt index 891a77529f6cb..54ce46d52dedb 100644 --- a/core/src/com/unciv/scripting/utils/Blackbox.kt +++ b/core/src/com/unciv/scripting/utils/Blackbox.kt @@ -1,6 +1,11 @@ package com.unciv.scripting.utils +/** + * Unified interface for anything that receives and responds to input without any access to or relevance for its internal states. + * + * Should be able to wrap STDIN/STDOUT, pipes, JNI, NDK, network sockets, external processes, embeded code, etc, and make them all interchangeable. + */ interface Blackbox { fun start() { } @@ -16,19 +21,30 @@ interface Blackbox { */ fun stop(): Exception? = null + /** + * Whether this black box is "running", and able to receive and respond to input. + */ val isAlive: Boolean get() = false /** - * Return approximate number of items ready to be read. Should try to always return 0 correctly, but may return 1 if a greater number of items are available. + * Approximate number of items ready to be read. Should try to always return 0 correctly, but may return 1 if a greater number of items are available. */ val readyForRead: Int get() = 0 + /** + * Whether this black box is ready to be written to. + */ val readyForWrite: Boolean get() = false - fun read(block: Boolean = true): String = ""// Return a single string if either blocking or ready to read, or throw an IllegalStateException() otherwise. + /** + * @param block Whether to wait for the next available output, or throw an exception if none are available. + * @throws IllegalStateException if blocking is disabled and black box is not ready for read. + * @return String output from black box. + */ + fun read(block: Boolean = true): String = "" /** * Read out all lines up to a limit if given a limit greater than zero. @@ -37,6 +53,7 @@ interface Blackbox { * @return Empty list if no lines are available and blocking is disabled. List of at least one string if blocking is allowed. */ fun readAll(block: Boolean = true, limit: Int = 0): List { + //Should probably be Final. val lines = ArrayList() var i = 0 if (block) { @@ -50,6 +67,11 @@ interface Blackbox { return lines } + /** + * Write a single string to the black box. + * + * @param string String to be written. + */ fun write(string: String) { } } diff --git a/core/src/com/unciv/scripting/utils/InstanceRegistry.kt b/core/src/com/unciv/scripting/utils/InstanceRegistry.kt index 34422958f7447..c7e63eecd52d5 100644 --- a/core/src/com/unciv/scripting/utils/InstanceRegistry.kt +++ b/core/src/com/unciv/scripting/utils/InstanceRegistry.kt @@ -2,8 +2,9 @@ package com.unciv.scripting.utils /** - * Namespace in ScriptingScope().apiHelpers, for scripts to do their own memory management by keeping references to objects alive. Wraps a MutableMap<>(). + * Namespace in ScriptingScope().apiHelpers, for scripts to do their own memory management by keeping references to objects alive. * + * Wraps a MutableMap<>(). * Currently all it does is throw an exception on an assignment colliding with an existing key, and reads and removals for non-existent keys.. * Was going to have functions named to fit creating and freeing fields, but so far Python's item assignment and deletion syntaxes are plenty clean */ diff --git a/core/src/com/unciv/scripting/utils/InstanceTokenizer.kt b/core/src/com/unciv/scripting/utils/InstanceTokenizer.kt index 1d7b5d2e0224c..e6de29485dda9 100644 --- a/core/src/com/unciv/scripting/utils/InstanceTokenizer.kt +++ b/core/src/com/unciv/scripting/utils/InstanceTokenizer.kt @@ -6,16 +6,44 @@ import java.lang.ref.WeakReference import java.util.UUID +/** + * Object that returns unique strings for any Kotlin/JVM instances, and then allows the original instances to be accessed given the token strings. + * + * Uses WeakReferences, so should not cause memory leaks on its own. + * + * Combined with TokenizingJson in ScriptingProtocol, allows scripts to handle unserializable objects, and use them in property/key/index assignments and function calls. + */ object InstanceTokenizer { + /** + * Weakmap of currently known token strings to WeakReferences of the Kotlin/JVM instances they represent. + */ private val instances = mutableMapOf>() + /** + * Prefix that all generated token strings should start with. + * + * A string should be identifiable as a token string by checking whether it starts with this prefix. + * As such, it is useful for this value to be defined somewhere that scripts can access too. + */ private val tokenPrefix + //I considered other structures like integer IDs, and objects with a particular structure and key. But semantically and syntactically, immutable and often-singleton/interned strings are really the best JSON representations of completely opaque Kotlin/JVM objects. get() = ScriptingConstants.apiConstants.kotlinInstanceTokenPrefix + /** + * Length to clip generated token strings to. Here in case token string generation uses the instance's .toString(), which it currently does. + */ private val tokenMaxLength = 100 + /** + * Generate a distinctive token string to represent a Kotlin/JVM object. + * + * Should be human-informing when read. But should not be parsed, and should not encourage being parsed, to extract information. + * Only creates string. Does not automatically register resulting token string for detokenization. + * + * @param value: Instance to tokenize. + * @return Token string. + */ private fun tokenFromInstance(value: Any?): String { - //Try to keep this human-informing, but don't parse it to extracting information. var stringified = value.toString() if (stringified.length > tokenMaxLength) { stringified = stringified.slice(0..tokenMaxLength-4) + "..." @@ -23,12 +51,20 @@ object InstanceTokenizer { } return "${tokenPrefix}${System.identityHashCode(value)}:${if (value == null) "null" else value::class.qualifiedName}/${stringified}:${UUID.randomUUID().toString()}" } - + + /** + * @param value Any value or instance. + * @return Whether or not it is a token string. + */ private fun isToken(value: Any?): Boolean { return value is String && value.startsWith(tokenPrefix) } - + + /** + * Remove all tokens and WeakReferences whose instances have already been garbage-collected. + */ fun clean(): Unit { + //FIXME (if I become a problem): Because a new unique token is currently generated even if the instance is already tokenized as something else, this will eventually get slower over time if a script makes lots of requests that result in new instance tokens for objects that last a long time (E.G. uncivGame). And since any stored instances should ideally be WeakReferences to prevent garbage collection from being broken for *all* instances, fixing that may not be as simple as checking for existing tokens to reuse them. val badtokens = mutableListOf() for ((t, o) in instances) { if (o.get() == null) { @@ -39,14 +75,27 @@ object InstanceTokenizer { instances.remove(t) } } - + + /** + * @param obj Instance to tokenize. + * @return Token string that can later be detokenized back into the original instance. + */ fun getToken(obj: Any?): String { clean() val token = tokenFromInstance(obj) instances[token] = WeakReference(obj) return token } - + + /** + * Detokenize a token string into the real Kotlin/JVM instance it represents. + * + * Accepts non-token values, and passes them through unchanged. So can be used to E.G. blindly transform a Collection/JSON Array that only maybe contains some token strings by being called on every element. + * + * @param token Previously generated token, or any instance or value. + * @throws NullPointerException (I think) if given a token string but not a valid one (E.G. if its object was garbage-collected, or if it's fake). + * @return Real instance from detokenizing input if given a token string, input value or instance unchanged if not given a token string. + */ fun getReal(token: Any?): Any? { clean() if (isToken(token)) { @@ -55,5 +104,5 @@ object InstanceTokenizer { return token } } - + } diff --git a/core/src/com/unciv/scripting/utils/SourceManager.kt b/core/src/com/unciv/scripting/utils/SourceManager.kt index a8c43f8fd0cca..9be86bc30ab6c 100644 --- a/core/src/com/unciv/scripting/utils/SourceManager.kt +++ b/core/src/com/unciv/scripting/utils/SourceManager.kt @@ -8,8 +8,14 @@ import com.unciv.scripting.ScriptingConstants import kotlin.concurrent.thread +/** + * Object for managing, and using (copying/instantiating) internal assets associated with each script interpreter engine type. + */ object SourceManager { + /** + * Return a file handle for a specific engine type's internal assets directory. + */ private fun getEngineLibraries(engine: String): FileHandle { return ScriptingConstants.assetFolders.enginefilesAssets.child("${engine}/") } @@ -18,11 +24,11 @@ object SourceManager { * Set up a directory tree with all known libraries and files for a scripting engine/language type. * * Creates temporary directory. - * Copies directory tree under `android/assets/scripting/sharedfiles/` into it, as specified in `ScriptingEngineConstants.json` - * Copies directory tree under `android/assets/scripting/enginefiles/{engine}` into it, as specified in `ScriptingEngineConstants.json`. + * Copies directory tree under android/assets/scripting/sharedfiles/ into it, as specified in ScriptingEngineConstants.json + * Copies directory tree under android/assets/scripting/enginefiles/{engine} into it, as specified in ScriptingEngineConstants.json. * * @param engine Name of the engine type, as defined in scripting constants. - * @return `FileHandle()` for the temporary directory. + * @return FileHandle() for the temporary directory. */ fun setupInterpreterEnvironment(engine: String): FileHandle { val enginedir = getEngineLibraries(engine) @@ -42,7 +48,7 @@ object SourceManager { addfile(enginedir, fp) } Runtime.getRuntime().addShutdownHook( - // Delete temporary directory on JVM shutdown, not on backend object destruction/termination. The copied files shouldn't be huge anyway, there's no reference to a `ScriptingBackend()` here, and I trust the shutdown hook to be run more reliably. + // Delete temporary directory on JVM shutdown, not on backend object destruction/termination. The copied files shouldn't be huge anyway, there's no reference to a ScriptingBackend() here, and I trust the shutdown hook to be run more reliably. thread(start = false, name = "Delete ${outdir.toString()}.") { outdir.deleteDirectory() } diff --git a/core/src/com/unciv/scripting/utils/StringifyException.kt b/core/src/com/unciv/scripting/utils/StringifyException.kt index 86af0f44d41ac..3e0562529681a 100644 --- a/core/src/com/unciv/scripting/utils/StringifyException.kt +++ b/core/src/com/unciv/scripting/utils/StringifyException.kt @@ -1,4 +1,8 @@ package com.unciv.scripting.utils +/** + * @param exception Any Exception. + * @return String of exception preceded by entire stack trace. + */ fun stringifyException(exception: Exception): String = listOf(*exception.getStackTrace(), exception.toString()).joinToString("\n") diff --git a/core/src/com/unciv/scripting/utils/TokenizingJson.kt b/core/src/com/unciv/scripting/utils/TokenizingJson.kt index 3d5f08ea4c863..9985eef4eef1e 100644 --- a/core/src/com/unciv/scripting/utils/TokenizingJson.kt +++ b/core/src/com/unciv/scripting/utils/TokenizingJson.kt @@ -17,21 +17,29 @@ import kotlinx.serialization.modules.SerializersModule /** - * Json serialization that accepts `Any?`, and converts non-primitive values to string keys from `InstanceTokenizer`. + * Json serialization that accepts Any?, and converts non-primitive values to string keys using InstanceTokenizer. */ object TokenizingJson { - + /** + * KotlinX serializer that automatically converts non-primitive values to string tokens on serialization, and automatically replaces string tokens with the real Kotlin/JVM objects they represent on detokenization. + * + * I tested the serialization function, but I'm not sure it's actually used anywhere since I think PathElements (which use it for params) are only ever received and not sent. + * + * Adapted from https://stackoverflow.com/a/66158603/12260302 + */ object TokenizingSerializer: KSerializer { - // Adapted from https://stackoverflow.com/a/66158603/12260302 override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Any?") + /** + * Only these types will be serialized. Everything else will be replaced with a string key from InstanceTokenizer. + */ @Suppress("UNCHECKED_CAST") private val dataTypeSerializers: Map> = - //Tried replacing this with a generic `fun serialize` and `serializer`. The change in signature prevents `serialize` from being overridden correctly, I guess. - //Also tried generic `fun getSerializer(v: T) = serialize() as KSerializer`. + //Tried replacing this with a generic funserialize and serializer. The change in signature prevents serialize from being overridden correctly, I guess. + //Also tried generic fungetSerializer(v:T)=serialize() as KSerializer. mapOf( "Boolean" to serializer(), "Byte" to serializer(), @@ -42,7 +50,6 @@ object TokenizingJson { "Long" to serializer(), "Short" to serializer(), "String" to serializer() - // Only these types will be serialized. Everything else will be replaced with a string key from InstanceTokenizer. ).mapValues { (_, v) -> v as KSerializer } override fun serialize(encoder: Encoder, value: Any?) { @@ -70,17 +77,26 @@ object TokenizingJson { } } + /** + * KotlinX JSON entrypoint with default properties to make it easier to make and work with IPC packets. + */ val json = Json { explicitNulls = true; //Disable these if it becomes a problem. encodeDefaults = true; // serializersModule = serializersModule } + /** + * Forbid some objects from being serialized as normal JSON values. + * + * com.unciv.models.ruleset.Building, for example, and resumably all other types that inherit from Stats, implements Iterable<*> and thus gets serialized as JSON Arrays by default even though it's probably better to tokenize them. + * Python example: civInfo.cities[0].cityConstructions.getBuildableBuildings() + * (Should return list of Building tokens, not list of lists of tokens for seemingly unrelated stats.) + * + * @param value Value or instance to check. + * @return Whether the given value is required to be tokenized. + */ private fun isTokenizationMandatory(value: Any?): Boolean { -// // Forbid some objects from being serialized as normal JSON values. -// // com.unciv.models.ruleset.Building(), for example, and resumably all other types that inherit from Stats(), implement Iterable<*> and thus get serialized as JSON Arrays by default even though it's probably better to tokenize them. -// // Python example: civInfo.cities[0].cityConstructions.getBuildableBuildings() -// // (Should return list of Building tokens, not list of lists of tokens for seemingly unrelated stats.) if (value == null) { return false } @@ -89,6 +105,12 @@ object TokenizingJson { } + /** + * Get a KotlinX JsonElement for any Kotlin/JVM value or instance. + * + * @param value Any Kotlin/JVM value or instance to turn into a JsonElement. + * @return Input value unchanged if value is already a JsonElement, otherwise JsonElement best representing value— Generally best effort to create JsonObject, JsonArray, JsonPrimitive, or JsonNull directly from value, and token string JsonPrimitive if best effort fails or tokenization is mandatory for the given value. + */ fun getJsonElement(value: Any?): JsonElement { if (value is JsonElement) { return value @@ -123,6 +145,12 @@ object TokenizingJson { return JsonPrimitive(InstanceTokenizer.getToken(value)) } + /** + * Get a real value or instance from any KotlinX JsonElement. + * + * @param value JsonElement to make into a real instance. + * @return Detokenized instance from input value if input value represents a token string, direct conversion of input value into a Kotlin/JVM primitive or container otherwise. + */ fun getJsonReal(value: JsonElement): Any? { if (value is JsonNull || value == JsonNull) { return null @@ -142,7 +170,7 @@ object TokenizingJson { ?: v.content.toDoubleOrNull() ?: v.content.toBooleanStrictOrNull() ?: v.content - } + }//TODO: This may be better ternary. } throw IllegalArgumentException("Unrecognized type of JsonElement: ${value::class}/${value}") } From 210a293ab45a73706baa26eea881883c28ef127f Mon Sep 17 00:00:00 2001 From: will-ca Date: Fri, 19 Nov 2021 19:47:06 +0000 Subject: [PATCH 43/93] Interface/extensions for opening ConsoleScreen. ConsoleScreen in Map Editor. Moved exception handling to flag and unify data field by convention in IPC protocol. Internal file and packed texture access in ScriptingScope. Mandelbrot, graph, heightmap, and image colour map generator examples. Docs, cleanup. --- .gitignore | 4 +- .../jsons/Civ V - Gods & Kings/Terrains.json | 4 +- .../jsons/Civ V - Vanilla/Terrains.json | 4 +- .../scripting/ScriptingEngineConstants.json | 8 +- .../enginefiles/python/PythonScripting.md | 51 ++- .../enginefiles/python/unciv_lib/api.py | 24 +- .../python/unciv_lib/autocompletion.py | 7 +- .../enginefiles/python/unciv_lib/ipc.py | 2 +- .../enginefiles/python/unciv_lib/wrapping.py | 55 ++- .../EarthTerrainFantasyHex.png | Bin 0 -> 549860 bytes .../EarthTerrainRaw.png | Bin 0 -> 200912 bytes .../EarthTopography.png | Bin 0 -> 66411 bytes .../MapEditingMacros.py | 401 ++++++++++++++++-- .../unciv_scripting_examples/StarryNight.jpg | Bin 0 -> 71017 bytes .../python/unciv_scripting_examples/Tests.py | 72 ++++ .../unciv_scripting_examples/TurboRainbow.png | Bin 0 -> 338 bytes .../unciv_scripting_examples/__init__.py | 4 +- core/Module.md | 49 ++- core/src/com/unciv/MainMenuScreen.kt | 19 +- core/src/com/unciv/UncivGame.kt | 10 +- .../com/unciv/scripting/ScriptingBackend.kt | 12 +- .../src/com/unciv/scripting/ScriptingScope.kt | 58 ++- .../src/com/unciv/scripting/ScriptingState.kt | 28 +- .../scripting/protocol/ScriptingProtocol.kt | 143 ++++--- .../unciv/scripting/reflection/Reflection.kt | 37 ++ .../scripting/utils/InstanceTokenizer.kt | 2 +- .../unciv/scripting/utils/SourceManager.kt | 1 + .../scripting/utils/StringifyException.kt | 18 +- .../unciv/scripting/utils/TokenizingJson.kt | 2 - .../unciv/ui/consolescreen/ConsoleScreen.kt | 80 ++-- .../consolescreen/IConsoleScreenAccessible.kt | 54 +++ .../com/unciv/ui/mapeditor/MapEditorScreen.kt | 9 +- .../com/unciv/ui/utils/ExtensionFunctions.kt | 39 +- core/src/com/unciv/ui/utils/Fonts.kt | 55 +-- .../com/unciv/ui/worldscreen/WorldScreen.kt | 24 +- .../ui/worldscreen/mainmenu/OptionsPopup.kt | 2 + 36 files changed, 970 insertions(+), 308 deletions(-) create mode 100644 android/assets/scripting/enginefiles/python/unciv_scripting_examples/EarthTerrainFantasyHex.png create mode 100644 android/assets/scripting/enginefiles/python/unciv_scripting_examples/EarthTerrainRaw.png create mode 100644 android/assets/scripting/enginefiles/python/unciv_scripting_examples/EarthTopography.png create mode 100644 android/assets/scripting/enginefiles/python/unciv_scripting_examples/StarryNight.jpg create mode 100644 android/assets/scripting/enginefiles/python/unciv_scripting_examples/TurboRainbow.png create mode 100644 core/src/com/unciv/ui/consolescreen/IConsoleScreenAccessible.kt diff --git a/.gitignore b/.gitignore index 9ec1b037bacc6..bb6c17052d778 100644 --- a/.gitignore +++ b/.gitignore @@ -117,7 +117,6 @@ Thumbs.db !/ios-moe/xcode/*.xcodeproj/xcshareddata !/ios-moe/xcode/*.xcodeproj/project.pbxproj /ios-moe/xcode/native/ -SaveFiles/ android/android-release.apk android/assets/GameSettings.json android/release/output.json @@ -144,6 +143,9 @@ android/assets/music/ .vscode/ # Unciv +maps/ +mods/ +SaveFiles/ GameSettings.json # Python diff --git a/android/assets/jsons/Civ V - Gods & Kings/Terrains.json b/android/assets/jsons/Civ V - Gods & Kings/Terrains.json index f18262e4dcc28..63d4a57f1df34 100644 --- a/android/assets/jsons/Civ V - Gods & Kings/Terrains.json +++ b/android/assets/jsons/Civ V - Gods & Kings/Terrains.json @@ -230,7 +230,7 @@ "uniques": ["Nullifies all other stats this tile provides", "Doesn't generate naturally"], // For map editor only - the generator won't place it without code or enabling uniques // If the map generator is ever updated to always take these into account, it should also take the "Doesn't generate naturally" unique into account - "occursOn": ["Grassland","Plains","Desert","Tundra","Snow","Forest","Jungle","Hill","Flood plains","Marsh","Oasis"] + "occursOn": ["Grassland","Plains","Desert","Tundra","Snow","Forest","Jungle","Hill","Flood plains","Marsh","Oasis"], "defenceBonus": -0.15 }, { @@ -490,7 +490,7 @@ "impassable": true, "unbuildable": true, "weight": 10 - }, + }//, /* // BNW wonders { diff --git a/android/assets/jsons/Civ V - Vanilla/Terrains.json b/android/assets/jsons/Civ V - Vanilla/Terrains.json index 74b41ae5a3a9f..bf71b2a802fa5 100644 --- a/android/assets/jsons/Civ V - Vanilla/Terrains.json +++ b/android/assets/jsons/Civ V - Vanilla/Terrains.json @@ -230,7 +230,7 @@ "uniques": ["Nullifies all other stats this tile provides", "Doesn't generate naturally"], // For map editor only - the generator won't place it without code or enabling uniques // If the map generator is ever updated to always take these into account, it should also take the "Doesn't generate naturally" unique into account - "occursOn": ["Grassland","Plains","Desert","Tundra","Snow","Forest","Jungle","Hill","Flood plains","Marsh","Oasis"] + "occursOn": ["Grassland","Plains","Desert","Tundra","Snow","Forest","Jungle","Hill","Flood plains","Marsh","Oasis"], "defenceBonus": -0.15 }, { @@ -490,7 +490,7 @@ "impassable": true, "unbuildable": true, "weight": 10 - }, + }//, /* // BNW wonders { diff --git a/android/assets/scripting/ScriptingEngineConstants.json b/android/assets/scripting/ScriptingEngineConstants.json index edb12eeb8e5e6..a7dee88e40372 100644 --- a/android/assets/scripting/ScriptingEngineConstants.json +++ b/android/assets/scripting/ScriptingEngineConstants.json @@ -14,11 +14,17 @@ main.py unciv_scripting_examples/ unciv_scripting_examples/__init__.py + unciv_scripting_examples/EarthTerrainFantasyHex.png + unciv_scripting_examples/EarthTerrainRaw.png + unciv_scripting_examples/EarthTopography.png unciv_scripting_examples/EndTimes.py - unciv_scripting_examples/Merfolk.py unciv_scripting_examples/ExternalPipe.py + unciv_scripting_examples/MapEditingMacros.py + unciv_scripting_examples/Merfolk.py unciv_scripting_examples/PlayerMacros.py + unciv_scripting_examples/StarryNight.jpg unciv_scripting_examples/Tests.py + unciv_scripting_examples/TurboRainbow.png ] syntaxHighlightingRegexStack: [ ] diff --git a/android/assets/scripting/enginefiles/python/PythonScripting.md b/android/assets/scripting/enginefiles/python/PythonScripting.md index d1cea8b96a5ea..93cdba9b151b4 100644 --- a/android/assets/scripting/enginefiles/python/PythonScripting.md +++ b/android/assets/scripting/enginefiles/python/PythonScripting.md @@ -8,8 +8,8 @@ The Python API described by this document is built on the [IPC protocols and exe There are basically two types of Python objects in this API: - * Wrappers. - * Tokens. +* Wrappers. +* Tokens. --- @@ -21,18 +21,31 @@ A wrapper object does not store any more values than that. When it is evaluated, However, because the wrapper class implements many Magic Methods that automatically call its evaluation method, many programming idioms common in Python are possible with them even without manual evaluation. Comparisons, equality, arithmetic, concatenation, in-place operations and more are all supported. +Generally, any foreign objects that are accessible in this API start out as foreign object wrappers. + Accessing an attribute on a wrapper object returns a new wrapper object that has an additional name set to "Property" at the end of its path list. Performing an array or dictionary index returns a new wrapper with an additional "Key" element in its path list. ```python3 -print(civInfo) # Wrapper object. -print(civInfo.cities[0]) # Also a wrapper object, with two extra path elements. -print(civInfo.cities[0].isCapital) # Still a wrapper object, with another extra path element. +print(civInfo) +# Wrapper object. + +print(civInfo.cities[0]) +# Also a wrapper object, with two extra path elements. + +print(civInfo.cities[0].isCapital) +# Still a wrapper object, with another extra path element. + +a = civInfo.cities +b = civInfo.cities +print(id(a) == id(b)) +# False, because each attribute access dynamically creates a new wrapper object. ``` Calling a wrapper object as a function or method also creates a new path list with an extra "Call" element at the end. But in this case, the new path list is immediately sent as a request to Kotlin/the JVM instead of being used in a new wrapper object, and the returned value is the naked result from the requested function call in the Kotlin/JVM namespace. ```python3 -print(civInfo.cities[0].isCapital()) # Goes through four wrapper objects, but ultimately sends its path as a request on the function call, and returns a real value. +print(civInfo.cities[0].isCapital()) +# Goes through four wrapper objects, but ultimately sends its path as a request on the function call, and returns a real value. ``` Likewise, assigning to an attribute or an index/key on a wrapper object sends an IPC request to assign to the Kotlin/JVM value at its path, instead of modifying the wrapper object. @@ -96,7 +109,7 @@ A **token** is a string that has been generated by `InstanceTokenizer.kt` to rep The `unciv_lib.api.isForeignToken()`/`unciv_pyhelpers.api.isForeignToken()` function can be used to check whether a Python object is either a foreign token string or a wrapper that resolves to a foreign token string. -When a Kotlin/JVM path requested by a script resolves to an immutable primative like a number or boolean, or something that is otherwise practical to serialize, then the value returned to Python is usually a real object. +When a Kotlin/JVM path requested by a script resolves to an immutable primitive like a number or boolean, or something that is otherwise practical to serialize, then the value returned to Python is usually a real object. However, if the value requested is an instance of a complicated Kotlin/JVM class, then the IPC protocol instead creates a unique string to identify it. @@ -117,10 +130,10 @@ isForeignToken(civInfo.getWorkerAutomation()) # True. This method returns a complicated type that gets turned into a token string. isForeignToken(uncivGame) -# True. This is actually a wrapper, but `isForeignToken` returns True based on evaluated results. +# True. `uncivGame` is technically a wrapper object, but `isForeignToken` returns True based on evaluated results. ``` -The original instance is stored in the JVM in a mapping as a weak reference. The string doesn't have any special properties as a Python object. But if it is sent back to Kotlin/the JVM at any point, then it will be parsed and substituted with the original instance (provided the original instance still exists). +The original instance is stored in the JVM in a mapping as a weak reference. The string doesn't have any special properties as a Python object. But if the string is sent back to Kotlin/the JVM at any point, then it will be parsed and substituted with the original instance (provided the original instance still exists). This is meant to allow Kotlin/JVM instances to be, E.G., used as function arguments and mapping keys from scripts. @@ -137,17 +150,17 @@ civInfo.removeUnit(unit) The rules for which classes are serialized as JSON values and which are serialized as token strings may be a little bit fuzzy and variable, as they are designed to maximise use cases. -In general, Kotlin/JVM instances that *can* be cast into a type compatible with a JSON type will be serialized as such— Unless they are of classes defined within the Unciv packages themselves, in which case they will always be tokenized. The exemption for certain classes prevents everything that inherits from iterable interfaces— Like `Building()`, which inherits from `Stats:Iterable`— From being stripped down into JSON arrays when having access to their members and instances is much more useful. +In general, Kotlin/JVM instances that *can* be cast into a type compatible with a JSON type will be serialized as such— Unless they are of classes defined within the Unciv packages themselves, in which case they will always be tokenized. The exemption for certain classes prevents everything that inherits from iterable interfaces— Like `Building()`, which inherits from `Stats:Iterable`— From being stripped down into JSON arrays, as having access to their members and instances is often much more useful. --- ## Assigning Tokens to Paths to Get Wrappers -Sometimes, you may want to access a path or call a method on an object that you have only as a token string— For example, an object returned from a foreign function or method call. +Sometimes, you may want to access a path or call a method on a foreign object that you have only as a token string— For example, an object returned from a foreign function or method call. -Usually this would be impossible because you need a path in order to access foreign attributes. Without a valid path to an object, the wrapper code and the IPC protocol it uses have no way to identify where an object is or what to do with it. In fact, if the Kotlin/JVM code hasn't kept its own references to the object, then the object may not even exist anymore. +Usually this would be impossible because you need a path in order to access foreign attributes. Without a valid path to an object, the wrapper code and the IPC protocol have no way to identify where an object is or what to do with it. In fact, if the Kotlin/JVM code hasn't kept its own references to the object, the object may not even exist anymore. -To get around this, you can use the foreign token to assign the object it represents to a concrete path. +To get around this, you can use the foreign token to assign the object it represents to a concrete path in Kotlin/the JVM. The `apiHelpers.registeredInstances` helper object can be used for this: @@ -156,7 +169,7 @@ token = civInfo.cities[0].getCenterTile() # Token string representing a `TileInfo()` instance. print(type(token)) -# . Cannot be used for attribute access. +# . Cannot be used for foreign attribute access. apiHelpers.registeredInstances["centertile"] = token # Token string gets transformed back into `TileInfo()` in Kotlin/JVM assignment. @@ -174,6 +187,8 @@ del apiHelpers.registeredInstances["centertile"] In order to use this technique properly, the assignment of an object to a concrete path should be done within the same REPL loop as the generation of the token used to assign it. This is because the Kotlin code responsible for generating tokens and managing the REPL loop keeps references to all returned objects within each REPL loop. Afterwards, these references are immediately cleared, so any objects that do not have references elsewhere in Kotlin/the JVM are liable to be garbage-collected in between REPL loops. ```python3 +# Each ">>>" represents a new script execution called from Kotlin— E.G., A new command entered into the console screen, or a new handler execution from the modding API— And not just a new line of code. Code on multiple lines can still be run in the same REPL loop, as long as the script's control isn't ended and handed back to Kotlin/the JVM between their running. + >>> apiHelpers.registeredInstances["x"] = apiHelpers.Factories.Vector2(1,2) >>> worldScreen.mapHolder.setCenterPosition(apiHelpers.registeredInstances["x"], True, True) # Works, because the instance creation and token-based assignment in Kotlin are done in the same REPL execution. @@ -222,19 +237,19 @@ memalloc = apiHelpers.registeredInstances["myCoolScript"] class MyForeignContextManager: def __init__(self, *tokens): self.tokens = tokens - self.memallocKeys = set() + self.memallocKeys = [] def __enter__(self): for token in self.tokens: key = f"{random.random()}_{time.time_ns()}" memalloc[key] = token - self.memallocKeys.add(key) + self.memallocKeys.append(key) return tuple(memalloc[k] for k in self.memallocKeys) def __exit__(self, *exc): for key in self.memallocKeys: del memalloc[key] self.memallocKeys.clear() -with MyForeignContextManager(apiHelpers.MapUnit(), ) as mapUnit, : +with MyForeignContextManager(apiHelpers.Factories.MapUnit(), ) as mapUnit, : mapUnit del apiHelpers.registeredInstances["myCoolScript"] @@ -244,7 +259,7 @@ del apiHelpers.registeredInstances["myCoolScript"] ## API Modules -The top-level namespace of the API can be imported as the `unciv` module in any script started running in the same interpreter as it.\ +The top-level namespace of the API can be imported as the `unciv` module in any script running in the same interpreter as it.\ Further tools can be imported as `unciv_pyhelpers`. This is useful when writing modules that are meant to be imported from the main Unciv Python namespace. diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/api.py b/android/assets/scripting/enginefiles/python/unciv_lib/api.py index 0a20e07a06f2c..c2abb41151edd 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/api.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/api.py @@ -36,7 +36,7 @@ def get_keys(obj): except (AttributeError, ipc.ForeignError): return () -def get_doc(obj): +def get_help(obj): """Get docstring of object. Fail silently if it has none, or get one through its IPC methods if it's a ForeignObject(). Used for PyAutocompleter.""" try: if isinstance(obj, wrapping.ForeignObject): @@ -45,9 +45,12 @@ def get_doc(obj): doc += "\n".join(f"\t{argname}: {argtype}" for argname, argtype in obj._args_()) return doc else: - return obj.__doc__ - except AttributeError: - return None + with ipc.FakeStdout() as fakeout: + print() + help(obj) + return fakeout.getvalue() + except Exception as e: + return f"Error accessing help text: {repr(e)}" @expose() @@ -61,7 +64,7 @@ def callable(obj): autocompleterkwargs = { 'get_keys': get_keys, - 'get_doc': get_doc, + 'get_help': get_help, 'check_callable': callable } @@ -110,9 +113,15 @@ def EvalForeignMotd(self, packet): from unciv import * from unciv_pyhelpers import * +Run "help()", or read PythonScripting.md, for an overview of this API. + +Extensive example scripts can be imported as the "unciv_scripting_examples" module. +These can also also accessed from the game files either externally or through the API: + print(apiHelpers.assetFileString()) + Press [TAB] at any time to trigger autocompletion at the current cursor position, or display help text for an empty function call. -""" +"""#TODO: Replace current imports with startup command managed by ConsoleScreen and GameSettings. @ipc.receiverMethod('autocomplete', 'autocomplete_response') def EvalForeignAutocomplete(self, packet): assert 'PassMic' in packet.flags, f"Expected 'PassMic' in packet flags: {packet}" @@ -132,8 +141,9 @@ def EvalForeignExec(self, packet): except SyntaxError: exec(compile(line, 'STDIN', 'exec'), self.scope, self.scope) else: - print(eval(code, self.scope, self.scope)) + print(repr(eval(code, self.scope, self.scope))) except Exception as e: + #TODO: Return 'Exception' flag in packet here. print(utils.formatException(e)) finally: self.passMic() diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py b/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py index 70efbebc763ec..39298c548443b 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py @@ -9,11 +9,11 @@ def __init__( scope=None, *, get_keys=lambda o: o.keys() if hasattr(o, 'keys') else (), - get_doc=lambda o: getattr(o, '__doc__', None), + get_help=lambda o: getattr(o, '__doc__', None), check_callable=lambda o: callable(o) ): self.scope = globals() if scope is None else scope - self.get_keys, self.get_doc, self.check_callable = get_keys, get_doc, check_callable + self.get_keys, self.get_help, self.check_callable = get_keys, get_help, check_callable def GetCommandComponents(self, command): """Try to return the the last atomic evaluable expression in a statement, everything before it, and the token at the end of everything before it.""" #Call recursively if you need to resolve multiple values. Test string: @@ -41,6 +41,7 @@ def GetCommandComponents(self, command): char = None continue if char in '([:,;+-*/|&<>=%{~^@': + # Should probably split at multi-character tokens like 'in', 'for', 'if', etc. too. prefixsplit += 1 lasttoken = char break @@ -87,7 +88,7 @@ def GetAutocomplete(self, command, cursorpos=None): if lasttoken == '(' and (not workingcode): # return f"Show docstring of {prefix_workingcode}." func_obj = self.Evaled(prefix_workingcode) - return (self.get_doc(func_obj) or "No documentation available.") + "\n" + return (self.get_help(func_obj) or "No help text available.") + "\n" # return f"Return attributes to complete {workingcode}." whitespaceadjusted_workingcode = workingcode.lstrip() whitespaceadjusted_prefix = prefix + workingcode[:len(workingcode)-len(whitespaceadjusted_workingcode)] diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/ipc.py b/android/assets/scripting/enginefiles/python/unciv_lib/ipc.py index 3d867702a112a..61ab962c613bc 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/ipc.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/ipc.py @@ -117,7 +117,7 @@ def ForeignREPL(self): class FakeStdout: - """Context manager that returns a StringIO and sets sys.stdout to it on entrance, then restores it to its original value on exit.""" + """Context manager that returns a StringIO and sets sys.stdout to it on entrance, then restores sys.stdout to its original value on exit.""" def __init__(self): self.stdout = sys.stdout def __enter__(self): diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py index 6b07263e2e654..3b65c78413f26 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py @@ -64,14 +64,15 @@ def dummyForeignRequester(actionparams, responsetype): def foreignValueParser(packet, *, raise_exceptions=True): """Value parser that reads a foreign request packet fitting a common structure.""" - if packet.data["exception"] is not None and raise_exceptions: - raise ipc.ForeignError(packet.data["exception"]) - return packet.data["value"] - -def foreignErrmsgChecker(packet): - """Value parser that processes a foreign request packet fitting a simple structure.""" - if packet.data is not None: + if 'Exception' in packet.flags and raise_exceptions: raise ipc.ForeignError(packet.data) + return packet.data + +# def foreignErrmsgChecker(packet): + # """Value parser that processes a foreign request packet fitting a simple structure.""" + # if packet.data is not None: + # raise ipc.ForeignError(packet.data) +#Redundant since replacing 'exception' key in data field with 'Exception' in flags field. def makePathElement(ttype='Property', name='', params=()): @@ -94,6 +95,7 @@ def stringPathList(pathlist): '__lt__', '__le__', '__eq__', # Kinda undefined behaviour for comparison with Kotlin object tokens. Well, tokens are just strings that will always equal themselves, but multiple tokens can refer to the same Kotlin object. `ForeignObject()`s resolve to new tokens, that are currently uniquely generated in InstanceTokenizer.kt, on every `._getvalue_()`, so I think even the same `ForeignObject()` will never equal itself. + # TODO: Add hash to protocol, magic methods, and hardcoded (in-)equality comparison. Actually, no. Hm. Hash-based equality implementation would be messy to account for token strings, and hash collisions are still a thing. Implement hash, but make user explicitly call it for comparisons. '__ne__', '__ge__', '__gt__', @@ -197,14 +199,18 @@ def _ipcjson_(self): return self._getvalue_() def _getpath_(self): return tuple(self._path) - #return ''.join(self._path) def __getattr__(self, name): - # TODO: Shouldn't I special-casing get_doc or _docstring_ here? Wait, no, I think I thought it would be accessed on the class. + # TODO: Shouldn't I special-casing get_help or _docstring_ here? Wait, no, I think I thought it would be accessed on the class. return self.__class__((*self._path, makePathElement(name=name)), self._foreignrequester) + def __getattribute__(self, name): + if name in ('values', 'keys', 'items'): + # Don't expose real .keys, .values, or .entries unless wrapping a foreign mapping. This prevents foreign members like TileMap.values from being blocked. + if not self._ismapping_(): + return self.__getattr__(name) + return object.__getattribute__(self, name) def __getitem__(self, key): - return self.__class__((*self._path, makePathElement(ttype='Key', params=(key,))), self._foreignrequester) #TODO: Should negative indexing from end be supported? -# def __hash__(self): -# return hash(stringPathList(self._getpath_())) + return self.__class__((*self._path, makePathElement(ttype='Key', params=(key,))), self._foreignrequester) + #TODO: Should negative indexing from end be supported? def __iter__(self): try: return iter(self.keys()) @@ -234,7 +240,17 @@ def _setvalue_(self, value): } }, 'assign_response', - foreignErrmsgChecker) + foreignValueParser) + @ForeignRequestMethod + def _ismapping_(self): + return ({ + 'action': 'ismapping', + 'data': { + 'path': self._getpath_() + } + }, + 'ismapping_response', + foreignValueParser) @ForeignRequestMethod def _callable_(self, *, raise_exceptions=True): return ({ @@ -275,6 +291,16 @@ def __dir__(self): }, 'dir_response', foreignValueParser) + # @ForeignRequestMethod + # def __hash__(self): + # return ({ + # 'action': 'hash', + # 'data': { + # 'path': self._getpath_() + # } + # }, + # 'hash_response', + # foreignValueParser) @ForeignRequestMethod def __call__(self, *args): return ({ @@ -294,7 +320,7 @@ def __delitem__(self, key): } }, 'delete_response', - foreignErrmsgChecker) + foreignValueParser) @ForeignRequestMethod def __len__(self): return ({ @@ -330,5 +356,6 @@ def values(self): return (self[k] for k in self.keys()) def entries(self): return ((k, self[k]) for k in self.keys()) + # FIXME: This should be .items, not .entries. diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EarthTerrainFantasyHex.png b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EarthTerrainFantasyHex.png new file mode 100644 index 0000000000000000000000000000000000000000..dd99ed3f84c062fb7d257bbcd7fe1272eee9ead9 GIT binary patch literal 549860 zcmX6^bx@T}-`;cRZUm&e8)*(ocZ(q1A&qn#6p)fm=`P`jfTX|!NVg*0-6dTw-+VK> zv%7!p-m|~G>WN-{FEuI?^2 z_V26#U`+^2kW}oFCJSHsp{!CRQui@JCVbcGrEa%YrDiB*{%gC*|JEm9b`$YMjUt%U zC}_c?mAKp^&0>w1AC~bVGBcA3qLZ(Twe1Mj{#%f2z5L1Jy##r%Nh!A(70Gi*O;BPA zGYZl}Z!5sl3bn&g8Tzv&%ZD?1@*a<7>4;Ebu;p-c96_v#dWF1dQ1YDg1oh-!NwLQw zOKi>eBK%yUIC9UIs|wYF-0g1MqWfDJd<>ZxEU^Sw#oVba=_xoVK2ajoWfU8?9^4#R z4I-73Vvb8m?Y!D!uTz`UJ;>NNEimYj`^pMGuyZonO)2svmiX zYlMHR_@9M_V8_|}TL^gX?%Qe!ik`j;hO4512LRj*|JNZt#V{`bXn~UKOI@GY^`Ewl z#FKxyB>qPn|tIqG!Eic?Ay#v}W+m-@zZr2WB=blH0yA^tl!@ND|9^_^X7&~i=NX6G)jNr-% zfN!^Fjk%P3a=11q)VF8TNxf1M&dj}bYC#CBrAOV^eld5|qr}3;oZPu&1Y&ECNn&K> z7It>7A@R_&mMg)%Rnu+RZD~ubzghDvZo&86`uK7BY5MV-bvJ@I0pIoeWlQSzHG8kE zJ#~qTK}yk}$@XH2k&VmCjlI7^TLD*X3t_oSIY*BJYf$QEXO6+gXI)n}L_tRwL5Biu zOCwXieea@O zR?f~WuNu|Y_aY@uZx(*v1;~gjdc_?-95cru&Tei`8;O>36EbXDgC2QJ@z(d)viP#K zkN_J3iG+IVIw1q!ILxyGMud?7sk^*v z1f-+g=`SjH{CI>5f?$=87DZFaDHo9O}RKxDLG zgA(sM)Lcs{aAn>95|}+iY_w7x%7Kn8A5y@JgK%#fpyIjw{(&J033$rTRO^tBQ{zW# z#W4eE0a7SDvTz)jHQxk7;MaX4s}g=#;12PCl~H=(2YAnTDZp5A4)dO1GYPoc+M%*2 zEl7fNc=r*Vv^3>|Wqs9N2>3gA00nIdH=H2d$xQ-WrBn^IR=Ht=tKsYJhF_7uEIdO8 z0=irWOktn?vw;EL=hB=#q$0?`HLr;r4B20}gJT|#wr#P%H^p zuzE1XypNK?)4^>ojcX92#>sHIl@L%D!}~EbAAE7cks;Ea6}iWJ+(?hG92~mZ^9@?p zI#}!+N^om<3_-y9W9GVfO@2LJd8|BwjjSC6JYrH}0FgnJP`~tq#?78}MsTdqpLE9J zli_Q7I|(|nE!O?L{rk^yPPY_|_Po)+Sz$~1b!s=ypby$0Qx|``LB_bFE+b*iJ`;B{ z$L{FWx!k5D%+>h4WosK^+SWrHOedYDMYk(1q5vaHskF;ABp;YCA0ocdK7yR>gz#eZ z`XXVAli>4AxZCg4cywC^!z&>X9e)e89tKAM-$9F1{M+>xfX@Oi{x){uY+n9dZQbj< zwtl4WE({}Id3D$6bMYK>Ts{N>-lRgV>-lz6An?a`lRyV_2+r}JwUL8mSjx#+TFG-o zqn#W8Y8{%N15ZciOaRpOik!gMv3V~e=sU3VMuN|)q)0;Yj)UBZzAfg3=NGGm2${Hu zl+-bO8+h^AB~2bY&vx+}HNn~D3%D;7$Ae(I{@Pgde>{SZ2=J+X5|Hxe#m2W_p67dJ z<3~kR%bbkO>24ẀLc$7Z+zHg}(8=VVD{uV1vwefB)pehoo(H4_j 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

UA;RX)`o9l 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

;N~ri6+GHFLYC=A&c^ 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 z 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 literal 0 HcmV?d00001 diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EarthTerrainRaw.png b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EarthTerrainRaw.png new file mode 100644 index 0000000000000000000000000000000000000000..e8bc5b8d890b9894cf06ca7062a8140aa73127aa GIT binary patch literal 200912 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%0A!JS|?faofeL#g7yCDJb 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�)u)gy9ST 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

Z|P%@rO1qei(h82+E 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%0DQ>UGA>_EIMu5A!apPGN)nSMkr5>1@yH|% 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�i2u%AV463vNCBG878l49q3=L@l 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

LRkW9{(Ld3G7 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 literal 0 HcmV?d00001 diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EarthTopography.png b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EarthTopography.png new file mode 100644 index 0000000000000000000000000000000000000000..8724bb2e847a6f06f6533acfc2e5ecbfca40d9f1 GIT binary patch literal 66411 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 !1Hwwf_u(5T8?dtHyS}L zLxbkexRZ`xOy7r?KN7f`o0@Ymu{$5zIXfGH_hq8#pxO)fx~$9XbglIor~sNuWO84g zseL-4p>MA$mkkhD49~VibSmx+21VEdHx%#3^m^K8dxV;I$&5lz`Di!uos1M4@T1uY z@s%v>A7sOFhqzM|4}YI7#;&DhKCE==iB|!uA%6mO*1+^Twlzdfr;M-%AY^zq9#OK}< z0HtDj4>usoDOQayZ5HoTQ1r8~2W#}~=xF*yZQf>1!h`P7uAob|)O;%iW44+#idhFo ziYn{8EiK@w3BT@FA>U~{b7XSy{9m}*5=bi;#PRA617Ex=wG{Q_>;0q^Wro<%doe{RjIFU4+~@4n!sE-r?;u&S}yR;5TeuzydcgQAnojMVGEK2$zd z0?h|2ySp*2h1KlPmV97#H+C%YR_{B1Qsug`X-WGNlrs9)o(2p=a;zJ}`zB1rTXJ1% zdQBpovXbjOw6%RggJwk4PT$#5%X)7l+8g1wZ$G{C+QZi1I{!DSoPc%jic)Gy>#a_Q z3OO)9A5;N6-z|w1^-Ibj#Pcki$Mnn$QEg^tS>1d$-MqMk03LQKDwe=?L9GZtJV%41 z!TMKneQ&q$c!)C6p<6wi!a_}~t8!LHV^%vC^wbJ9i}@hSZP8CY=aSW?OY?4Q*H^t8 z-+$i0t5M*Z&f`mFgYJ?Hmi+zAcbx__dq)Owf=f8f*6`yE()YIe=NA>l*q5}O95e*XIfuv{eVzekOJ`tZS#6Gx^%xYxTT?1R?-jD&Cxd69b z^_`FE(=$a9(e6%Fdei1AuIuxe?n7M08eOmV4oh>dh-H(^hr4l;$Hq_9u2@B!E1r4~6# z-8HxPr%jU|QkfhQwd<Kv!^5}}qB5CA#q!0^^!W+z|gg>Z^(WyU%T#=FiQcR^FtIcam+?DL-3 zjej&wh<*{No854lcq(geeGz?QbNNBPICNSKeAeakC-C0om5+k4wUuR`hKEcF7*=9N zmWj)nik$H+7S1|73l%=EZ`JN>Kj!PDMHQq=q9V6HR>_3LX6B)X@+bce<+Ba0j4g5( zN4p*S&-65Y{&3yJ<8o+cMd&Yj$0`7Xj-rN5Ex!9yvl@*|wmeJqCjD}*>FVRQgGP)# z$(?s+UYN%8Z?6{cPPh-TuygP~oQc7#N{* zdCL!shFmN)>zdg3N6-XDPiK3Hnk0c8^+wX2ywxqo*2tyqA<53?U)q}j&1zj^WJ|GY z!8j(f21g;`1a+AoLjYl_xY*c4jCfd}B5$S{5<4vesAil-tMYS$;Y; z-C&lzm@Hr?2cKOuX+M^*;MuOxY6%{H17&)jsHoFwqV=M+(0|CG$S-y)%zbBlcXMmKXtd0-j;2seEhWj3AQ13_UQUBScWtE| z1D+Mqh1t&Iyhe6+VdUQn#t|lIA*L%2WZmv&vH=s$_V?;dzG=N*rdzxQfX*Fy*1f}8k1#)F`ivOvih}(S-j=Fb;{*r*P$`VQa|S_`G+}2=i{rzFBi6>2 z&-Mwan{8!r5zvhSi?f zc&ABW%!}^m?OgMj8^2b~G`VtO!>)7lDU5_9WT`U&ZRsMXfrZeiDzvVlMS&lU?`Oy~ z8w}s}-(9s-3zf~D3u~AhbnE*3p#6hiS9AF&PQ}q00@WetDEvG$!hP&1j^1}F@+iY| z$yDRk60?U~s-S`)~1G_7M{r&Y!3ImgXR85Q@rt85Ikls{y>RWvIcr7-$ z>eI`y_T8Z|^DP;7NhtL%j_Fu(Ii$b3a3{?-u)|EPJN;3qUIMxdR0VL9Qu%d*JSL85 zfs=>uS&`2Ug8cE;@g?_W+-6MI=l&84++#PMOC^#Wac=D66cyj4wu^ixzMzzR#TH0; zRNog+@6ph=8MC{}lU$vNS+_OonC48o_pJXXu>0}JpzD)&&-@t^9r>l>u3$zg=^G2M z@$%}=ckpCTuvjRY))gk}2)dDVOT3AxW=( zYL{@=hukGAY|e|Fe{$Q^{nMQfr)zW9@g@qD0+cz^e#GAPbPS@~Gl~ZG(M?=9$(d}D&WCU+J zip^)_^?hCmKAgprY&T6uN4_}c-n7};6BDr`jbMuEDPawHlaaZLLB%xf=CBqV#ek`z zf(4sXd+fi$BkF_M_UEepu}#kAVt9keL_L#)CBmU-$833|9>~b4Wve@Spu!Qz__DDm zP~TVym=EV{?aaoS1P1d1pB^5{3N5H+J4m&%+eOz&C? zS$x5BzFeHX_PjOS?~`t>aQn{CPOzEj+Kxn1J$92HHpE?D5sP2)c|=%z=+dH~TQ+5_ zGHI2S(mXf+@{W_2(dDJaCG(!h&Kqf(!cFe_vMi`$(8j<{#lpprmev10z55iF0*%MO z*i&EqWZ`CmTyY;5GjQd5{am3a;IQagRx%Dtf|ZgfSHPV|0*FM%SK~9Rt{~``%&NxO zmH)7X?mMf(p(?GZO;TF9cvkx5-2}2CnE|C|e5Esu@jSrFooYivmoMjDH=+Ls=q>8^x<%>d3e|d&=p29#*A+4;Kc6E z`kF0pOb;)*yx8G?=3UP9pK-6MFtF=2^Z(v7y>Ckp^VD~sKBV8%ZNk&suSq5W{Y|xy zbSy-?IC+WK)x~!;cU|o_TnUbf*#6Ieb(@$zb5_S;~SNu&hKa5V^u0FhQyErf-hmK?a@6ic_ps@ivDm)$Qx(Hi?myjnHE zRQDBxA}c>3qc5Pl-mlO%%>*<~N<@;(?jYBFt?JmBfdDt@`iA6}zL>U|K@YdU#r}cx zBLDO-(S1WhLtkxX2%8o;J*}3T*S9um^ZFr|SG>EbYyNZ;9IMczL_JanS^L5e#cWFu zmfLsBt!XIB2CJw9PL%{O)PX&qK_@ztD=D`8(rC+xe*4v>MUES`n1nV5CGPP%i7?33_q>9g4Pw0AxtDnVLH&6q?+}No*qZ zGnGV8s#yg6txeDRHeCD^5pAZf9wS-)ET|9$)Tdu`DSV<875!Ci;g1EI*!QRh>icOtbBaAcFnLhC!~Is zOah}_$OGhLtR4mm&=INfWXC$OP6ZK%#gIUv?}QYDsy%?#L08o3pfj*6Cw{$j<$1mP zGd>Glq4cL+H66$^!OqRQ=051Y>sBe6h|2FZ+-6DHuMI0Ks_cSm)tV!$L+zcTjwuv!edRmw6nbX@{E*fQdpVt z!*~Es$VP*UQOj%xJvx3|4@8z>Gs#E`fK1V_uGXPLt849bsA!DOy;{SSoi2X&=E}$1 zq9ZD$HEI|5&}wQ4rEw-EDuS~(&1fnfn%$N}JYRiL%=9#1;t~KtYxC5I9*_*~*iyHL z%vb-a0!fRDa>INJ-TlEW-0fr1$&UV>nb20}fpf8&qM37U=?cP2+F2w9PM~Cgql^LG z`SJjuYY*a-2zq*>XrB@S0+W=4jHBzYSQt4v5mM`*B4-&_R?SFqjoDr-;A}ut%TW-W0aZ^lSwm?ZW5I?0&o>-ClkW435F;z6MP4Z7c=r2_oW& zNF@z;JV-r40N(++uwWtM2RT$TOcV66Ah}8LA z>9C?VFwpgVclUH{Rd*WqGSj4}_&C=&E6CiKGI#noH^0}D0fSO9;^aYhiELzPI$yLQ zUIs~KB+sOu1N?q#oR8iE0H5a9=Q|@@(_*^}G3Hx%-_Ee<-LDZHjjQ)1?rImuyxn$-V5~q2?dE z8HHdaLdT%ww5nWCVck&!AGzTGA$*GH26dO{nr}l$eXD^Xg+W0jCnP1oG!l@oI1C&P z>RT{@cwrsTbfaTwQHr+K%`S7uDboSX#cJ)`@XhrT=1;*j?qA^V&qY50zb}UW050kb zxHx9B`s8({jTHk_BFRW_YH4JDZ3CASVUbZ$J3n)}1!{(e-V@3u9svE4&u`YHZBDXn z>3e8KRqOE!bDv+1jiHlqhP^h7WO%kbMhl^r!$^@=RK#dm>wuh`0E`Y0yYX~4iS zaGBsxIS_Zxv5v-HY!NQ@*Y}=lsAay7EKYptBKL*YM8|rj^^N6poHIY-t3}a9S!gqv zFiXoaB17Ib5vv8aAX}D02q`*XFF`P%X!}+pIUO{Dpo;(qju+Wepln#do2z#S!t?>) z-*hZkn}{NlMv;L&d-FpTSFjcnV5`0kZZ@eeGyvn%YiO3>=R9 zDQiR$5cUKP!L&@AUg~SyfDV;PC6odcR79e^C4`WVC6D-|>y@3DiQe2Ek`2X1ju8R+9^$WHwH`ceBA+z$ zJ#qn@=A(tZcpp6q|CDdwIqU|w=I4qS4s)28H z1I>*PdwZm9xrGD4ZmLM=QQWmD=o5pd$fB{!AWo4EVrv09;OS++?8SNfili8{czIQV zqM{y*bXBUC=ww|AU`W#Z*2sm$?yfNY)MnI&MSq!XP`FIcRV6Y~o}ZO|G-Wc4dO`87 zlne(kxu`f;JO+$X;O&jbh`AyB-}13DFGMFR*6Jadpk&WqSt>PL*&>)P-z+sr634>&ypVjJh#PAdarKz0NpNBI*DM1`+ zz*s_08XCZDL3dZ3sd{=lKYFO&HLcIJxaew`V+IbK7Rg8*5)nzjK#3R(00rO)aE&Aa z9b5(gKnXaOsPuTK90GxsL+DU}3I`X@u=c>iQjpxPI3nDPSJ&=7J-B&n_v&Ihr@Na| zZ{{ZyOQma1yO%caL!eNKL9)$+XT$92iTvwXOpy5t{OQLjpT{ZHSo>VO7{+gNrsdpo z5lk^iKWQ>?nhHcDmhw$*BH>U73><6Vh=5uoU|K0g0eEbc4lTRtn?UaB{`KDJpt%HNi5tmn2rZ4#cFTy$(PxW>)LW%j2e z)O&bMCLfi~xT=BVf z&w>6sNtY^1-%Sd7J_q(chTFqPgp!3e>x-ufpFpi^9+7bxfHwexV=&0-VE}-`zcm)1 zTRlucB!Z#^3KmMfeKUXoSGIJ(9xTT}0ZRgvOSDtfJyxinnlPR#*iX^6O(4{qACm#t zy#T~vgxqS60KP3Y;c}o~D*!I9sl+`FR0x0vAC5qPXF1R`}MPVEXoj@NXGJX*>c=0Cecz3jZg;{+Gf08x|Nj4W>Hp-wcl>|TZmh-6 x1h7cQB<-Xd|L;0s&$pofYSjNHeY+nC{5(;9_u8$tS;Du!Q7kW!8i_aK|36|9!m|JX literal 0 HcmV?d00001 diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py index a6a2673de3a6e..607636e03442f 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py @@ -10,38 +10,127 @@ And most importantly, they can be made and distributed by the user themselves as external files in their interpreter's library path. This lets very obscure and niche features be implemented and used by those who want them, without adding anything to the maintenance burden for the core Unciv codebase. """ +import math, random, os, json, re, base64, io + +import unciv +from unciv_pyhelpers import * + # If you modify this file, please add any new functions to the build tests. -_defaultterrainsequence = () + +t = re.sub("//.*", "", re.sub('/\*.*\*/', "", unciv.apiHelpers.assetFileString("jsons/Civ V - Gods & Kings/Terrains.json"), flags=re.DOTALL)) +terrainsjson = json.loads(t) +# In an actual implementation, you would want to read from the ruleset instead of the JSON. But this is eaiser for me. +del t + +terrainbases = {t['name']: t for t in terrainsjson if t['type'] in ('Water', 'Land')} +terrainfeatures = {t['name']: t for t in terrainsjson if t['type'] == 'TerrainFeature'} +# del terrainfeatures['Fallout'] + +def genValidTerrains(*, forbid=('Fallout',)): + #Searches only two layers deep. I.E. Only combinations with at most two TerrainFeatures will be found. + terrains = set() + + for terrain in terrainbases: + if terrain in forbid: + continue + terrains.add(terrain) + for feature, fparams in terrainfeatures.items(): + if feature not in forbid and terrain in fparams['occursOn']: + terrains.add(f"{terrain}/{feature}") + for otherfeature in fparams['occursOn']: + if otherfeature not in forbid and otherfeature in terrainfeatures: + otherparams = terrainfeatures[otherfeature] + if terrain in otherparams['occursOn']: + terrains.add(f"{terrain}/{otherfeature},{feature}") + return terrains + +naturalterrains = tuple(sorted(genValidTerrains())) + + +_altitudeterrainsequence = ( + "Ocean", + ("Ocean", "Coast"), + (*("Coast",)*9, "Coast/Atoll"), + ("Desert/Flood plains", "Desert", "Desert/Marsh"), + (*("Plains",)*5, "Plains/Forest"), + (*("Grassland",)*3, "Grassland/Forest"), + ("Grassland/Hill", "Grassland/Hill,Forest"), + ("Plains/Hill,Forest", "Grassland/Hill,Jungle", "Plains/Hill,Jungle", "Desert/Hill", "Lakes"), + ("Mountain", "Tundra/Hill"), + ("Mountain", "Snow/Hill", "Mountain/Ice") +) + +_hardterrainsequence = ( + "Ocean", + "Coast", + "Desert/Marsh", + "Desert/Flood plains", + "Desert", + "Plains", + "Grassland", + "Plains/Forest", + "Grassland/Forest", + "Grassland/Hill" + "Plains/Hill", + "Desert/Hill", + "Mountain", + "Snow/Hill", + "Tundra/Hill", + "Tundra", + "Snow", + "Snow/Ice" +) #[(setattr(t, "baseTerrain", "Mountain") if real(t) else None) for r in gameInfo.tileMap.tileMatrix for t in r] #Apparently there's no distinction between base terrain types and terrain features. #[([setattr(t, "baseTerrain", "Grassland" if t.position.x % 2 else "Coast"), setattr(t, "naturalWonder", None if t.position.y%3 else "Krakatoa")] if real(t) else None) for r in gameInfo.tileMap.tileMatrix for t in r] -def hexCoordToRectCoord(vector): - pass +# def hexCoordToRectCoord(vector): + # pass -def rectCoordToHexCoord(vector): - pass +# def rectCoordToHexCoord(vector): + # pass + +def indexClipped(items, index): + return items[max(0, min(len(items)-1, index))] + +def defaultArgTilemap(tileMap=None): + if tileMap is None: + if unciv.apiHelpers.isInGame: + return unciv.gameInfo.tileMap + else: + return unciv.mapEditorScreen.tileMap + else: + return tileMap def terrainAsString(tileInfo): - s = real(t.baseTerrain) - if len(t.terrainFeatures): - s += "/" + ",".join() + s = real(tileInfo.baseTerrain) + if len(tileInfo.terrainFeatures): + s += "/" + ",".join(real(f) for f in tileInfo.terrainFeatures) return s def terrainFromString(terrainstring): + baseterrain, _, terrainfeatures = terrainstring.partition("/") + terrainfeatures = terrainfeatures.split(",") if terrainfeatures else [] return baseterrain, tuple(terrainfeatures) -def setTerrain(tileInfo, terrainstring): - pass +def setTerrain(tileInfo, terraintype): + if not isinstance(terraintype, str): + terraintype = random.sample(terraintype, 1)[0] + base, features = terrainFromString(terraintype) + tileInfo.baseTerrain = base + tileInfo.terrainFeatures.clear() + for f in features: + tileInfo.terrainFeatures.add(f) + #setTerrain(mapEditorScreen.tileMap.values[0], "") -def checkValidValue(value, checktype="TileType"): - if value not in unciv.mapEditorScreen.ruleset.whatever: - raise Exception() +# def checkValidValue(value, checktype="TileType"): + # if value not in unciv.mapEditorScreen.ruleset.whatever: + # raise Exception() def spreadResources(resourcetype="Horses", mode="random", restrictfrom=(), restrictto=None): @@ -52,18 +141,6 @@ def spreadResources(resourcetype="Horses", mode="random", restrictfrom=(), restr else: raise TypeError() -def makeMandelbrot(): - pass - -def graph2D(expr="sin(x/5)*5", north="Ocean", south="Desert"): - pass - -def graph3D(expr="sqrt(x**2+y**2)/5%5*len(terrains)", terrains=_defaultterrainsequence) - pass - -def loadImageHeightmap(imagepath=None, tiletypes("Ocean", "Coast")): - import PIL - pass def dilateTileTypes(tiletypes=("Coast", "Flood Plains"), chance=1.0, forbidreplace=("Ocean", "Mountain"), dilateas=("Desert/Flood Plains", "Coast"), iterations=1): # .terrainFeatures and .baseTerrain @@ -76,21 +153,273 @@ def floodFillSelected(start=None, fillas=None, *, alsopropagateto=()): pass -def _computeTerrainColours(terraintypes) - global _ +mandlebrotpresets = { + "Minibrot": {'center': (-0.105, -0.925), 'viewport': 0.006, 'indexer': "round((1/(i/8+1))*len(terrains))"}, + # "Minibrot2": {'center': (-1.786440683703552459, 0), 'viewport': 5E-8}, + # "cruciform": {'center': (-1.7859974480735000169, -0.00021290140715606698619), 'viewport': 9.1739918430357151341e-07, 'iterations': 900}, + "Hat": {'center': (-1.301, -0.063), 'viewport': 2E-2, 'iterations': 300, 'indexer': "round(i/300*len(terrains))"}, + "TwinLakes": {'center': (-1.4476, -0.0048), 'viewport': 1E-3, 'iterations': 50, 'indexer': "round(i/200*len(terrains))"}, + "Curly": {'center': (-0.221, -0.651), 'viewport': 6E-3, 'iterations': 70, 'indexer': "round((1/(i/6+1))*len(terrains))"}, + "Crater": {'center': (-1.447858, -0.004673), 'viewport': 3.5E-5, 'iterations': 80, 'indexer': "round((1-1/(i+1))*len(terrains))"}, + "Rift": {'center': (-0.700, -0.295), 'viewport': 3E-3, 'iterations': 100}, + "Spiral": {'center': (-0.676, -0.362), 'viewport': 3E-3, 'iterations': 100, 'indexer': "round((1-1/(i+1))*len(terrains))"}, + # "": {'center': (), 'viewport': , 'iterations': }, + "Pentabrot": {'expo': 6} +} + +def _mandelbrot(x, y, iterations=100, *, expo=2, escaperadius=12, innervalue=None): + c=complex(x,y) + z = 0+0j + dist = 0 + if innervalue is None: + innervalue = iterations + for i in range(iterations): + dist = math.sqrt(z.real**2+z.imag**2) + if dist > escaperadius: + break + z = z**expo+c + return innervalue if dist <= escaperadius else i + 1 - math.log(math.log(dist), expo) + + +def makeMandelbrot(tileMap=None, *, viewport=4, center=(-0.5,0), iterations=100, expo=2, indexer="round((1/(i+1))*len(terrains))", terrains=_hardterrainsequence, innervalue=0): + tileMap = defaultArgTilemap(tileMap) + scalefac = viewport / max(tileMap.mapParameters.mapSize.width, tileMap.mapParameters.mapSize.height) + offset_x, offset_y = center + indexer = compile(indexer, filename="indexer", mode='eval') + def coordsfromtile(tile): + return -tile.longitude*scalefac+offset_x, -tile.latitude*scalefac+offset_y + for tile in tileMap.values: + setTerrain( + tile, + indexClipped( + terrains, + eval( + indexer, + { + 'i': _mandelbrot(*coordsfromtile(tile), iterations=iterations, expo=expo, innervalue=innervalue), + 'terrains': terrains, + 'iterations': iterations + } + ) + ) + ) -def requireComputedColours(terraintypes, allowcompute=False): - global _ - if not _ or not all(t in _ for t in terraintypes): - if allow_compute_colours: - pass + +#from unciv_scripting_examples.MapEditingMacros import * + + +def graph2D(tileMap=None, expr="sin(x/3)*5", north="Ocean", south="Desert"): + tileMap = defaultArgTilemap(tileMap) + expr = compile(expr, filename="expr", mode='eval') + for tile in tileMap.values: + setTerrain( + tile, + north if tile.latitude > eval(expr, {**math.__dict__, 'x': tile.longitude}) else south + ) + +def graph3D(tileMap=None, expr="sqrt(x**2+y**2)/6%5/5*len(terrains)", terrains=_hardterrainsequence): + tileMap = defaultArgTilemap(tileMap) + expr = compile(expr, filename="expr", mode='eval') + for tile in tileMap.values: + setTerrain( + tile, + indexClipped( + terrains, + math.floor(eval( + expr, + { + **math.__dict__, + 'x': real(tile.longitude), + 'y': real(tile.latitude), + 'terrains': terrains + } + )) + ) + ) + + +def setMapFromImage(tileMap, image, pixelinterpreter=lambda pixel: "Ocean"): + + longitudes, latitudes = ([real(getattr(t, a)) for t in tileMap.values] for a in ('longitude', 'latitude')) + min_long, max_long, min_lat, max_lat = (f(c) for c in (longitudes, latitudes) for f in (min, max)) + del longitudes, latitudes + width = max_long - min_long + height = max_lat - min_lat + + width_fac = (image.size[0] - 1) / width + height_fac = (image.size[1] - 1) / height + + for tile in tileMap.values: + # Since this just uses PIL images, we could also blur the image, or perhaps just jitter the sampled coordinates, by the projected tile radius here. Or could have earlier functions in the call stack wrap the image in a class that does that. + setTerrain( + tile, + pixelinterpreter(image.getpixel(( + round((-tile.longitude + max_long) * width_fac), + round((-tile.latitude + max_lat) * height_fac) + ))) + ) + + +def loadImageHeightmap(tileMap=None, imagepath="EarthTopography.png", transform="pixel*len(terrains)", terrains=_altitudeterrainsequence, normalizevalues=255): + tileMap = defaultArgTilemap(tileMap) + import PIL.Image + if not os.path.exists(imagepath): + _fallbackpath = os.path.join(os.path.dirname(__file__), imagepath) + if os.path.exists(_fallbackpath): + imagepath = _fallbackpath + print(f"Invalid image path given. Interpreting as example path at {repr(imagepath)}") + del _fallbackpath + # if imagepath is None: + # imagepath = os.path.join(os.path.dirname(__file__), "EarthTopography.png") + #https://visibleearth.nasa.gov/images/73934/topography + #https://visibleearth.nasa.gov/images/73963/bathymetry + #NASA stuff is usually public domain by law. + # longitudes, latitudes = ([real(getattr(t, a)) for t in tileMap.values] for a in ('longitude', 'latitude')) + # min_long, max_long, min_lat, max_lat = (f(c) for c in (longitudes, latitudes) for f in (min, max)) + # del longitudes, latitudes + # width = max_long - min_long + # height = max_lat - min_lat + transform = compile(transform, filename="transform", mode='eval') + + def pixinterp(pixel): + if isinstance(pixel, tuple): + pixel = sum(pixel)/len(pixel) + pixel /= normalizevalues + pixel = round(eval(transform, {'pixel': pixel, 'terrains': terrains})) + return indexClipped(terrains, pixel) + + with PIL.Image.open(imagepath) as image: + setMapFromImage(tileMap=tileMap, image=image, pixelinterpreter=pixinterp) + # width_fac = (image.size[0] - 1) / width + # height_fac = (image.size[1] - 1) / height + # for tile in tileMap.values: + # x = round((-tile.longitude + max_long) * width_fac) + # y = round((-tile.latitude + max_lat) * height_fac) + # print(x, y) + # v = image.getpixel((x, y)) + # if isinstance(v, tuple): + # v = sum(v)/len(v) + # v /= normalizevalues + # v = round(eval(transform, {'v': v, 'terrains': terrains})) + # print(v) + # setTerrain( + # tile, + # indexClipped( + # terrains, + # v + # ) + # ) + + + +def terrainImagePath(feature): + # Look in TileGroup.kt if you want to replace this with something that handles different tilesets. + return f"TileSets/FantasyHex/Tiles/{feature}" + +def compositedTerrainImage(terrain): + import PIL.Image + base, features = terrainFromString(terrain) + image = PIL.Image.open(io.BytesIO(base64.b64decode(unciv.apiHelpers.assetImageB64(terrainImagePath(base))))) + for feature in features: + with PIL.Image.open(io.BytesIO(base64.b64decode(unciv.apiHelpers.assetImageB64(terrainImagePath(feature))))) as layer: + image.alpha_composite(layer, (0, image.size[1]-layer.size[1])) + return image + #compositedTerrainImage("Desert/Hill,Jungle").show() + +def getImageAverageRgb(image): + #image.convert('P', palette=PIL.Image.ADAPTIVE, colors=1) + depth = 255 + assert image.mode in ("RGB", "RGBA") + hasalpha = image.mode == "RGBA" + r_sum = g_sum = b_sum = total_alpha = 0 + for x in range(image.size[0]): + for y in range(image.size[1]): + if hasalpha: + r, g, b, a = image.getpixel((x, y)) + a /= depth + else: + r, g, b = image.getpixel((x, y)) + a = 1.0 + r_sum += r * a + g_sum += g * a + b_sum += b * a + total_alpha += a + return tuple(c/total_alpha for c in (r_sum, g_sum, b_sum)) + + +def computeTerrainAverageColours(terrains=naturalterrains): + # FIXME: Increases memory use with each run. + def terraincol(terrain): + with compositedTerrainImage(terrain) as i: + return getImageAverageRgb(i) + return {terrain: tuple(round(n) for n in terraincol(terrain)) for terrain in terrains} + + +# _terraincolours = None + +# def _computeTerrainColours(terraintypes): + # global _terraincolours + # #This will requires some kind of access to GDX internal files. + +# def requireComputedColours(terraintypes, allowcompute=False): + # global _ + # if not _ or not all(t in _ for t in terraintypes): + # if allow_compute_colours: + # pass + # else: + # print(f"This function requires the average colour to be computed for the following terrain types:\n{terraintypes}\n\nDoing so may take a long time. The interface will be unresponsive during the process.\nPass `allow_compute_colours=True` to this function, or run `_computeTerrainColours({terraintypes})`, in order to compute the required colours.") + +class _TerrainColourInterpreter: + # To actually look good, this should use CIE, YUV, or at least HSV with a compressed saturation axis. + def __init__(self, terraincolours, maxdither=0): + self.terraincolours = terraincolours + self.maxdither = maxdither + if self.maxdither: + self.dithererror = [0, 0, 0] + @classmethod + def rgb_distance(cls, rgb1, rgb2): + return math.sqrt(sum([(a-b)**2 for a, b in zip(rgb1, rgb2)])) + def get_terrainandcolour(self, rgb): + return min(self.terraincolours.items(), key=lambda item: self.rgb_distance(item[1], rgb)) + def get_terraindithered(self, rgb): + rgb_compensated = tuple(c_target-c_error for c_target, c_error in zip(rgb, self.dithererror)) + terrain, rgb_final = self.get_terrainandcolour(rgb_compensated) + for i, (c_target, c_final, error_current) in enumerate(zip(rgb, rgb_final, self.dithererror)): + self.dithererror[i] = max(-self.maxdither*256, min(self.maxdither*256, error_current + (c_final - c_target))) + #Because the "colour palette" is usually very limited in range, and particularly because it often doesn't have any low-green values to bring the green error down (I.E. the mean channel value over the whole image may well be darker than the minimum available colour), limiting the maximum accumulatable error is necessary to avoid it running away. + # print(self.dithererror) + return terrain + def __call__(self, pixel): + if self.maxdither: + return self.get_terraindithered(pixel) else: - print(f"This function requires the average colour to be computed for the following terrain types:\n{terraintypes}\n\nDoing so may take a long time. The interface will be unresponsive during the process.\nPass `allow_compute_colours=True` to this function, or run `_computeTerrainColours({terraintypes})`, in order to compute the required colours.") + return self.get_terrainandcolour(pixel)[0] + +def loadImageColours(tileMap=None, imagepath="EarthTerrainFantasyHex.png", terraincolours=None, maxdither=0, visualspace=True): + """ + Set a given tileMap or the active tileMap's terrain based on an image file and a mapping of terrain strings to RGB tuples. + Recommended example values for imagepath: EarthTerrainFantasyHex.png, StarryNight.jpg, TurboRainbow.png (Try maxdither=0.5.) + """ + #https://visibleearth.nasa.gov/images/73801/september-blue-marble-next-generation-w-topography-and-bathymetry + #Generate TurboRainbow.png: from matplotlib import cm; from PIL import Image, ImageDraw; width=512; image = Image.new('RGB', (width,1), "white"); draw=ImageDraw.Draw(image); [draw.point((x,0), tuple(int(c*256) for c in cm.turbo(x/width)[:3])) for x in range(width)]; image.show("TurboRainbow.png"); image.close() + assert visualspace + tileMap = defaultArgTilemap(tileMap) + import PIL.Image + if not os.path.exists(imagepath): + _fallbackpath = os.path.join(os.path.dirname(__file__), imagepath) + if os.path.exists(_fallbackpath): + imagepath = _fallbackpath + print(f"\nInvalid image path given. Interpreting as example path at {repr(imagepath)}") + del _fallbackpath + if terraincolours is None: + print(f"\nNo terrain colours given. Computing average tile colours based on FantasyHex tileset. This may take several seconds.") + terraincolours = computeTerrainAverageColours(naturalterrains) + print(f"\nTerrain colours computed:\n{repr(terraincolours)}") + pixinterp = _TerrainColourInterpreter(terraincolours, maxdither=maxdither) + with PIL.Image.open(imagepath) as image: + setMapFromImage(tileMap, image, pixinterp) -def loadImageColours(imagepath=None, allowedterrains=(), allow_compute_colours=False, visualspace=True): - global _ - requireComputedColours(allowedterrains, allow_compute_colours) def makeImageFromTerrainColours(allowedterrains, allow_compute_colours, visualspace=True): requireComputedColours diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/StarryNight.jpg b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/StarryNight.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0b9e70fd804b4214eaaca562fd1b74dfc37826c3 GIT binary patch literal 71017 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 literal 0 HcmV?d00001 diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py index 8b137891791fe..d25af3685a2fa 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py @@ -1 +1,73 @@ +import unciv, unciv_lib + +from . import EndTimes, ExternalPipe, MapEditingMacros, Merfolk, PlayerMacros + + +try: + assert False + # Can also check __debug__. +except: + pass +else: + raise RuntimeError("Assertions must be enabled to run Python tests.") + + +tests = [] + +class Test: + def __init__(self, func, name=None, runwith=None, args=(), kwargs={}): + #Args and kwargs here, but not used. + self.func = func + self.name = func.__name__ if name is None else name + self.runwith = runwith + self.args = args + self.kwargs = kwargs + def __call__(self): + if self.runwith is None: + self.func(*self.args, **self.kwargs) + else: + with self.runwith: + self.func(*self.args, **self.kwargs) + +def test(name=None, *, runwith=None): + def _testmaker(func): + tests.append(Test(test, name=name, runwith=runwith)) + return func + return _testmaker + + +class NewGameTest: + pass + +class MapEditorTest: + pass + +class MainMenuTest: + pass + + +#TODO: Add tests. Will probably require exception field in IPC protocol to use. + +#Basic IPC protocol specs and Pythonic operators. + +#No error in any examples. + +#ScriptingScope properties correctly set and nullified by different screens. + +#Token reuse, once that's implemented. + +#Probably don't bother with DOCTEST, or anything. Just use assert statements where needed, print out any errors, and check in the build tests that there's no exceptions (by flag, or by printout value). + + +def run_tests(): + failures = [] + for test in tests: + try: + test() + except Exception as e: + failures.append(e) + print(f"Python test FAILED: {test._testname}\n{unciv_lib.utils.formatException(exc)}") + else: + print(f"Python test PASSED: {test._testname}") + assert not failures diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/TurboRainbow.png b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/TurboRainbow.png new file mode 100644 index 0000000000000000000000000000000000000000..78c91f947432b16dbcbd5730ade782012c44a84b GIT binary patch literal 338 zcmV-Y0j>UtP)(YU3mC*b94@I(5Cn4QnT_+~nv zfcYurnCy*@>BV&K1)!AaZ^p&!>>C@9H#UI6;Oq<#!dViClF|kcp0o8Bn~MX;#$p<$ zS21-g)o1r>+Y%3eW^6TPE1L&kRsmPG0Nfi_pxDayd<{OtA2E4#jAI+g>k&K6q#{d8T07*qoM6N<$f*W$2I{*Lx literal 0 HcmV?d00001 diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/__init__.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/__init__.py index 09a2bf8ee9e73..f48ed5cf4c9e2 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/__init__.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/__init__.py @@ -1,3 +1,3 @@ -__all__ = ["EndTimes", "ExternalPipe", "Merfolk", "PlayerMacros"] +__all__ = ["EndTimes", "ExternalPipe", "MapEditingMacros", "Merfolk", "PlayerMacros", "Tests"] -from . import EndTimes, ExternalPipe, Merfolk, PlayerMacros +from . import EndTimes, ExternalPipe, MapEditingMacros, Merfolk, PlayerMacros, Tests diff --git a/core/Module.md b/core/Module.md index 63ebc649ed982..8d62961f530ac 100644 --- a/core/Module.md +++ b/core/Module.md @@ -211,8 +211,9 @@ Some action types, data formats, and expected response types and data formats fo ``` 'read': {'path': List<{'type':String, 'name':String, 'params':List}>} -> - 'read_reponse': {'value': Any?, 'exception': String?} + 'read_reponse': Any? or String //Attribute/property access, by list of `PathElement` properties. + //Response must be String if sent with Exception flag. ``` ``` @@ -237,45 +238,73 @@ Some action types, data formats, and expected response types and data formats fo ``` 'dir': {'path': List<{'type':String, 'name':String, 'params':List}>} -> - 'dir_response': {'value': Collection, 'exception': String?} + 'dir_response': Collection or String //Names of all members/properties/attributes/methods. + //Response must be String if sent with Exception flag. + ``` + + ``` + //'hash': {'path': List<{'type':String, 'name':String, 'params':List}>} -> + //'hash_response': Any? or String + //Response must be String if sent with Exception flag. + //Implemented, but disabled. I'm not actually sure what the use of this would be. Hashes are usually just a shortcut for (in)equality, so I think a Kotlin-side equality or identity operator might be needed for this to be useful. ``` ``` 'keys': {'path': List<{'type':String, 'name':String, 'params':List}>} -> - 'keys_response': {'value': Collection, 'exception': String?} + 'keys_response': Collection or String + //Response must be String if sent with Exception flag. //Keys of Map-interfaced instances. Used by Python bindings for iteration and autocomplete. ``` ``` 'length': {'path': List<{'type':String, 'name':String, 'params':List}>} -> - 'length_response': {'value': Int?, 'exception': String?} + 'length_response': Int or String + //Response must be String if sent with Exception flag. //Used by Python bindings for length and also for iteration. ``` ``` 'contains': {'path': List<{'type':String, 'name':String, 'params':List}>, 'value': Any?} -> - 'contains_response': {'value': Boolean?, 'exception': String?} + 'contains_response': Boolean or String //Doing this through an IPC call instead of in the script interpreter should let tokenized instances be checked for properly. + //Response must be String if sent with Exception flag. + ``` + + ``` + //'isiterable': {'path': List<{'type':String, 'name':String, 'params':List}>} -> + //'isiterable_response': Boolean or String + //Response must be String if sent with Exception flag. + //Not implemented. + ``` + + ``` + 'ismapping': {'path': List<{'type':String, 'name':String, 'params':List}>} -> + 'isiterable_response': Boolean or String + //Response must be String if sent with Exception flag. + //Used by Python bindings to hide Python-emulating .values, .keys, and .entries to allow access to Kotlin objects when not a mapping. ``` ``` 'callable': {'path': List<{'type':String, 'name':String, 'params':List}>} -> - 'callable_response': {'value': Boolean?, 'exception': String?} + 'callable_response': Boolean or String + //Response must be String if sent with Exception flag. //Used by Python autocompleter to add opening bracket to methods and function suggestions. Quite useful for exploring API at a glance. ``` ``` 'args': 'path': List<{'type':String, 'name':String, 'params':List}>} -> - 'args_response': {'value': List>?, 'exception': String?} + 'args_response': List> or String //Names and types of arguments accepted by a function. + //Response must be String if sent with Exception flag. //Currently just used by Python autocompleter to generate help text. //Could also be used to control functions in scripting environment. If so, then names of types should be standardized. ``` ``` 'docstring': {'path': List<{'type':String, 'name':String, 'params':List}>} -> - 'docstring_response': {'value': String?, 'exception': String?} + 'docstring_response': String or String + //Response must be String if sent with Exception flag. //Used by Python wrappers and autocompleter to get help text showing arguments and types for callables. Useful for exploring API without having to browse code. ``` @@ -294,9 +323,9 @@ Flags are string values for communicating extra information that doesn't need a ``` ``` - //'Exception' + 'Exception' //Indicates that this packet is associated with an error. - //Not implemented. Currently Kotlin-side exceptions while processing a request are communicated to the script interpreter with an error message at an expected place in the data field of the response, and exceptions in the script interpreter while executing a command are stringified and returned to the Kotlin side the same as any REPL output. Miight be useful for a modding API, though. + //Currently sent only by Kotlin side and handled only by Python backend. Modding API and build tests will need this to be implemented in the other direction too. ``` ``` diff --git a/core/src/com/unciv/MainMenuScreen.kt b/core/src/com/unciv/MainMenuScreen.kt index 585f12ec2e66e..8579c3d42ade8 100644 --- a/core/src/com/unciv/MainMenuScreen.kt +++ b/core/src/com/unciv/MainMenuScreen.kt @@ -15,11 +15,10 @@ import com.unciv.logic.map.MapSizeNew import com.unciv.logic.map.MapType import com.unciv.logic.map.mapgenerator.MapGenerator import com.unciv.models.ruleset.RulesetCache -import com.unciv.scripting.ScriptingState import com.unciv.ui.MultiplayerScreen import com.unciv.ui.mapeditor.* import com.unciv.models.metadata.GameSetupInfo -import com.unciv.ui.consolescreen.ConsoleScreen +import com.unciv.ui.consolescreen.IConsoleScreenAccessible import com.unciv.ui.newgamescreen.NewGameScreen import com.unciv.ui.pickerscreens.ModManagementScreen import com.unciv.ui.saves.LoadGameScreen @@ -27,17 +26,11 @@ import com.unciv.ui.utils.* import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip import kotlin.concurrent.thread -class MainMenuScreen: BaseScreen() { +class MainMenuScreen: BaseScreen(), IConsoleScreenAccessible { private val autosave = "Autosave" private val backgroundTable = Table().apply { background=ImageGetter.getBackground(Color.WHITE) } private val singleColumn = isCrampedPortrait() - private val consoleScreen: ConsoleScreen - get() = game.consoleScreen - - private val scriptingState: ScriptingState - get() = game.scriptingState - /** Create one **Main Menu Button** including onClick/key binding * @param text The text to display on the button * @param icon The path of the icon to display on the button @@ -157,11 +150,9 @@ class MainMenuScreen: BaseScreen() { ExitGamePopup(this) } - keyPressDispatcher[Input.Keys.GRAVE] = { game.setConsoleScreen() } - consoleScreen.closeAction = { game.setScreen(this) } - scriptingState.gameInfo = null - scriptingState.civInfo = null - scriptingState.worldScreen = null + setOpenConsoleScreenHotkey() + setConsoleScreenCloseAction() + updateScriptingState() } diff --git a/core/src/com/unciv/UncivGame.kt b/core/src/com/unciv/UncivGame.kt index bd8dea66985ed..a3b1a33e4e197 100644 --- a/core/src/com/unciv/UncivGame.kt +++ b/core/src/com/unciv/UncivGame.kt @@ -73,10 +73,11 @@ class UncivGame(parameters: UncivGameParameters) : Game() { val translations = Translations() lateinit var scriptingState: ScriptingState - lateinit var consoleScreen: ConsoleScreen // Keep same ConsoleScreen() when possible, to avoid having to manually persist/restore history, input field, etc. + lateinit var consoleScreen: ConsoleScreen + // Keep same ConsoleScreen() when possible, to avoid having to manually persist/restore history, input field, etc. override fun create() { - + Gdx.input.setCatchKey(Input.Keys.BACK, true) if (Gdx.app.type != Application.ApplicationType.Desktop) { viewEntireMapForDebug = false @@ -157,10 +158,7 @@ class UncivGame(parameters: UncivGameParameters) : Game() { scriptingState = ScriptingState( ScriptingScope( - null, - null, - this, - null + uncivGame = this ) ) diff --git a/core/src/com/unciv/scripting/ScriptingBackend.kt b/core/src/com/unciv/scripting/ScriptingBackend.kt index 82485e48ff88a..a161bf76e6957 100644 --- a/core/src/com/unciv/scripting/ScriptingBackend.kt +++ b/core/src/com/unciv/scripting/ScriptingBackend.kt @@ -94,10 +94,12 @@ open class ScriptingBackendBase(val scriptingScope: ScriptingScope): ScriptingBa /** * For the UI, a way is needed to list all available scripting backend types with 1. A readable display name and 2. A way to create new instances. + * * So every ScriptngBackend has a Metadata:ScriptingBackend_metadata companion object, which is stored in the ScriptingBackendType enums. */ companion object Metadata: ScriptingBackend_metadata() { override fun new(scriptingScope: ScriptingScope) = ScriptingBackendBase(scriptingScope) + // Trying to instantiate from a KClass seems messy when the constructors are expected to be called normally. This is easier. override val displayName: String = "Dummy" } @@ -490,7 +492,15 @@ abstract class SubprocessScriptingBackend(scriptingScope: ScriptingScope): Black override val replManager: ScriptingReplManager by lazy { ScriptingRawReplManager(scriptingScope, blackbox) } override fun motd(): String { - return "\n\nWelcome to the Unciv '${metadata.displayName}' API. This backend relies on running the system ${processCmd.firstOrNull()} command as a subprocess.\n\nIf you do not have an interactive REPL below, then please make sure the below command is valid on your system:\n\n${processCmd.joinToString(" ")}\n\n${super.motd()}\n" + return """ + + Welcome to the Unciv '${metadata.displayName}' API. This backend relies on running the system ${processCmd.firstOrNull()} command as a subprocess. + + If you do not have an interactive REPL below, then please make sure the following command is valid on your system: + + ${processCmd.joinToString(" ")} + + """.trimIndent() + super.motd() } } diff --git a/core/src/com/unciv/scripting/ScriptingScope.kt b/core/src/com/unciv/scripting/ScriptingScope.kt index 9c6b95fd88fe0..7e2070fc04b1a 100644 --- a/core/src/com/unciv/scripting/ScriptingScope.kt +++ b/core/src/com/unciv/scripting/ScriptingScope.kt @@ -1,12 +1,20 @@ package com.unciv.scripting +import com.badlogic.gdx.Gdx +//import com.badlogic.gdx.graphics.glutils.FileTextureData +import com.badlogic.gdx.graphics.PixmapIO +import com.badlogic.gdx.utils.Base64Coder import com.unciv.UncivGame import com.unciv.logic.GameInfo import com.unciv.logic.civilization.CivilizationInfo import com.unciv.scripting.utils.ScriptingApiEnums import com.unciv.scripting.utils.InstanceFactories import com.unciv.scripting.utils.InstanceRegistry +import com.unciv.ui.mapeditor.MapEditorScreen +import com.unciv.ui.utils.ImageGetter +import com.unciv.ui.utils.toPixmap import com.unciv.ui.worldscreen.WorldScreen +import java.io.ByteArrayOutputStream /** @@ -14,34 +22,68 @@ import com.unciv.ui.worldscreen.WorldScreen * * Also where to put any future PlayerAPI, CheatAPI, ModAPI, etc. * - * For LuaScriptingBackend, UpyScriptingBackend, QjsScriptingBackend, etc, the hierarchy of data under this class definition should probably directly mirror the wrappers in the namespace exposed to the scripting language. + * For LuaScriptingBackend, UpyScriptingBackend, QjsScriptingBackend, etc, the hierarchy of data under this class definition should probably directly mirror the wrappers in the namespace exposed to running scripts. * * WorldScreen gives access to UnitTable.selectedUnit, MapHolder.selectedTile, etc. Useful for contextual operations. + * + * The members of this class and its subclasses should not be used in implementing the protocol level of scripting backends. + * E.G.: If you need access to a file to build the scripting environment, then add it to ScriptingEngineConstants.json instead of using apiHelpers.assetFileB64. If you need access to some new type of property, then geneneralize it as much as possible and add an IPC request type for it in ScriptingProtocol.kt. + * API calls are for running scripts, and may be less stable. Building the scripting environment itself should be done directly using the IPC protocol and other lower-level constructs. */ class ScriptingScope( - var civInfo: CivilizationInfo?, - var gameInfo: GameInfo?, - var uncivGame: UncivGame?, - var worldScreen: WorldScreen? - //mapEditorScreen + //If this is going to be exposed to downloaded mods, then every declaration here, as well as *every* declaration that is safe for scripts to have access to, should probably be whitelisted with annotations and checked or errored at the point of reflection. + var civInfo: CivilizationInfo? = null, + var gameInfo: GameInfo? = null, + var uncivGame: UncivGame? = null, + var worldScreen: WorldScreen? = null, + var mapEditorScreen: MapEditorScreen? = null //val _availableNames = listOf("civInfo", "gameInfo", "uncivGame", "worldScreen", "apiHelpers") // Nope. Annotate instead. ) { val apiHelpers = ApiHelpers(this) class ApiHelpers(val scriptingScope: ScriptingScope) { + // This could probably eventually include ways for scripts to create and inject their own UI elements too. Create, populate, show even popups for mods, inject buttons that execute script strings for macros. val isInGame: Boolean get() = (scriptingScope.civInfo != null && scriptingScope.gameInfo != null && scriptingScope.uncivGame != null) val Factories = InstanceFactories val Enums = ScriptingApiEnums val registeredInstances = InstanceRegistry() - fun unchanged(obj: Any?) = obj //Debug/dev identity function for both Kotlin and scripts. Check if value survives serialization, force something to be added to ScriptingProtocol.instanceSaver, etc. + //Debug/dev identity function for both Kotlin and scripts. Check if value survives serialization, force something to be added to ScriptingProtocol.instanceSaver, etc. + fun unchanged(obj: Any?) = obj fun printLn(msg: Any?) = println(msg) - //fun readLine + //fun readLn() //Return a line from the main game process's STDIN. fun toString(obj: Any?) = obj.toString() + fun copyToClipboard(value: Any?) { + //Better than scripts potentially doing it themselves. In Python, for example, a way to do this would involve setting up an invisible TKinter window. + Gdx.app.clipboard.contents = value.toString(); + } //fun typeOf(obj: Any?) = obj::class.simpleName //fun typeOfQualified(obj: Any?) = obj::class.qualifiedName + // @param path Path of an internal file as exposed in Gdx.files.internal. + // @return The contents of the internal file read as a text string. + fun assetFileString(path: String) = Gdx.files.internal(path).readString() + // @param path Path of an internal file as exposed in Gdx.files.internal. + // @return The contents of the internal file encoded as a Base64 string. + fun assetFileB64(path: String) = String(Base64Coder.encode(Gdx.files.internal(path).readBytes())) + // @param path Path of an internal image as exposed in ImageGetter as a TextureRegionDrawable from an atlas. + // @return The image encoded as a PNG file encoded as a Base64 string. + fun assetImageB64(path: String): String { + // To test in Python: + // import PIL.Image, io, base64; PIL.Image.open(io.BytesIO(base64.b64decode(apiHelpers.assetImage("StatIcons/Resistance")))).show() + val fakepng = ByteArrayOutputStream() + //Close this? Well, the docs say doing so "has no effect", and it should clearly get GC'd anyway. + val pixmap = ImageGetter.getDrawable(path).getRegion().toPixmap() + val exporter = PixmapIO.PNG() + exporter.setFlipY(false) + exporter.write(fakepng, pixmap) + pixmap.dispose() // Doesn't seem to help with the memory leak. + exporter.dispose() // Both of these should be called automatically by GC anyway. + return String(Base64Coder.encode(fakepng.toByteArray())) + +// return String(Base64Coder.encode((ImageGetter.getDrawable(path).getRegion().getTexture().getTextureData() as FileTextureData).getFileHandle().readBytes())) // Cool. This works. But it freezes everything for several dozen seconds on the first run, and it returns the massive packed file. + } } } diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index ad0b437a633c7..440d27abec092 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -56,21 +56,25 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr // Actually inverted, because history items are added to end of list and not start. 0 means nothing, 1 means most recent command at end of list. //Wrapper for property of scriptingScope. - var civInfo: CivilizationInfo? - get() = scriptingScope.civInfo - set(value) { scriptingScope.civInfo = value } +// var civInfo: CivilizationInfo? +// get() = scriptingScope.civInfo +// set(value) { scriptingScope.civInfo = value } - var gameInfo: GameInfo? - get() = scriptingScope.gameInfo - set(value) { scriptingScope.gameInfo = value } +// var gameInfo: GameInfo? +// get() = scriptingScope.gameInfo +// set(value) { scriptingScope.gameInfo = value } - var uncivGame: UncivGame? - get() = scriptingScope.uncivGame - set(value) { scriptingScope.uncivGame = value } +// var uncivGame: UncivGame? +// get() = scriptingScope.uncivGame +// set(value) { scriptingScope.uncivGame = value } - var worldScreen: WorldScreen? - get() = scriptingScope.worldScreen - set(value) { scriptingScope.worldScreen = value } +// var worldScreen: WorldScreen? +// get() = scriptingScope.worldScreen +// set(value) { scriptingScope.worldScreen = value } + +// var worldScreen: WorldScreen? +// get() = scriptingScope.worldScreen +// set(value) { scriptingScope.worldScreen = value } init { diff --git a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt index bafa57561d0c4..1367b08a09707 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt @@ -22,7 +22,15 @@ import java.util.UUID // See Module.md for a description of the protocol. // Please update the specifications there if you add, remove, or change any action types or packet structures here. +// To add a new packet type: +// Add to Module.md. +// Add to responseTypes. +// Add to makeActionRequests and parseActionResponses, or to makeActionResponse. +// Use in scripting backend, E.G. wrapping.py +//TODO: Hash request. + +//TODO: Remove "exception" packet fields, and instead unify error handling with exception flag. /** * Implementation of IPC packet specified in Module.md. @@ -91,7 +99,8 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = * @property value Serialized string value of this flag. */ enum class KnownFlag(val value: String) { - PassMic("PassMic")//TODO: Would getting rid of the string value work? I think I may have decided that having KotlinX Serialization implicitly coerce enums to/from strings would be worse than explicitly accessing the string even if raw enums do work. + PassMic("PassMic"),//TODO: Would getting rid of the string value work? I think I may have decided that having KotlinX Serialization implicitly coerce enums to/from strings would be worse than explicitly accessing the string even if raw enums do work. + Exception("Exception") } companion object { @@ -107,7 +116,7 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = * Map of valid request action types to their required response action types. */ val responseTypes = mapOf( - // Deliberately repeating myself because I don't want to imply a hard specification for the name of response packets. + // Deliberately repeating myself because I don't want to imply a hard specification for the names of response packets. "motd" to "motd_response", "autocomplete" to "autocomplete_response", "exec" to "exec_response", @@ -116,9 +125,11 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = "assign" to "assign_response", "delete" to "delete_response", "dir" to "dir_response", +// "hash" to "hash_response", "keys" to "keys_response", "length" to "length_response", "contains" to "contains_response", + "ismapping" to "ismapping_response", "callable" to "callable_response", "args" to "args_response", "docstring" to "docstring_response" @@ -229,28 +240,22 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = val action = ScriptingProtocol.responseTypes[packet.action]!! var data: JsonElement? = null val flags = mutableListOf() - var value: JsonElement = JsonNull - var exception: JsonElement = JsonNull when (packet.action) { // There's a lot of repetition here, because I don't want to enforce any specification on what form the request and response data fields for actions must take. // I prefer to try to keep the code for each response type independent enough to be readable on its own. // This is kinda the reference (and only) implementation of the protocol spec. So the serialization and such can be and is handled with functions, but the actual structure and logic of each response should be hardcoded manually IMO. "read" -> { try { - value = TokenizingJson.getJsonElement(trySaveInstance( + data = TokenizingJson.getJsonElement(trySaveInstance( Reflection.resolveInstancePath( scope, TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) ) )) } catch (e: Exception) { - value = JsonNull - exception = JsonPrimitive(stringifyException(e)) + data = JsonPrimitive(stringifyException(e)) + flags.add(KnownFlag.Exception.value) } - data = JsonObject(mapOf( - "value" to value, - "exception" to exception - )) } "assign" -> { try { @@ -260,9 +265,9 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = TokenizingJson.getJsonReal((packet.data as JsonObject)["value"]!!) ) } catch (e: Exception) { - exception = JsonPrimitive(stringifyException(e)) + data = JsonPrimitive(stringifyException(e)) + flags.add(KnownFlag.Exception.value) } - data = exception } "delete" -> { try { @@ -271,9 +276,9 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) ) } catch (e: Exception) { - exception = JsonPrimitive(stringifyException(e)) + data = JsonPrimitive(stringifyException(e)) + flags.add(KnownFlag.Exception.value) } - data = exception } "dir" -> { try { @@ -281,31 +286,35 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = scope, TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) ) - value = TokenizingJson.getJsonElement(leaf!!::class.members.map{it.name}) + data = TokenizingJson.getJsonElement(leaf!!::class.members.map{it.name}) } catch (e: Exception) { - value = JsonNull - exception = JsonPrimitive(stringifyException(e)) + data = JsonPrimitive(stringifyException(e)) + flags.add(KnownFlag.Exception.value) } - data = JsonObject(mapOf( - "value" to value, - "exception" to exception - )) } +// "hash" -> { +// try { +// val leaf = Reflection.resolveInstancePath( +// scope, +// TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) +// ) +// data = TokenizingJson.getJsonElement(leaf.hashCode()) +// } catch (e: Exception) { +// data = JsonPrimitive(stringifyException(e)) +// flags.add(KnownFlag.Exception.value) +// } +// } "keys" -> { try { val leaf = Reflection.resolveInstancePath( scope, TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) ) - value = TokenizingJson.getJsonElement((leaf as Map).keys) + data = TokenizingJson.getJsonElement((leaf as Map).keys) } catch (e: Exception) { - value = JsonNull - exception = JsonPrimitive(stringifyException(e)) + data = JsonPrimitive(stringifyException(e)) + flags.add(KnownFlag.Exception.value) } - data = JsonObject(mapOf( - "value" to value, - "exception" to exception - )) } "length" -> { try { @@ -314,23 +323,19 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) ) try { - value = TokenizingJson.getJsonElement((leaf as Array<*>).size) - // Once/If I get Reflection working with properties/Transients/whatever, I could replace these casts with a single reflective call. + data = TokenizingJson.getJsonElement((leaf as Array<*>).size) + // AFAICT avoiding these casts/checks would require reflection. } catch (e: Exception) { try { - value = TokenizingJson.getJsonElement((leaf as Map<*, *>).size) + data = TokenizingJson.getJsonElement((leaf as Map<*, *>).size) } catch (e: Exception) { - value = TokenizingJson.getJsonElement((leaf as Collection<*>).size) + data = TokenizingJson.getJsonElement((leaf as Collection<*>).size) } } } catch (e: Exception) { - value = JsonNull - exception = JsonPrimitive(stringifyException(e)) + data = JsonPrimitive(stringifyException(e)) + flags.add(KnownFlag.Exception.value) } - data = JsonObject(mapOf( - "value" to value, - "exception" to exception - )) } "contains" -> { try { @@ -340,18 +345,32 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = ) val _check = TokenizingJson.getJsonReal((packet.data as JsonObject)["value"]!!) try { - value = TokenizingJson.getJsonElement(_check in (leaf as Map)) + data = TokenizingJson.getJsonElement(_check in (leaf as Map)) + } catch (e: Exception) { + data = TokenizingJson.getJsonElement(_check in (leaf as Collection)) + } + } catch (e: Exception) { + data = JsonPrimitive(stringifyException(e)) + flags.add(KnownFlag.Exception.value) + } + } + "ismapping" -> { + try { + val leaf = Reflection.resolveInstancePath( + scope, + TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) + ) + try { + leaf as Map + //Ensure same behaviour as "keys" action. + data = TokenizingJson.getJsonElement(true) } catch (e: Exception) { - value = TokenizingJson.getJsonElement(_check in (leaf as Collection)) + data = TokenizingJson.getJsonElement(false) } } catch (e: Exception) { - value = JsonNull - exception = JsonPrimitive(stringifyException(e)) + data = JsonPrimitive(stringifyException(e)) + flags.add(KnownFlag.Exception.value) } - data = JsonObject(mapOf( - "value" to value, - "exception" to exception - )) } "callable" -> { try { @@ -359,15 +378,11 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = scope, TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) ) - value = TokenizingJson.getJsonElement(leaf is KCallable<*>) + data = TokenizingJson.getJsonElement(leaf is KCallable<*>) } catch (e: Exception) { - value = JsonNull - exception = JsonPrimitive(stringifyException(e)) + data = JsonPrimitive(stringifyException(e)) + flags.add(KnownFlag.Exception.value) } - data = JsonObject(mapOf( - "value" to value, - "exception" to exception - )) } "args" -> { try { @@ -375,15 +390,11 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = scope, TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) ) - value = TokenizingJson.getJsonElement((leaf as KCallable<*>).parameters.map { listOf(it.name.toString(), it.type.toString()) }) + data = TokenizingJson.getJsonElement((leaf as KCallable<*>).parameters.map { listOf(it.name.toString(), it.type.toString()) }) } catch (e: Exception) { - value = JsonNull - exception = JsonPrimitive(stringifyException(e)) + data = JsonPrimitive(stringifyException(e)) + flags.add(KnownFlag.Exception.value) } - data = JsonObject(mapOf( - "value" to value, - "exception" to exception - )) } "docstring" -> { try { @@ -391,15 +402,11 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = scope, TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) ) - value = TokenizingJson.getJsonElement(leaf.toString()) + data = TokenizingJson.getJsonElement(leaf.toString()) } catch (e: Exception) { - value = JsonNull - exception = JsonPrimitive(stringifyException(e)) + data = JsonPrimitive(stringifyException(e)) + flags.add(KnownFlag.Exception.value) } - data = JsonObject(mapOf( - "value" to value, - "exception" to exception - )) } else -> { throw IllegalArgumentException("Unknown action received in scripting request packet: ${packet.action}") diff --git a/core/src/com/unciv/scripting/reflection/Reflection.kt b/core/src/com/unciv/scripting/reflection/Reflection.kt index 075861eed90c5..0be626f5d782a 100644 --- a/core/src/com/unciv/scripting/reflection/Reflection.kt +++ b/core/src/com/unciv/scripting/reflection/Reflection.kt @@ -4,12 +4,16 @@ import com.unciv.scripting.utils.TokenizingJson import kotlin.collections.ArrayList import kotlin.reflect.KCallable import kotlin.reflect.KMutableProperty1 +//import kotlin.reflect.KParameter import kotlin.reflect.KProperty1 +//import kotlin.reflect.KType //import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import java.util.* +//TODO: Method dispatch to right methods? + object Reflection { @Suppress("UNCHECKED_CAST") fun readInstanceProperty(instance: Any, propertyName: String): R? { @@ -18,6 +22,39 @@ object Reflection { .first { it.name == propertyName } as KProperty1 return property.get(instance) as R? } +/* + class InstanceMethodDispatcher(val instance: Any, val methodName: String) { + val methods: List> by lazy { instance::class.members.filter{ it.name == methodName }.map{ it as KCallable } } + fun call(arguments: Array): Any? { + val matches = methods.filter { + val params = it.parameters + params.size == arguments.size + 1 + // Check that the parameters size is same as arguments size plus one for the receiver. + && (arguments zip params.slice(1..arguments.size)).all{ + (arg, kparam) -> + // Check that every argument can be cast to the expected type. + if (arg == null) + kparam.type.isMarkedNullable + else + kparam.type in arg::class.supertypes + // Not totally sure if these KTypes are singleton-like. Hopefully equality-comparison works for containment here even if they aren't? + } + } + if (matches.size < 1) { + throw IllegalArgumentException("No matching signatures found for calling ${instance::class?.simpleName}.${methodName} with given arguments: (${arguments.map{if (it == null) "null" else it::class?.simpleName ?: "null"}.joinToString(", ")})") + //FIXME: A lot of non-null assertions and null checks can probably be replaced with safe calls. + } + if (matches.size > 1) { + //I guess could also allow this, producing ambiguous behaviour and leaving it up to the methods to avoid creating such scenarios. Actually no, that sounds like a terrible idea reading it back. + //TODO: Should try to choose most specific signatures based on inheritance hierarchy. + throw IllegalArgumentException("Multiple matching signatures found for calling ${methodName}:\n\t${matches.map{it.toString()}.joinToString("\n\t")}") + } + return matches[0]!!.call( + instance, + *arguments + ) + } + }*/ fun readInstanceMethod(instance: Any, methodName: String): KCallable { val method = instance::class.members diff --git a/core/src/com/unciv/scripting/utils/InstanceTokenizer.kt b/core/src/com/unciv/scripting/utils/InstanceTokenizer.kt index e6de29485dda9..cd76a30987b26 100644 --- a/core/src/com/unciv/scripting/utils/InstanceTokenizer.kt +++ b/core/src/com/unciv/scripting/utils/InstanceTokenizer.kt @@ -47,7 +47,6 @@ object InstanceTokenizer { var stringified = value.toString() if (stringified.length > tokenMaxLength) { stringified = stringified.slice(0..tokenMaxLength-4) + "..." - println("Slicing.") } return "${tokenPrefix}${System.identityHashCode(value)}:${if (value == null) "null" else value::class.qualifiedName}/${stringified}:${UUID.randomUUID().toString()}" } @@ -65,6 +64,7 @@ object InstanceTokenizer { */ fun clean(): Unit { //FIXME (if I become a problem): Because a new unique token is currently generated even if the instance is already tokenized as something else, this will eventually get slower over time if a script makes lots of requests that result in new instance tokens for objects that last a long time (E.G. uncivGame). And since any stored instances should ideally be WeakReferences to prevent garbage collection from being broken for *all* instances, fixing that may not be as simple as checking for existing tokens to reuse them. + //TODO: Probably keep another map of instance hashes to weakrefs and their existing token? val badtokens = mutableListOf() for ((t, o) in instances) { if (o.get() == null) { diff --git a/core/src/com/unciv/scripting/utils/SourceManager.kt b/core/src/com/unciv/scripting/utils/SourceManager.kt index 9be86bc30ab6c..6adbf3b958484 100644 --- a/core/src/com/unciv/scripting/utils/SourceManager.kt +++ b/core/src/com/unciv/scripting/utils/SourceManager.kt @@ -49,6 +49,7 @@ object SourceManager { } Runtime.getRuntime().addShutdownHook( // Delete temporary directory on JVM shutdown, not on backend object destruction/termination. The copied files shouldn't be huge anyway, there's no reference to a ScriptingBackend() here, and I trust the shutdown hook to be run more reliably. + // I guess you could wrap the outdir folder handler in something with a .finalize(), then keep it around for the duration of each backend, if you wanted to clear scripting runtimes when they're no longer in use. May become more pressing if a modding API is implemented and it involves spinning up new ScriptingStates/ScriptingBackends for every loaded game, I guess. thread(start = false, name = "Delete ${outdir.toString()}.") { outdir.deleteDirectory() } diff --git a/core/src/com/unciv/scripting/utils/StringifyException.kt b/core/src/com/unciv/scripting/utils/StringifyException.kt index 3e0562529681a..60ebea90678b8 100644 --- a/core/src/com/unciv/scripting/utils/StringifyException.kt +++ b/core/src/com/unciv/scripting/utils/StringifyException.kt @@ -5,4 +5,20 @@ package com.unciv.scripting.utils * @param exception Any Exception. * @return String of exception preceded by entire stack trace. */ -fun stringifyException(exception: Exception): String = listOf(*exception.getStackTrace(), exception.toString()).joinToString("\n") +fun stringifyException(exception: Exception): String { + val causes = arrayListOf() + var cause: Throwable? = exception + while (cause != null) { + causes.add(cause) + cause = cause.cause + //I swear this is okay to do. + } + return listOf( + "\n", + *exception.getStackTrace(), + "\n", + *causes.asReversed().map{ it.toString() }.toTypedArray() + ).joinToString("\n") +} +// TODO: Recursively add .getcause too. +// TODO: Move this to ExtensionFunctions.kt. diff --git a/core/src/com/unciv/scripting/utils/TokenizingJson.kt b/core/src/com/unciv/scripting/utils/TokenizingJson.kt index 9985eef4eef1e..7934e659e450c 100644 --- a/core/src/com/unciv/scripting/utils/TokenizingJson.kt +++ b/core/src/com/unciv/scripting/utils/TokenizingJson.kt @@ -125,8 +125,6 @@ object TokenizingJson { } if (value is Sequence<*>) { var v = (value as Sequence).toList() - println(v) - println(v[0]!!::class.qualifiedName) return getJsonElement(v) } if (value is String) { diff --git a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt index 93913edd00852..c47abc3c96e13 100644 --- a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt @@ -23,21 +23,21 @@ import kotlin.math.min class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Unit): CameraStageBaseScreen() { private val layoutTable: Table = Table() - + private val topBar: Table = Table() private var backendsScroll: ScrollPane private val backendsAdders: Table = Table() private val closeButton: TextButton = Constants.close.toTextButton() - + private var middleSplit: SplitPane private var printScroll: ScrollPane private val printHistory: Table = Table() private val runningContainer: Table = Table() private val runningList: Table = Table() - + private val inputBar: Table = Table() private val inputField: TextField = TextField("", skin) - + private val inputControls: Table = Table() private val tabButton: TextButton = "TAB".toTextButton() private val upButton: Image = ImageGetter.getImage("OtherIcons/Up") @@ -50,7 +50,7 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un var inputText: String get() = inputField.text set(value: String) { inputField.setText(value) } - + var cursorPos: Int get() = inputField.getCursorPosition() set(value: Int) { @@ -58,7 +58,7 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un } init { - + backendsAdders.add("Launch new backend:".toLabel()).padRight(30f).padLeft(20f) for (backendtype in ScriptingBackendType.values()) { var backendadder = backendtype.metadata.displayName.toTextButton() @@ -69,86 +69,86 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un backendsAdders.add(backendadder) } backendsScroll = ScrollPane(backendsAdders) - + backendsAdders.left() - + val cell_backendsScroll = topBar.add(backendsScroll) layoutUpdators.add( { cell_backendsScroll.minWidth(stage.width - closeButton.getPrefWidth()) } ) topBar.add(closeButton) - + printHistory.left() printHistory.bottom() printScroll = ScrollPane(printHistory) - + runningContainer.add("Active Backends:".toLabel()).row() runningContainer.add(runningList) - + middleSplit = SplitPane(printScroll, runningContainer, false, skin) middleSplit.setSplitAmount(0.8f) - + inputControls.add(tabButton) inputControls.add(upButton.surroundWithCircle(40f)) inputControls.add(downButton.surroundWithCircle(40f)) inputControls.add(runButton) - + val cell_inputField = inputBar.add(inputField) layoutUpdators.add( { cell_inputField.minWidth(stage.width - inputControls.getPrefWidth()) } ) inputBar.add(inputControls) - + layoutUpdators.add( { layoutTable.setSize(stage.width, stage.height) } ) - + val cell_topBar = layoutTable.add(topBar) layoutUpdators.add( { cell_topBar.minWidth(stage.width) } ) cell_topBar.row() - + val cell_middleSplit = layoutTable.add(middleSplit) layoutUpdators.add( { cell_middleSplit.minWidth(stage.width).minHeight(stage.height - topBar.getPrefHeight() - inputBar.getPrefHeight()) } ) cell_middleSplit.row() - + layoutTable.add(inputBar) - + runButton.onClick({ run() }) keyPressDispatcher[Input.Keys.ENTER] = { run() } keyPressDispatcher[Input.Keys.NUMPAD_ENTER] = { run() } - + tabButton.onClick({ autocomplete() }) keyPressDispatcher[Input.Keys.TAB] = { autocomplete() } - + upButton.onClick({ navigateHistory(1) }) keyPressDispatcher[Input.Keys.UP] = { navigateHistory(1) } downButton.onClick({ navigateHistory(-1) }) keyPressDispatcher[Input.Keys.DOWN] = { navigateHistory(-1) } - + onBackButtonClicked({ closeConsole() }) closeButton.onClick({ closeConsole() }) - + updateLayout() - + stage.addActor(layoutTable) - + echoHistory() - + updateRunning() } - + fun updateLayout() { for (func in layoutUpdators) { func() } } - + fun openConsole() { game.setScreen(this) keyPressDispatcher.install(stage) this.isOpen = true } - + fun closeConsole() { closeAction() keyPressDispatcher.uninstall() this.isOpen = false } - + private fun updateRunning() { runningList.clearChildren() var i = 0 @@ -175,11 +175,11 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un i += 1 } } - + private fun clear() { printHistory.clearChildren() } - + fun setText(text: String, cursormode: SetTextCursorMode=SetTextCursorMode.End) { val originaltext = inputText val originalcursorpos = cursorPos @@ -192,21 +192,21 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un (SetTextCursorMode.SelectAfter) -> { throw UnsupportedOperationException("NotImplemented.") } } } - + fun setScroll(x: Float, y: Float, animate: Boolean = true) { printScroll.scrollTo(x, y, 1f, 1f) if (!animate) { printScroll.updateVisualScroll() } } - + private fun echoHistory() { // Doesn't restore autocompletion. I guess that's by design. Autocompletion is a protocol/UI-level feature IMO, and not part of the emulated STDIN/STDOUT. Call `echo()` in `ScriptingState`'s `autocomplete` method if that's a problem. for (hist in scriptingState.getOutputHistory()) { echo(hist) } } - + private fun autocomplete() { val original = inputText val cursorpos = cursorPos @@ -239,11 +239,11 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un // Splice the longest starting substring with the text after the cursor, to let autocomplete implementations work on the middle of current input. } } - + private fun navigateHistory(increment:Int) { setText(scriptingState.navigateHistory(increment), SetTextCursorMode.End) } - + private fun echo(text: String) { val label = Label(text, skin) val width = stage.width * 0.75f @@ -260,12 +260,12 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un printHistory.invalidate() setScroll(0f,0f) } - + private fun run() { echo(scriptingState.exec(inputText)) setText("") } - + fun clone(): ConsoleScreen { return ConsoleScreen(scriptingState, closeAction).also { it.inputText = inputText @@ -273,7 +273,7 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un it.setScroll(printScroll.getScrollX(), printScroll.getScrollY(), animate = false) } } - + override fun resize(width: Int, height: Int) { if (stage.viewport.screenWidth != width || stage.viewport.screenHeight != height) { // Right. Actually resizing seems painful. game.consoleScreen = clone() @@ -283,7 +283,7 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un } } } - + enum class SetTextCursorMode() { End(), Unchanged(), diff --git a/core/src/com/unciv/ui/consolescreen/IConsoleScreenAccessible.kt b/core/src/com/unciv/ui/consolescreen/IConsoleScreenAccessible.kt new file mode 100644 index 0000000000000..1896d2d4400f6 --- /dev/null +++ b/core/src/com/unciv/ui/consolescreen/IConsoleScreenAccessible.kt @@ -0,0 +1,54 @@ +package com.unciv.ui.consolescreen + +import com.badlogic.gdx.Input +import com.unciv.logic.GameInfo +import com.unciv.logic.civilization.CivilizationInfo +import com.unciv.scripting.ScriptingState +import com.unciv.ui.consolescreen.ConsoleScreen +import com.unciv.ui.utils.CameraStageBaseScreen +import com.unciv.ui.worldscreen.WorldScreen +import com.unciv.ui.mapeditor.MapEditorScreen + +//Interface that extends CameraStageBaseScreen with methods for exposing the global ConsoleScreen. +interface IConsoleScreenAccessible { + + val CameraStageBaseScreen.consoleScreen: ConsoleScreen + get() = this.game.consoleScreen + + val CameraStageBaseScreen.scriptingState: ScriptingState + get() = this.game.scriptingState + + + //Set the console screen tilde hotkey. + fun CameraStageBaseScreen.setOpenConsoleScreenHotkey() { + this.keyPressDispatcher[Input.Keys.GRAVE] = { this.game.setConsoleScreen() } + } + + //Set the console screen to return to the right screen when closed. + + //Defaults to setting the game's screen to this instance. Can also use a lambda, for E.G. WorldScreen and UncivGame.setWorldScreen(). + fun CameraStageBaseScreen.setConsoleScreenCloseAction(closeAction: (() -> Unit)? = null) { + this.consoleScreen.closeAction = closeAction ?: { this.game.setScreen(this) } + } + + //Extension method to update scripting API scope variables that are expected to change over the lifetime of a ScriptingState. + + //Unprovided arguments default to null. This way, screens inheriting this interface don't need to explicitly clear everything they don't have. They only need to provide what they do have. + + //@param gameInfo Active GameInfo. + //@param civInfo Active CivilizationInfo. + //@param worldScreen Active WorldScreen. + fun CameraStageBaseScreen.updateScriptingState( + gameInfo: GameInfo? = null, + civInfo: CivilizationInfo? = null, + worldScreen: WorldScreen? = null, + mapEditorScreen: MapEditorScreen? = null + ) { + this.scriptingState.scriptingScope.also { + it.gameInfo = gameInfo + it.civInfo = civInfo + it.worldScreen = worldScreen + it.mapEditorScreen = mapEditorScreen + } // .apply errors on compile with "val cannot be reassigned". + } +} diff --git a/core/src/com/unciv/ui/mapeditor/MapEditorScreen.kt b/core/src/com/unciv/ui/mapeditor/MapEditorScreen.kt index 6b7a2438bc9be..1514c24a66486 100644 --- a/core/src/com/unciv/ui/mapeditor/MapEditorScreen.kt +++ b/core/src/com/unciv/ui/mapeditor/MapEditorScreen.kt @@ -16,9 +16,10 @@ import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.RulesetCache import com.unciv.models.metadata.GameSetupInfo import com.unciv.models.translations.tr +import com.unciv.ui.consolescreen.IConsoleScreenAccessible import com.unciv.ui.utils.* -class MapEditorScreen(): BaseScreen() { +class MapEditorScreen(): BaseScreen(), IConsoleScreenAccessible { var mapName = "" var tileMap = TileMap() var ruleset = Ruleset().apply { add(RulesetCache.getBaseRuleset()) } @@ -140,6 +141,12 @@ class MapEditorScreen(): BaseScreen() { isPainting = false } }) + + setOpenConsoleScreenHotkey() + setConsoleScreenCloseAction() + updateScriptingState( + mapEditorScreen = this + ) } private fun checkAndFixMapSize() { diff --git a/core/src/com/unciv/ui/utils/ExtensionFunctions.kt b/core/src/com/unciv/ui/utils/ExtensionFunctions.kt index d611665c6d545..3deb2739e485e 100644 --- a/core/src/com/unciv/ui/utils/ExtensionFunctions.kt +++ b/core/src/com/unciv/ui/utils/ExtensionFunctions.kt @@ -3,6 +3,8 @@ package com.unciv.ui.utils import com.badlogic.gdx.Gdx import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.scenes.scene2d.* +import com.badlogic.gdx.graphics.Pixmap +import com.badlogic.gdx.graphics.g2d.TextureRegion import com.badlogic.gdx.scenes.scene2d.ui.* import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener import com.badlogic.gdx.scenes.scene2d.utils.ClickListener @@ -127,7 +129,7 @@ fun Table.addSeparator(color: Color = Color.WHITE, colSpan: Int = 0, height: Flo /** * Create a vertical separator as an empty Container with a colored background. - * + * * Note: Unlike the horizontal [addSeparator] this cannot automatically span several rows. Repeat the separator if needed. */ fun Table.addSeparatorVertical(color: Color = Color.WHITE, width: Float = 2f): Cell { @@ -237,11 +239,11 @@ fun String.toLabel(fontColor: Color = Color.WHITE, fontSize: Int = 18): Label { fun String.toCheckBox(startsOutChecked: Boolean = false, changeAction: ((Boolean)->Unit)? = null) = CheckBox(this.tr(), BaseScreen.skin).apply { isChecked = startsOutChecked - if (changeAction != null) onChange { + if (changeAction != null) onChange { changeAction(isChecked) } // Add a little distance between the icon and the text. 0 looks glued together, - // 5 is about half an uppercase letter, and 1 about the width of the vertical line in "P". + // 5 is about half an uppercase letter, and 1 about the width of the vertical line in "P". imageCell.padRight(1f) } @@ -288,6 +290,37 @@ fun List.randomWeighted(weights: List, random: Random = Random): T return this.last() } +/** + * Turn a TextureRegion into a Pixmap. + * + * Originally a function defined in the Fonts Object and used only in Fonts.kt. + * Turned into an extension method to 1. Clean up the syntax and 2. Use it to expose internal, packed images to scripts in class ScriptingScope. + * + * @return New Pixmap with all the size and pixel data from this TextureRegion copied into it. + */ +// From https://stackoverflow.com/questions/29451787/libgdx-textureregion-to-pixmap +fun TextureRegion.toPixmap(): Pixmap { + val textureData = this.texture.textureData + if (!textureData.isPrepared) { + textureData.prepare() + } + val pixmap = Pixmap( + this.regionWidth, + this.regionHeight, + textureData.format + ) + pixmap.drawPixmap( + textureData.consumePixmap(), // The other Pixmap + 0, // The target x-coordinate (top left corner) + 0, // The target y-coordinate (top left corner) + this.regionX, // The source x-coordinate (top left corner) + this.regionY, // The source y-coordinate (top left corner) + this.regionWidth, // The width of the area from the other Pixmap in pixels + this.regionHeight // The height of the area from the other Pixmap in pixels + ) + return pixmap +} + /** * Standardize date formatting so dates are presented in a consistent style and all decisions * to change date handling are encapsulated here diff --git a/core/src/com/unciv/ui/utils/Fonts.kt b/core/src/com/unciv/ui/utils/Fonts.kt index 2b26efa7d5355..6600a85dbbe18 100644 --- a/core/src/com/unciv/ui/utils/Fonts.kt +++ b/core/src/com/unciv/ui/utils/Fonts.kt @@ -86,22 +86,22 @@ class NativeBitmapFontData( private fun getPixmapFromChar(ch: Char): Pixmap { // Images must be 50*50px so they're rendered at the same height as the text - see Fonts.ORIGINAL_FONT_SIZE return when (ch) { - Fonts.strength -> Fonts.extractPixmapFromTextureRegion(ImageGetter.getDrawable("StatIcons/Strength").region) - Fonts.rangedStrength -> Fonts.extractPixmapFromTextureRegion(ImageGetter.getDrawable("StatIcons/RangedStrength").region) - Fonts.range -> Fonts.extractPixmapFromTextureRegion(ImageGetter.getDrawable("StatIcons/Range").region) - Fonts.movement -> Fonts.extractPixmapFromTextureRegion(ImageGetter.getDrawable("StatIcons/Movement").region) - Fonts.turn -> Fonts.extractPixmapFromTextureRegion(ImageGetter.getDrawable("EmojiIcons/Turn").region) - Fonts.production -> Fonts.extractPixmapFromTextureRegion(ImageGetter.getDrawable("EmojiIcons/Production").region) - Fonts.gold -> Fonts.extractPixmapFromTextureRegion(ImageGetter.getDrawable("EmojiIcons/Gold").region) - Fonts.food -> Fonts.extractPixmapFromTextureRegion(ImageGetter.getDrawable("EmojiIcons/Food").region) - Fonts.science -> Fonts.extractPixmapFromTextureRegion(ImageGetter.getDrawable("EmojiIcons/Science").region) - Fonts.culture -> Fonts.extractPixmapFromTextureRegion(ImageGetter.getDrawable("EmojiIcons/Culture").region) - Fonts.faith -> Fonts.extractPixmapFromTextureRegion(ImageGetter.getDrawable("EmojiIcons/Faith").region) - Fonts.happiness -> Fonts.extractPixmapFromTextureRegion(ImageGetter.getDrawable("EmojiIcons/Happiness").region) - MayaCalendar.tun -> Fonts.extractPixmapFromTextureRegion(ImageGetter.getDrawable(MayaCalendar.tunIcon).region) - MayaCalendar.katun -> Fonts.extractPixmapFromTextureRegion(ImageGetter.getDrawable(MayaCalendar.katunIcon).region) - MayaCalendar.baktun -> Fonts.extractPixmapFromTextureRegion(ImageGetter.getDrawable(MayaCalendar.baktunIcon).region) - in MayaCalendar.digits -> Fonts.extractPixmapFromTextureRegion(ImageGetter.getDrawable(MayaCalendar.digitIcon(ch)).region) + Fonts.strength -> ImageGetter.getDrawable("StatIcons/Strength").region.toPixmap() + Fonts.rangedStrength -> ImageGetter.getDrawable("StatIcons/RangedStrength").region.toPixmap() + Fonts.range -> ImageGetter.getDrawable("StatIcons/Range").region.toPixmap() + Fonts.movement -> ImageGetter.getDrawable("StatIcons/Movement").region.toPixmap() + Fonts.turn -> ImageGetter.getDrawable("EmojiIcons/Turn").region.toPixmap() + Fonts.production -> ImageGetter.getDrawable("EmojiIcons/Production").region.toPixmap() + Fonts.gold -> ImageGetter.getDrawable("EmojiIcons/Gold").region.toPixmap() + Fonts.food -> ImageGetter.getDrawable("EmojiIcons/Food").region.toPixmap() + Fonts.science -> ImageGetter.getDrawable("EmojiIcons/Science").region.toPixmap() + Fonts.culture -> ImageGetter.getDrawable("EmojiIcons/Culture").region.toPixmap() + Fonts.faith -> ImageGetter.getDrawable("EmojiIcons/Faith").region.toPixmap() + Fonts.happiness -> ImageGetter.getDrawable("EmojiIcons/Happiness").region.toPixmap() + MayaCalendar.tun -> ImageGetter.getDrawable(MayaCalendar.tunIcon).region.toPixmap() + MayaCalendar.katun -> ImageGetter.getDrawable(MayaCalendar.katunIcon).region.toPixmap() + MayaCalendar.baktun -> ImageGetter.getDrawable(MayaCalendar.baktunIcon).region.toPixmap() + in MayaCalendar.digits -> ImageGetter.getDrawable(MayaCalendar.digitIcon(ch)).region.toPixmap() else -> fontImplementation.getCharPixmap(ch) } } @@ -135,29 +135,6 @@ object Fonts { font.setOwnsTexture(true) } - // From https://stackoverflow.com/questions/29451787/libgdx-textureregion-to-pixmap - fun extractPixmapFromTextureRegion(textureRegion:TextureRegion):Pixmap { - val textureData = textureRegion.texture.textureData - if (!textureData.isPrepared) { - textureData.prepare() - } - val pixmap = Pixmap( - textureRegion.regionWidth, - textureRegion.regionHeight, - textureData.format - ) - pixmap.drawPixmap( - textureData.consumePixmap(), // The other Pixmap - 0, // The target x-coordinate (top left corner) - 0, // The target y-coordinate (top left corner) - textureRegion.regionX, // The source x-coordinate (top left corner) - textureRegion.regionY, // The source y-coordinate (top left corner) - textureRegion.regionWidth, // The width of the area from the other Pixmap in pixels - textureRegion.regionHeight // The height of the area from the other Pixmap in pixels - ) - return pixmap - } - const val turn = '⏳' // U+23F3 'hourglass' const val strength = '†' // U+2020 'dagger' const val rangedStrength = '‡' // U+2021 'double dagger' diff --git a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt index 440034080e51f..3c2f40c8142fd 100644 --- a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt +++ b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt @@ -25,10 +25,9 @@ import com.unciv.models.Tutorial import com.unciv.models.UncivSound import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.translations.tr -import com.unciv.scripting.ScriptingState import com.unciv.ui.cityscreen.CityScreen import com.unciv.ui.civilopedia.CivilopediaScreen -import com.unciv.ui.consolescreen.ConsoleScreen +import com.unciv.ui.consolescreen.IConsoleScreenAccessible import com.unciv.ui.overviewscreen.EmpireOverviewScreen import com.unciv.ui.pickerscreens.* import com.unciv.ui.saves.LoadGameScreen @@ -57,7 +56,7 @@ import kotlin.concurrent.timer * @property mapHolder A [MinimapHolder] instance * @property bottomUnitTable Bottom left widget holding information about a selected unit or city */ -class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : BaseScreen() { +class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : BaseScreen(), IConsoleScreenAccessible { var isPlayersTurn = viewingCiv == gameInfo.currentPlayerCiv private set // only this class is allowed to make changes @@ -88,12 +87,6 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas private val notificationsScroll: NotificationsScroll var shouldUpdate = false - private val consoleScreen: ConsoleScreen - get() = game.consoleScreen - - private val scriptingState: ScriptingState - get() = game.scriptingState - companion object { /** Switch for console logging of next turn duration */ private const val consoleLog = false @@ -200,11 +193,12 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas // know what the viewing civ is. shouldUpdate = true - consoleScreen.closeAction = { game.setWorldScreen() } - scriptingState.gameInfo = gameInfo - scriptingState.civInfo = selectedCiv - scriptingState.worldScreen = this - + setConsoleScreenCloseAction({ game.setWorldScreen() }) + updateScriptingState( + gameInfo = gameInfo, + civInfo = selectedCiv, + worldScreen = this + ) } private fun stopMultiPlayerRefresher() { @@ -253,7 +247,7 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas } // Space and N are assigned in createNextTurnButton - keyPressDispatcher[Input.Keys.GRAVE] = { game.setConsoleScreen() } // CLI console + setOpenConsoleScreenHotkey() // CLI console keyPressDispatcher[Input.Keys.F1] = { game.setScreen(CivilopediaScreen(gameInfo.ruleSet, this)) } keyPressDispatcher['E'] = { game.setScreen(EmpireOverviewScreen(selectedCiv)) } // Empire overview last used page /* diff --git a/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt b/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt index c056eef45de25..f9e2e3d93baa1 100644 --- a/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt +++ b/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt @@ -250,6 +250,8 @@ class OptionsPopup(val previousScreen: BaseScreen) : Popup(previousScreen) { settings.enableScriptingConsole) { settings.enableScriptingConsole = it } + //TODO: Persist command history. + //TODO: Startup macros per backend type. if (previousScreen.game.limitOrientationsHelper != null) { addYesNoRow("Enable portrait orientation", settings.allowAndroidPortrait) { From 9af0976178e13cbdaf2efd1fe03c5ddcc734746a Mon Sep 17 00:00:00 2001 From: will-ca Date: Fri, 19 Nov 2021 20:30:07 +0000 Subject: [PATCH 44/93] Rename "CameraStageBaseScreen" to "BaseScreen" in ConsoleScreen classes. --- .../com/unciv/ui/consolescreen/ConsoleScreen.kt | 2 +- .../ui/consolescreen/IConsoleScreenAccessible.kt | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt index c47abc3c96e13..6e40184932a61 100644 --- a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt @@ -20,7 +20,7 @@ import kotlin.math.max import kotlin.math.min -class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Unit): CameraStageBaseScreen() { +class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Unit): BaseScreen() { private val layoutTable: Table = Table() diff --git a/core/src/com/unciv/ui/consolescreen/IConsoleScreenAccessible.kt b/core/src/com/unciv/ui/consolescreen/IConsoleScreenAccessible.kt index 1896d2d4400f6..5a9d4bbe4d4f0 100644 --- a/core/src/com/unciv/ui/consolescreen/IConsoleScreenAccessible.kt +++ b/core/src/com/unciv/ui/consolescreen/IConsoleScreenAccessible.kt @@ -5,29 +5,29 @@ import com.unciv.logic.GameInfo import com.unciv.logic.civilization.CivilizationInfo import com.unciv.scripting.ScriptingState import com.unciv.ui.consolescreen.ConsoleScreen -import com.unciv.ui.utils.CameraStageBaseScreen +import com.unciv.ui.utils.BaseScreen import com.unciv.ui.worldscreen.WorldScreen import com.unciv.ui.mapeditor.MapEditorScreen -//Interface that extends CameraStageBaseScreen with methods for exposing the global ConsoleScreen. +//Interface that extends BaseScreen with methods for exposing the global ConsoleScreen. interface IConsoleScreenAccessible { - val CameraStageBaseScreen.consoleScreen: ConsoleScreen + val BaseScreen.consoleScreen: ConsoleScreen get() = this.game.consoleScreen - val CameraStageBaseScreen.scriptingState: ScriptingState + val BaseScreen.scriptingState: ScriptingState get() = this.game.scriptingState //Set the console screen tilde hotkey. - fun CameraStageBaseScreen.setOpenConsoleScreenHotkey() { + fun BaseScreen.setOpenConsoleScreenHotkey() { this.keyPressDispatcher[Input.Keys.GRAVE] = { this.game.setConsoleScreen() } } //Set the console screen to return to the right screen when closed. //Defaults to setting the game's screen to this instance. Can also use a lambda, for E.G. WorldScreen and UncivGame.setWorldScreen(). - fun CameraStageBaseScreen.setConsoleScreenCloseAction(closeAction: (() -> Unit)? = null) { + fun BaseScreen.setConsoleScreenCloseAction(closeAction: (() -> Unit)? = null) { this.consoleScreen.closeAction = closeAction ?: { this.game.setScreen(this) } } @@ -38,7 +38,7 @@ interface IConsoleScreenAccessible { //@param gameInfo Active GameInfo. //@param civInfo Active CivilizationInfo. //@param worldScreen Active WorldScreen. - fun CameraStageBaseScreen.updateScriptingState( + fun BaseScreen.updateScriptingState( gameInfo: GameInfo? = null, civInfo: CivilizationInfo? = null, worldScreen: WorldScreen? = null, From e01b597e040d0f13db9bd4919a43a21faf240249 Mon Sep 17 00:00:00 2001 From: will-ca Date: Fri, 19 Nov 2021 21:29:25 +0000 Subject: [PATCH 45/93] Remove code disabled in last commit. --- .../enginefiles/python/unciv_lib/wrapping.py | 7 +- .../MapEditingMacros.py | 71 +------------------ core/Module.md | 2 +- .../src/com/unciv/scripting/ScriptingState.kt | 22 ------ .../scripting/protocol/ScriptingProtocol.kt | 4 +- 5 files changed, 5 insertions(+), 101 deletions(-) diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py index 3b65c78413f26..28f6c6de07d51 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py @@ -68,12 +68,6 @@ def foreignValueParser(packet, *, raise_exceptions=True): raise ipc.ForeignError(packet.data) return packet.data -# def foreignErrmsgChecker(packet): - # """Value parser that processes a foreign request packet fitting a simple structure.""" - # if packet.data is not None: - # raise ipc.ForeignError(packet.data) -#Redundant since replacing 'exception' key in data field with 'Exception' in flags field. - def makePathElement(ttype='Property', name='', params=()): assert ttype in ('Property', 'Key', 'Call'), f"{repr(ttype)} not a valid path element type." @@ -301,6 +295,7 @@ def __dir__(self): # }, # 'hash_response', # foreignValueParser) + # Implemented and works, but disabled for now. See ScriptingProtocol.kt and Module.md. @ForeignRequestMethod def __call__(self, *args): return ({ diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py index 607636e03442f..bb0966e369d12 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py @@ -25,7 +25,7 @@ terrainbases = {t['name']: t for t in terrainsjson if t['type'] in ('Water', 'Land')} terrainfeatures = {t['name']: t for t in terrainsjson if t['type'] == 'TerrainFeature'} -# del terrainfeatures['Fallout'] + def genValidTerrains(*, forbid=('Fallout',)): #Searches only two layers deep. I.E. Only combinations with at most two TerrainFeatures will be found. @@ -82,16 +82,6 @@ def genValidTerrains(*, forbid=('Fallout',)): "Snow/Ice" ) -#[(setattr(t, "baseTerrain", "Mountain") if real(t) else None) for r in gameInfo.tileMap.tileMatrix for t in r] -#Apparently there's no distinction between base terrain types and terrain features. -#[([setattr(t, "baseTerrain", "Grassland" if t.position.x % 2 else "Coast"), setattr(t, "naturalWonder", None if t.position.y%3 else "Krakatoa")] if real(t) else None) for r in gameInfo.tileMap.tileMatrix for t in r] - - -# def hexCoordToRectCoord(vector): - # pass - -# def rectCoordToHexCoord(vector): - # pass def indexClipped(items, index): return items[max(0, min(len(items)-1, index))] @@ -125,12 +115,6 @@ def setTerrain(tileInfo, terraintype): tileInfo.terrainFeatures.clear() for f in features: tileInfo.terrainFeatures.add(f) - #setTerrain(mapEditorScreen.tileMap.values[0], "") - - -# def checkValidValue(value, checktype="TileType"): - # if value not in unciv.mapEditorScreen.ruleset.whatever: - # raise Exception() def spreadResources(resourcetype="Horses", mode="random", restrictfrom=(), restrictto=None): @@ -143,7 +127,6 @@ def spreadResources(resourcetype="Horses", mode="random", restrictfrom=(), restr def dilateTileTypes(tiletypes=("Coast", "Flood Plains"), chance=1.0, forbidreplace=("Ocean", "Mountain"), dilateas=("Desert/Flood Plains", "Coast"), iterations=1): - # .terrainFeatures and .baseTerrain pass def erodeTileType(tiletypes=("Mountains", "Plains/Hill", "Grassland/Hill")): @@ -155,15 +138,12 @@ def floodFillSelected(start=None, fillas=None, *, alsopropagateto=()): mandlebrotpresets = { "Minibrot": {'center': (-0.105, -0.925), 'viewport': 0.006, 'indexer': "round((1/(i/8+1))*len(terrains))"}, - # "Minibrot2": {'center': (-1.786440683703552459, 0), 'viewport': 5E-8}, - # "cruciform": {'center': (-1.7859974480735000169, -0.00021290140715606698619), 'viewport': 9.1739918430357151341e-07, 'iterations': 900}, "Hat": {'center': (-1.301, -0.063), 'viewport': 2E-2, 'iterations': 300, 'indexer': "round(i/300*len(terrains))"}, "TwinLakes": {'center': (-1.4476, -0.0048), 'viewport': 1E-3, 'iterations': 50, 'indexer': "round(i/200*len(terrains))"}, "Curly": {'center': (-0.221, -0.651), 'viewport': 6E-3, 'iterations': 70, 'indexer': "round((1/(i/6+1))*len(terrains))"}, "Crater": {'center': (-1.447858, -0.004673), 'viewport': 3.5E-5, 'iterations': 80, 'indexer': "round((1-1/(i+1))*len(terrains))"}, "Rift": {'center': (-0.700, -0.295), 'viewport': 3E-3, 'iterations': 100}, "Spiral": {'center': (-0.676, -0.362), 'viewport': 3E-3, 'iterations': 100, 'indexer': "round((1-1/(i+1))*len(terrains))"}, - # "": {'center': (), 'viewport': , 'iterations': }, "Pentabrot": {'expo': 6} } @@ -205,9 +185,6 @@ def coordsfromtile(tile): ) -#from unciv_scripting_examples.MapEditingMacros import * - - def graph2D(tileMap=None, expr="sin(x/3)*5", north="Ocean", south="Desert"): tileMap = defaultArgTilemap(tileMap) expr = compile(expr, filename="expr", mode='eval') @@ -269,16 +246,6 @@ def loadImageHeightmap(tileMap=None, imagepath="EarthTopography.png", transform= imagepath = _fallbackpath print(f"Invalid image path given. Interpreting as example path at {repr(imagepath)}") del _fallbackpath - # if imagepath is None: - # imagepath = os.path.join(os.path.dirname(__file__), "EarthTopography.png") - #https://visibleearth.nasa.gov/images/73934/topography - #https://visibleearth.nasa.gov/images/73963/bathymetry - #NASA stuff is usually public domain by law. - # longitudes, latitudes = ([real(getattr(t, a)) for t in tileMap.values] for a in ('longitude', 'latitude')) - # min_long, max_long, min_lat, max_lat = (f(c) for c in (longitudes, latitudes) for f in (min, max)) - # del longitudes, latitudes - # width = max_long - min_long - # height = max_lat - min_lat transform = compile(transform, filename="transform", mode='eval') def pixinterp(pixel): @@ -290,25 +257,6 @@ def pixinterp(pixel): with PIL.Image.open(imagepath) as image: setMapFromImage(tileMap=tileMap, image=image, pixelinterpreter=pixinterp) - # width_fac = (image.size[0] - 1) / width - # height_fac = (image.size[1] - 1) / height - # for tile in tileMap.values: - # x = round((-tile.longitude + max_long) * width_fac) - # y = round((-tile.latitude + max_lat) * height_fac) - # print(x, y) - # v = image.getpixel((x, y)) - # if isinstance(v, tuple): - # v = sum(v)/len(v) - # v /= normalizevalues - # v = round(eval(transform, {'v': v, 'terrains': terrains})) - # print(v) - # setTerrain( - # tile, - # indexClipped( - # terrains, - # v - # ) - # ) @@ -324,7 +272,6 @@ def compositedTerrainImage(terrain): with PIL.Image.open(io.BytesIO(base64.b64decode(unciv.apiHelpers.assetImageB64(terrainImagePath(feature))))) as layer: image.alpha_composite(layer, (0, image.size[1]-layer.size[1])) return image - #compositedTerrainImage("Desert/Hill,Jungle").show() def getImageAverageRgb(image): #image.convert('P', palette=PIL.Image.ADAPTIVE, colors=1) @@ -355,20 +302,6 @@ def terraincol(terrain): return {terrain: tuple(round(n) for n in terraincol(terrain)) for terrain in terrains} -# _terraincolours = None - -# def _computeTerrainColours(terraintypes): - # global _terraincolours - # #This will requires some kind of access to GDX internal files. - -# def requireComputedColours(terraintypes, allowcompute=False): - # global _ - # if not _ or not all(t in _ for t in terraintypes): - # if allow_compute_colours: - # pass - # else: - # print(f"This function requires the average colour to be computed for the following terrain types:\n{terraintypes}\n\nDoing so may take a long time. The interface will be unresponsive during the process.\nPass `allow_compute_colours=True` to this function, or run `_computeTerrainColours({terraintypes})`, in order to compute the required colours.") - class _TerrainColourInterpreter: # To actually look good, this should use CIE, YUV, or at least HSV with a compressed saturation axis. def __init__(self, terraincolours, maxdither=0): @@ -387,7 +320,7 @@ def get_terraindithered(self, rgb): for i, (c_target, c_final, error_current) in enumerate(zip(rgb, rgb_final, self.dithererror)): self.dithererror[i] = max(-self.maxdither*256, min(self.maxdither*256, error_current + (c_final - c_target))) #Because the "colour palette" is usually very limited in range, and particularly because it often doesn't have any low-green values to bring the green error down (I.E. the mean channel value over the whole image may well be darker than the minimum available colour), limiting the maximum accumulatable error is necessary to avoid it running away. - # print(self.dithererror) + # print(self.dithererror) # This should generally tend back towards [0,0,0]. return terrain def __call__(self, pixel): if self.maxdither: diff --git a/core/Module.md b/core/Module.md index 8d62961f530ac..cc1dae0da165a 100644 --- a/core/Module.md +++ b/core/Module.md @@ -275,7 +275,7 @@ Some action types, data formats, and expected response types and data formats fo //'isiterable': {'path': List<{'type':String, 'name':String, 'params':List}>} -> //'isiterable_response': Boolean or String //Response must be String if sent with Exception flag. - //Not implemented. + //Not implemented. Implement if needed. ``` ``` diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index 440d27abec092..eb00566d673ae 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -55,28 +55,6 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr var activeCommandHistory: Int = 0 // Actually inverted, because history items are added to end of list and not start. 0 means nothing, 1 means most recent command at end of list. - //Wrapper for property of scriptingScope. -// var civInfo: CivilizationInfo? -// get() = scriptingScope.civInfo -// set(value) { scriptingScope.civInfo = value } - -// var gameInfo: GameInfo? -// get() = scriptingScope.gameInfo -// set(value) { scriptingScope.gameInfo = value } - -// var uncivGame: UncivGame? -// get() = scriptingScope.uncivGame -// set(value) { scriptingScope.uncivGame = value } - -// var worldScreen: WorldScreen? -// get() = scriptingScope.worldScreen -// set(value) { scriptingScope.worldScreen = value } - -// var worldScreen: WorldScreen? -// get() = scriptingScope.worldScreen -// set(value) { scriptingScope.worldScreen = value } - - init { if (initialBackendType != null) { echo(spawnBackend(initialBackendType)) diff --git a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt index 1367b08a09707..a429f034f0d51 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt @@ -28,9 +28,6 @@ import java.util.UUID // Add to makeActionRequests and parseActionResponses, or to makeActionResponse. // Use in scripting backend, E.G. wrapping.py -//TODO: Hash request. - -//TODO: Remove "exception" packet fields, and instead unify error handling with exception flag. /** * Implementation of IPC packet specified in Module.md. @@ -304,6 +301,7 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = // flags.add(KnownFlag.Exception.value) // } // } + // Implemented and works, but disabled for now. See Module.md. "keys" -> { try { val leaf = Reflection.resolveInstancePath( From c31edfbee0348381f8160477f8946175dd4cd07a Mon Sep 17 00:00:00 2001 From: will-ca Date: Fri, 19 Nov 2021 22:06:16 +0000 Subject: [PATCH 46/93] Remove resolved TODOs. --- .../assets/scripting/enginefiles/python/PythonScripting.md | 2 +- .../scripting/enginefiles/python/unciv_lib/wrapping.py | 6 ++---- core/src/com/unciv/scripting/ScriptingBackend.kt | 2 +- core/src/com/unciv/scripting/utils/InstanceTokenizer.kt | 5 ++++- core/src/com/unciv/scripting/utils/StringifyException.kt | 1 - 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/assets/scripting/enginefiles/python/PythonScripting.md b/android/assets/scripting/enginefiles/python/PythonScripting.md index 93cdba9b151b4..8313ad82718af 100644 --- a/android/assets/scripting/enginefiles/python/PythonScripting.md +++ b/android/assets/scripting/enginefiles/python/PythonScripting.md @@ -62,7 +62,7 @@ When a Kotlin/JVM class implements a property for size or keys, Python wrappers ```python3 print([real(city.name)+str(real(city.population.population)) for city in civInfo.cities]) -print({name: real(empire.cities and empire.cities[0]) for name, empire in gameInfo.ruleSet.nations.entries()}) +print({name: real(empire.cities and empire.cities[0]) for name, empire in gameInfo.ruleSet.nations.items()}) ``` In the Python implementation of the IPC protocol, wrapper objects are automatically evaluated and serialized as their resolved values when used in IPC requests and responses. diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py index 28f6c6de07d51..ae6ccb1ab3fec 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py @@ -89,12 +89,11 @@ def stringPathList(pathlist): '__lt__', '__le__', '__eq__', # Kinda undefined behaviour for comparison with Kotlin object tokens. Well, tokens are just strings that will always equal themselves, but multiple tokens can refer to the same Kotlin object. `ForeignObject()`s resolve to new tokens, that are currently uniquely generated in InstanceTokenizer.kt, on every `._getvalue_()`, so I think even the same `ForeignObject()` will never equal itself. - # TODO: Add hash to protocol, magic methods, and hardcoded (in-)equality comparison. Actually, no. Hm. Hash-based equality implementation would be messy to account for token strings, and hash collisions are still a thing. Implement hash, but make user explicitly call it for comparisons. '__ne__', '__ge__', '__gt__', '__not__', - ('__bool__', 'truth'),#TODO: Allow foreign tokens for this, and other unary operators. # Wait, no. That wouldn't make any sense. + ('__bool__', 'truth'), # @is # This could get messy. It's probably best to just not support identity comparison. What do you compare? JVM Kotlin value? Resolved Python value? Python data path? Token strings from InstanceTokenizer.kt— Which are currently randomly re-generated for multiple accesses to the same Kotlin object, and thus always unique, and which would require another protocol-level guarantee to not do that, in addition to being (kinda by design) procedurally indistinguishable from "real" resovled Python values? # @is_not # Also, these aren't even magic methods. '__abs__', @@ -349,8 +348,7 @@ def keys(self): foreignValueParser) def values(self): return (self[k] for k in self.keys()) - def entries(self): + def items(self): return ((k, self[k]) for k in self.keys()) - # FIXME: This should be .items, not .entries. diff --git a/core/src/com/unciv/scripting/ScriptingBackend.kt b/core/src/com/unciv/scripting/ScriptingBackend.kt index a161bf76e6957..64eb72f536bfb 100644 --- a/core/src/com/unciv/scripting/ScriptingBackend.kt +++ b/core/src/com/unciv/scripting/ScriptingBackend.kt @@ -76,7 +76,7 @@ interface ScriptingBackend { * @return REPL printout. */ fun exec(command: String): String { - //TODO: To support modding (and more specifically, error catching in mod development and use), this (and everything that parallels/implements it) should eventually support returning a Boolean to flag the REPL printout as an error message, in addition to the + //TODO: To support modding (and more specifically, error catching in mod development and use), this (and everything that parallels/implements it) should eventually support returning a Boolean to flag the REPL printout as an error message, in addition to the fake STDOUT printout. return command } diff --git a/core/src/com/unciv/scripting/utils/InstanceTokenizer.kt b/core/src/com/unciv/scripting/utils/InstanceTokenizer.kt index cd76a30987b26..cb1b357779786 100644 --- a/core/src/com/unciv/scripting/utils/InstanceTokenizer.kt +++ b/core/src/com/unciv/scripting/utils/InstanceTokenizer.kt @@ -20,6 +20,9 @@ object InstanceTokenizer { */ private val instances = mutableMapOf>() +// private val instanceHashes = mutableMapOf>>()>>() + // TODO: See note under clean(). + /** * Prefix that all generated token strings should start with. * @@ -64,7 +67,7 @@ object InstanceTokenizer { */ fun clean(): Unit { //FIXME (if I become a problem): Because a new unique token is currently generated even if the instance is already tokenized as something else, this will eventually get slower over time if a script makes lots of requests that result in new instance tokens for objects that last a long time (E.G. uncivGame). And since any stored instances should ideally be WeakReferences to prevent garbage collection from being broken for *all* instances, fixing that may not be as simple as checking for existing tokens to reuse them. - //TODO: Probably keep another map of instance hashes to weakrefs and their existing token? + //TODO: Probably keep another map of instance hashes to weakrefs and their existing token? The hashes will have to have counts instead of just containment, since otherwise a hash collision would cause the earlier token to become inaccessible. val badtokens = mutableListOf() for ((t, o) in instances) { if (o.get() == null) { diff --git a/core/src/com/unciv/scripting/utils/StringifyException.kt b/core/src/com/unciv/scripting/utils/StringifyException.kt index 60ebea90678b8..1a8391fcb725b 100644 --- a/core/src/com/unciv/scripting/utils/StringifyException.kt +++ b/core/src/com/unciv/scripting/utils/StringifyException.kt @@ -20,5 +20,4 @@ fun stringifyException(exception: Exception): String { *causes.asReversed().map{ it.toString() }.toTypedArray() ).joinToString("\n") } -// TODO: Recursively add .getcause too. // TODO: Move this to ExtensionFunctions.kt. From 04315d7940fd248f56443852060201954ef393b7 Mon Sep 17 00:00:00 2001 From: will-ca Date: Mon, 22 Nov 2021 04:18:16 +0000 Subject: [PATCH 47/93] Multiple dispatch for foreign script calls. Previous behaviour would randomly change. Docs. --- .../scripting/ScriptingEngineConstants.json | 3 +- .../enginefiles/python/PythonScripting.md | 22 ++- .../enginefiles/python/unciv_lib/api.py | 7 +- .../python/unciv_lib/autocompletion.py | 1 - .../enginefiles/python/unciv_lib/ipc.py | 6 +- .../EarthTerrainFantasyHex.jpg | Bin 0 -> 169575 bytes .../EarthTerrainFantasyHex.png | Bin 549860 -> 0 bytes .../MapEditingMacros.py | 8 +- .../unciv_scripting_examples/PlayerMacros.py | 13 +- .../python/unciv_scripting_examples/Utils.py | 17 +++ .../unciv_scripting_examples/WheatField.jpg | Bin 0 -> 81048 bytes .../unciv_scripting_examples/__init__.py | 5 + .../com/unciv/scripting/ScriptingBackend.kt | 3 +- .../src/com/unciv/scripting/ScriptingScope.kt | 11 +- .../scripting/protocol/ScriptingProtocol.kt | 8 +- .../unciv/scripting/reflection/Reflection.kt | 137 +++++++++++++----- .../unciv/scripting/utils/ApiSpecGenerator.kt | 5 +- .../scripting/utils/ScriptingApiAccessible.kt | 17 +++ .../scripting/utils/ScriptingApiEnums.kt | 2 + .../unciv/ui/consolescreen/ConsoleScreen.kt | 1 + .../com/unciv/ui/utils/ExtensionFunctions.kt | 2 +- 21 files changed, 198 insertions(+), 70 deletions(-) create mode 100644 android/assets/scripting/enginefiles/python/unciv_scripting_examples/EarthTerrainFantasyHex.jpg delete mode 100644 android/assets/scripting/enginefiles/python/unciv_scripting_examples/EarthTerrainFantasyHex.png create mode 100644 android/assets/scripting/enginefiles/python/unciv_scripting_examples/Utils.py create mode 100644 android/assets/scripting/enginefiles/python/unciv_scripting_examples/WheatField.jpg create mode 100644 core/src/com/unciv/scripting/utils/ScriptingApiAccessible.kt diff --git a/android/assets/scripting/ScriptingEngineConstants.json b/android/assets/scripting/ScriptingEngineConstants.json index a7dee88e40372..966e58f03eeaf 100644 --- a/android/assets/scripting/ScriptingEngineConstants.json +++ b/android/assets/scripting/ScriptingEngineConstants.json @@ -14,7 +14,7 @@ main.py unciv_scripting_examples/ unciv_scripting_examples/__init__.py - unciv_scripting_examples/EarthTerrainFantasyHex.png + unciv_scripting_examples/EarthTerrainFantasyHex.jpg unciv_scripting_examples/EarthTerrainRaw.png unciv_scripting_examples/EarthTopography.png unciv_scripting_examples/EndTimes.py @@ -25,6 +25,7 @@ unciv_scripting_examples/StarryNight.jpg unciv_scripting_examples/Tests.py unciv_scripting_examples/TurboRainbow.png + unciv_scripting_examples/WheatField.jpg ] syntaxHighlightingRegexStack: [ ] diff --git a/android/assets/scripting/enginefiles/python/PythonScripting.md b/android/assets/scripting/enginefiles/python/PythonScripting.md index 8313ad82718af..9fffe96f8bb77 100644 --- a/android/assets/scripting/enginefiles/python/PythonScripting.md +++ b/android/assets/scripting/enginefiles/python/PythonScripting.md @@ -216,9 +216,9 @@ For any complicated script in Python, it is suggested that you write a context m It is also recommended that all scripts create a separate mapping with a unique and identifiable key in `apiHelpers.registeredInstances`, instead of assigning directly to the top level. ```python3 -apiHelpers.registeredInstances["myCoolScript"] = {} +apiHelpers.registeredInstances["module:myName/myCoolScript.py"] = {} -memalloc = apiHelpers.registeredInstances["myCoolScript"] +memalloc = apiHelpers.registeredInstances["module:myName/myCoolScript.py"] memalloc["capitaltile"] = civInfo.cities[0].getCenterTile() @@ -226,13 +226,13 @@ worldScreen.mapHolder.setCenterPosition(memalloc["capitaltile"].position, True, del memalloc["capitaltile"] -del apiHelpers.registeredInstances["myCoolScript"] +del apiHelpers.registeredInstances["module:myName/myCoolScript.py"] ``` ```python3 -apiHelpers.registeredInstances["myCoolScript"] = {} +apiHelpers.registeredInstances["module:myName/myCoolScript.py"] = {} -memalloc = apiHelpers.registeredInstances["myCoolScript"] +memalloc = apiHelpers.registeredInstances["module:myName/myCoolScript.py"] class MyForeignContextManager: def __init__(self, *tokens): @@ -252,7 +252,13 @@ class MyForeignContextManager: with MyForeignContextManager(apiHelpers.Factories.MapUnit(), ) as mapUnit, : mapUnit -del apiHelpers.registeredInstances["myCoolScript"] +del apiHelpers.registeredInstances["module:myName/myCoolScript.py"] +``` + +The recommended format for keys added to `apiHelpers.registeredInstances` is as follows: + +``` +<'mod'|'module'|'package'>:/. ``` --- @@ -286,6 +292,10 @@ In Unciv: --- +## Examples + +--- + ## Other Languages The Python-specific behaviour is not meant as a hard standard, in that it doesn't have to be copied exactly in any other languages. Some other design may be more suited for ECMAScript, Lua, and other possible backends. If implementing another language, I think some attempt should still be made to keep a similar API and feature equivalence, though. diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/api.py b/android/assets/scripting/enginefiles/python/unciv_lib/api.py index c2abb41151edd..c47e9d280e2eb 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/api.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/api.py @@ -98,7 +98,7 @@ def populateApiScope(self): self.scope.update({**self.apiscope, **self.scope}) # TODO: Replace this update with Kotlin-side init? def passMic(self): - """Send a 'PassMic' packet.""" + """Send a 'PassMic' packet. See Module.md.""" #TODO: This should use ForeignPacket(), no? self.SendForeignAction({'action':None, 'identifier': None, 'data':None, 'flags':('PassMic',)}) @ipc.receiverMethod('motd', 'motd_response') @@ -117,7 +117,7 @@ def EvalForeignMotd(self, packet): Extensive example scripts can be imported as the "unciv_scripting_examples" module. These can also also accessed from the game files either externally or through the API: - print(apiHelpers.assetFileString()) + print(apiHelpers.assetFileString("scripting/enginefiles/python/unciv_scripting_examples/PlayerMacros.py")) Press [TAB] at any time to trigger autocompletion at the current cursor position, or display help text for an empty function call. @@ -136,6 +136,7 @@ def EvalForeignExec(self, packet): print(f">>> {str(line)}") try: # TODO: See if you can use signals to catch infinite loops/excess run duration. + # Actually, no. Interactivity should be retained from the Kotlin side by running/calling ScriptingState in a different thread. try: code = compile(line, 'STDIN', 'eval') except SyntaxError: @@ -155,4 +156,4 @@ def EvalForeignTerminate(self, packet): from . import wrapping -# Should only need it at run time anyway, so import at end makes a circular import more predictable. Basically, this whole file gets prepended to `wrapping` under the `api` name. +# Should only need it at run time anyway, so import at end makes a circular import more predictable. Basically, this whole file gets prepended to wrapping under the api name. diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py b/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py index 39298c548443b..2fca1d796e6ee 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py @@ -117,7 +117,6 @@ def GetAutocomplete(self, command, cursorpos=None): if a.startswith(working_leaf) ]) except Exception as e: -# except (NameError, AttributeError, KeyError, IndexError, SyntaxError, AssertionError) as e: return "No autocompletion found: "+utils.formatException(e) diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/ipc.py b/android/assets/scripting/enginefiles/python/unciv_lib/ipc.py index 61ab962c613bc..36f106ad2eec7 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/ipc.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/ipc.py @@ -5,6 +5,7 @@ #def ResolvePath(path, scope): ## return eval(path, scope, scope) # raise NotImplementedError() + # Needed if access to Python internals from Kotlin is ever implemented. But that would go against the current execution model. (See Module.md/REPL Loop.) class IpcJsonEncoder(json.JSONEncoder): """JSONEncoder that lets classes define a special ._ipcjson_() method to control how they'll be serialized. Used by ForeignObject to send its resolved value.""" @@ -106,11 +107,6 @@ def RespondForeignAction(self, request): self.sender(ForeignPacket(raction, decoded.identifier, rdata).serialized()) def AwaitForeignAction(self):#, *, ignoreempty=True): self.RespondForeignAction(self.receiver()) -# while True: -# line = self.receiver() -# if line or not ignoreempty: -# self.RespondForeignAction(line) -# break def ForeignREPL(self): while True: self.AwaitForeignAction() diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EarthTerrainFantasyHex.jpg b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EarthTerrainFantasyHex.jpg new file mode 100644 index 0000000000000000000000000000000000000000..05f7aa1d64f765907fbd00e7623ec361f3c55c92 GIT binary patch literal 169575 zcmbTd1ymee*CpJzLvU-{HMqM=fZ*=#?wVl1-QA&aX&e&VA-EIVC1`*E`8&`1zVm(S zpEa{)=G3aXyYD&Y*4?*ibyeNk^=IYJHsGCtjJym03JMzF1z7-pcBr?ceQd1&0C{;v z03zV8p+iDn-$tO2kdRPO zPzlh`2skK!6deE8_U9V_6A=&s1tCibfX0M^!G!uV2q1&>6AtPh`xlkJL}6gz;1Lj! zkWnB3;CFvV3I-Y$1`ZAu7UB(r_yMq(a99-V;_%q2rU;a-I2@mniV&$J8vAh7W`9z1 znz?;OLdL@MESl#-T_m6KOc*U;3`*3s27x3ILbwz0Ky_we-c z_VM)#4GWKm{1O$NoRXTBo{^cAU0hOHR$ftA1!`(;0k^ibcXaj-3=R#CjE;@Z%`Yr2 zEw8Mu?dSXpPXLb+}_$qx&;QuJnEn5FVM2I8!@|PABK+kA1?~Np zI3_F{1v@;JxGI9FD>fy^Cqx{Hq@u^W z|AW|n@md3*!azX|9tz1k6*#)FA|e;5pC^MvCM@)f8@d~lqFLKMaP6+I=nZv=2_bo^%vS(UWzDcqBzF8 z7D&bBgJ^#Mf!U2jNC*>lN5}WyZSzT!3Dqy&btnz^%iy#MZeuU?AkU!*(6Z}O$pBEB z48#dGM5Vz4t#9AYe>epB?i3@dkD9(Y6hr`b#aX8N4z?c{t701F_+vM@I1ywv9b{x%uA1fRcrI)na86#Mj zUIs~J#9=W{<{Ha#c0pYV#R(HCy~B$xIOG}3!1~pTH!G7z`^r(MIFF}~e*gq#2&Zr| zL0G3L@)^m~ICMZ@(j~*g8}BuD*-?hC3t`DlaWXa3S8`D{`+2T7r)8ZlEeJ=NY6JW* zqHmzBUQxFG^=w6>Y7H3-jL-G7MU&abtA{5)&5Q!3v?xwmIqaAcLcNhlO&83)?wm93 z0n7E<+2e~~e}4jb--~Z=EOVdYg9?{<9}Y!JuWpR@{W!jv&UMWn&R{JBK>3#UUkMN8 zG8ZNRGwgJ>y#U($v^mrtV18Yevb~~tiBK#mS9lrm?Zugoe@3EsIy0DB+E%q_M~Drn zjtxq?&kCZ#RYO{~yDGW`GrhOjlmA_PWx;MUuZ)m7I`|Xdko#-`qa7K_eF$5g=F;bv zT;>WnZOjToM&rw*I;O>ugB4C{uDBJeU18k~--Q7!>M$G%4zhjDEdvCOVoRJwA6+G? z)mrL)>l|ACT&i;9O>?y{4VEXvB|1(?iCfw((B@tkGxwR)lOU4er}D89t*SJd)5)Yx zY-;uxzkk-IVLn=`_i}ZfcmocD^?rIE*b*{rzlxndo&Vg#Si?uSezA25pz(Bwwdj~1 z-U78TP6ql+`-mRlcA1*v% z@ED2h9NGUUplp zudJ@l+e*HUA`9tOI)0ZC(!l}Sz`d;|R*#qo*;Q9(F2`lmFzpuFyz=$o1p5+cVutZC z>KfO_OI$~z@w-2O^0K5gwxS}If}#t9hTSKIHPz1FU$y*1SJQwVX$3!?srrPn@Y@bs z_Io#lTu{Xe zV4N6b@5Tbt3hp0)YJIzlS4yH;{dyxpNzUo?IimfPQ_Sgw+cEt3$NEoJJ;*Fj>&omK z;+EL&q|~Xpa{Y1}#*Wrcbbn=|t$5s&HVyM*UXsm-PPgw3u6V1h2gz`AOd4nl7}L0i z@T5?TalAidw71!h*Yr6|)0HEyPBJvN75Lm}+fPPIr$SngH#apLMN`?Q(096p=2n()aLi ziGmNf21jQ`F7M|!GcTxS3`Qqi@A`15J1@&<#X8vQEu$p%u7Ph>O+yx`)#CFNmlelf zWgO+r7#7qIzt;XZ^$TC>u{S41=_)6;XcJ;TN2q$>MyrqF%4zz5pENw=-s+R3EL(x_ zw4Bn?B$$yD(|9`V7Oa=g@UZ<^yvFutI_7+Hj@#8A0N{?4r|EpgDA|j>#Fv4RZ@k4) zY^0L^=;f}GJdY}2N}Ap~V9VS!_x2Cqw2oI{vAgBw;EP-`mJUoC-SXMXSwpMrJN>R9 zy2Ppe#_6qYj=PuOvB#Pt*7}TveP`LWf*0%xj4#i_)iP~dDqoy%MSZs4D~*et-0>Hm z4dT15`6hD8iTCxL+*fY(Pa8E9id2u>)Hq6v>yy+v+^|t!3XdEZNi*Ar%~#g{%F|r* zk*Ukyki|1yy0%~EV%D}qkiZ+!qMN%tLZc|>y?R|P8E>?qj^JHJT7iq;EvM=(*LW#!f-3=T&L_+W?Gho%^2mLgSByC`t zmYItmb_gGt!uZ$u!Bc9rM!ub_-j9J#^>q`t%*rID*B(!P<@p7)_4V%4=@hDi0*UGM zy>D?|=N`SZkD-B8rx>Iu?#w(20HrJ~XEpnr`1?YN&0k}=`?sMw(1&s~7N|8ENgc}K z&l+WAHiMyF{>!s!6E>=!!|)1X?&&aR^b8is?w?5%Pg6G5lW6j4?1HQkWg|u7Gy(+# zn~xmb0E8ptF!LumUe87H-#UyrP)%T7=$5xS<7gk|F0b(rBGkVR{{c{lgj^dr32<{f zJNfuHR=1hsE0ls*y`8y4rq?#jlPr8gG>KtN@XQ}W=SuVM57u$?li>(-1}x$Qla`;B zhI;cHfql(~M^p!NFpXq&{MDEP<_vD>#}+bBpY{ky+<$-r@I?6scG;1u4^y>eowP1q zz3+qt2#6L=_>N1T&)joVX+`!BNBA=0IUnCN-Dr}`$vRPSu4tl6DjH!5g|zxVfZ{@_rWPrJs*RC|*=|@KXG)L;NTk z#CVRS*!@{fbw1wiSB^8VS3c=O=Je!kLK(9aEYv%6VS8RR)j8!6PazuFW(r$G;?G^! zeS(Z-lTGg@V>$CVm~=YPIpF>M7POf<;Fkt0fbqpGn$&llz@K2e2oJxS2_#!@5Y+VS z$XrFF&PJDcP;h6}Em2Z&T#oAu9i4FVGETK6vT{?E5fb0AD4Fqr<^>V>pfC+6CHqt# zH=QSIswU}ma`RbihLEs=O$}Yd*ssDhWzc|aKUt2T^co3H*+*Eu4qRlYuIO)%zn2=*WpHbUU+F@jBnm&$ykMU1P|79Y2sL^ZP8)lDZB0F^vCu^#*1doJ~ne zdfgcnjSHlEZ%%wis6Y^{Ah;-TvTDPz*>fgIiB%Z#ZMi{jW5m}Q^BgHQ{n6oN-eNUw zP*Voky}t=Nog8`ioD!Sk3K?doKHJZGz|L9CGMB|KPiJW`c(zer-A(h=$@V`(Ez!`b z^&>u6&A@zzIx{Ljm3M|!m0?RFmm&>8)$Y5lbB`#3i|BG!zM!*ba(MM8eNYL>^X*JG zs8$3Aw;z@^FFQF0jPdG^e3mkE1sfv(QTIJYkIpnj+tR0RmM?7T3@+0OQLzbzy$W_o zlm(V8sK?G!!Qie<>MK|!Mr-@DeOF_Oh}~GB`HJ{~I(=O?+u19xx3@DZKitP4`6|;> z&*dEB&L0`ZRQbujl@^WY>zy(MzFy9;e3L_G&+qMB^qNp?ZA~>?onniY9!>D2m#zP> zIM&jIkTc~^>@sqNNdUX}#fT@i65t?+Y<>AVkUiGltO7SrqMUTqFFE?LgvqV+H%(oA z{ZnznlQxaMnXmrw;^iMe-4G^fb>&qQMjGYYT?Cnr2j3q6a+5e(E|hW-35mkz(G?hH zO`-d-6HTkd8uL_1mT&O}pAI@X3hEF~uI4(JzUP~dsbRV`X{&a58U9*#P6E@ZAk7d8 z+Wndh_$r!ZG5RfD4kvtAe2T4_Z^0Fwh} zH_-u|+4#$jw$6t#U$#jl*e>?)5%?z(l_w_Y94t0E)GTl|x<>zaicAMRuWTKg7plMJT zakH^IZI9KQN9)kYv(!aMSt#yF?l0V_F{%kFd8+0vPb+@fnsiPb(*kHbzL|hOfM-xy zP?St)MUb`ER^8HD3xCxRQ?!i0{G`*Mse2;g2!A;zp4s4jet8PD2Vd=7hhGAEp)0}I zsS4Mkl?-a>XTebU0%bQa=ma-fRb%b45`}6tCha=6kh|&NS44@LK23)i1C{ z>)Vdxe5iywUhR)KnY|xaNs(&;OL~g0#-Fs-?E_P;y3RF`gVr2tG5e>?JNsCv!uMR0 zPvwV&eXoYpLn>3TWWnAX8w38mL;NycY$ky{Mp89Uw|pR5 z*@tt&(w|!=ced19+qZR`Z`mvu3}-CqTw&-OAY<{dDzvX}>`ilp-uAa1I z;iw_;34tvog*YDL55TCc_}Z{Vp=6?i+Rc|F5H3h_D3>XIXzw6qKsHZ#oh!SkO{*&? zvMuBAo~hcbyre}PhUd);;z$%lX36t1lMl8ifSI2 z%bU~1+D)XfIIq7UxC`74YAq#pw_XeHC?m#_gvRFfB5wOxbswoMSucPYSP%Kk`zwXW zNdUFZBtFq3S`UJPNW6Ob$&&*mWAh0nd}Qhu2K}IE04&p;NW@o@9fJgnkO9!vM0#m@ zo441=tbR}J{(}(eGND?JERsq#Q&ZDxFz5FGI#AC+dt1JQz7rMoQHAsEi3WF`pL3g% zH408V9sNkxJ9`+#-arltcj8^S(kC}zX|2w$zdf%C4iG0UP+_cDLQt$XZne>g2mS!; zW(fJ{@LFy%w4GIkj|wi17)i%}^VVmW*Ueup?S7XOiu4zG$u%b}U04oxxcD_dMUceV z)kcSqwEV0^8Ykr5EJHx3VNzd-5F12LD;P2-{$NY|%e>*3b8NR8sHGH9lJna}F@0@} zyQ~wu{zf)=*meC>T$$)+*W8g_4uq8rn8?uU(EFK$Ua@% zc)bLR&vy&2GWp`xqEpjk!fs>vd6La@wyO9)`U0W_A@A^ z-N#^|1Dm8BH+AJQ)FoDuWz2lZ{Oc*(lZfS13JK}HYuoQ!II)=S&DFDnjwcl`I+cbK zKK1;B+}-7!?Y0k4=W}Eas(QKTJv(`u0<|w6z8+$(DpwMks2S{t9VK1NwYAg=@B7A{ zj+S1mR5hyfA|Tw<%CU^T-oiP30{~#XM`{h_W@QNo`N{qA@1OLF_@?J(OO}el4}%^? z1a~n|kB=1EJAIb6bY=^t)I+A?p=Nq~Kbo&3oum2j4$A-1*ISQTm7`e`+U7M;nJ<{` z-oCG2@v$@`H3BW>WN^;CV$BB&&%7r%@wX7=5CRFO2#92n`sDp7;rq4zCP zLYUTT(^teX!2rpFN-<$};{w+yB2Naj+?KSpujvoL6_a;Or|7lv^u0O3#!JIa1tkL* z*>h5av|{mL7MSxDMKhGm0lv&XPuL{UwHQd+5;2VUxR+>=Ev!%B+(^k6Oc$?e@1*3# z^usYIU(at4VXo_8B5Xx#)w2y|BkJeBj-7k$?N*v@@|^#;H=o{?o740Udr!c}NK7cI z*T|9;CM#r;Y48&FiqYffGe@k?A~B=bln~?7Yvtf%WuC7xk8oX+c+JK3EmqV!-An~_ zWc~*CxfdI&k;~i&sf_itQ?%gJ2X~Lrt=Zp6t81lU7|7A)d$3+D;p?tHrb@ev zuAFE%-s9E_%$JqWV;6gEkA49$IlAO6&vecR^{3z3x|TJz88$+zy(8oRwnRLwUwc9i zb)u~dr?ovY66wd2E+QRuKrQ5Wh6+`?6zv=Tx+zgEere(9Vfz^EcJQ5tL5*(hf@z^}uBl0L=2jZ?mp-L!zzOHfg~7 z7U*gBf?&I+STDy<6IAzpOgUp7ulO5|;p5kQGk(akZ&qJf-`e=_8d|M5*~G+7OA-TJ zd_)V%ET?-l`$x$oN#!_-2p_iHjl!OBJ9u3ul_%2Hpa@pn^Gi06x4|U1EoZr(sMTO1 zK6*3ENH7^hvK2*D@kulXIy@UX)Yl~}7sd0!8KV*W_9^qPu3ws7t5q%V#&Hw*nX`hL zSF5WmZ-%Sz*-xuWZw788v!9oI&n`Bv2MOWpTy8j}^OoE?6rbsjx!w+y%_eXDBK0I) zJ8L_2q$_=37%{^At2`(z=uEM_ZK8KbeRN|lyVx})IyJ4uTAGejqj(Sjfo}#Z-@%y% z7Z_D}TpcG9W7CvO%He)rt;)0a86M3V!3lmZy4i`KeXKUGv@W&+4F!u5UX(@pCjD5H zF|dy!em**!MvZL`VMP_^!i_So4r7!NKl)e*jr77zJ=U?!M5HV=n0Zp)ZYZ>RP!gL)$?4hqae zmgY%qR0)1LI!kIprA9gUN9ODC?9n%`vL91EcGwcM_VqIe!v&CG{Cxc&?HMz~ByN{+ z8~jb+x*g1~5cgi@x3Axu)coXH*tXDUBrwB?G55Q#&VUnw&~9-R=1&>-6XS|L8HEW8 z-*Hw!2Ng#$HCckn57IvnOAG8eb3OnI#;Zqp>2+=j?`NRvekTzMHt_>T5I-p-B_2f9 ziT3v>_I0LuXLIOdp(fU$3nUf1_4vw5AQ8g)#YfcW{@6H~%VMFkurlBgAiCzv1@Y%& z?(CmmdAv+-&{FE?jn{D72Mgx+)Y{D+FU8)7%xQc{A>(XQMkqYXz9n?7`!QH0AVR0B zB_{Vq5vn^k(>+3ww)IiOOn&b~ZCzhsF0kkqOx{F%>5{LEyev!UZULtsuTx&s@E+Zl zf|m5WjLZwWQ~OsV|E#QhU5rWB4cAW>b5$6LH@uUz-G_NYr+g@Sdv4O-gExHb-0&y)aDm#K{Rey5 zQsxU0Ld%1Sp($1T<7FvMV=P_v?wb+bMmT%8IBQo6zc%BTYvwMBb%V_AG@sfN`YyA-}bP zN?TAQE)^2>`!?8Qxf!xZFc<3LP!rh@`3suh*k{^50NsMZx*@ngPl4A`FKNy2nV$vy zrFd>l=^xPrt!e{{-kGtvpGs>b`y$a5^w+1q!j|#y@X0ywjqGWwO3@R<`)j$&jZ~Fe zlN_fU^NUY->1Ru*=+)>P=A>Ln12324+J`8mQbU()3rIrhHB6U=$!)m`4decg#3QRKwqX`+x)nmSY{EHHqHEwHP+ zhNXt5YY>zpysK6TL~izV!86$R4YR)6E%cw86_TLVbg&jGSn5HnAW0LJ=d&$9NYErf&s!&$u zA#n6E)+-=IYXV%}z)1S?nhCXy>DdeWPt+DiP+hMA59O=MiOM9kgQaf5vOC)6FLd>% z$a?eg9cg0I5+*@bps_MLFV58brNcGT)!C%*5BBOgE=qyN%1Dxi1*1z@HDH3+rPK6F zzMC0Q_3VLUwIv4fjWs!Pc7* zV79^jL|hL1c2KFui_P;PcJ|h+@gfQ7*k#J^teIxnX*gyh#lW2@$Eoe$cYPfF&xT`3 zsSU_k_jgw#3jQym*HYGpvzG!eH*uRmS)k`bq923CeoU)f4wIjtyO<a$Ho^+IlL8v=)%EOmYCA!T(Qak z&H5s2^`vz&>#QILw9!}5t8Cu|wgXi4lw4Qrq^CXHo5o$gYpny7E8%uap-z9rmjn#( zqv>u;)2^aiZR@Xm6VAK!?8V6K!@3`5_kuXE!SE0KFB;nQHRrz@QFA>;hl2%D=zf2# zH5Z{8p4hSVdw<)p(DcmUDU`pyL!lw*UiGOxctX%Cfb0X%^}br^XB6^|C*tTm+lAW18Jx$BU_}SGq2)0ux!mYBOS-^D^x}=qG(y zRy=0!NF+~@Dgd8|7UBi_2XNoHoNeTTuMvx%q13hi2Qa}b`WBk~Fq4B&cy}c>dK%2Q zj*z9*FPok}%vaNzPZ=~jR?!+cbL&I(kr>tM7fZJVW4}&9od8*_t?eHG5;$h;VWV?( zDB(VtRXLCc($Y)*xe~45azJ|m!Jf@(z?X`&Vb+@*`+eWboS(sxt%z&kQ&Z=ePiZRY zSq~$kwT>g(qNw_E`Pz{?DYge?X_-ON@xhFz^z}UDmNAuAj0sn5`(7g`@_{L-?LUXJ z&_7;b|Eui)GYtVdp-YZj&>fetvuhN zsJ$h`*dT|l8D94iM3-o9x3o1b*vw3%CP@|B(hsXL(_^Em&bUZ(E#IJF(utDnKZh4! zLN*=E#Br{fqtw)C;h!Vuo2Tq8B$oTUSm*)(LVZW4FRsp78wA-3mcky!7Uatb`!ueP18_C-&W7%c^~o+tfT{j&?*ATKFt{83FsM*f2|M7;!29u+#6JG#TN z0RWCp9&T!~666pe4LQOA00n>p(Hp@5SWV5{UBo|r{O}(+&;MLLEd5m`0RZzXf7|+> z{r}qszXX@$O;6=0^|WofR6xjfEmCOU<+^ncmP-+r4yva9iRsBOZ+eL ziT=s|2+1;oWZ44DAsJEtXMiKX^q+jd-(vuYf!KeIt-BQ`$3In2s1g7G+}58zw=@6% zLJ9!zn)v6>@1j3{UW*~hvkd^?o6~>gI~M`~eD{$0#Q&9!T^BQ!T+i=%K!k{ zA^`w`RTon?(|`5@1KC1bLDXkii} zkgsMXg&JHpSoI|&ZqH1NxSu)l_L5-|N^W*|f8=bGion^!QAg5|Kr`qL88!NVj@Om6 zVh+TCn#{pAx~aQZWJBMK=cc2HkHUEjgG(~;SCznmAA3BbRf3rzkIq@sSibR-q?2;f zB%P+1(iM8o<6N7EsJkp~FFeV$#wIcoHSB(dKrfAasm|K#vqwHy%wYhJ1x%v8!bSh^ zggn%IR@G*ZX{VUYw9?fe|wK_d3P@8sc%gGOF)KRzN z&|pbHoVISXUHQwQq(x+`xneQpOjJ*8Q{%EOU-1Y{JMElZHUiF+*-k8~n=FcwfV+*# zY}Kl;Gk=Cj7d+1@(M+0vV#ZES9Hwh%BKPEQ*%Wa*&xj!xjVgTp*z%b|_{I*A+*yEB z-#rQWBDcql38+7m87bCu1X&P`i5AzGC<<)d<= zGX*w|59qyToM1fJO-}qC@lk5)UAHenZ&MCe0ZepjevRFjIn{#!@5eDh*pw<*3SfxI zY->)3WO?p&6w%_3$Y#eh$9+5!uNn#0qYbWwZa1moe8vg32(giMS@mH-PCf3S_p`aL zv&>T+pvU^Q4pPYlLFctCrHkV=O`Kce%BO;CZ`QIz?qgdjX=iw=<)y~f4FuphLu^w0 zPBO2^D9wA(P9w&={yBR>OalHiVQvIB`wogxsk9BkxhRxh)Sk~z+zyT8r{T`*b{9?# zs{_a`UV76KEw;(bJXlkE=ET$}5!Gd}jQl+1hTCzE7bT14DuqFQ&znQZUf?j`GBy%I z0k7%fT{+~Jhhl=6L{3ld)t>HO*X6b8Liu}n#pQ{p&{2{DU$Xj>+ z#_sQu>K$Cw<(Mo{YD!7PzMw|V*^+cy{l3j_OzS~rHq8* z6*;hvE64$ijR5+}-NcE{y{lB{ z%?5JKLKV9&$;twjjw?tj6FYWBP@`CvM^UwwxU%KOzIoz#tDhk(vpLL}MIDoe3D2)p z9;XLLg6L@I<9x zj}JYq$O`&3BlnIH9~b!ELNHe!{=~+DFxHGu)l_c|9^oYrZm*(m6_@ZAKXy4Z2QzPn{NXe}BbSn1R z8^`@qc|z&R<*LqR!%7%k3c5XgMqy~dqDt;{1^{?ijLSbS@igwb6TEbZeO+E9%+(Vt&Aj4-!MPy{CC3nC-oEw&#`$5 zYJK7!1}brO#OJvhVug-z9;=Ezg9h%%-9E(uT^cASxM*HIgd?ZsMa+VdfS1{g1RehI zU3c}1QyN7zuQLZ(WKi{@rY2~b$)W`JDw_}COTf3ipei5EjVHXcu+{7NXN#Mj0D3NE zAaYO)GmbAvOiKSGK~P#aWR5#$e6D>$bz^0blE=s5E7G##?l9FZ*3N*DcSgZMg%RJD zgVIr%xl?Gw=(EM%uMC=cil#S<%RK7XD#DS(hb1hzsA<2=x_I3mOThi|>&WBH@25-U zwP+j?xpwk_E>-Yzv3QE$l@1N@{B_+M-iqfS>a(e!^Y1Dhdu3&Xn<71$>78ZYKa5$1 zy~q z=a)?*v8&g4d^V@vbIr+;pn86SA_HbL;H4Mns967eIV$gdJNBt?97KVU!MfVwh~zon z^8Egxw9eqf*F4qhcZ#CJ@Rj!QKD*Wxy3Fw)=a)-Ek~;MX*{X$`DPlZfcSp}r0_=m6 zYl1=66@g)NScw}Yrwe|R{tfO7@u*GZ zcZJU?2WH~w)ET(z*Sn`vys6^D!Xnq4oyI`pIYr3%Oztp$szk81_1r_BGDY}Mlil71 zim@kLR}8SZCr%}dvt=SCb>XuqdQYw|kc<43(sI1hCD`QKPrtd)M+1LjGR|e-2@t6ZLu+RL#iFLelgI9gREIQ_~|v z&Hh7Eh~WH5G3Gf&gDYEh(mUS?1Vu3yKze*#G)KPIBp@)?V--R8wb@Nq-cCPI+V7f4xkpW`y0-z%KWx~O1~F18dm90vCy1% z!P#wmHX=U)$barq>XMv|$G|4~yQ-*Q=OJ#t_ZGT7?46$_5gmf{M;VUqHcN=RRjKP< z=~5>Oc=BzJn1n8$}1N0;f@o!djOgbCC` zHVvuQ!#<2m0@wr;0S+!H_zDt%z$e>tZ8lreo%z?;=#NdX7R+MrK~X0v~WGHTH2~@2vlpKO+HGo2q~*N@%RpLfrL&(n~cGAhEc*sh8Np85CJRyI3N9> zF?b_q$e@L?c!(Go{_PiNxaRce12WFR8%xt@zMv7Erk=WbkS&&cc8h+N3AcnTpELsf z>=)V*L8bwb7^rEK0j$HOLEnvF0eJ1zII__|e_HU3S5!iqd?o?KDQ=#SXQ&dRxjtc} z;!}nhM8z4O+8Io@zzIcw>x%+bifTt3b48-bKLV@`&c?>FdY z>q#yHH1YC$S;AbpSvIoP=hx9=6a;fc*k1Az6S(Z|WF>qgsgsF!EoBObvC$%(T1{;l zB_5;g#dyUYg~_}#BytRz3L8aD4IG26i6gb;?vadbJ4+6$?n4QUlzSfzrU4zC z*@T^US&dFSNkcxPq}C@#<(RM+vx>!eGilm=ns3``g<5*k=#>5HX_TkRgEU(u9;mPO zUL=~XxE1hP!$}&o)E_UjATGD0&4s+5**@@ET~T{!@q>MiM%95#%cJm=)));bN*bjQ zbPSS5&Xh-9U{uNs^>HiX11UO{(X>>+ws(pr*M)2-3ChE4{m zxFK*kUCG@+=lrxQc6BkOJ}Rdg4X2s#?-3d}cGGPPT)Cb8!;1_a8`H#1e$XnTqn^?o zeWgPb2Jq_pG;+?@fY%k}ASWa$!lX=LF1||*od#oU;hWu+U8BQ3*5Z$;VY$NHQUeBr z>+oe2>;5cz8H&C9{bFE*oc*DNL+r_VBcH<$b8ck9uSnQzgGG+!&cuq@DA;t$b*F`B zQZq;SyyNQz6}6x|mfE`!!=Hz{xxVT({4pJmLK~7EuJ}cM8c_YC1|1>*Wf|rJSi(_) z_gr$n)R;O)H|ySpa7?>!W_WNIZeOkkW3()F6VM+Mt@o2adzKAKo>OQ9D{@E0z5b3y zg}aVn>IIQxiH@7ArLzSmSJZ*W`zzl{XAp<=50HeLVtI2j;qaN#33?1=di;KtfJp`1 zT`w%N2w!Bs2ovs99{I`8U6P&{vkH69vMzihaN=Ux{V-YD8fW;yjaS*vYW^O+ zGTEG@F0Oqo)=9s^3%OP+`4VbYV5#nOIL`OtwU4qU4hxjC3CbuJ zZ$5iJRKQ#)tq*+dIlo(nq#Ef!?Byl%17Sh}Y=)!y-xCD9M9rS?kLLnEAtLqO1!r%8 zUCm{5nF8$T0vdaXO?suNnGHVxpo28bW|YDR7Hk5|(30k}aKi-G6R>)q1I2jJwR@fB zO>H@eNTQV5xW1<5&$_ssI5j(was%bdC5+xNAeu~i)09JQ=QPcU19F3+U{JtQL%Fq$O4W~NdszqE96I;5ZgTGXz_?3|DD>Kw2;>41mh`=)PEHI-@7 zh}TD!x}0toT5&cL7N~qOhea^$Sk)f0x}|k+vD|WBmM7Xs<&eJF9BUJJLdm|qjhK!J zr1Ap;>%tb2bRN8wq8dG1wGr~8T`LKDrJ5x~DZ4+9%N70sz+ewB4+>MV5A-RBDkGVp zt#)xwC3WdmQBR$+@_r9!cdrfT-%O-4vv-2McTg`S7x@xuJCBK1M5Uh8mCB{aoX55{ z-sqSHx&DHxH4Ue3Qnyw2@Lj%K^TcLHs6={6Rcj$9ITcao6a7>HiBSKN#CJY1lDadG z?|^aeK=x;W9Jka2T&wkNcv(db(jT-YnxbMuOW}A;nuT_C0iRkoi99NirLfU~_j{;! zwe6@=%Z>BJ8@o`cHp7sivd0&onZH|Q&42@=sbl)8+YP;`?b8wkh_H$5XKs`6TTt^h zv`yu1F|;WMxp%xX3@oFSsZ^HVmg%_I}S^2Xos#sXzEw1q3sj3xUZK1TH~*UM>> ze7z8QsV-htsWj+eFO0q`6b79|G0I_DD zy%z_SCK6OH`|#b_xY_5}dt4?g%^VAfLp~)r&<%R((Tmf%{gNV`+`XP?_dSNzu_;21 zc6Naraalz?ksk9zN{4S2U+-yEevmfK413m@)2ofvn~WNf(TaN=o|twDq8nL(ZY(W2 z<{m3#{s|uG2^j|VOCSVDmy10sxOenRuu;ClZ_rM;Lt;8JmI`vLBr7tfI7&!N> zy`OltzF{Q2TcQaKs2g4;({^@n>k+E3&-@)Y=2=S=K3X>47&e0Db{CrNjssO7c7}A0 z-(nuC8^+uv{+u?g99naY#6eGPt!x+NP9#+$&J*3;j~PoVS%%osV)!lJ&OiU*1CXj! zsfyWE#%m+(8;&1hlD0#TDA5PnP7N(kW3POwHu8iO0g(mwG8wdL3YsoMytjXUy!`j9 zE+cWc0mRlCC{F~D3z6M9!HZY)l@0uPA;^t>65D#B}rlZ_}|}crq<)@ z{6Nz=3YcH@*SpUrw5zF&GiK)5PiER4phwBG3MrO^;)?{`;aE56ddl)p4rCnSxt1jcFwi^~h9k)c2t9JeRc7o?AKieehFtL42a*b`VjTTy2 z#4ueGYw5x>o76wSmqp2WS+n536%;brO1rIv%zZs13}RIT5bU+y@iZpmj*a9AIy9G` zY4Mu}aMjGaWgewf11~A$G2j2l<&m!_$yQGn_?LKD5Vtne_S zR84Fw1bXPX(ym^s^C>CWICEf}k98WC}SRcr#*HdeM*M>(4ze!BwX*IM~eqC$8koicQ3JC(V7Ofgv^e!zk_^A5BU}x_U{(<@7n|z z$m~2E03H(o3;P`oE(JLv9wj>)2Nga6wK$E~N63UZB4jQd3J&@~D-- zj;;=Kz&j9)t1BbTR}u6X5xiN=1+U2wX)2-`H#6?De7fC`sQitne0*9T&Tk7d*qnpR zGMw)mX8@c-`P4JWM_)R2SbZk)N~cK*W-vF*aC}zTRi!PpCM}*RO@`{_?ZJo_ty z$do(DsZK#B@1Cg;pysAXi3{)ME2dyU=sBw28p&ivP44zony~$7s@>3|Py4uxGHic- z$>vEY0(>80bizu(UnB-aA9RW{$x-tS}c-tyYsjHAS&L9 zcmY0vT@#P|SiSdfkMd4~3q$9bw!tC1{M15d>V0|R4_nzXX%u^qTzpyC1q}%@_MT?} zH-;SeB9ir)I9M&6h;+b!Q}Y8lGw<#&t-tJ^nXpz}6=-)T{ah~xzkXPk`%IT5voWnb z)S5D3TtpK2Gc;9!DNV5oV8qnrq;{IU#%&)lmf!9~XwII?eL1Rc5i}*@Ro{r(ylwv^ z6docPsj8=~vkcv<{GEMpjYqh6+>%U}mHsn&Z|3L1GijyNbpCL%;v@PjfdF)GYz=Cd z3BdZT$Y!)T{~putBKahog`DsOuB6uUCT3a{k19kd&Fe3jMOOY^jN(;kX10Fi^^)-^ zMI&Pak*tb%9a-q?M-ictUF|8@#j}!ev*2jmde&Z@+W9;FCvT7nToM#+1?yK4%9nDxhhEF6=PzmhG|G@Th_$+NNEBF9u(MjB!JhnTTzGq)lWFn`_Rj=otRvsfLn zQKuH*+x36w&|^3dLmx7NZGzde_mWWwi3fU8#Xb?;*I2;@c^3vBl;t!_)W(2xxr1$| z3C-D9>S2jm!(8@c{3Ze-Kcfy%6#D#xyaViCH=!5x9Y)avkTP#ej7F&px zCi%F^8l#YiGw->BH6Z&2J6oe^dfZISriUF$OS@x2yEaqXsBj1kk!yVhk^{Z}@FN6onh1K1GgN4+J8I|>p zgL_{1vU}@~G-R4cB0rQTl(uUL(QLx^-$1ftrQ^ugsB>Gl{{Z6lNHK`OYUuhKSkGK} z9nBW`vr+?W-|jauj72K6z>Lz(db%tU86Flv089S>j93!lVVI^A&4%K5ODWG2K=-md zdvh_hv7_MOG;A$jutrig@KI^X7d%Doj?xXRB+g7Z=;h?tb_lB!GRB9nv#kt!!lQ#e zT*M4VxNh?`UF2L5o{pbmUs5vd_R3V-ZSr01ovCZ}(?_nA)O(R7=F@p5Anfb}VpoZz z6SeyH6Mcgj#ow>rlPv74ajq9=*XUB5Csj>r?IE?h3~Kd}TxgMBCRugr(&P0ejlUms z$z!*W4EIg9%POA$7`3qRELtH3~D78z{}j>@S2E z5uw>dMg_^P*8;>I!b_yrPDINaKqoj0~a3n-ZahwfNDExyX5WPIqXfLZ^=oEIgn z(QT*tD|M>!M80dhoW?s*15IexzmaT60Y zZI*tsJ#T7jWx<@uakjas7Lm7hXH(YQK*rc1MYvJUL!zSo5Sq^moZnCqq~R&ZVUM1xW_7@-$b1@4OPJHp|9~l zG;JSJ_Ym2f5>>gX?b62){QG&EnjSXF)vCCC}H@ zj%T8-L~}Kp%=kw>>|?pIFDPAvncziH>R*BAYI)YWGK@Sp9l@duvsX z6vVQ#HO7;%#0I?r#xmd4c+nRlNou-ad}Q>mur+D**(`n3t)Kq@A{D2*3rsfRNCUBw z)fePn$s`f@IS=epBXT{%Bue%$>GDqa*S~C&cV&iqQBb+Amzf%!n=C83i!`p;m~5z- z@$9a);#nuG_*bWWRA;`%*no>6DQ!gf_D%l)WNV(Wqyh_ZIb3QzT+k8B$2*kc_@b&~ z9EgR6U^npXTM=mm{WMq@BNHH-Kyl9Ci&v*Qj-e4tzc|L8nuS_vGds8C4j*}kUuxx4G8PJFkfZkskid>lzEg@RYk}BpBl+m3N z1T`Ys`4=2c=GDhTN3oCMcWPr=kNgG_6+JmzM`Ehyc5#XHiGyR;8s8ErzQZ-q%Ik8K z-8XcLS+dIe8S@1cjP~*S-RuUkj|@%)Wa;pv^ungxyGDa&K=oe7dC$Wf?&$4PDZ$vN zimH$BuC;78x|+5ESIMDtXU?HB;lEq1xTFIE8T_ncC z)liwoIa_^WSonQQuX4%UY7H_>cgqxwG!86EXb{4_-S}dP1EkhMv@l^FmF1HU#z?1d z3d8p`uXE=>Svf8{Vi^@$vt~YVQIB>+t^}e#T#^$@0V0vV(_79WV4B=B@Qm+sSk%H1 znCJ6~oynx^u%}g8YHn@4g8Oi?eU7C!Sg_agNZV6q&|4YMTd zePFniU#><6ozJjmfJBC=JPd2N^bZZD^gxwE8L++87MV8blp28Q44issVlgY)Sw&Qkb+mkD>=GC{&+G6XYn)g`8(Yg&k{!z)%M&Z3VZYnmqAu66PK_eM)bapQc^%K7+scez@Dyvx zrx{$FNwStX7CkAjWSHb1T1S%KqvCSKsj7eK3rdLv8#gS$vB)P$FakA;=_<> z=T~D2sgYdlI(D_v9E!q)>On7Sxn?WAphg_K3V2BxDR|<--XkIHH^hWDyo7h79Op1@ z;Ub{irU@LL)p(L}5#FC|%C4ZxgI-DYcH)Xcf%1;&2dDE2OX8Mmib2HAd0hKUsS=#B zxbnHKemp9;<|};xCRAL7wBOgX`!2^SL(L?vi?iKBX1UaM25YyN#tz}OG9IwTwXLhP zEo=KlU_dfc9cu;P3H4ayWau=}v*|xJ;|7@(xg&*Aw;SBWWXGZ>VO8;lnM4E$D*+%? z`%SEf1-G|lG*cYX&G<3uix`bXIT5&rk)8po%F#LfsF~#c85+&e1KjRmRS@amGBB{- znJ7w$h`()4ax=UL_XEZykJ>>Z;Y0V~M8^+1bB|R{b62`7NA4tvWhc{CG_%`Fr&^fv zqRO0;#Dp>;C*j6$zKERQ!_EjUbr7p^`F zkTKQK4LxbBL&i0#?9#o(Xy(3UndC>PYq<4BrZy_M8w{(AhSgJ34H3|sB@zh`B1WOf z%8|*#pNs0p@VchP+DVBvvuv)NX;)llY?R~#i+Je2C{9XP^MYy(akY|2rhjqQ5mC6u zwYd^n4;DmjT(M;^&x0MyY09j)jzXLq-08CI3gi&LQ?c1)+uZveRbFU5+lVu2Dzje8Un*l-H`uDrdM?V~NgxS>8z-=lxH=21MTJOEE8KW|JL zRME|_F>B<+li`KqR>upjoC zM|ereIPYG0UU=s?&ehH{?;7_ahC`c!AkMFbj>!-usu~o4=hl7ah6-np3~}az9boQV zJp4GsBb=P$n&kZc@i<&W&c(sUSFJXiVP9=eQSN@)M zrmn&Ib=fK!?v?I{Wze=TLP#^?qg3Z}**+l(K@$*hxt*^~7*(%kck!NHbDZOh?p)#T zK|+#c_(?2LQ6sVX+q=JQ$THOQ#^&}<{lD)?h3Pf;3|$fRtQNCaOx84tF$4aa>Im;~ zCc4k7u|2-oGNM_CdJ8ne*2xYmcjATEqE_WjtFzzb7wOA=wvP++zy^vZ>7P<$gMOYm zZ3Us|pG!h&{*d&TCKwJ;$+ed2DbepHNX+NeNJhOdXyRK4=#O}p&u?|MzByibO z^fyR8mFY~L0Y->FBk^AEPIJB4+_E(orev4A1 zYt1%YJeSkU8?7{5oN7%Lz?fd2P3Etp8$HCe{WNTCln>K(ge8yBu9pa4>CTH9EoP0Z z1TcX%wS}pzn$OyLv$1r&NP?`^HR@(3IN#%vCPxAW6>MVisE=oR16vd?zv)d?{L}PO z+<7mh+sslkQ?d48>CW9VBlIHV@76kBr|WRyj1}>f&UPFHHb$aI=e(kQ$k(Uu=k^`G zGe^1`!9%x2kO>>$n&jM$3fQ(mXR3{uh>cJ4edXEXAnfaUiFbQ1raRh8cTV@Q=W#R2 zT=JI{?_B%NKf(V1zehE%MvbiXUK0pmi5gUMdzU=L=e_HSisVN9@V=kl{{VCEB{3Y< z9M!LVtZ}O+MI#z!yBrOn@&YM-?pMkAT77_rUy<*0c|{5h=+qMOp>vnp@V&g}V{?Sz z5gX~kE^+Dio-S9w7iS5dUxy$57a8m!=d+ET&L@BW!~j$g009F61Oo*G2LuNM1_=cL z0RjUN00adRAu$FMK?fpHVQ~~PfsrFZQlYWI@DLSZ(FhhGGlG)gBy!U67eiBHld>f+ zG*yG5qr%e|CPZ_Tf?Y z>9r2#fBaWJ;-iWz>V+(S@f<(>)$NZg{{Za{d=$U<=v_Xa{{WKb`P}~i@>1{AG*#Q6 z{{a1p{{V`Wiavo2UcEkQC;8FF?`OMw?0>so)*N{s-Mr$z!ARn6DH`Ni9${66V|d%- zs^JAA_@w>O_^O)4E>cOlQe&h?->2tF#uysSD~PNb)#oGOU)GEht;gJdm48`qAe`#B z)6o9_Dn}IH4wz-)uS7V11e$LBr&p0ESU<$mi2Kx#C^~~makQ9GuV`p(ucovvnLFuT z*3yXxNk1>l(78#|Dfw5mX(6-v_)xi7{{X5#AM~2%xv_8W{{Tu4zISi${{WRK*GAum z`qG|I9mMNK>g!~*pNT(^p!|E<`Uw93?lf)>M1B7N>0Z>Hsr&x`(!HUN3igY5nh&sg z*D;-{QBvQCr6P7zdPOORqq^w_(Lc(UWqzUEyCDAnxGLiX$6VE{Nkh;9{OQamidhCP zioH~SD5tJCzR7uT=qMTIy%QUpvk9kY~0K|9xDq8TcFYD03>&oB*$1* zqoMH{RlUus)ert3m-^MZEL4(P2{fnhhI@{L4~P`PFlSZtC_z3%AHdS^>sgex5`8Q8 zTQfPn;Yq?+Ns?#bLdwlLgw|G)APRBDQms?`zVFViZ*AEUxQ|MEi15^y49Tasg#rp~ z1Roy1jW>kxh5fp3FzgxLQnd;UEomdf9I^4MUL4|UtJZBU+-lt`_ms7^65f(I!8waW z?3B0?RCF5{!}|qFh;tFe%dPk}grJWC6s}++q-7h!QLAZBnUPJ=Sn_?1aD zeZ&2VVplwidqh6y6BfMXZevox$Ts@NPmO=j=Vz~j%EPk%0P*fW;*XUd=<~qqaH!l| zM}~B1^K74)t54|nz*f)moKw!H?5$!y`4WYF^9Lw$bTf))d=C?zRNXp*HE1HGpDYGQ{5cBk%} z7f<-D1pIRlJ!tTcXpGu6^VoX;m;@@}Af|UxyMZvCxeAZj2Dp~jjd*3RX~ZmT`EfEX zn$|wbR8qBgfRvN*(wD`U>-mE$A+;nDLdY9*5~P(LkOcWr;{Gdh+{iv(nXl_t5`}W! zaU;)29(rc`fvXm=YwWHsiLDTVb)4Xir@E_j^x8-YFp#ONJvX;ENw>v#jh0fSDIldK z%~HIQVJS)*SG*QfJFsJTy}>ZcW`nKqMr5bnS1XtnPM{?%p+x`z3Rx}yNE_)~MDkc^ zx3El5xZ<~Lwo{Nm)Vkss3)k$nnDI2cXCGx*7V)2ft9)CDUu3+~iOQIq%0hSW1z}2# zxl&_#zsE8AJEhyRaF-G`QWK#c9Y@FTt4w2xm6i9uxt&f_lO16tXq`6H1LIsX!kC8; zEh;$0vuIzql9{+BGZ3~?=?G97z;R|ZAXgCZXJ|MQ-F2%pT-v8zV{QOWqyfuRFFDh= zkiASLN;LJ(Glwzlc;*`YyJPDurvMY?Qv>H#*!CN9{nPRBicTWX{{XBC`2LM1^{v`5 z_*d@+{;}4Vh%}VWVOZum=1xm)AwT2SwR38U>#J?z+Sizi(|Qv;@jDbQ*2>P=xBIE*oV z+WVZ#f+A$)PmWR6dXuMuMI9)SOLehWwjk}R3U!E7RhY*(w-aj%u+`@bB>;raUDQC^ z#ERjL$}KSF6a8S}x8HlaDmP3KIYhz|>s{Q_-C0(VD(PRkxF-Qp9dgh2bJ+)fkeF_I zm&zP#Fg@K|g%|`XMPl!8XBXSKfa3W3w|5zp$>rND2FRUxOtcd5$_P@4J=rbt@g_DMj{mJOd%y*#brX&sS4*d5hSG`6sVKGLXlX0H-#e) z7uIFx7*}{Gvv#!{#zEBuMLOs`&^CnC&lRx6vfXKy-B?K}K(x9^QRo3vP$zA6>s)=s z+*%lD?4ZmK0{{Zo>6z$K4vFpKMh_b6^6k#VebRPg!l_V&gyRrOJr2+`d zKWY7n$^@G2YZKw?Y$g0Z5V&Qx-cEr@Qj|`iB&D)d5u7DTK}r`32QpHVX@}jq=4&Jb zpvQS#)2{n>kHbp-t(&(JyX(S0=$$_Q0KSuLY=-jlWnLyfDtU(TLW+N13cG2y11_k- z+kgnsK#g+)_z||=6@vDq#XKMjO}?9!x+U_r3VL@t$te-g81mk(`xylCTW-6;#y z1PJ9MAFIny%6(J{x{^+{iQ?GCH+sAK^&mQ_l7zO{GrWe9kV&0M)b%EdN=TX3i_fz{ z;mQ||q~;s?R}OIJ5b<^Qu;7ey7u4($Cv_<3KOZAkU7c{R3#>84xbOQ&8nJBATIa)< zqX{H!%t|!ZYI5(gy9d(aEMHu~?;+P(Z87T7RI+9?l=}3nLhxPPzliYVn~d55At0$i z6C14SsqyiOajbFV=W(@V@iW)YYOvufa}l*$_SP@DK?xkTiqqm{L#P>hoAD*=@xF^LbN`z>4mpFFW`w!jsEvanj@2zV(83d_HDv&@RlOTXb zqOnfI_#XVN+ooJDFHGUpsYAfY&8X>~M~v4Nc4d#?+(mI^!xmR=TBLxL7L;eYC%lB^ zP?>@w%uNh0irKwR9OF)*>GxuA9}z1a^wke@kI;5jr;Khl*f&&Y;LT zh^FkWMZrbe30jXwG$YOefuD)=tRsWFGsMU4>{}9AZic6EXK27D_Tw^S&~okPs8{lE@p$m=?P zy%mXI>{>XlV|@;#BUmKtAdsMxgRY=-(0BJLwRtT2Nl$WOX(CmX8`ZWIj~7ENFtByp z5H$1aPw=K8XKI(P9)*xhM?t)1Axa~4LNpy~jc^Vq!PFmK3G8fvyJ~UNboZPtx{(o} zNk*H4y(bdH(wEEb|hwq|e!mq0HTV z%)WJJ*k(I?qA@H{2}kgPXUkl_pPgX6mSUVAi!pt|xVvHOs!N>4f!A;oAdIUcDNxF^ zjYe$53`)bbm)W*{%I3_$%;FKuOu+u^DO|uo@q}szNfm8={{XNw{xH|Gty`p&st>A8 zsp<&;#AZrNfzQHDOsW#2-GFsIN7L@oxXXmB@q5;B)-GPP#2HG09BDJ=1A;rr2kPcX zR%HTDObwni!~?dN#ln4sCn=WJhuuPxtgI!ZAb>=2lBm@j%*oDp?}KkR#|ujE%cmH8 z5AM{Za+d@sb6XjNj)V!&#Vz zLLFMvgsEB7Ga1yx9nXw;`_#hj4}3YVU5!~Amu`)!)*!)

PQBUzFM1pEQ<+N?Vb$JE*! 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 MN=z(n!j9U*5;Im~&OYYW+Svpy7wrRRa3mWsv(T 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ẀLc$7Z+zHg}(8=VVD{uV1vwefB)pehoo(H4_j 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

UA;RX)`o9l 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
    ;G~Tr-KiRMK`B> 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 literal 0 HcmV?d00001 diff --git a/extraImages/ScriptingCallTrace.png b/extraImages/ScriptingCallTrace.png new file mode 100644 index 0000000000000000000000000000000000000000..01fd91f51e1e9a0fad0a817459baebb303eaa05b GIT binary patch literal 303058 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%0Aa+Z15&Tt#GD*` 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%0D)vDwi`a=W_Zwk?uHH@9ZdTLsNH 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%0DxRJwRpE$Iv>GIR31w(D}~z;bdty{~EiseEx{ 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 + + +LuaJSCPythoncivInfogameInfoScriptingScope(Dynamic Access)Interpreter(Foreign)ConsoleScreen(Input)ScriptingStateScriptingBackendScriptingReplManagerBlackboxScriptingProtocolTokenizingJsonInstanceTokenizerReflectionScriptingBackendModHandlers(Input)ScriptingStateScriptingBackendBlack:CommandRed:Reflective Access (Repeatable)Purple:ResultRectangular:HardcodedOval:Dynamic/UnconstrainedLuaJSCPythoncivInfogameInfoScriptingScope(Dynamic Access)Interpreter(Foreign)ConsoleScreen(Input)ScriptingStateScriptingBackendScriptingReplManagerBlackboxScriptingProtocolTokenizingJsonInstanceTokenizerReflectionScriptingBackendModHandlers(Input)ScriptingStateScriptingBackendBlack:CommandRed:Reflective Access (Repeatable)Purple:ResultRectangular:HardcodedOval:Dynamic/Unconstrained From 20c9913f0637f6cc2b576be75506ac4e14aafbf9 Mon Sep 17 00:00:00 2001 From: will-ca Date: Tue, 23 Nov 2021 17:38:11 +0000 Subject: [PATCH 52/93] Rixed missprerring of rinciple. --- .../assets/scripting/enginefiles/python/PythonScripting.md | 6 ++++-- core/Module.md | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/android/assets/scripting/enginefiles/python/PythonScripting.md b/android/assets/scripting/enginefiles/python/PythonScripting.md index eee7045bbe762..9786ec664ccec 100644 --- a/android/assets/scripting/enginefiles/python/PythonScripting.md +++ b/android/assets/scripting/enginefiles/python/PythonScripting.md @@ -315,6 +315,7 @@ def slow(): # +1 IPC "read" action for the current .naturalWonder if reaching the "else" block. (Only gets resolved on serialization in the next step.) # +1 IPC "assign" action to update .naturalWonder, even if it's not changing. # This happens once for every tile— Hundreds or thousands of times in total. +# Total IPC actions: ~1,000 to ~15,000. Just below 3 on average for every tile on the map. def faster(): sizex = len(gameInfo.tileMap.tileMatrix) - 1 @@ -333,11 +334,12 @@ def faster(): # Only done after already selecting coordinates and checking validity. i += 1 # Only iterate for as long as needed to make the wanted changes +# Total IPC actions: ~40 to ~60. Only one assignment, plus one check, for each tile that actually changes. def fastest(): apiHelpers.scatterRandomFeature("Krakatoa", random.randint(15, 25)) - # Only 1 IPC "assign" action. - # This function doesn't actually exist. But the point is that when available, a single IPC call that causes all of the work to then be done in the JVM is likely to be faster than a script-micromanaged solution. E.G., use one call to List<*>.addAll() instead of many calls to List<*>.add(). +# Total IPC actions: 1. All the heavy lifting is done in the Kotlin function it calls. +# The "scatterRandomFeature" function doesn't actually exist. But the point is that when available, a single IPC call that causes all of the work to then be done in the JVM is likely to be faster than a script-micromanaged solution. E.G., use one call to List<*>.addAll() instead of many calls to List<*>.add(). ``` Every time you access an attribute or item on a foreign wrapper in Python creates and initializes a new foreign wrapper object. So for code blocks that use a wrapper object at the same path multiple times, it may be worth saving a single wrapper at the start instead. diff --git a/core/Module.md b/core/Module.md index fed90fe7b8f36..812e004089b49 100644 --- a/core/Module.md +++ b/core/Module.md @@ -3,6 +3,7 @@ ## Table of Contents * [Package `com.unciv.scripting`](#package-comuncivscripting) + * [Design Principles](#design-principles) * [Class Overview](#class-overview) * [Package `com.unciv.scripting.protocol`](#package-comuncivscriptingprotocol) * [REPL Loop](#repl-loop) @@ -11,7 +12,7 @@ # Package com.unciv.scripting -## Design rinciples +## Design Principles **The Kotlin/JVM code should neither know nor care about the language running on the other end of its scripting API.** If a behaviour is specific to a particular language, then it's also too messy and complex to try to take special account for from the other side of an IPC channel. Instead, the complexity of each specific scripting language should be handled entirely within that language itself, such that the only thing exposed to the Kotlin code is a common interface built around structures that exist in most computer programming languages (like command strings, attributes, keys, calls, assignments, collections, etc). This not only keeps the scripting protocols and interfaces compatible with multiple backends, it also serves as a test that helps keep their design relatively clean and maintainable by forcing messy or complicated behaviours to be implemented in more appropriate places. From 1612bfa3740c0e1898994fe96162777e637dfba7 Mon Sep 17 00:00:00 2001 From: will-ca Date: Wed, 24 Nov 2021 03:10:23 +0000 Subject: [PATCH 53/93] Docs, diagram. --- .../enginefiles/python/PythonScripting.md | 53 +- .../enginefiles/python/unciv_lib/api.py | 1 + .../enginefiles/python/unciv_lib/wrapping.py | 2 +- .../unciv_scripting_examples/PlayerMacros.py | 2 + .../ProceduralTechtree.py | 16 +- core/Module.md | 4 +- .../com/unciv/scripting/ScriptingBackend.kt | 4 + .../src/com/unciv/scripting/ScriptingScope.kt | 2 + .../scripting/protocol/ScriptingProtocol.kt | 3 + .../protocol/ScriptingReplManager.kt | 4 +- .../scripting/utils/InstanceFactories.kt | 2 +- extraImages/ScriptingCallTrace.odg | Bin 20198 -> 20006 bytes extraImages/ScriptingCallTrace.png | Bin 303058 -> 251599 bytes extraImages/ScriptingCallTraceExported.pdf | Bin 43337 -> 43335 bytes extraImages/ScriptingCallTraceFiltered.svg | 5139 +++++++++++------ 15 files changed, 3382 insertions(+), 1850 deletions(-) diff --git a/android/assets/scripting/enginefiles/python/PythonScripting.md b/android/assets/scripting/enginefiles/python/PythonScripting.md index 9786ec664ccec..60e1c3eafe4ff 100644 --- a/android/assets/scripting/enginefiles/python/PythonScripting.md +++ b/android/assets/scripting/enginefiles/python/PythonScripting.md @@ -297,7 +297,7 @@ def printCivilizations(): ## Performance and Gotchas -Initiating a foreign actions is likely to be expensive. They have to be encoded as request packets, serialized as JSON, sent to Kotlin/the JVM, decoded there, and evaluated in Kotlin/JVM using slow reflective mechanisms. The results then have to go through this entire process in reverse in order to return a value to Python. +Initiating a foreign action is likely to be expensive. They have to be encoded as request packets, serialized as JSON, sent to Kotlin/the JVM, decoded there, and evaluated in Kotlin/JVM using slow reflective mechanisms. The results then have to go through this entire process in reverse in order to return a value to Python. However, code running in Kotlin/the JVM is also likely to be much faster than code running in Python. The danger is in wasting lots of time bouncing back and forth just to exchange small amounts of data. @@ -312,7 +312,7 @@ def slow(): tile.naturalWonder = "Krakatoa" if random.random() < 1/len(gameInfo.tileMap.values)*20 else tile.naturalWonder # On every loop: # +1 IPC "length" action in the "if". - # +1 IPC "read" action for the current .naturalWonder if reaching the "else" block. (Only gets resolved on serialization in the next step.) + # +1 IPC "read" action for the current .naturalWonder if reaching the "else" expression. (Only gets resolved on serialization in the next step.) # +1 IPC "assign" action to update .naturalWonder, even if it's not changing. # This happens once for every tile— Hundreds or thousands of times in total. # Total IPC actions: ~1,000 to ~15,000. Just below 3 on average for every tile on the map. @@ -327,13 +327,13 @@ def faster(): x = random.randint(0, sizex) y = random.randint(0, sizey) if real(gameInfo.tileMap.tileMatrix[x][y]) is not None: - # +1 IPC "read" action. - # On hexagonal maps, check for validity. To be faster yet, could numerically do this in Python. - gameInfo.tileMap.tileMatrix[x][y] = "Krakatoa" + # +1 IPC "read" action. + # On hexagonal maps, check for validity. To be faster yet, this could also be done numerically in Python, or short-circuited on rectangular maps. + gameInfo.tileMap.tileMatrix[x][y].naturalWonder = "Krakatoa" # +1 IPC "assign" action. # Only done after already selecting coordinates and checking validity. i += 1 - # Only iterate for as long as needed to make the wanted changes + # Only iterate for as long as needed to change the target number of tiles. # Total IPC actions: ~40 to ~60. Only one assignment, plus one check, for each tile that actually changes. def fastest(): @@ -397,13 +397,52 @@ for i in range(len(civInfo.cities[0].cityStats.currentCityStats.values)): for e in real(civInfo.cities[0].cityStats.currentCityStats.values): print(e) - # Works. + # Works. But yields only primitive JSON-serializable values and token strings, not wrappers. ``` Because the elements yielded this way do not have equivalent paths in the Kotlin/JVM namespace, and are not foreign object wrappers, any complex objects will have to be assigned as token strings to a concrete path in order to do anything with them. --- +## Error Handling + +Usually, errors in Kotlin/the JVM during foreign calls are caught by the Kotlin implementation of the IPC protocol. They are then gracefully serialized and returned in a specially flagged packet, which causes a `unciv_lib.ipc.ForeignError()`/`unciv_pyhelpers.ForeignError()` to be raised in Python. + +```python3 +>>> uncivGame.fakeAttributeName +#TODO + +>>> civInfo.addGold("Fifty-Nine") + +``` + +Because of this, malformed foreign actions requested by Python usually cannot crash Unciv. In fact, it is possible to catch such errors in Python to create Python-style exception-controlled program flow. + +```python3 +try: + print(gameInfo.civilizations) + # gameInfo is null in the main menu screen. +except ForeignError: + # Comes here with ForeignError("java.lang.NullPointerException"). + print("Currently not in game!") + +# (I still don't like it personally. I guess it doesn't incur any major overhead since you'd need an IPC action anyway to check validity/LBYL, but this seems kinda gross.) +``` + +The only major caveat to the robustness of this error handling is that it does not protect against valid Kotlin/JVM actions that lead to unexpected states which then cause exceptions in later use by unrelated game code. Assigning an inappropriate value to a Kotlin/JVM member, or deleting a key-value pair where it is required by internal game code, for example, will likely cause the core game to crash the next time the invalid value is used. + +``` +>>> gameInfo.tileMap.values[0].naturalWonder = "Crash" +# Executes and sets .naturalWonder to "Crash" successfully. +# But the game crashes when you click on the changed tile because there aren't any textures or stats for the "Crash" natural wonder. + +>>> del gameInfo.ruleSet.technologies["Sailing"] +# Executes and removes "Sailing" technology from tech tree successfully. +# But the game crashes if you try to select any techs that required "Sailing", because they still have "Sailing" in their dependencies. +``` + +--- + ## Other Languages The Python-specific behaviour is not meant as a hard standard, in that it doesn't have to be copied exactly in any other languages. Some other design may be more suited for ECMAScript, Lua, and other possible backends. If implementing another language, I think some attempt should still be made to keep a similar API and feature equivalence, though. diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/api.py b/android/assets/scripting/enginefiles/python/unciv_lib/api.py index fe284389704cb..1ac2b2adfffde 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/api.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/api.py @@ -2,6 +2,7 @@ from . import ipc, utils +#TODO: Expose ForeignError class. enginedir = os.path.dirname(__file__) diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py index 8baf7ed37631a..0a9fb13f6b965 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py @@ -88,7 +88,7 @@ def stringPathList(pathlist): _magicmeths = ( '__lt__', '__le__', - '__eq__', # Kinda undefined behaviour for comparison with Kotlin object tokens. Well, tokens are just strings that will always equal themselves, but multiple tokens can refer to the same Kotlin object. `ForeignObject()`s resolve to new tokens, that are currently uniquely generated in InstanceTokenizer.kt, on every `._getvalue_()`, so I think even the same `ForeignObject()` will never equal itself. + '__eq__', # Kinda undefined behaviour for comparison with Kotlin object tokens. Well, tokens are just strings that will always equal themselves, but multiple tokens can refer to the same Kotlin object. `ForeignObject()`s resolve to new tokens, that are currently uniquely generated in InstanceTokenizer.kt, on every `._getvalue_()`, so I think even the same `ForeignObject()` will never equal itself. # Actually, now raises exception when used with tokens, I think. '__ne__', '__ge__', '__gt__', diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/PlayerMacros.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/PlayerMacros.py index c45841ab4e001..c4956c98ce1b0 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/PlayerMacros.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/PlayerMacros.py @@ -74,3 +74,5 @@ def rebaseUnitsEvenly(units=('Guided Missile',), ): #focusCitiesFood(clearCitiesSpecialists(civInfo.cities)) #apiHelpers.toString(worldScreen.bottomUnitTable.selectedCity.cityConstructions.getBuildableBuildings()) + +#worldScreen.mapHolder.selectedTile.terrainFeatures.addAll([k for k, v in gameInfo.ruleSet.terrains.items() if v.type.name == 'NaturalWonder']) diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/ProceduralTechtree.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/ProceduralTechtree.py index 890256de71e4b..20cb53d156b69 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/ProceduralTechtree.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/ProceduralTechtree.py @@ -27,7 +27,7 @@ ), "Unit": ( ("Combat ", "Missile ", "Patrol ", "Gun-", "Tac-", "Xeno ", "Rock-", "Battle-", "LEV ", "Drone ", "Auto-", "Nano-", "Gelio-", "All-", "Laser-", "Phasal ", "Solar ", "Wolf ", "Raptor ", "Siege ", "Sea ", "Hydra-", "Tide-", "Under-", "Needle-", "Evolved ", "True ", "Prime ", "First ", "Master ", "Elder "), - ("Explorer", "Soldier", "Ranger", "Rover", "Boat", "Submarine", "Carrier", "Jet", "Swar", "Cavalry", "Octopus", "Titan", "Suit", "Aegis", "Tank", "Destroyer", "CNDR", "CARVR", "SABR", "ANGEL", "Immortal", "Architect", "Throne", "Cage", "Sled", "Golem", "Hive", "Pod", "Aquilon", "Seer", "Matrix", "Laser", "Carver", "Siren", "Batle", "Bug", "Worm", "Drones", "Manticore", "Dragon", "Kraken", "Coral", "Makara", "Ripper", "Scarab", "Marine", "Brawler", "Sentinel", "Disciple", "Maurauder", "Centurion", "Apostle", "Champion", "Eidolon", "Hellion", "Striker", "Guardian", "Overseer", "Shredder", "Warden", "Executor", "Kodiak", "Virtuoso", "Fury", "Armor", "Viper", "Lancer", "Prophet", "Cobra", "Dragoon", "Redeemer", "Gladiator", "Maestro", "Savage", "Artillery", "Centaur", "Punisher", "Educator", "Minotaur", "Devastator", "Ambassador", "Cutter", "Screamer", "Broadside", "Tenet", "Reaver", "Cannonade", "Edict", "Argo", "Baron", "Vortex", "Cruiser", "Triton", "Destroyer", "Arbiter", "Poseidon", "Dreadnought", "Vindicator", "Mako", "Countess", "Wrath", "Hunter", "Lurker", "Taker", "Whisper", "Leviathan", "Eradicator", "Shroud", "Hydra", "Bastion", "Shepherd", "Locust", "Raider", "Herald", "Shrike", "Predator", "Seraph") + ("Explorer", "Soldier", "Ranger", "Rover", "Boat", "Submarine", "Carrier", "Jet", "Swarm", "Cavalry", "Octopus", "Titan", "Suit", "Aegis", "Tank", "Destroyer", "CNDR", "CARVR", "SABR", "ANGEL", "Immortal", "Architect", "Throne", "Cage", "Sled", "Golem", "Hive", "Pod", "Aquilon", "Seer", "Matrix", "Laser", "Carver", "Siren", "Batle", "Bug", "Worm", "Drones", "Manticore", "Dragon", "Kraken", "Coral", "Makara", "Ripper", "Scarab", "Marine", "Brawler", "Sentinel", "Disciple", "Maurauder", "Centurion", "Apostle", "Champion", "Eidolon", "Hellion", "Striker", "Guardian", "Overseer", "Shredder", "Warden", "Executor", "Kodiak", "Virtuoso", "Fury", "Armor", "Viper", "Lancer", "Prophet", "Cobra", "Dragoon", "Redeemer", "Gladiator", "Maestro", "Savage", "Artillery", "Centaur", "Punisher", "Educator", "Minotaur", "Devastator", "Ambassador", "Cutter", "Screamer", "Broadside", "Tenet", "Reaver", "Cannonade", "Edict", "Argo", "Baron", "Vortex", "Cruiser", "Triton", "Destroyer", "Arbiter", "Poseidon", "Dreadnought", "Vindicator", "Mako", "Countess", "Wrath", "Hunter", "Lurker", "Taker", "Whisper", "Leviathan", "Eradicator", "Shroud", "Hydra", "Bastion", "Shepherd", "Locust", "Raider", "Herald", "Shrike", "Predator", "Seraph") ), "Wonder": ( ("Spy ", "Culper ", "Tessellation ", "Machine-Assisted ", "Dimensional ", "Folding ", "Dimensional Folding ", "Quantum ", "Temporal ", "Relativistic ", "Abyssal ", "Archimedes ", "Arma-", "Benthic ", "Byte-", "Daedaleus ", "Deep ", "Drone ", "Ecto-", "Genesis ", "Euphotic ", "Faraday ", "Gene ", "Guo Pu ", "Holon ", "Human ", "Markov ", "Mass ", "Master ", "Memet-", "Nano-", "New Terran ", "Pan-", "Precog ", "Promethean ", "Quantum ", "Resurrection ", "Stellar ", "Tectonic ", "The ", "Xeno-", "Emancipation ", "Exodus ", "Mind ", "Transcendental ", "Decode "), @@ -42,13 +42,21 @@ def genRandomName(nametype): #Mix and match from BE. prefixes, suffixes = name_parts[nametype] prefix, suffix = random.sample(prefixes, 1)[0], random.sample(suffixes, 1)[0] return prefix[:-1]+suffix[0].lower()+suffix[1:] if prefix[-1] == "-" else prefix+suffix + # Could make first letter fit second letter's capitalization. -def genRandomNameUnused(nametype): - name = None - while name is None or name in usedNames: +def genRandomNameUnused(nametype, *, maxattempts=1000): + for i in range(maxattempts): name = genRandomName(nametype) + if name not in usedNames: + break + else: + raise Exception() + usedNames.add(name) return name +def genRandomIcon(): + pass #Define randomized number of randomized shapes. Randomize order. Draw area union. Draw layer-occluded boundary lines separately. Subtract edges from area. + # def genRandomEra(): # pass diff --git a/core/Module.md b/core/Module.md index 812e004089b49..b08fdb458d218 100644 --- a/core/Module.md +++ b/core/Module.md @@ -16,7 +16,7 @@ **The Kotlin/JVM code should neither know nor care about the language running on the other end of its scripting API.** If a behaviour is specific to a particular language, then it's also too messy and complex to try to take special account for from the other side of an IPC channel. Instead, the complexity of each specific scripting language should be handled entirely within that language itself, such that the only thing exposed to the Kotlin code is a common interface built around structures that exist in most computer programming languages (like command strings, attributes, keys, calls, assignments, collections, etc). This not only keeps the scripting protocols and interfaces compatible with multiple backends, it also serves as a test that helps keep their design relatively clean and maintainable by forcing messy or complicated behaviours to be implemented in more appropriate places. -**Parts should be kept as modular and interchangeable as possible.** Each component type should have a somewhat well-defined job, and should not contain or be inseparably entwined with code that does things aside from that job. If a base class's primary role is to expose an REPL, for example, then extra features like command history or implementation details like running a subprocess can be moved into either another class or a subclass. Again, IMO this both makes it easy to support versatile configurations and helps with keeping a reasonably neat codebase and architecture. +**Parts should be kept as modular and interchangeable as possible.** Each component type should have a somewhat well-defined job, and should not contain or be inseparably entwined with code that does things aside from that job. If a base class's primary role is to expose an REPL, for example, then extra features like command history or implementation details like running a subprocess can be moved into either another class or a subclass. If an interface's job is just to propagate code strings or resolve request packets into arbitrary Kotlin objects, then TODO: Finish this sentence. Again, IMO this both makes it easy to support versatile configurations and helps with keeping a reasonably neat codebase and architecture. **Different levels of execution/evaluation should not mix.** The IPC protocol defines the packet structures, types, and communication order that are for *implementing* scripting language semantics for accessing Kotlin/JVM data. Therefore, the IPC protocol should not itself become a *part of* scripting language semantics; No user/mod script in any language should ever have to manually create and send or receive and parse IPC packets. Certain API functions have been defined to provide additional capabilities that are *accessible through* scripting language semantics (in class ApiHelpers). Therefore, those functions should never be used in *implementing* scripting language semantics; No overloaded operator presented to a user script as part of the core Unciv API should ever implicitly call such a method as part of its basic functionality. The entrypoints for the scripting system have the roles of taking code strings (from user input, from mods, etc) and returning a result string (to print out, log, etc) (and possibly an exception Boolean flag). Therefore, they should never have to understand, or even be able to use, any data aside from opaque strings (such as IPC packets or structured return results). @@ -99,6 +99,8 @@ Plus, letting the script interpreter run completely in parallel would probably i ![This simple, thirty-step process is all it takes to execute a single scripted command…](/extraImages/ScriptingCallTrace.png) +(There may be more calls to TokenizingJson than shown in the above diagram, because it's used as the universal serialization tool for encoding and decoding every packet, as well as the arbitrarily-structured JsonElement data in every packet.) + ## IPC Protocol *Implemented by `ScriptingProtocol.kt`, `ipc.py`, and `wrapping.py`.* diff --git a/core/src/com/unciv/scripting/ScriptingBackend.kt b/core/src/com/unciv/scripting/ScriptingBackend.kt index e8560beb466be..e749b9e698e3e 100644 --- a/core/src/com/unciv/scripting/ScriptingBackend.kt +++ b/core/src/com/unciv/scripting/ScriptingBackend.kt @@ -91,6 +91,10 @@ interface ScriptingBackend { open class ScriptingBackendBase(val scriptingScope: ScriptingScope): ScriptingBackend { +//A little bit confusing. +//Hm. Couldn't the metadata getter go on the interface? +//That would mean this exists only as a code example for defining the companion object. +//TODO: Move getter, delete class or rename to DummyScriptingBackend? /** * For the UI, a way is needed to list all available scripting backend types with 1. A readable display name and 2. A way to create new instances. diff --git a/core/src/com/unciv/scripting/ScriptingScope.kt b/core/src/com/unciv/scripting/ScriptingScope.kt index 486cead7c63da..d1956090a603a 100644 --- a/core/src/com/unciv/scripting/ScriptingScope.kt +++ b/core/src/com/unciv/scripting/ScriptingScope.kt @@ -36,6 +36,8 @@ import java.io.ByteArrayOutputStream * To reduce the chance of E.G. name collisions in .apiHelpers.registeredInstances, or one misbehaving mod breaking everything by unassigning .gameInfo, different ScriptingState()s should each have their own ScriptingScope(). */ class ScriptingScope( + // This entire API should still be considered unstable. It may be drastically changed at any time. + //If this is going to be exposed to downloaded mods, then every declaration here, as well as *every* declaration that is safe for scripts to have access to, should probably be whitelisted with annotations and checked or errored at the point of reflection. var civInfo: CivilizationInfo? = null, var gameInfo: GameInfo? = null, diff --git a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt index a99399019f310..414df48ec9175 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt @@ -28,6 +28,9 @@ import java.util.UUID // Use in scripting backend, E.G. wrapping.py +// FIXME: ...Numerical types are annoying: civInfo.cities[0].location.x += 1. + + /** * Implementation of IPC packet specified in Module.md. * diff --git a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt index c42b0b877d06e..a2b9c764d5332 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt @@ -48,7 +48,7 @@ class ScriptingRawReplManager(scriptingScope: ScriptingScope, blackbox: Blackbox */ class ScriptingProtocolReplManager(scriptingScope: ScriptingScope, blackbox: Blackbox): ScriptingReplManager(scriptingScope, blackbox) { -// TODO: scriptingScope can be an Any. +// TODO: scriptingScope can be an Any. Hm. "scriptingScope" is more readable within Unciv, but Any communicate a cleaner design and cleaner design constraints. Hm. That means "scriptingScope" communicates the wrong thing with its readability. It's called "scope" in ScriptingProtocol, which should be plently clear enough. /** * ScriptingProtocol puts references to pre-tokenized returned objects in here. @@ -96,7 +96,7 @@ class ScriptingProtocolReplManager(scriptingScope: ScriptingScope, blackbox: Bla return ScriptingProtocol.parseActionResponses.motd( getRequestResponse( ScriptingProtocol.makeActionRequests.motd(), - execLoop = { foreignExecLoop() } + execLoop = { foreignExecLoop() } //TODO: Replace with reflective method access? ) ) } diff --git a/core/src/com/unciv/scripting/utils/InstanceFactories.kt b/core/src/com/unciv/scripting/utils/InstanceFactories.kt index f6ccb14c914a3..f47db983a4e48 100644 --- a/core/src/com/unciv/scripting/utils/InstanceFactories.kt +++ b/core/src/com/unciv/scripting/utils/InstanceFactories.kt @@ -19,7 +19,7 @@ object InstanceFactories { object GUI { } fun Array() = "NotImplemented" - fun Vector2(x: Float, y: Float) = com.badlogic.gdx.math.Vector2(x, y) + fun Vector2(x: Float, y: Float) = com.badlogic.gdx.math.Vector2(x, y) //TODO: Accept number and cast instead. fun MapUnit() = "NotImplemented" fun Technology() = "NotImplemented" } diff --git a/extraImages/ScriptingCallTrace.odg b/extraImages/ScriptingCallTrace.odg index 6696ae0c95df3b3526d482d7a7e958b9e3d34c25..a12dceaf3f5ec0d46672e80c57e55e3a5dbf72d0 100644 GIT binary patch delta 18034 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 GAT0ox5It%D delta 18184 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≈wF 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 z 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 II08v6{Lh_(1nkbo-fR2`2gwKK? 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 Zc!xeQz#pXaj$1;2LIT45ulVwX{{wzmu;c&$ literal 303058 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%0Aa+Z15&Tt#GD*` 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 Ks=E5SaRC4oPRJ(! diff --git a/extraImages/ScriptingCallTraceFiltered.svg b/extraImages/ScriptingCallTraceFiltered.svg index a4f0e198f6f80..076c3810b9d3b 100644 --- a/extraImages/ScriptingCallTraceFiltered.svg +++ b/extraImages/ScriptingCallTraceFiltered.svg @@ -8,10 +8,11 @@ width="1056" height="816" viewBox="0 0 1056 816" - sodipodi:docname="ScriptingCallTrace.svg" + sodipodi:docname="ScriptingCallTraceFiltered.svg" inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)" inkscape:export-xdpi="120.47059" inkscape:export-ydpi="120.47059" + inkscape:export-filename="ScriptingCallTrace.png" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" @@ -192,7 +193,7 @@ id="path1048" />LuaJSCPythoncivInfogameInfoScriptingScope(Dynamic Access)Interpreter(Foreign)ConsoleScreen(Input)ScriptingStateScriptingBackendScriptingReplManagerBlackboxScriptingProtocolTokenizingJsonInstanceTokenizerReflectionScriptingBackendModHandlers(Input)ScriptingStateScriptingBackendBlack:CommandRed:Reflective Access (Repeatable)Purple:ResultRectangular:HardcodedOval:Dynamic/UnconstrainedLuaJSCPythoncivInfogameInfoLuaJSCPythoncivInfogameInfoScriptingScope(Dynamic Access)ScriptingScope(Dynamic Access)Interpreter(Foreign)Interpreter(Foreign)ConsoleScreen(Input)ConsoleScreen(Input)ScriptingStateScriptingStateScriptingBackendScriptingBackendScriptingReplManagerScriptingReplManagerBlackboxBlackboxScriptingProtocolScriptingProtocolTokenizingJsonTokenizingJsonInstanceTokenizerInstanceTokenizerReflectionScriptingBackendReflectionScriptingBackendModHandlers(Input)ModHandlers(Input)ScriptingStateScriptingBackendBlack:CommandRed:Reflective Access (Repeatable)Purple:ResultRectangular:HardcodedOval:Dynamic/Unconstrained + style="fill:none;stroke:#3465a4;stroke-width:0.75;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1" + id="path10590" />ScriptingStateScriptingBackendBlack:CommandRed:Reflective Access (Repeatable)Purple:ResultRectangular:HardcodedOval:Dynamic/Unconstrained From fb1505072f3e8549d0f9d54d37899457fdf87587 Mon Sep 17 00:00:00 2001 From: will-ca Date: Wed, 24 Nov 2021 16:07:05 +0000 Subject: [PATCH 54/93] Docs, fixes. --- .../scripting/ScriptingEngineConstants.json | 2 + .../enginefiles/python/PythonScripting.md | 9 ++- .../unciv_scripting_examples/EndTimes.py | 7 +- .../unciv_scripting_examples/Merfolk.py | 10 +++ .../ProceduralTechtree.py | 10 +-- .../python/unciv_scripting_examples/Utils.py | 3 +- .../unciv_scripting_examples/__init__.py | 4 +- .../src/com/unciv/scripting/ScriptingScope.kt | 2 + .../scripting/protocol/ScriptingProtocol.kt | 3 - .../unciv/scripting/reflection/Reflection.kt | 66 ++++++++++++++++--- .../scripting/utils/InstanceFactories.kt | 6 +- .../unciv/ui/consolescreen/ConsoleScreen.kt | 3 +- 12 files changed, 97 insertions(+), 28 deletions(-) diff --git a/android/assets/scripting/ScriptingEngineConstants.json b/android/assets/scripting/ScriptingEngineConstants.json index 966e58f03eeaf..bc06cc732f921 100644 --- a/android/assets/scripting/ScriptingEngineConstants.json +++ b/android/assets/scripting/ScriptingEngineConstants.json @@ -22,9 +22,11 @@ unciv_scripting_examples/MapEditingMacros.py unciv_scripting_examples/Merfolk.py unciv_scripting_examples/PlayerMacros.py + unciv_scripting_examples/ProceduralTechtree.py unciv_scripting_examples/StarryNight.jpg unciv_scripting_examples/Tests.py unciv_scripting_examples/TurboRainbow.png + unciv_scripting_examples/Utils.py unciv_scripting_examples/WheatField.jpg ] syntaxHighlightingRegexStack: [ diff --git a/android/assets/scripting/enginefiles/python/PythonScripting.md b/android/assets/scripting/enginefiles/python/PythonScripting.md index 60e1c3eafe4ff..659dd6357a04d 100644 --- a/android/assets/scripting/enginefiles/python/PythonScripting.md +++ b/android/assets/scripting/enginefiles/python/PythonScripting.md @@ -326,6 +326,7 @@ def faster(): while i < targetcount: x = random.randint(0, sizex) y = random.randint(0, sizey) + # Instead of iterating over every tile on the map, generate the coords in Python, and then work with foreign objects only after having determined the coordinates. if real(gameInfo.tileMap.tileMatrix[x][y]) is not None: # +1 IPC "read" action. # On hexagonal maps, check for validity. To be faster yet, this could also be done numerically in Python, or short-circuited on rectangular maps. @@ -369,7 +370,7 @@ def alsoSlow(): uselesscache = for i in range(1000): uselesscache += - # Assigning the wrapper object to a name in Python saves on Python attribute time. But it doesn't actually shorten its Kotlin/JVM path, so the same number of steps still have to be taken when the Kotlin/JVM side processes the packet sent by Python. + # Assigning the wrapper object to a name in Python saves on Python attribute access time. But it doesn't actually shorten its Kotlin/JVM path, so the same number of steps still have to be taken when the Kotlin/JVM side processes the packet sent by Python. def fast(): apiHelpers.registeredInstances["usefulcache"] = @@ -397,7 +398,7 @@ for i in range(len(civInfo.cities[0].cityStats.currentCityStats.values)): for e in real(civInfo.cities[0].cityStats.currentCityStats.values): print(e) - # Works. But yields only primitive JSON-serializable values and token strings, not wrappers. + # Works. But yields only primitive JSON-serializable values and/or token strings, not wrappers. ``` Because the elements yielded this way do not have equivalent paths in the Kotlin/JVM namespace, and are not foreign object wrappers, any complex objects will have to be assigned as token strings to a concrete path in order to do anything with them. @@ -439,6 +440,10 @@ The only major caveat to the robustness of this error handling is that it does n >>> del gameInfo.ruleSet.technologies["Sailing"] # Executes and removes "Sailing" technology from tech tree successfully. # But the game crashes if you try to select any techs that required "Sailing", because they still have "Sailing" in their dependencies. + +>>> civInfo.cities[0].tiles.add("Crash") +# Executes and adds "Crash" string to set containing capital's tiles' coordinates. +# But the game breaks when you press "Next Turn", because the JVM thread processing the turn tries to use the string "Crash" as a Vector2. ``` --- diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EndTimes.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EndTimes.py index 29fc9f53e928e..c22339035fdc6 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EndTimes.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EndTimes.py @@ -27,15 +27,14 @@ def scatterFallout(focus, improvementtype, maxdistance, tileselector=lambda t: T pass def spawnNewDisasters(naturalwonder, *falloutparams): - # Foreign calls are expensive. So instead of iterating over every tile on the map, we generate the coords in Python and then only work with the foreign object when we've already chosen the coordinates. - # Every attribute access in Python creates a new ForeignObject() wrapper, and every path element requires an extra reflective member resolution in Kotlin. So we reduce the overhead by reusing the same wrapper in Python, and by storing its target at a shorter path in the JVM. pass def spreadFalloutType(improvementtype, tilepermitter=lambda t: True): pass - - +#worldScreen.mapHolder.selectedTile.position in civInfo.exploredTiles +#civInfo.addNotification("A volcano has risen from the Earth!", worldScreen.mapHolder.selectedTile.position, apiHelpers.Factories.arrayOfString(["TileSets/FantasyHex/Tiles/Krakatoa"])) +#civInfo.exploredTiles.add(worldScreen.mapHolder.selectedTile.position) def eruptVolcanoes(): pass diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Merfolk.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Merfolk.py index cbb30b826e817..3bc81e553a05b 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Merfolk.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Merfolk.py @@ -26,5 +26,15 @@ #civInfo.cities[0].cityStats.cityInfo.resistanceCounter +#civInfo.addNotification("Test", civInfo.cities[0].location, apiHelpers.Factories.arrayOfString(["StatIcons/Gold"])) + +def moveCity(): + tileInfo.owningCity = city #Seems to be used for rendering only. + #city.tiles.add(tile) Causes exception in worker thread in next turn. + cities.tiles.add(tileInfo.position) #Requires next turn for visual update. + tileInfo.improvement = None #Requires next turn for visual update. + tileInfo.improvement = "City center" + city.location.x, city.location.y = x, y + def onGameStart(): pass diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/ProceduralTechtree.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/ProceduralTechtree.py index 20cb53d156b69..ae01cfe9bf462 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/ProceduralTechtree.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/ProceduralTechtree.py @@ -14,11 +14,11 @@ import random -# from unciv import * -# from unciv_pyhelpers import * +from unciv import * +from unciv_pyhelpers import * -# techtree = gameInfo.ruleSet.technologies +def techtree(): return gameInfo.ruleSet.technologies name_parts = { "Building": ( @@ -91,9 +91,9 @@ def extendTechTree(iterations=1): pass def clearTechTree(*, safe=True): - for name in techtree.keys(): + for name in techtree().keys(): if (not safe) or not any(name in civinfo.tech.techsResearched or name in civinfo.tech.techsInProgress or name == civinfo.tech.currentTechnologyName() for civinfo in gameInfo.civilizations): - del techtree[name] + del techtree()[name] def scrambleTechTree(): pass diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Utils.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Utils.py index bcf48cd3e7994..84373ffa4e16c 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Utils.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Utils.py @@ -5,7 +5,7 @@ RegistryKey = "package:unciv/unciv_scripting_examples.py" -unciv.apiHelpers.instanceRegistry[RegistryKey] = {} +unciv.apiHelpers.registeredInstances[RegistryKey] = {} class TokensAsWrappers: @@ -14,4 +14,5 @@ class TokensAsWrappers: @atexit.register def on_exit(): + # Don't think this actually works. del unciv.apiHelpers.instanceRegistry[RegistryKey] diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/__init__.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/__init__.py index 7d16762861ca2..dfee43e97e3eb 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/__init__.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/__init__.py @@ -3,6 +3,6 @@ Nothing should depend on anything in this module. """ -__all__ = ["EndTimes", "ExternalPipe", "MapEditingMacros", "Merfolk", "PlayerMacros", "Tests"] +__all__ = ["EndTimes", "ExternalPipe", "MapEditingMacros", "Merfolk", "PlayerMacros", "ProceduralTechtree", "Tests", "Utils"] -from . import EndTimes, ExternalPipe, MapEditingMacros, Merfolk, PlayerMacros, Tests +from . import EndTimes, ExternalPipe, MapEditingMacros, Merfolk, PlayerMacros, ProceduralTechtree, Tests, Utils diff --git a/core/src/com/unciv/scripting/ScriptingScope.kt b/core/src/com/unciv/scripting/ScriptingScope.kt index d1956090a603a..ba4de6920500c 100644 --- a/core/src/com/unciv/scripting/ScriptingScope.kt +++ b/core/src/com/unciv/scripting/ScriptingScope.kt @@ -94,6 +94,8 @@ class ScriptingScope( // fun applyProperties(instance: Any, properties: Map) { // } //setTimeout? + fun lambdifyScript(code: String ): () -> Unit = fun(){ scriptingScope.uncivGame!!.scriptingState.exec(code); return } // FIXME: Requires awareness of which scriptingState and which backend to use. + //Directly invoking the resulting lambda from a running script will almost certainly break the REPL loop/IPC protocol. } } diff --git a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt index 414df48ec9175..a99399019f310 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt @@ -28,9 +28,6 @@ import java.util.UUID // Use in scripting backend, E.G. wrapping.py -// FIXME: ...Numerical types are annoying: civInfo.cities[0].location.x += 1. - - /** * Implementation of IPC packet specified in Module.md. * diff --git a/core/src/com/unciv/scripting/reflection/Reflection.kt b/core/src/com/unciv/scripting/reflection/Reflection.kt index dca10ec29dcde..cc323706e4735 100644 --- a/core/src/com/unciv/scripting/reflection/Reflection.kt +++ b/core/src/com/unciv/scripting/reflection/Reflection.kt @@ -3,9 +3,11 @@ package com.unciv.scripting.reflection import com.unciv.scripting.utils.TokenizingJson import kotlin.collections.ArrayList import kotlin.reflect.KCallable +//import kotlin.reflect.KClass import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KParameter import kotlin.reflect.KProperty1 +import kotlin.reflect.full.isSubclassOf import kotlin.reflect.full.isSuperclassOf import kotlin.reflect.jvm.jvmErasure import kotlinx.serialization.Serializable @@ -13,6 +15,7 @@ import java.util.* object Reflection { + @Suppress("UNCHECKED_CAST") fun readInstanceProperty(instance: Any, propertyName: String): R? { // From https://stackoverflow.com/a/35539628/12260302 @@ -22,19 +25,28 @@ object Reflection { return property.get(instance) as R? } + /** * Dynamic multiple dispatch for Any Kotlin instances by methodName. * * Uses reflection to first find all members matching the expected method name, and then to narrow them down to members that have the correct signature for a given array of arguments. * + * Varargs can be used, but they must be supplied as a single correctly typed array instead of separate arguments. + * * @property instance The receiver on which to find and call a method. * @property methodName The name of the method to resolve and call. + * @property matchNumbersLeniently Whether to treat all numeric types as the same. Useful for E.G. untyped deserialized data. + * @property matchClassesQualnames Whether to treat classes as the same if they have the same qualifiedName. Useful for E.G. ignoring the invariant arrays representing vararg parameters. */ - class InstanceMethodDispatcher(val instance: Any, val methodName: String) { + class InstanceMethodDispatcher(val instance: Any, val methodName: String, val matchNumbersLeniently: Boolean = false, val matchClassesQualnames: Boolean = false) { // This isn't just a nice-to-have feature. Before I implemented it, identical calls from demo scripts to methods with multiple versions (E.G. ArrayList().add()) would rarely but randomly fail because the member/signature that was found would change between runs or compilations. + // TODO: This is going to need unit tests. + // Could try to implement KCallable interface. But not sure it's be worth it or map closely enough— What do lambdas do? I guess isOpen, isAbstract, etc should just all be False? + // Not supporting varargs for now. Doing so would require rebuilding the arguments array to move all vararg arguments into a new array for KCallable.call(). + /** * Lazily evaluated list of KCallables for every method that matches the given name. */ @@ -50,8 +62,7 @@ object Reflection { * @return Whether a given argument value can be cast to the type of a given KParameter. */ private fun checkParameterMatches(kparam: KParameter, arg: Any?): Boolean { - // These could be static. - // TODO: Inline these, actually. + // TODO: If performance becomes an issue, try inlining these. Then again, the JVM presumably optimizes it at runtime already (and there's far more calls than this containing function). if (arg == null) { // Multiple dispatch of null between Any and Any? seems ambiguous in Kotlin even without reflection. // Here, I'm resolving it myself, so it seems fine. @@ -59,10 +70,20 @@ object Reflection { // KCallable.toString() shows the nullable signature, and KParam.name shows the argument name from the nullable version. But an exception is still thrown on .call() with null, and its text will use the argument name from the non-nullable version. // I suppose it's not a problem here as it seems broken in Kotlin generally. return kparam.type.isMarkedNullable - } else { - return kparam.type.jvmErasure.isSuperclassOf(arg::class) - //Seems to also work for generics, I guess. } + val argcls = arg::class + val kparamcls = kparam.type.jvmErasure + if (matchNumbersLeniently && argcls.isSubclassOf(Number::class) && kparamcls.isSubclassOf(Number::class)) { + // I think/hope this basically causes Java-style implicit conversions (or Kotlin implicit casts?). + // NOTE: However, doesn't correctly forbid unconvertible types. + // Info: https://docs.oracle.com/javase/specs/jls/se7/html/jls-5.html#jls-5.1.2 + return true + } + return kparamcls.isSuperclassOf(argcls) + // Seems to also work for generics, I guess. + || (matchClassesQualnames && kparamcls.qualifiedName != null && argcls.qualifiedName != null && kparamcls.qualifiedName == argcls.qualifiedName) + // Lets different types be matched to invariants, such as for vararg arrays. + // However, the JVM still throws its own error in that case, so leaving this disabled for now. } /** @@ -75,6 +96,34 @@ object Reflection { && (params.slice(1..arguments.size) zip arguments).all { // Check argument classes match parameter types, skipping the receiver. (kparam, arg) -> checkParameterMatches(kparam, arg) } + /* TODO: Dead code. + when { + useVararg -> { // Checking for varargs is somewhat more complex/slower, so hide it behind a flag. + for ((kparam, arg) in params.slice(1..params.size-1) zip arguments) { + // Check each argument's class matches its coresponding parameter type, skipping the receiver. + if (kparam.isVararg) { + // Wait, varargs probably need to be an array in KCallable.call(), which means using them would require reformatting the arguments, which, to do efficiently, would require bubbling reults from this check out all the way out to this.call(). (Could do something with nullable array return, I guess.) + return true + } + if (!checkParameterMatches(kparam, arg)) { + return false + } + } + if (params.size != arguments.size + 1) { + // Check that the parameters size is same as arguments size plus one for the receiver. + // Because the entire parameter list has to be iterated through to handle vararg, this has to come at the end. + return false + } + return true + } + else -> { + return params.size == arguments.size + 1 // Check that the parameters size is same as arguments size plus one for the receiver. + && (params.slice(1..arguments.size) zip arguments).all { // Check argument classes match parameter types, skipping the receiver. + (kparam, arg) -> checkParameterMatches(kparam, arg) + } + } + } + */ } /** @@ -110,10 +159,11 @@ object Reflection { return matches[0]!!.call( instance, *arguments - ) + ) } } + fun readInstanceItem(instance: Any, keyOrIndex: Any): Any? { if (keyOrIndex is Int) { return (instance as List)[keyOrIndex] @@ -285,7 +335,7 @@ object Reflection { try { obj = readInstanceProperty(obj!!, element.name) } catch (e: ClassCastException) { - obj = InstanceMethodDispatcher(obj!!, element.name) + obj = InstanceMethodDispatcher(obj!!, element.name, matchNumbersLeniently = true, matchClassesQualnames = false) } } PathElementType.Key -> { diff --git a/core/src/com/unciv/scripting/utils/InstanceFactories.kt b/core/src/com/unciv/scripting/utils/InstanceFactories.kt index f47db983a4e48..2a4351015a80b 100644 --- a/core/src/com/unciv/scripting/utils/InstanceFactories.kt +++ b/core/src/com/unciv/scripting/utils/InstanceFactories.kt @@ -18,8 +18,10 @@ object InstanceFactories { } object GUI { } - fun Array() = "NotImplemented" - fun Vector2(x: Float, y: Float) = com.badlogic.gdx.math.Vector2(x, y) //TODO: Accept number and cast instead. + fun arrayOf(elements: Collection): Array<*> = elements.toTypedArray() + fun arrayOfAny(elements: Collection): Array = elements.toTypedArray() + fun arrayOfString(elements: Collection): Array = elements.toTypedArray() + fun Vector2(x: Number, y: Number) = com.badlogic.gdx.math.Vector2(x.toFloat(), y.toFloat()) fun MapUnit() = "NotImplemented" fun Technology() = "NotImplemented" } diff --git a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt index aed63c7f279ce..06dd41747afc5 100644 --- a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt @@ -140,7 +140,8 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un fun openConsole() { game.setScreen(this) keyPressDispatcher.install(stage) - //TODO: See if can activate text input automatically. Already checked, but check again. + stage.setKeyboardFocus(inputField) + inputField.getOnscreenKeyboard().show(true) this.isOpen = true } From d28b762bdb2fe25d8d53b773845ee5b142213ede Mon Sep 17 00:00:00 2001 From: will-ca Date: Fri, 26 Nov 2021 14:31:02 +0000 Subject: [PATCH 55/93] Resolve ambiguous multiple dispatch by type hierarchy. Python testing. Docs, fixes. --- .../scripting/ScriptingEngineConstants.json | 15 +- .../enginefiles/python/PythonScripting.md | 29 ++- .../python/unciv_lib/autocompletion.py | 1 + .../enginefiles/python/unciv_lib/shadow.py | 3 + .../enginefiles/python/unciv_lib/wrapping.py | 7 + .../unciv_scripting_examples/EndTimes.py | 19 +- .../MapEditingMacros.py | 35 +-- .../unciv_scripting_examples/Merfolk.py | 11 +- .../unciv_scripting_examples/PlayerMacros.py | 8 + .../ProceduralTechtree.py | 9 + .../python/unciv_scripting_examples/Tests.py | 208 ++++++++++++++---- .../python/unciv_scripting_examples/Utils.py | 49 ++++- .../EarthTerrainFantasyHex.jpg | Bin .../{ => example_assets}/EarthTerrainRaw.png | Bin .../{ => example_assets}/EarthTopography.png | Bin .../example_assets/Elizabeth300 | 1 + .../{ => example_assets}/StarryNight.jpg | Bin .../{ => example_assets}/TurboRainbow.png | Bin .../{ => example_assets}/WheatField.jpg | Bin .../assets/scripting/enginefiles/qjs/main.js | 3 + core/Module.md | 1 + .../com/unciv/scripting/ScriptingBackend.kt | 8 + .../src/com/unciv/scripting/ScriptingScope.kt | 10 +- .../src/com/unciv/scripting/ScriptingState.kt | 6 + .../unciv/scripting/reflection/Reflection.kt | 146 ++++++++---- .../src/com/unciv/scripting/utils/Blackbox.kt | 4 +- .../scripting/utils/InstanceFactories.kt | 15 +- .../unciv/ui/consolescreen/ConsoleScreen.kt | 2 +- .../com/unciv/ui/worldscreen/WorldScreen.kt | 2 +- .../src/com/unciv/scripting/ScriptedTests.kt | 0 .../InstanceMethodDispatcherTests.kt | 0 31 files changed, 454 insertions(+), 138 deletions(-) create mode 100644 android/assets/scripting/enginefiles/python/unciv_lib/shadow.py rename android/assets/scripting/enginefiles/python/unciv_scripting_examples/{ => example_assets}/EarthTerrainFantasyHex.jpg (100%) rename android/assets/scripting/enginefiles/python/unciv_scripting_examples/{ => example_assets}/EarthTerrainRaw.png (100%) rename android/assets/scripting/enginefiles/python/unciv_scripting_examples/{ => example_assets}/EarthTopography.png (100%) create mode 100644 android/assets/scripting/enginefiles/python/unciv_scripting_examples/example_assets/Elizabeth300 rename android/assets/scripting/enginefiles/python/unciv_scripting_examples/{ => example_assets}/StarryNight.jpg (100%) rename android/assets/scripting/enginefiles/python/unciv_scripting_examples/{ => example_assets}/TurboRainbow.png (100%) rename android/assets/scripting/enginefiles/python/unciv_scripting_examples/{ => example_assets}/WheatField.jpg (100%) create mode 100644 tests/src/com/unciv/scripting/ScriptedTests.kt create mode 100644 tests/src/com/unciv/scripting/reflection/InstanceMethodDispatcherTests.kt diff --git a/android/assets/scripting/ScriptingEngineConstants.json b/android/assets/scripting/ScriptingEngineConstants.json index bc06cc732f921..3147568a9343e 100644 --- a/android/assets/scripting/ScriptingEngineConstants.json +++ b/android/assets/scripting/ScriptingEngineConstants.json @@ -1,4 +1,4 @@ -{ +{ // Internal directories can't be identified, traversed, or copied on Desktop because all assets are indistinguishable on the classpath or something. So manually list out all files that are part of each engine's runtime environment instead. engines: { python: { files: [ @@ -14,20 +14,21 @@ main.py unciv_scripting_examples/ unciv_scripting_examples/__init__.py - unciv_scripting_examples/EarthTerrainFantasyHex.jpg - unciv_scripting_examples/EarthTerrainRaw.png - unciv_scripting_examples/EarthTopography.png unciv_scripting_examples/EndTimes.py unciv_scripting_examples/ExternalPipe.py unciv_scripting_examples/MapEditingMacros.py unciv_scripting_examples/Merfolk.py unciv_scripting_examples/PlayerMacros.py unciv_scripting_examples/ProceduralTechtree.py - unciv_scripting_examples/StarryNight.jpg unciv_scripting_examples/Tests.py - unciv_scripting_examples/TurboRainbow.png unciv_scripting_examples/Utils.py - unciv_scripting_examples/WheatField.jpg + unciv_scripting_examples/example_assets/EarthTerrainFantasyHex.jpg + unciv_scripting_examples/example_assets/EarthTerrainRaw.png + unciv_scripting_examples/example_assets/EarthTopography.png + unciv_scripting_examples/example_assets/Elizabeth300 + unciv_scripting_examples/example_assets/StarryNight.jpg + unciv_scripting_examples/example_assets/TurboRainbow.png + unciv_scripting_examples/example_assets/WheatField.jpg ] syntaxHighlightingRegexStack: [ ] diff --git a/android/assets/scripting/enginefiles/python/PythonScripting.md b/android/assets/scripting/enginefiles/python/PythonScripting.md index 659dd6357a04d..eff582b6194ff 100644 --- a/android/assets/scripting/enginefiles/python/PythonScripting.md +++ b/android/assets/scripting/enginefiles/python/PythonScripting.md @@ -186,11 +186,16 @@ del apiHelpers.registeredInstances["centertile"] In order to use this technique properly, the assignment of an object to a concrete path should be done within the same REPL loop as the generation of the token used to assign it. This is because the Kotlin code responsible for generating tokens and managing the REPL loop keeps references to all returned objects within each REPL loop. Afterwards, these references are immediately cleared, so any objects that do not have references elsewhere in Kotlin/the JVM are liable to be garbage-collected in between REPL loops. +Note that because of this, it is also perfectly safe to use token strings as arguments for foreign functions without assigning them to concrete paths, as long as they are requested and used within the same REPL loop. + ```python3 -# Each ">>>" represents a new script execution called from Kotlin— E.G., A new command entered into the console screen, or a new handler execution from the modding API— And not just a new line of code. Code on multiple lines can still be run in the same REPL loop, as long as the script's control isn't ended and handed back to Kotlin/the JVM between their running. +# Each ">>>" represents a new script execution initiated from Kotlin— E.G., A new command entered into the console screen, or a new handler execution from the modding API— And not just a new line of code. Code on multiple lines can still be run in the same REPL loop, as long as the script's control isn't handed back to Kotlin/the JVM in between. + +>>> worldScreen.mapHolder.setCenterPosition(apiHelpers.Factories.Vector2(1,2), True, True) +# Works, because the instance creation and the call with a tokenized argument happen in the same REPL execution. >>> apiHelpers.registeredInstances["x"] = apiHelpers.Factories.Vector2(1,2) ->>> worldScreen.mapHolder.setCenterPosition(apiHelpers.registeredInstances["x"], True, True) +>>> worldScreen.mapHolder.setCenterPosition(apiHelpers.registeredInstances["x"], True, True) #TODO: This doesn't actually use any subpath. # Works, because the instance creation and token-based assignment in Kotlin are done in the same REPL execution. >>> x = apiHelpers.Factories.Vector2(1,2); civInfo.endTurn(); apiHelpers.registeredInstances["x"] = x @@ -216,9 +221,9 @@ For any complicated script in Python, it is suggested that you write a context m It is also recommended that all scripts create a separate mapping with a unique and identifiable key in `apiHelpers.registeredInstances`, instead of assigning directly to the top level. ```python3 -apiHelpers.registeredInstances["module:myName/myCoolScript.py"] = {} +apiHelpers.registeredInstances["python-module:myName/myCoolScript"] = {} -memalloc = apiHelpers.registeredInstances["module:myName/myCoolScript.py"] +memalloc = apiHelpers.registeredInstances["python-module:myName/myCoolScript"] memalloc["capitaltile"] = civInfo.cities[0].getCenterTile() @@ -226,13 +231,14 @@ worldScreen.mapHolder.setCenterPosition(memalloc["capitaltile"].position, True, del memalloc["capitaltile"] -del apiHelpers.registeredInstances["module:myName/myCoolScript.py"] +del apiHelpers.registeredInstances["python-module:myName/myCoolScript"] ``` ```python3 -apiHelpers.registeredInstances["module:myName/myCoolScript.py"] = {} +apiHelpers.registeredInstances["python-module:myName/myCoolScript"] = {} -memalloc = apiHelpers.registeredInstances["module:myName/myCoolScript.py"] +memalloc = apiHelpers.registeredInstances["python-module:myName/myCoolScript"] +# Wrapper object. class MyForeignContextManager: def __init__(self, *tokens): @@ -240,7 +246,9 @@ class MyForeignContextManager: self.memallocKeys = [] def __enter__(self): for token in self.tokens: - key = f"{random.random()}_{time.time_ns()}" + assert isForeignToken(token) + key = f"{random.getrandbits(30)}_{time.time_ns()}" + # Actual uses should locally check for key uniqueness. memalloc[key] = token self.memallocKeys.append(key) return tuple(memalloc[k] for k in self.memallocKeys) @@ -252,13 +260,13 @@ class MyForeignContextManager: with MyForeignContextManager(apiHelpers.Factories.MapUnit(), ) as mapUnit, : mapUnit -del apiHelpers.registeredInstances["module:myName/myCoolScript.py"] +del apiHelpers.registeredInstances["python-module:myName/myCoolScript"] ``` The recommended format for keys added to `apiHelpers.registeredInstances` is as follows: ``` -<'mod'|'module'|'package'>:/. +-<'mod'|'module'|'package'>:/ ``` --- @@ -393,6 +401,7 @@ for e in civInfo.cities[0].cityStats.cityInfo.tiles: # Fails. CityInfo.tiles is a set-like instance that does not take indices. for i in range(len(civInfo.cities[0].cityStats.currentCityStats.values)): + #civInfo.units() returns a sequence. print(i) # Also fails. CityStats.currentCityStats.values is an iterator-like instance without a known length. diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py b/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py index 2fca1d796e6ee..aa000188742a4 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py @@ -42,6 +42,7 @@ def GetCommandComponents(self, command): continue if char in '([:,;+-*/|&<>=%{~^@': # Should probably split at multi-character tokens like 'in', 'for', 'if', etc. too. + # TODO: Maybe just put read spaces as a token type for now? prefixsplit += 1 lasttoken = char break diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/shadow.py b/android/assets/scripting/enginefiles/python/unciv_lib/shadow.py new file mode 100644 index 0000000000000..2e8decb9fa885 --- /dev/null +++ b/android/assets/scripting/enginefiles/python/unciv_lib/shadow.py @@ -0,0 +1,3 @@ + + +# Implement a class/object that just returns itself for all magic methods. diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py index 0a9fb13f6b965..d344243863cd2 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py @@ -180,6 +180,12 @@ def alreadyhas(name): return cls +# class ForeignToken(str): + # __slots__ = () + # TODO: Could do this for more informative error messages, hidden magic methods that don't make sense. + # Would have to instantiate in the JSON decoder, though. + # I'm not sure it's necessary, since tokens will still have to be encoded as strings in JSON, which means you'd still need apiconstants['kotlinInstanceTokenPrefix'] and isForeignToken in api.py. + @ResolveForOperators class ForeignObject: """Wrapper for a foreign object. Implements the specifications on IPC packet action types and data structures in Module.md.""" @@ -193,6 +199,7 @@ def _ipcjson_(self): def _getpath_(self): return tuple(self._path) def __getattr__(self, name): + # Due to lazy IPC calling, hasattr will never work with this. Instead, check for in dir(). # TODO: Shouldn't I special-casing get_help or _docstring_ here? Wait, no, I think I thought it would be accessed on the class. return self.__class__((*self._path, makePathElement(name=name)), self._foreignrequester) def __getattribute__(self, name): diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EndTimes.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EndTimes.py index c22339035fdc6..25f17f7e30e91 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EndTimes.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EndTimes.py @@ -1,6 +1,11 @@ """ Example and developmental test case for potential future modding API. +Adds scripted events that dynamically modify map and civilization state. + +Watch out for volcanoes, meteor strikes, wildfires, and alien invaders! + + Call onNewTurn() on every new turn. Call onUnitMove(worldScreen.bottomUnitTable.selectedUnit) every time a unit moves. @@ -14,6 +19,14 @@ # But randomness is entropy, so random changes to the world around you are by definition apocalyptic. # ...At which point, the lore writes itself. + +# Could inject new tiles for wildfires, fissures, etc into the ruleset. +# Nah. Let's try to keep this and Merfolk.py save-compatible, and leave the ruleset modification to ProceduralTechTree. + +# Midgame: Defanged unkillable Barbarian "alien" scouts. +# Late game: Barbarian GDS, Helis. +# I guess could also give positive bonuses from alien encounters. + turnNotifications = [] @@ -23,6 +36,10 @@ def _gaussianTileSelector(tile): return _gaussianTileSelector +def depopulateCity(city, migrationsuccess=0.5): + pass + + def scatterFallout(focus, improvementtype, maxdistance, tileselector=lambda t: True): pass @@ -50,7 +67,7 @@ def depopulateBurningCities(): # Meh. I'll comfort myself by telling myself I go to war less than most Civ players. pass -def landAlienInvaders(): +def landAlienInvaders():#TileMap.placeUnitNearTile pass def erodeMountains(): diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py index 656c7b9f2351a..82d7fab253f7a 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py @@ -15,6 +15,8 @@ import unciv from unciv_pyhelpers import * +from . import Utils + # If you modify this file, please add any new functions to the build tests. @@ -119,7 +121,7 @@ def setTerrain(tileInfo, terraintype): def spreadResources(resourcetype="Horses", mode="random", restrictfrom=(), restrictto=None): if mode == "random": - pass + raise NotImplementedError() elif mode in ("jittered", "grid", "clustered", "bluenoise"): raise NotImplementedError(f"") else: @@ -127,13 +129,13 @@ def spreadResources(resourcetype="Horses", mode="random", restrictfrom=(), restr def dilateTileTypes(tiletypes=("Coast", "Flood Plains"), chance=1.0, forbidreplace=("Ocean", "Mountain"), dilateas=("Desert/Flood Plains", "Coast"), iterations=1): - pass + raise NotImplementedError() def erodeTileType(tiletypes=("Mountains", "Plains/Hill", "Grassland/Hill")): - pass + raise NotImplementedError() def floodFillSelected(start=None, fillas=None, *, alsopropagateto=()): - pass + raise NotImplementedError() mandlebrotpresets = { @@ -238,17 +240,22 @@ def setMapFromImage(tileMap, image, pixelinterpreter=lambda pixel: "Ocean"): ) -def loadImageHeightmap(tileMap=None, imagepath="EarthTopography.png", transform="pixel*len(terrains)", terrains=_altitudeterrainsequence, normalizevalues=255): - tileMap = defaultArgTilemap(tileMap) - import PIL.Image +def _imageFallbackPath(imagepath): if not os.path.exists(imagepath): - _fallbackpath = os.path.join(os.path.dirname(__file__), imagepath) + _fallbackpath = Utils.exampleAssetPath(imagepath) if os.path.exists(_fallbackpath): imagepath = _fallbackpath print(f"Invalid image path given. Interpreting as example path at {repr(imagepath)}") del _fallbackpath - transform = compile(transform, filename="transform", mode='eval') + return imagepath + + +def loadImageHeightmap(tileMap=None, imagepath="EarthTopography.png", transform="pixel*len(terrains)", terrains=_altitudeterrainsequence, normalizevalues=255): + tileMap = defaultArgTilemap(tileMap) + import PIL.Image + transform = compile(transform, filename="transform", mode='eval') + imagepath = _imageFallbackPath(imagepath) def pixinterp(pixel): if isinstance(pixel, tuple): pixel = sum(pixel)/len(pixel) @@ -275,7 +282,7 @@ def compositedTerrainImage(terrain): return image def getImageAverageRgb(image): - #image.convert('P', palette=PIL.Image.ADAPTIVE, colors=1) + #image.convert('P', palette=PIL.Image.ADAPTIVE, colors=1) (Doesn't work by nearest.) depth = 255 assert image.mode in ("RGB", "RGBA") hasalpha = image.mode == "RGBA" @@ -296,7 +303,6 @@ def getImageAverageRgb(image): def computeTerrainAverageColours(terrains=naturalterrains): - # FIXME: Increases memory use with each run. def terraincol(terrain): with compositedTerrainImage(terrain) as i: return getImageAverageRgb(i) @@ -340,12 +346,7 @@ def loadImageColours(tileMap=None, imagepath="EarthTerrainFantasyHex.jpg", terra import PIL.Image assert visualspace tileMap = defaultArgTilemap(tileMap) - if not os.path.exists(imagepath): - _fallbackpath = os.path.join(os.path.dirname(__file__), imagepath) - if os.path.exists(_fallbackpath): - imagepath = _fallbackpath - print(f"\nInvalid image path given. Interpreting as example path at {repr(imagepath)}") - del _fallbackpath + imagepath = _imageFallbackPath(imagepath) if terraincolours is None: print(f"\nNo terrain colours given. Computing average tile colours based on FantasyHex tileset. This may take several seconds.") terraincolours = computeTerrainAverageColours(naturalterrains) diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Merfolk.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Merfolk.py index 3bc81e553a05b..2c98095707e9a 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Merfolk.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Merfolk.py @@ -3,7 +3,7 @@ Adds peaceful, playful Merpeople communities. -But they won't hesitate to defend themselves if you bully them or others! +But they won't hesitate to defend themselves with powerful magic if you bully them or others! Call onGameStart() **once** at start of game. @@ -25,6 +25,7 @@ # If you really piss them off, flood/destroy your capital and displace its population. #civInfo.cities[0].cityStats.cityInfo.resistanceCounter +#CityFlags.Resistance #civInfo.addNotification("Test", civInfo.cities[0].location, apiHelpers.Factories.arrayOfString(["StatIcons/Gold"])) @@ -36,5 +37,13 @@ def moveCity(): tileInfo.improvement = "City center" city.location.x, city.location.y = x, y + +def hammerOfTheOceans(): + pass + + +#TileMap.placeUnitNearTile + + def onGameStart(): pass diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/PlayerMacros.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/PlayerMacros.py index c4956c98ce1b0..b61296a870938 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/PlayerMacros.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/PlayerMacros.py @@ -56,6 +56,7 @@ def focusCitiesFood(cities): def buildCitiesQueue(cities, order): """Assign all given cities to follow a given build order after their current queue.""" + raise NotImplementedError() for city in cities: with TokensAsWrappers(city.cityConstructions.getBuildableBuildings()) as queue: pass @@ -63,6 +64,7 @@ def buildCitiesQueue(cities, order): #civInfo.cities[0].cityStats.cityInfo.cityConstructions.builtBuildings # HashSet(). Can do "in" via IPC magic, and made real(). But not iterable since __iter__ requires indexing. def rebaseUnitsEvenly(units=('Guided Missile',), ): + raise NotImplementedError() if isinstance(units, str): units = (units,) @@ -76,3 +78,9 @@ def rebaseUnitsEvenly(units=('Guided Missile',), ): #apiHelpers.toString(worldScreen.bottomUnitTable.selectedCity.cityConstructions.getBuildableBuildings()) #worldScreen.mapHolder.selectedTile.terrainFeatures.addAll([k for k, v in gameInfo.ruleSet.terrains.items() if v.type.name == 'NaturalWonder']) + +##[setattr(city, 'civInfo', gameInfo.getCivilization(city.previousOwner)) for civ in gameInfo.civilizations for city in civ.cities if real(city.previousOwner)] +##[(lambda prev: [city.civInfo.cities.remove(city), prev.cities.add(city)] if 'remove' in dir(city.civInfo.cities) and 'add' in dir(prev.cities) else None )(next(ci for ci in gameInfo.civilizations if ci.nation.name == city.previousOwner)) for civ in gameInfo.civilizations for city in civ.cities if real(city.previousOwner)] +#[setattr(city, 'resistanceCounter', 0) for civ in gameInfo.civilizations for city in civ.cities if real(city.previousOwner)] +#[setattr(city, 'isPuppet', False) for civ in gameInfo.civilizations for city in civ.cities if real(city.previousOwner)] +#[setattr(city, 'previousOwner', "") for civ in gameInfo.civilizations for city in civ.cities if real(city.previousOwner)] diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/ProceduralTechtree.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/ProceduralTechtree.py index ae01cfe9bf462..5b083d0768d47 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/ProceduralTechtree.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/ProceduralTechtree.py @@ -12,6 +12,10 @@ """ +# This means that some kind of handler for the modding API, once it's implemented, would have to be called before the JVM has a chance to crash, so the script can read its own serialized data out of GameInfo and inject items into the ruleset… You can already provably have a GameInfo with invalid values that doesn't crash until WorldScreen tries to render it, so running onGameLoad immediately after deserializing the save might be good enough. +# Or I guess there could be a Kotlin-side mechanism for serializing injected rules. But on the Kotlin side, I think it would be cleaner to just let the script handle everything. Such a mechanism still may not cover the entire range of wild behaviours that can be done by scripts, and the entire point of having a dynamic scripting API is to avoid having to statically hard-code niche or esoteric uses. + + import random from unciv import * @@ -57,6 +61,8 @@ def genRandomNameUnused(nametype, *, maxattempts=1000): def genRandomIcon(): pass #Define randomized number of randomized shapes. Randomize order. Draw area union. Draw layer-occluded boundary lines separately. Subtract edges from area. + + # def genRandomEra(): # pass @@ -87,7 +93,9 @@ def _getInvalidTechs(): pass + def extendTechTree(iterations=1): + raise NotImplementedError() pass def clearTechTree(*, safe=True): @@ -96,4 +104,5 @@ def clearTechTree(*, safe=True): del techtree()[name] def scrambleTechTree(): + raise NotImplementedError() pass diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py index d25af3685a2fa..90624d96d939e 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py @@ -1,7 +1,22 @@ -import unciv, unciv_lib +""" +Automated testing of practical Python scripting examples. +Intended to also catch breaking changes to the scripting API, IPC protocol, and reflective tools. -from . import EndTimes, ExternalPipe, MapEditingMacros, Merfolk, PlayerMacros +Call TestRunner.run_tests() to use. + +Pass debugprint=False if running from Kotlin as part of build tests, because running scripts' STDOUT is already captured by the Python REPL and sent to Kotlin code, which should then check for the presence of the 'Exception' IPC packet flag. +""" + + +import os + +import unciv, unciv_pyhelpers#, unciv_lib + +from . import EndTimes, ExternalPipe, MapEditingMacros, Merfolk, PlayerMacros, ProceduralTechtree, Utils + + +# from unciv_scripting_examples.Tests import *; TestRunner.run_tests() try: assert False @@ -12,38 +27,163 @@ raise RuntimeError("Assertions must be enabled to run Python tests.") -tests = [] - -class Test: - def __init__(self, func, name=None, runwith=None, args=(), kwargs={}): - #Args and kwargs here, but not used. - self.func = func - self.name = func.__name__ if name is None else name - self.runwith = runwith - self.args = args - self.kwargs = kwargs - def __call__(self): - if self.runwith is None: - self.func(*self.args, **self.kwargs) - else: - with self.runwith: +with open(Utils.exampleAssetPath("Elizabeth300"), 'r') as save: + # TODO: Compress this. + # Unciv uses Base64 and GZIP. + Elizabeth300 = save.read() + + +def getTestGame(): + return unciv.GameSaver.gameInfoFromString(Elizabeth300) + +def goToMainMenu(): + unciv.uncivGame.setScreen(unciv.apiHelpers.Factories.Gui.MainMenuScreen()) + + +@Utils.singleton() +class InGame: + """Context manager object that loads a test save on entrance and returns to the main menu on exit.""" + def __enter__(self): + unciv.uncivGame.loadGame(getTestGame()) + def __exit__(self, *exc): + goToMainMenu() + +@Utils.singleton() +class InMapEditor: + """Context manager object that loads a test map in the map editor on entrance and returns to the main menu on exit.""" + def __enter__(self): + with Utils.TokensAsWrappers(getTestGame()) as (gameinfo,): + unciv.uncivGame.setScreen(unciv.apiHelpers.Factories.Gui.MapEditorScreen(gameinfo.tileMap)) + def __exit__(self, *exc): + goToMainMenu() + + +@Utils.singleton() +class TestRunner: + """Class for registering and running tests.""" + # No point using any third-party or Standard Library testing framework, IMO. The required behaviour's simple enough, and the output format to Kotlin ('Exception' flag or not) is direct enough that it's easier and more concise to just implement everything here. + def __init__(self): + self._tests = [] + class _TestCls: + """Class to define and run a single test. Accepts the function to test, a human-readable name for the test, a context manager with which to run it, and args and kwargs with which to call the funciton.""" + def __init__(self, func, name=None, runwith=None, args=(), kwargs={}): + self.func = func + self.name = getattr(func, '__name__', None) if name is None else name + self.runwith = runwith + self.args = args + self.kwargs = kwargs + def __call__(self): + if self.runwith is None: self.func(*self.args, **self.kwargs) + else: + with self.runwith: + self.func(*self.args, **self.kwargs) + def Test(self, *args, **kwargs): + """Return a decorator that registers a function to be run as a test, and then returns it unchanged. Accepts the same configuration arguments as _TestCls.""" + # Return values aren't checked. A call that completes is considered a pass. A call that raises an exception is considered a fail. + # If you need to check return values for a function, then just wrap them in another function with an assert. + def _testdeco(func): + self._tests.append(self._TestCls(func, *args, **kwargs)) + return func + return _testdeco + def run_tests(self, *, debugprint=True): + """Run all registered tests, printing out their results, and raising an exception if any of them fail.""" + failures = {} + def _print(*args, **kwargs): + print(*args, **kwargs) + if debugprint: + # When run as part of build, the Kotlin test-running code should be capturing the Python STDOUT anyway. + unciv.apiHelpers.printLine(str(args[0]) if len(args) == 1 and not kwargs else " ".join(str(a) for a in args)) + for test in self._tests: + try: + test() + except Exception as e: + failures[test] = e + n, t = '\n\t' + _print(f"Python test FAILED: {test.name}\n\t{repr(e).replace(n, n+t)}") + else: + _print(f"Python test PASSED: {test.name}") + _print("\n") + if failures: + exc = AssertionError(f"{len(failures)} Python tests FAILED: {[test.name for test in failures]}\n\n") + _print(exc) + raise exc + else: + _print(f"All {len(self._tests)} Python tests PASSED!\n\n") -def test(name=None, *, runwith=None): - def _testmaker(func): - tests.append(Test(test, name=name, runwith=runwith)) - return func - return _testmaker -class NewGameTest: - pass +#### Tests begin here. #### -class MapEditorTest: - pass -class MainMenuTest: - pass +@TestRunner.Test(runwith=InGame) +def NewGameTest(): + """Example test. Explicitly tests that the InGame context manager is working.""" + # Everything below is the same, just explicitly passing existing functions to the registration function instead of using it as a decorator. + assert unciv.apiHelpers.isInGame + for v in (unciv.gameInfo, unciv.civInfo, unciv.worldScreen): + assert unciv_pyhelpers.real(v) is not None + assert unciv_pyhelpers.isForeignToken(v) + + +# Tests for PlayerMacros.py. + +TestRunner.Test(runwith=InGame, args=(unciv.civInfo.cities, 0.5))( + PlayerMacros.gatherBiggestCities +) +TestRunner.Test(runwith=InGame, args=(unciv.civInfo.cities,))( + PlayerMacros.clearCitiesProduction +) +TestRunner.Test(runwith=InGame, args=(unciv.civInfo.cities, ("Scout", "Warrior", "Worker")))( + PlayerMacros.addCitiesProduction +) +TestRunner.Test(runwith=InGame, args=(unciv.civInfo.cities,))( + PlayerMacros.clearCitiesSpecialists +) +TestRunner.Test(runwith=InGame, args=(unciv.civInfo.cities,))( + PlayerMacros.focusCitiesFood +) +TestRunner.Test(runwith=InGame, args=(unciv.civInfo.cities, ("Monument", "Shrine", "Worker")))( + PlayerMacros.buildCitiesQueue +) +TestRunner.Test(runwith=InGame)( + PlayerMacros.rebaseUnitsEvenly +) + + +# Tests for MapEditingMacros.py. + +_m = MapEditingMacros + +for _func in ( + _m.spreadResources, + _m.dilateTileTypes, + _m.erodeTileType, + _m.floodFillSelected, + _m.makeMandelbrot, + _m.graph2D, + _m.graph3D, + _m.loadImageHeightmap, + _m.loadImageColours +): + for _cm in (InGame, InMapEditor): + TestRunner.Test(runwith=_cm, name=f"{_func.__name__}-{_cm.__class__.__name__}")(_func) + +del _m, _func, _cm + + +# Tests for ProceduralTechTree.py. + +_m = ProceduralTechtree + +for _func in ( + _m.extendTechTree, + _m.clearTechTree, + _m.scrambleTechTree +): + TestRunner.Test(runwith=InGame)(_func) + +del _m, _func #TODO: Add tests. Will probably require exception field in IPC protocol to use. @@ -59,15 +199,3 @@ class MainMenuTest: #Probably don't bother with DOCTEST, or anything. Just use assert statements where needed, print out any errors, and check in the build tests that there's no exceptions (by flag, or by printout value). -def run_tests(): - failures = [] - for test in tests: - try: - test() - except Exception as e: - failures.append(e) - print(f"Python test FAILED: {test._testname}\n{unciv_lib.utils.formatException(exc)}") - else: - print(f"Python test PASSED: {test._testname}") - assert not failures - diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Utils.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Utils.py index 84373ffa4e16c..e7d8c11366242 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Utils.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Utils.py @@ -1,18 +1,57 @@ -import atexit +import os, atexit, random, time -import unciv +import unciv, unciv_pyhelpers -RegistryKey = "package:unciv/unciv_scripting_examples.py" +RegistryKey = "python-package:Unciv/unciv_scripting_examples" unciv.apiHelpers.registeredInstances[RegistryKey] = {} +memalloc = unciv.apiHelpers.registeredInstances[RegistryKey] + + +def singleton(*args, **kwargs): + def _singleton(cls): + return cls(*args, **kwargs) + return _singleton + + +def exampleAssetPath(*path): + return os.path.join(os.path.dirname(__file__), "example_assets", *path) + class TokensAsWrappers: - pass + def __init__(self, *tokens): + self.tokens = tokens + self.memallocKeys = [] + currentRegisteredKeys = set() + @classmethod + def genUniqueKey(cls): + key = None + while key is None or key in cls.currentRegisteredKeys: + key = f"{random.getrandbits(30)}_{time.time_ns()}" + cls.currentRegisteredKeys.add(key) + return key + @classmethod + def freeUniqueKey(cls, key): + cls.currentRegisteredKeys.remove(key) + def __enter__(self): + global memalloc + for token in self.tokens: + assert unciv_pyhelpers.isForeignToken(token) + key = self.genUniqueKey() + memalloc[key] = token + self.memallocKeys.append(key) + return tuple(memalloc[k] for k in self.memallocKeys) + def __exit__(self, *exc): + global memalloc + for key in self.memallocKeys: + del memalloc[key] + self.freeUniqueKey(key) + self.memallocKeys.clear() @atexit.register def on_exit(): - # Don't think this actually works. + # I don't think this actually works. del unciv.apiHelpers.instanceRegistry[RegistryKey] diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EarthTerrainFantasyHex.jpg b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/example_assets/EarthTerrainFantasyHex.jpg similarity index 100% rename from android/assets/scripting/enginefiles/python/unciv_scripting_examples/EarthTerrainFantasyHex.jpg rename to android/assets/scripting/enginefiles/python/unciv_scripting_examples/example_assets/EarthTerrainFantasyHex.jpg diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EarthTerrainRaw.png b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/example_assets/EarthTerrainRaw.png similarity index 100% rename from android/assets/scripting/enginefiles/python/unciv_scripting_examples/EarthTerrainRaw.png rename to android/assets/scripting/enginefiles/python/unciv_scripting_examples/example_assets/EarthTerrainRaw.png diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EarthTopography.png b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/example_assets/EarthTopography.png similarity index 100% rename from android/assets/scripting/enginefiles/python/unciv_scripting_examples/EarthTopography.png rename to android/assets/scripting/enginefiles/python/unciv_scripting_examples/example_assets/EarthTopography.png diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/example_assets/Elizabeth300 b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/example_assets/Elizabeth300 new file mode 100644 index 0000000000000..7344834a6e203 --- /dev/null +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/example_assets/Elizabeth300 @@ -0,0 +1 @@ +{civilizations:[{gold:-318,civName:Barbarians,tech:{techsResearched:[Drama and Poetry,Optics,Mathematics,Guilds,Mining,Engineering,Sailing,Archery,Currency,Pottery,Agriculture,Iron Working,Theology,Metal Casting,Construction,Calendar,Masonry,Compass,Horseback Riding,Physics,Trapping,Bronze Working,The Wheel,Animal Husbandry,Writing,Civil Service,Philosophy]},policies:{},civConstructions:{},questManager:{},religionManager:{storedFaith:7},goldenAges:{storedHappiness:1134,numberOfGoldenAges:3},greatPeople:{},victoryManager:{},ruinsManager:{},diplomacy:{Vancouver:{otherCivName:Vancouver,diplomaticModifiers:{DeclaredWarOnUs:-12.5}},Quebec City:{otherCivName:Quebec City,diplomaticModifiers:{DeclaredWarOnUs:-12.5}},Florence:{otherCivName:Florence,diplomaticModifiers:{DeclaredWarOnUs:-6.875}}},popupAlerts:[{type:GoldenAge,value:""},{type:GoldenAge,value:""},{type:FirstContact,value:Florence},{type:WarDeclaration,value:Florence},{type:GoldenAge,value:""},{type:FirstContact,value:Quebec City},{type:WarDeclaration,value:Quebec City},{type:FirstContact,value:Vancouver},{type:WarDeclaration,value:Vancouver}],exploredTiles:[{x:1,y:12},{x:3},{x:8,y:6},{x:6,y:-4},{x:-1,y:-12},{x:3,y:-2},{x:4,y:-3},{x:7,y:5},{x:6,y:4},{x:4,y:3},{x:3,y:2},{x:6,y:5},{x:5,y:4},{x:5,y:-4},{x:6,y:-5},{x:8,y:7},{x:7,y:6},{x:1,y:8},{x:8,y:4},{x:4,y:-2},{x:-1,y:8},{x:2,y:-1},{x:1,y:-8},{x:10,y:5},{y:1},{x:4},{y:-1},{x:-1,y:-8},{x:6,y:-3},{x:6,y:3},{x:4,y:2},{x:2,y:1},{x:-2,y:-1},{x:-1,y:9},{x:1,y:9},{x:9,y:5},{x:-1,y:-9},{x:-1,y:-10},{x:5,y:-3},{x:8,y:5},{x:-1,y:10},{x:1,y:-10},{x:7,y:4},{x:5,y:3},{x:1,y:10},{x:-7,y:4},{x:7,y:-4},{x:9,y:6},{x:1,y:11},{x:-1,y:-11},{x:6},{x:-2,y:-12},{x:-8,y:3},{x:6,y:-2},{x:8,y:-3},{x:1,y:6},{x:-1,y:-6},{x:3,y:-1},{x:-1,y:6},{x:1,y:-6},{x:8,y:3},{x:2,y:12},{x:3,y:1},{x:6,y:2},{x:11,y:4},{x:2,y:13},{x:5,y:2},{x:5},{x:5,y:-2},{x:7,y:3},{x:-1,y:-7},{x:1,y:7},{x:-10,y:-4},{x:-1,y:7},{x:10,y:4},{x:1,y:-7},{x:-7,y:3},{x:7,y:-3},{x:9,y:4},{x:-8,y:-2},{x:4,y:-1},{x:12,y:3},{x:8,y:2},{y:8},{x:1,y:-4},{x:-8},{x:-2,y:8},{x:2,y:-8},{x:-8,y:2},{x:8,y:-2},{x:8},{y:-8},{x:1,y:4},{x:4,y:1},{x:3,y:12},{x:2,y:8},{x:-1,y:-4},{x:-2,y:-8},{x:-11,y:-3},{y:9},{x:-2,y:9},{x:11,y:3},{y:-9},{x:2,y:9},{x:-10,y:-3},{x:2,y:10},{y:-10},{x:-7,y:2},{x:7,y:-2},{y:10},{x:-7},{x:1,y:-5},{x:-1,y:5},{x:10,y:3},{x:7,y:2},{x:-2,y:-10},{x:7},{x:-1,y:-5},{x:1,y:5},{y:11},{x:2,y:11},{y:-11},{x:-2,y:-11},{x:-9,y:-3},{x:9,y:3},{x:6,y:-1},{x:3,y:-8},{x:-2,y:6},{x:2,y:-6},{x:-3,y:8},{x:12,y:2},{x:-12,y:-2},{x:1,y:-3},{y:6},{x:6,y:1},{x:2,y:6},{x:1,y:3},{x:-2,y:-6},{x:-1,y:-3},{y:-6},{x:3,y:8},{x:11},{x:11,y:2},{x:-11},{x:-11,y:-2},{x:3,y:9},{x:2,y:7},{x:10},{y:-7},{x:5,y:1},{x:3,y:10},{x:-2,y:-7},{x:5,y:-1},{x:-2,y:7},{x:2,y:-7},{x:-10,y:-2},{x:10,y:2},{x:-10},{y:7},{x:-9},{x:-9,y:-2},{x:-3,y:-11},{x:-9,y:2},{x:9},{x:9,y:2},{x:9,y:-2},{x:3,y:11},{x:8,y:-1},{x:3,y:-6},{x:1,y:-2},{x:-1,y:2},{x:-1},{x:2,y:-4},{x:-3,y:6},{x:-8,y:1},{x:-8,y:-1},{x:8,y:1},{x:3,y:6},{x:2,y:4},{x:1,y:2},{x:1},{y:-4},{x:-1,y:-2},{x:4,y:8},{x:5,y:10},{x:4,y:9},{y:-5},{x:7,y:-1},{x:3,y:-7},{x:3,y:7},{x:2,y:5},{x:-2,y:-5},{x:7,y:1},{y:5},{x:4,y:10},{x:-7,y:1},{x:-3,y:7},{x:2,y:-5},{x:13,y:2},{x:4,y:11},{x:5,y:7},{x:4,y:-6},{x:3,y:-4},{x:-4,y:6},{x:2,y:-3},{x:12,y:1},{y:3},{x:-12,y:-1},{x:6,y:8},{x:4,y:6},{x:3,y:4},{x:2,y:3},{x:-2,y:-3},{y:-3},{x:11,y:1},{x:-11,y:-1},{x:6,y:9},{x:10,y:-1},{x:4,y:-7},{x:3,y:-5},{x:-4,y:7},{x:-10,y:-1},{x:10,y:1},{x:4,y:7},{x:3,y:5},{x:5,y:8},{x:-10,y:1},{x:-9,y:-1},{x:-9,y:1},{x:9,y:1},{x:9,y:-1},{x:5,y:9},{x:4,y:4},{x:5,y:-5},{x:4,y:-4},{x:6,y:6},{x:7,y:7},{x:1,y:-1},{x:-1,y:1},{y:2},{x:2,y:-2},{x:3,y:-3},{x:5,y:5},{x:3,y:3},{x:2,y:2},{x:2},{x:1,y:1},{x:-1,y:-1},{},{x:-2,y:-2},{y:-2},{x:-5,y:6},{x:7,y:8},{x:4,y:-5},{x:5,y:-6},{x:6,y:7},{x:5,y:6},{x:4,y:5}],hasEverOwnedOriginalCapital:false,totalFaithForContests:7},{playerType:Human,gold:126,civName:England,tech:{freeTechs:1,scienceOfLast8Turns:[254,321,321,321,254,254,254,254],techsResearched:[Optics,Steam Power,Acoustics,Mining,Sailing,Education,Archery,Currency,Gunpowder,Fertilizer,Metal Casting,Economics,Construction,Architecture,Banking,Compass,Horseback Riding,Chemistry,Industrialization,Bronze Working,Scientific Theory,Philosophy,Drama and Poetry,Military Science,Printing Press,Mathematics,Guilds,Biology,Machinery,Engineering,Archaeology,Dynamite,Electricity,Pottery,Agriculture,Iron Working,Rifling,Theology,Navigation,Calendar,Masonry,Steel,Physics,Trapping,Chivalry,The Wheel,Animal Husbandry,Writing,Civil Service,Metallurgy,Astronomy],techsToResearch:[Radio],techsInProgress:{Radio:1137}},policies:{storedCulture:1826,adoptedPolicies:[Honor,Meritocracy,Warrior Code,Tradition,Liberty Complete,Mandate Of Heaven,Citizenship,Piety,Commerce,Representation,Reformation,Liberty,Republic,Naval Tradition,Aristocracy,Organized Religion,Collective Rule],numberOfAdoptedPolicies:15},civConstructions:{boughtItemsWithIncreasingPrice:{Great Prophet:{class:java.lang.Integer,value:4}}},questManager:{},religionManager:{storedFaith:3077,religionState:EnhancedReligion},goldenAges:{storedHappiness:118,numberOfGoldenAges:2},greatPeople:{pointsForNextGreatPerson:3200,greatPersonPointsCounter:{Great Engineer:{class:java.lang.Integer,value:1542},Great Scientist:{class:java.lang.Integer,value:84},Great Merchant:{class:java.lang.Integer,value:489},Great Artist:{class:java.lang.Integer,value:676}},greatGeneralPoints:161},victoryManager:{},ruinsManager:{lastChosenRewards:[a stash of gold,discover holy symbols]},diplomacy:{Inca:{otherCivName:Inca,diplomaticStatus:Peace,diplomaticModifiers:{YearsOfPeace:30}},Vancouver:{otherCivName:Vancouver,diplomaticStatus:Peace,diplomaticModifiers:{YearsOfPeace:30,SharedEnemy:5}},Singapore:{otherCivName:Singapore,diplomaticStatus:Peace,diplomaticModifiers:{YearsOfPeace:30}},Quebec City:{otherCivName:Quebec City,diplomaticStatus:Peace,diplomaticModifiers:{YearsOfPeace:30,SharedEnemy:5}},Songhai:{otherCivName:Songhai,diplomaticStatus:Peace,diplomaticModifiers:{YearsOfPeace:30}},Florence:{otherCivName:Florence,diplomaticStatus:Peace,diplomaticModifiers:{YearsOfPeace:30,SharedEnemy:5}}},proximity:{Inca:Close,Vancouver:Far,Singapore:Far,Quebec City:Close,Songhai:Close,Florence:Neighbors},notifications:[{text:"Work has started on [Science]",icons:[StatIcons/Production],action:{class:com.unciv.logic.civilization.CityAction,city:{x:6,y:-5}}},{text:"Work has started on [Amphitheater]",icons:[StatIcons/Production],action:{class:com.unciv.logic.civilization.CityAction,city:{x:10,y:2}}},{text:"[Temple] has been built in [Nottingham]",icons:[StatIcons/Production,BuildingIcons/Temple],action:{class:com.unciv.logic.civilization.LocationAction,locations:[{x:10,y:2}]}},{text:"Work has started on [Amphitheater]",icons:[StatIcons/Production],action:{class:com.unciv.logic.civilization.CityAction,city:{x:1,y:-9}}},{text:"[Temple] has been built in [Hastings]",icons:[StatIcons/Production,BuildingIcons/Temple],action:{class:com.unciv.logic.civilization.LocationAction,locations:[{x:1,y:-9}]}},{text:"A [Great Engineer] has been born in [London]!",icons:[Great Engineer],action:{class:com.unciv.logic.civilization.LocationAction,locations:[{x:6,y:-4}]}},{text:"A [Great Scientist] has been born in [London]!",icons:[Great Scientist],action:{class:com.unciv.logic.civilization.LocationAction,locations:[{x:5,y:-5}]}}],naturalWonders:[Krakatoa],cities:[{location:{x:6,y:-5},id:8aa5d987-030c-4577-b34c-41543f4430a3,name:London,foundingCiv:England,turnAcquired:9,health:300,religion:{religionsAtSomePointAdopted:[Christianity,God of War],pressures:{The religion of TheLegend27:{class:java.lang.Integer,value:200},God of War:{class:java.lang.Integer,value:1000},Christianity:{class:java.lang.Integer,value:24608}},religionThisIsTheHolyCityOf:Christianity},population:{population:21,foodStored:322,specialistAllocations:{Scientist:{class:java.lang.Integer,value:3},Engineer:{class:java.lang.Integer,value:1}}},cityConstructions:{builtBuildings:[Monument,Amphitheater,Mosque,Ironworks,Lighthouse,Terracotta Army,The Oracle,Statue of Zeus,The Great Lighthouse,Colosseum,Aqueduct,Windmill,Hospital,Granary,Palace,Forbidden Palace,Circus Maximus,Military Academy,Himeji Castle,Market,Stonehenge,Hagia Sophia,Barracks,Opera House,Shrine,University,Theatre,Museum,Seaport,Library,Walls,Bank,Circus,Harbor,Alhambra,Factory,The Great Library,Hanging Gardens,The Pyramids,Great Mosque of Djenne,Public School,Arsenal,Workshop,Temple of Artemis,Armory,Stock Exchange,Garden,The Louvre,National Treasury,National Epic,Stable,National College,Colossus,Brandenburg Gate,Temple,Great Wall,Castle,Kremlin],inProgressConstructions:{Monument:5,Shrine:5,Library:5,Granary:5,Barracks:11},constructionQueue:[Science],productionOverflow:65,freeBuildingsProvidedFromThisCity:{8aa5d987-030c-4577-b34c-41543f4430a3:[{class:java.lang.String,value:Garden},{class:java.lang.String,value:Mosque}]}},expansion:{cultureStored:4895},tiles:[{x:5,y:-5},{x:6,y:-4},{x:8,y:-3},{x:4,y:-6},{x:6,y:-3},{x:8,y:-2},{x:8,y:-1},{x:3,y:-8},{x:3,y:-6},{x:2,y:-6},{x:11},{x:7,y:-2},{x:6,y:-5},{x:5,y:-4},{x:4,y:-7},{x:4,y:-5},{x:3,y:-7},{x:2,y:-7},{x:7,y:-1},{x:7,y:-3},{x:5,y:-6},{x:7,y:-4},{x:10,y:-1},{x:10},{x:9,y:-2},{x:9,y:-1},{x:9}],workedTiles:[{x:4,y:-6},{x:8,y:-3},{x:6,y:-3},{x:3,y:-8},{x:8,y:-2},{x:5,y:-5},{x:6,y:-4},{x:3,y:-6},{x:7,y:-2},{x:4,y:-5},{x:7,y:-3},{x:7,y:-4},{x:5,y:-6},{x:4,y:-7},{x:5,y:-4},{x:3,y:-7},{x:9,y:-2}],isOriginalCapital:true},{location:{x:4,y:-3},id:72e90b7f-27fd-40f4-a3e9-d63ed9584e70,name:York,foundingCiv:England,turnAcquired:70,health:300,religion:{religionsAtSomePointAdopted:[Christianity,God of War],pressures:{The religion of TheLegend27:{class:java.lang.Integer,value:200},God of War:{class:java.lang.Integer,value:1300},Christianity:{class:java.lang.Integer,value:13958},Sikhism:{class:java.lang.Integer,value:2601}}},population:{population:20,foodStored:178,specialistAllocations:{Scientist:{class:java.lang.Integer,value:3}}},cityConstructions:{builtBuildings:[Monument,Amphitheater,Bank,Circus,Harbor,Leaning Tower of Pisa,Factory,Lighthouse,Colosseum,Aqueduct,Public School,Arsenal,Windmill,Workshop,Hospital,Granary,Armory,Stock Exchange,Angkor Wat,Garden,Big Ben,Stable,Market,Barracks,Opera House,Shrine,Temple,University,Theatre,Castle,Seaport,Library,Walls],inProgressConstructions:{Monument:2,Shrine:2,Museum:111,Library:3,Granary:2,Walls:3},constructionQueue:[Museum]},expansion:{cultureStored:174},tiles:[{x:6,y:-1},{x:3,y:-4},{x:3,y:-3},{x:3,y:-2},{x:2,y:-3},{x:2,y:-4},{x:4,y:-2},{x:4,y:-1},{x:4,y:-3},{x:4,y:-4},{x:6,y:-2},{x:4},{x:6},{x:4,y:1},{x:8,y:1},{x:6,y:1},{x:5},{x:5,y:-2},{x:5,y:-3},{x:5,y:-1},{x:2,y:-5},{x:3,y:-5},{x:7},{x:7,y:1},{x:7,y:2},{x:5,y:1}],workedTiles:[{x:4,y:-2},{x:4,y:-1},{x:6,y:-1},{x:6,y:-2},{x:4},{x:6},{x:3,y:-3},{x:3,y:-2},{x:2,y:-3},{x:2,y:-4},{x:4,y:-4},{x:3,y:-4},{x:5,y:-3},{x:5},{x:5,y:-1},{x:5,y:-2},{x:2,y:-5}]},{location:{x:10,y:2},id:3d84c4d3-bfd4-499b-a8d3-5508f608ea5b,name:Nottingham,foundingCiv:England,turnAcquired:147,health:250,religion:{religionsAtSomePointAdopted:[Christianity],pressures:{The religion of TheLegend27:{class:java.lang.Integer,value:100},Christianity:{class:java.lang.Integer,value:10111},Sikhism:{class:java.lang.Integer,value:63}}},population:{population:6,foodStored:1},cityConstructions:{builtBuildings:[Monument,Bank,Harbor,Forge,Lighthouse,Market,Stone Works,Colosseum,Barracks,Shrine,University,Temple,Theatre,Windmill,Seaport,Library,Workshop,Granary,Walls],constructionQueue:[Amphitheater],productionOverflow:36},expansion:{cultureStored:5},tiles:[{x:10,y:2},{x:10,y:1},{x:10,y:3},{x:12,y:3},{x:8},{x:12,y:2},{x:8,y:2},{x:12,y:1},{x:9,y:1},{x:11,y:3},{x:11,y:2},{x:9,y:2},{x:9,y:3},{x:11,y:1},{x:11,y:4},{x:13,y:2}],workedTiles:[{x:9,y:3},{x:8},{x:8,y:2},{x:9,y:1},{x:11,y:1},{x:12,y:3}]},{location:{x:1,y:-9},id:febdacbf-d1bf-43b5-bf2f-b9b45b1a2121,name:Hastings,foundingCiv:England,turnAcquired:186,health:250,religion:{religionsAtSomePointAdopted:[Christianity],pressures:{The religion of TheLegend27:{class:java.lang.Integer,value:100},Christianity:{class:java.lang.Integer,value:7912}}},population:{population:6,foodStored:50},cityConstructions:{builtBuildings:[Monument,Bank,Harbor,Stable,Lighthouse,Market,Colosseum,Barracks,Shrine,University,Temple,Theatre,Windmill,Seaport,Library,Workshop,Granary,Walls],constructionQueue:[Amphitheater],productionOverflow:24},expansion:{cultureStored:156},tiles:[{y:-10},{x:2,y:-8},{x:1,y:-10},{x:1,y:-8},{x:-1,y:-10},{y:-8},{x:-2,y:-12},{x:-1,y:-12},{x:1,y:-9},{x:2,y:-9},{y:-9},{y:-11},{x:-1,y:-11}],workedTiles:[{y:-11},{x:1,y:-10},{x:1,y:-8},{y:-8},{y:-10},{x:2,y:-8}]},{location:{x:1,y:-5},id:9a89b8bb-12f8-4c47-939e-580379c14abf,name:Canterbury,foundingCiv:England,turnAcquired:191,health:220,religion:{religionsAtSomePointAdopted:[Christianity],pressures:{The religion of TheLegend27:{class:java.lang.Integer,value:100},Christianity:{class:java.lang.Integer,value:9738},Sikhism:{class:java.lang.Integer,value:2250}}},population:{population:9,foodStored:71,specialistAllocations:{Engineer:{class:java.lang.Integer,value:1},Merchant:{class:java.lang.Integer,value:1}}},cityConstructions:{builtBuildings:[Monument,Shrine,Harbor,Lighthouse,Library,Workshop,Granary,Market,Colosseum,Walls],inProgressConstructions:{Artillery:15},constructionQueue:[Artillery]},expansion:{cultureStored:207},tiles:[{x:1,y:-5},{y:-6},{x:1,y:-6},{x:1,y:-4},{y:-5},{y:-7},{x:1,y:-7},{x:-1,y:-6}],workedTiles:[{y:-6},{y:-7},{x:1,y:-7},{x:-1,y:-6},{x:1,y:-6},{x:1,y:-4},{y:-5}]},{location:{y:10},id:821b6b50-b57d-4739-8cc5-a646521afcc2,name:Coventry,foundingCiv:England,turnAcquired:209,religion:{religionsAtSomePointAdopted:[Christianity],pressures:{The religion of TheLegend27:{class:java.lang.Integer,value:100},Christianity:{class:java.lang.Integer,value:2538},Sikhism:{class:java.lang.Integer,value:2250}}},population:{population:10,foodStored:24,specialistAllocations:{Engineer:{class:java.lang.Integer,value:1}}},cityConstructions:{builtBuildings:[Monument,Shrine,Lighthouse,Library,Workshop,Granary,Market],inProgressConstructions:{Harbor:69},constructionQueue:[Harbor]},expansion:{cultureStored:49},tiles:[{y:10},{x:1,y:10},{x:-1,y:10},{x:1,y:12},{x:-1,y:8},{y:8},{x:-2,y:8},{x:-3,y:7},{x:-1,y:9},{x:1,y:11},{y:9},{y:11},{x:-2,y:9}],workedTiles:[{x:-1,y:8},{y:8},{x:-3,y:7},{x:1,y:10},{x:-2,y:8},{x:-2,y:9},{x:-1,y:9},{x:1,y:11},{y:9}]}],citiesCreated:6,exploredTiles:[{x:-1,y:-12},{x:6,y:-4},{x:6,y:4},{x:7,y:5},{x:8,y:6},{x:4,y:3},{x:3,y:2},{x:1,y:12},{x:3},{x:4,y:-3},{x:-6,y:4},{x:3,y:-2},{x:6,y:-5},{x:5,y:-4},{x:6,y:5},{x:7,y:6},{x:5,y:4},{x:-5,y:4},{x:-6,y:5},{x:2,y:1},{x:6,y:-3},{x:-6,y:3},{x:4},{x:1,y:-8},{x:2,y:-1},{x:-1,y:-8},{x:1,y:8},{x:4,y:2},{x:6,y:3},{x:10,y:5},{y:1},{x:-1,y:8},{x:8,y:4},{x:4,y:-2},{x:1,y:-9},{x:-1,y:-9},{x:9,y:5},{x:1,y:9},{x:-1,y:9},{x:-1,y:-10},{x:5,y:-3},{x:1,y:-10},{x:-1,y:10},{x:7,y:4},{x:8,y:5},{x:5,y:3},{x:7,y:-4},{x:1,y:10},{x:-7,y:4},{x:-5,y:3},{x:-1,y:-11},{x:1,y:11},{x:6,y:2},{x:8,y:-3},{x:6,y:-2},{x:-8,y:3},{x:1,y:-6},{x:-1,y:6},{x:3,y:1},{x:2,y:12},{x:8,y:3},{x:-2,y:-12},{x:6},{x:-1,y:-6},{x:1,y:6},{x:3,y:-1},{x:11,y:4},{x:2,y:13},{x:-1,y:-7},{x:7,y:-3},{x:1,y:-7},{x:-1,y:7},{x:10,y:4},{x:5,y:2},{x:1,y:7},{x:7,y:3},{x:5},{x:-7,y:3},{x:5,y:-2},{x:9,y:4},{x:1,y:4},{y:-8},{x:8,y:-2},{x:8},{x:-1,y:-4},{x:12,y:3},{x:1,y:-4},{x:-1,y:4},{y:8},{x:-2,y:-8},{x:8,y:2},{x:4,y:1},{x:3,y:12},{x:2,y:8},{x:4,y:-1},{x:2,y:-8},{x:-2,y:8},{x:2,y:-9},{y:-9},{x:11,y:3},{x:-2,y:-9},{y:9},{x:2,y:9},{x:-2,y:9},{x:1,y:5},{x:-2,y:-10},{x:7,y:-2},{x:7},{x:-1,y:-5},{x:10,y:3},{x:1,y:-5},{y:10},{x:-1,y:5},{x:7,y:2},{x:2,y:10},{y:-10},{y:-11},{x:-2,y:-11},{x:9,y:3},{x:2,y:11},{y:11},{x:6,y:1},{x:12,y:2},{x:1,y:-3},{y:6},{x:-1,y:3},{x:-1,y:-3},{x:1,y:3},{x:-3,y:-8},{x:-2,y:-6},{y:-6},{x:6,y:-1},{x:2,y:-6},{x:-2,y:6},{x:3,y:-8},{x:-3,y:8},{x:11},{x:11,y:2},{x:-3,y:-9},{x:-2,y:-7},{x:-3,y:-10},{x:10},{x:5,y:1},{x:10,y:2},{y:7},{y:-7},{x:5,y:-1},{x:2,y:-7},{x:-2,y:7},{x:9,y:-2},{x:9},{x:9,y:2},{x:-3,y:-11},{x:3,y:11},{x:-2,y:-4},{x:8,y:-1},{y:-4},{x:1},{x:8,y:1},{x:1,y:2},{x:2,y:4},{x:-3,y:-6},{x:3,y:6},{x:-4,y:-8},{x:1,y:-2},{x:-1,y:2},{y:4},{x:3,y:-6},{x:-3,y:6},{x:2,y:-4},{x:-2,y:4},{x:-4,y:-9},{x:-2,y:-5},{x:7,y:-1},{y:5},{x:7,y:1},{x:-4,y:-10},{x:2,y:5},{x:-3,y:-7},{x:3,y:-7},{x:2,y:-5},{x:-2,y:5},{x:-3,y:7},{y:-5},{x:13,y:2},{x:-4,y:-6},{x:12,y:1},{y:3},{x:4,y:6},{x:3,y:4},{x:-5,y:-7},{y:-3},{x:2,y:3},{x:4,y:-6},{x:2,y:-3},{x:-2,y:3},{x:3,y:-4},{x:-4,y:6},{x:-3,y:4},{x:11,y:1},{x:3,y:5},{x:-4,y:-7},{x:10,y:1},{x:-3,y:-5},{x:-5,y:-8},{x:10,y:-1},{x:-4,y:7},{x:3,y:-5},{x:-3,y:5},{x:4,y:-7},{x:9,y:-1},{x:9,y:1},{x:-5,y:-9},{x:2,y:2},{x:2},{x:1,y:1},{},{y:2},{x:-1,y:1},{x:1,y:-1},{x:4,y:4},{x:5,y:5},{x:3,y:3},{x:4,y:-4},{x:-4,y:4},{x:-5,y:5},{x:5,y:-5},{x:2,y:-2},{x:-2,y:2},{x:3,y:-3},{x:5,y:-6},{x:4,y:5},{x:5,y:6},{x:4,y:-5},{x:-4,y:5},{x:-5,y:6}],hasEverOwnedOriginalCapital:true,numMinorCivsAttacked:3,totalCultureForContests:11171,totalFaithForContests:4293},{gold:-1997,civName:Songhai,tech:{scienceOfLast8Turns:[89,89,91,106,89,89,89,89],techsResearched:[Drama and Poetry,Printing Press,Optics,Mathematics,Guilds,Acoustics,Machinery,Mining,Engineering,Sailing,Education,Archery,Currency,Pottery,Agriculture,Iron Working,Gunpowder,Metal Casting,Theology,Construction,Navigation,Calendar,Banking,Masonry,Steel,Compass,Horseback Riding,Physics,Trapping,Bronze Working,Chivalry,The Wheel,Animal Husbandry,Writing,Civil Service,Metallurgy,Philosophy,Astronomy],techsToResearch:[Chemistry],techsInProgress:{Chemistry:580}},policies:{storedCulture:762,adoptedPolicies:[Honor,Warrior Code,Citizenship,Representation,Military Caste,Professional Army,Military Tradition,Liberty,Discipline,Honor Complete,Republic,Collective Rule],numberOfAdoptedPolicies:11},civConstructions:{boughtItemsWithIncreasingPrice:{Great Prophet:{class:java.lang.Integer,value:4}}},questManager:{},religionManager:{storedFaith:97,religionState:Pantheon},goldenAges:{storedHappiness:178,numberOfGoldenAges:3},greatPeople:{pointsForNextGreatPerson:800,greatPersonPointsCounter:{Great Merchant:{class:java.lang.Integer,value:309},Great Engineer:{class:java.lang.Integer,value:168},Great Scientist:{class:java.lang.Integer,value:251}},greatGeneralPoints:75},victoryManager:{},ruinsManager:{},diplomacy:{Inca:{otherCivName:Inca,diplomaticStatus:Peace,diplomaticModifiers:{YearsOfPeace:30}},Vancouver:{otherCivName:Vancouver,diplomaticStatus:Peace,diplomaticModifiers:{YearsOfPeace:30,SharedEnemy:5}},Singapore:{otherCivName:Singapore,diplomaticStatus:Peace,diplomaticModifiers:{YearsOfPeace:30}},Quebec City:{otherCivName:Quebec City,diplomaticStatus:Peace,diplomaticModifiers:{YearsOfPeace:30,SharedEnemy:5}},Florence:{otherCivName:Florence,diplomaticStatus:Peace,diplomaticModifiers:{YearsOfPeace:30,SharedEnemy:5}},England:{otherCivName:England,diplomaticStatus:Peace,diplomaticModifiers:{YearsOfPeace:30}}},proximity:{Inca:Far,Vancouver:Far,Singapore:Far,Quebec City:Neighbors,Florence:Far,England:Close},cities:[{location:{x:2,y:6},id:07711111-a63e-424d-a0ab-7d08a3eb7c7b,name:Gao,foundingCiv:Songhai,health:300,religion:{religionsAtSomePointAdopted:[Desert Folklore],pressures:{The religion of TheLegend27:{class:java.lang.Integer,value:1100},Sikhism:{class:java.lang.Integer,value:4977},Desert Folklore:{class:java.lang.Integer,value:2200},Christianity:{class:java.lang.Integer,value:5678}}},population:{population:15,foodStored:85,specialistAllocations:{Merchant:{class:java.lang.Integer,value:1},Scientist:{class:java.lang.Integer,value:2}}},cityConstructions:{builtBuildings:[Monument,Amphitheater,Palace,Harbor,National Epic,Stable,Lighthouse,Market,Colosseum,Barracks,Aqueduct,Shrine,Mud Pyramid Mosque,University,Theatre,Arsenal,Castle,Seaport,Library,Workshop,Granary,Walls,Armory],inProgressConstructions:{Bank:141},constructionQueue:[Bank]},expansion:{cultureStored:180},tiles:[{x:2,y:6},{x:3,y:6},{x:4,y:6},{x:3,y:8},{x:4,y:8},{x:1,y:6},{x:1,y:4},{y:4},{y:6},{x:-1,y:6},{x:2,y:4},{x:1,y:8},{x:2,y:8},{x:3,y:3},{x:1,y:5},{x:3,y:7},{x:2,y:5},{x:2,y:7},{x:3,y:5},{x:4,y:7},{x:1,y:7},{y:7}],workedTiles:[{x:2,y:4},{x:1,y:4},{y:4},{y:6},{x:-1,y:6},{x:3,y:6},{x:1,y:6},{x:1,y:8},{x:2,y:5},{x:2,y:7},{x:3,y:5},{y:7}],isOriginalCapital:true},{location:{x:6,y:7},id:29c5388e-ae9c-426c-9de8-cdac3b056419,name:Tombouctu,foundingCiv:Songhai,turnAcquired:69,health:300,religion:{religionsAtSomePointAdopted:[Desert Folklore,Sikhism,Christianity],pressures:{The religion of TheLegend27:{class:java.lang.Integer,value:400},Sikhism:{class:java.lang.Integer,value:3267},Desert Folklore:{class:java.lang.Integer,value:600},Christianity:{class:java.lang.Integer,value:9127}}},population:{population:8,foodStored:56},cityConstructions:{builtBuildings:[Monument,Amphitheater,Harbor,Lighthouse,National College,Market,Colosseum,Barracks,Aqueduct,Shrine,Mud Pyramid Mosque,University,Theatre,Arsenal,Castle,Seaport,Library,Workshop,Granary,Walls,Armory],inProgressConstructions:{Bank:25},constructionQueue:[Bank]},expansion:{cultureStored:1},tiles:[{x:6,y:6},{x:5,y:7},{x:6,y:8},{x:7,y:7},{x:5,y:5},{x:8,y:6},{x:3,y:4},{x:4,y:4},{x:6,y:4},{x:6,y:2},{x:4,y:3},{x:6,y:9},{x:6,y:7},{x:5,y:6},{x:7,y:8},{x:4,y:5},{x:8,y:7},{x:5,y:8},{x:6,y:5},{x:7,y:6},{x:5,y:4}],workedTiles:[{x:4,y:5},{x:5,y:5},{x:4,y:4},{x:8,y:6},{x:6,y:9},{x:3,y:4},{x:6,y:4},{x:6,y:6}]},{location:{x:5,y:10},id:19ca83b7-175c-4364-b926-06376fbf6c1a,name:Jenne,foundingCiv:Songhai,turnAcquired:76,health:275,religion:{religionsAtSomePointAdopted:[Desert Folklore,Christianity],pressures:{The religion of TheLegend27:{class:java.lang.Integer,value:400},Desert Folklore:{class:java.lang.Integer,value:600},Christianity:{class:java.lang.Integer,value:3617},Sikhism:{class:java.lang.Integer,value:792}}},population:{population:4},cityConstructions:{builtBuildings:[Monument,Amphitheater,Market,Colosseum,Barracks,Aqueduct,Shrine,Mud Pyramid Mosque,University,Castle,Library,Workshop,Granary,Walls],inProgressConstructions:{Armory:104},constructionQueue:[Armory]},expansion:{cultureStored:154},tiles:[{x:5,y:10},{x:4,y:10},{x:3,y:10},{x:2,y:10},{x:3,y:12},{x:2,y:12},{x:4,y:9},{x:5,y:9},{x:4,y:11},{x:3,y:9},{x:3,y:11},{x:2,y:9},{x:2,y:11},{x:1,y:9}],workedTiles:[{x:4,y:11},{x:2,y:10},{x:3,y:9},{x:3,y:11}]},{location:{x:9,y:6},id:4b30a528-ca87-423a-add6-d8af7b10c30f,name:Taghaza,foundingCiv:Songhai,turnAcquired:162,religion:{religionsAtSomePointAdopted:[Desert Folklore],pressures:{The religion of TheLegend27:{class:java.lang.Integer,value:100},Desert Folklore:{class:java.lang.Integer,value:200},Christianity:{class:java.lang.Integer,value:8186},Sikhism:{class:java.lang.Integer,value:63}}},population:{population:10,foodStored:70,specialistAllocations:{Merchant:{class:java.lang.Integer,value:1}}},cityConstructions:{builtBuildings:[Monument,Shrine,Lighthouse,Library,Granary,Market],inProgressConstructions:{Workshop:5},constructionQueue:[Workshop]},expansion:{cultureStored:56},tiles:[{x:8,y:5},{x:10,y:5},{x:7,y:4},{x:8,y:4},{x:7,y:5},{x:10,y:4},{x:6,y:3},{x:7,y:3},{x:9,y:6},{x:9,y:5},{x:9,y:4}],workedTiles:[{x:10,y:5},{x:7,y:4},{x:8,y:4},{x:6,y:3},{x:7,y:3},{x:8,y:5},{x:7,y:5},{x:9,y:5},{x:9,y:4}]}],citiesCreated:4,exploredTiles:[{x:-7,y:-5},{x:-6,y:-4},{x:1,y:12},{x:-4,y:3},{x:3},{x:4,y:-3},{x:-6,y:4},{x:-3,y:2},{x:3,y:-2},{x:-8,y:-6},{x:6,y:4},{x:7,y:5},{x:8,y:6},{x:-3},{x:-4,y:-3},{x:4,y:3},{x:-3,y:-2},{x:3,y:2},{x:6,y:5},{x:-6,y:-5},{x:-5,y:-4},{x:-5,y:4},{x:-6,y:5},{x:-7,y:-6},{x:8,y:7},{x:7,y:6},{x:5,y:4},{x:6,y:3},{x:-6,y:-3},{x:-4,y:2},{x:-6,y:3},{x:4,y:-2},{y:-1},{y:1},{x:10,y:5},{x:8,y:4},{x:-8,y:-4},{x:-4},{x:-2,y:1},{x:2,y:-1},{x:-1,y:8},{x:1,y:-8},{x:-4,y:-2},{x:4,y:2},{x:-2,y:-1},{x:1,y:8},{x:4},{x:-1,y:-8},{x:2,y:1},{x:-9,y:-5},{x:1,y:9},{x:-1,y:9},{x:9,y:5},{x:-1,y:-9},{x:1,y:-9},{x:-5,y:-3},{x:5,y:3},{x:-1,y:10},{x:-5,y:3},{x:-7,y:4},{x:1,y:10},{x:7,y:4},{x:-7,y:-4},{x:-8,y:-5},{x:8,y:5},{x:1,y:11},{x:9,y:6},{x:6,y:2},{x:-6,y:2},{x:-3,y:1},{x:3,y:-1},{x:8,y:3},{x:-8,y:-3},{x:-6,y:-2},{x:-6},{x:-1,y:6},{x:1,y:-6},{x:1,y:6},{x:-1,y:-6},{x:6},{x:-8,y:3},{x:2,y:12},{x:-3,y:-1},{x:3,y:1},{x:2,y:13},{x:11,y:4},{x:-7,y:-3},{x:-5,y:-2},{x:5,y:2},{x:1,y:7},{x:-1,y:-7},{x:-5,y:2},{x:-7,y:3},{x:5,y:-2},{x:5},{x:-5},{x:10,y:4},{x:-1,y:7},{x:1,y:-7},{x:-10,y:-4},{x:7,y:3},{x:-9,y:-4},{x:9,y:4},{x:2,y:8},{x:1,y:4},{x:-1,y:-4},{x:-2,y:-8},{x:8},{y:-8},{x:8,y:-2},{x:-4,y:1},{x:-2,y:8},{x:4,y:-1},{x:2,y:-8},{y:8},{x:-8,y:-2},{x:8,y:2},{x:12,y:3},{x:-1,y:4},{x:1,y:-4},{x:-4,y:-1},{x:4,y:1},{x:3,y:12},{x:2,y:9},{y:9},{x:-2,y:9},{x:-2,y:-9},{y:-9},{x:11,y:3},{x:7,y:2},{x:-7},{x:1,y:5},{x:7},{x:7,y:-2},{x:-1,y:-5},{x:-1,y:5},{x:1,y:-5},{x:-7,y:-2},{y:10},{x:10,y:3},{x:2,y:10},{x:-9,y:-3},{x:2,y:11},{y:11},{x:9,y:3},{x:1,y:3},{x:2,y:6},{x:-2,y:-6},{x:-6,y:1},{x:6,y:-1},{y:-6},{x:-3,y:8},{x:-2,y:6},{x:2,y:-6},{x:-6,y:-1},{y:6},{x:12,y:2},{x:-1,y:3},{x:1,y:-3},{x:3,y:8},{x:6,y:1},{x:3,y:9},{x:11,y:2},{x:11},{x:2,y:7},{x:-5,y:-1},{x:-5,y:1},{y:-7},{x:10},{x:-2,y:7},{x:2,y:-7},{y:7},{x:3,y:10},{x:-2,y:-7},{x:10,y:2},{x:5,y:-1},{x:5,y:1},{x:-9,y:-2},{x:3,y:11},{x:9,y:2},{x:9},{x:9,y:-2},{x:2,y:4},{x:3,y:6},{x:4,y:8},{x:5,y:10},{x:-2,y:4},{x:1,y:2},{y:-4},{x:1},{x:-3,y:6},{x:3,y:-6},{x:8,y:-1},{x:2,y:-4},{y:4},{x:-1},{x:-8,y:-1},{x:8,y:1},{x:-1,y:2},{x:1,y:-2},{x:4,y:9},{x:3,y:7},{x:2,y:5},{x:-2,y:-5},{x:7,y:-1},{y:-5},{x:4,y:10},{y:5},{x:-2,y:5},{x:2,y:-5},{x:-3,y:7},{x:-7,y:-1},{x:7,y:1},{x:4,y:11},{x:13,y:2},{x:4,y:6},{x:5,y:7},{x:-3,y:4},{x:-2,y:3},{x:2,y:-3},{y:3},{x:-5,y:-7},{x:6,y:8},{x:12,y:1},{x:3,y:4},{x:-4,y:-6},{x:2,y:3},{y:-3},{x:3,y:-4},{x:-4,y:6},{x:-3,y:-4},{x:6,y:9},{x:11,y:1},{x:-3,y:-5},{x:4,y:7},{x:3,y:5},{x:5,y:8},{x:-3,y:5},{x:10,y:1},{x:3,y:-5},{x:-4,y:7},{x:10,y:-1},{x:5,y:9},{x:9,y:1},{x:9,y:-1},{x:-5,y:-5},{x:-3,y:3},{x:3,y:-3},{x:-4,y:4},{x:1,y:1},{x:2},{y:-2},{x:-5,y:5},{x:-2,y:2},{x:2,y:-2},{y:2},{x:-7,y:-7},{x:-6,y:-6},{x:7,y:7},{x:6,y:6},{x:5,y:5},{x:-2},{x:-1,y:1},{x:1,y:-1},{x:-4,y:-4},{x:4,y:4},{x:2,y:2},{},{x:-3,y:-3},{x:3,y:3},{x:-6,y:-7},{x:-4,y:5},{x:7,y:8},{x:-5,y:-6},{x:-4,y:-5},{x:4,y:5},{x:-5,y:6},{x:5,y:6},{x:6,y:7}],hasEverOwnedOriginalCapital:true,numMinorCivsAttacked:2,totalCultureForContests:4652,totalFaithForContests:1323},{gold:1289,civName:Inca,tech:{freeTechs:1,scienceOfLast8Turns:[417,422,422,419,358,358,358,377],techsResearched:[Flight,Optics,Steam Power,Acoustics,Mining,Sailing,Education,Archery,Currency,Gunpowder,Fertilizer,Metal Casting,Economics,Construction,Architecture,Banking,Compass,Horseback Riding,Chemistry,Industrialization,Bronze Working,Scientific Theory,Philosophy,Drama and Poetry,Military Science,Printing Press,Mathematics,Guilds,Biology,Machinery,Engineering,Archaeology,Electricity,Dynamite,Pottery,Agriculture,Iron Working,Rifling,Theology,Navigation,Calendar,Masonry,Steel,Physics,Trapping,Chivalry,The Wheel,Animal Husbandry,Writing,Civil Service,Metallurgy,Astronomy],techsToResearch:[Radio],techsInProgress:{Radio:859}},policies:{storedCulture:1594,adoptedPolicies:[Free Speech,Tradition,Mandate Of Heaven,Freedom Complete,Commerce,Reformation,Tradition Complete,Freedom,Free Religion,Piety Complete,Civil Society,Trade Unions,Theocracy,Constitution,Mercantilism,Piety,Legalism,Aristocracy,Naval Tradition,Oligarchy,Organized Religion,Landed Elite,Monarchy,Democracy,Universal Suffrage],numberOfAdoptedPolicies:22},civConstructions:{boughtItemsWithIncreasingPrice:{Great Prophet:{class:java.lang.Integer,value:6}},freeBuildings:{f7388e2f-e868-41b2-ab19-4c1e7d0338a5:[{class:java.lang.String,value:Monument}],c03b1f6d-e58c-4adb-9817-b0de51e68981:[{class:java.lang.String,value:Monument}],906f9a8b-7b46-4076-bc66-15921d8fa82f:[{class:java.lang.String,value:Monument}],ca160eed-468f-4a65-8444-3d2e42de02c1:[{class:java.lang.String,value:Amphitheater}]},freeStatBuildingsProvided:{Gold:[],Happiness:[],Production:[],Science:[],Faith:[],Culture:[{class:java.lang.String,value:f7388e2f-e868-41b2-ab19-4c1e7d0338a5},{class:java.lang.String,value:c03b1f6d-e58c-4adb-9817-b0de51e68981},{class:java.lang.String,value:906f9a8b-7b46-4076-bc66-15921d8fa82f},{class:java.lang.String,value:ca160eed-468f-4a65-8444-3d2e42de02c1}],Food:[]}},questManager:{},religionManager:{storedFaith:3453,religionState:EnhancedReligion},goldenAges:{storedHappiness:409,numberOfGoldenAges:5},greatPeople:{pointsForNextGreatPerson:1600,greatPersonPointsCounter:{Great Engineer:{class:java.lang.Integer,value:395},Great Merchant:{class:java.lang.Integer,value:390},Great Scientist:{class:java.lang.Integer,value:451},Great Artist:{class:java.lang.Integer,value:483}}},victoryManager:{},ruinsManager:{},diplomacy:{Vancouver:{otherCivName:Vancouver,diplomaticStatus:Peace,diplomaticModifiers:{YearsOfPeace:30,SharedEnemy:5,OpenBorders:1.25}},Singapore:{otherCivName:Singapore,diplomaticStatus:Peace,diplomaticModifiers:{YearsOfPeace:30}},Quebec City:{otherCivName:Quebec City,diplomaticStatus:Peace,flagsCountdown:{EverBeenFriends:-1},diplomaticModifiers:{YearsOfPeace:30,SharedEnemy:5,OpenBorders:7.375}},Songhai:{otherCivName:Songhai,diplomaticStatus:Peace,diplomaticModifiers:{YearsOfPeace:30}},Florence:{otherCivName:Florence,diplomaticStatus:Peace,flagsCountdown:{EverBeenFriends:-1},diplomaticModifiers:{YearsOfPeace:30,SharedEnemy:5,OpenBorders:7.75}},England:{otherCivName:England,diplomaticStatus:Peace,diplomaticModifiers:{YearsOfPeace:30}}},proximity:{Vancouver:Neighbors,Singapore:Close,Quebec City:Neighbors,Songhai:Far,Florence:Close,England:Close},naturalWonders:[Krakatoa],cities:[{location:{x:-3,y:-2},id:ca160eed-468f-4a65-8444-3d2e42de02c1,name:Cuzco,foundingCiv:Inca,health:300,religion:{religionsAtSomePointAdopted:[Dance of the Aurora,Sikhism],pressures:{The religion of TheLegend27:{class:java.lang.Integer,value:600},Dance of the Aurora:{class:java.lang.Integer,value:1500},Sikhism:{class:java.lang.Integer,value:18281},Christianity:{class:java.lang.Integer,value:7057}},religionThisIsTheHolyCityOf:Sikhism},population:{population:20,foodStored:180,specialistAllocations:{Scientist:{class:java.lang.Integer,value:3}}},cityConstructions:{builtBuildings:[Monument,Amphitheater,Bank,Harbor,Hermitage,Sistine Chapel,Ironworks,Lighthouse,Colosseum,Grand Temple,Aqueduct,Public School,Arsenal,Oxford University,Workshop,Porcelain Tower,Hospital,Taj Mahal,Granary,Armory,Stock Exchange,Machu Picchu,Mausoleum of Halicarnassus,National Treasury,Palace,Mint,National Epic,Chichen Itza,Stable,Military Academy,National College,Market,Barracks,Opera House,Shrine,Temple,University,Notre Dame,Theatre,Heroic Epic,Petra,Castle,Museum,Seaport,Library,Walls],inProgressConstructions:{Lancer:18},constructionQueue:[Science]},expansion:{cultureStored:9139},tiles:[{x:-4,y:-1},{x:1,y:3},{x:-3,y:-2},{x:-1},{x:-4,y:-4},{x:-4,y:-3},{x:-4,y:-2},{x:-3,y:-6},{x:-3,y:-3},{x:-3,y:-4},{y:1},{x:-2},{x:-3},{x:-1,y:1},{y:2},{x:-2,y:-1},{x:-1,y:-1},{x:1,y:1},{x:1,y:2},{x:-2,y:-2},{x:2,y:2},{x:2,y:3},{x:-3,y:-1},{x:-2,y:-3},{x:-5,y:-4},{x:-4,y:-5},{x:-5,y:-2},{x:-5,y:-3},{x:-3,y:-5},{x:-2,y:-5},{x:-1,y:-5}],workedTiles:[{x:-4,y:-2},{y:1},{x:-4,y:-1},{x:-3,y:-3},{x:-2},{x:-1,y:1},{x:-3},{x:-4,y:-3},{x:-4,y:-4},{x:-3,y:-1},{x:-2,y:-1},{x:-2,y:-2},{x:-5,y:-2},{x:-4,y:-5},{x:-5,y:-4},{x:-5,y:-3},{x:-3,y:-5}],isOriginalCapital:true},{location:{x:-7,y:-6},id:c03b1f6d-e58c-4adb-9817-b0de51e68981,name:Tiwanaku,foundingCiv:Inca,turnAcquired:32,health:300,religion:{religionsAtSomePointAdopted:[Dance of the Aurora,Sikhism],pressures:{The religion of TheLegend27:{class:java.lang.Integer,value:800},Dance of the Aurora:{class:java.lang.Integer,value:1100},Sikhism:{class:java.lang.Integer,value:5976},Christianity:{class:java.lang.Integer,value:4041}}},population:{population:12,foodStored:64,specialistAllocations:{Scientist:{class:java.lang.Integer,value:1}}},cityConstructions:{builtBuildings:[Monument,Amphitheater,Bank,Harbor,Factory,Lighthouse,Stone Works,Colosseum,Aqueduct,Public School,Arsenal,Windmill,Workshop,Granary,Armory,Stock Exchange,Circus Maximus,Stable,Market,Barracks,Opera House,Shrine,Temple,University,Theatre,Castle,Museum,Seaport,Library,Walls],inProgressConstructions:{Theatre:109,Museum:144,Artillery:58,Cavalry:59},constructionQueue:[Artillery]},expansion:{cultureStored:1319},tiles:[{x:-6,y:-4},{x:-7,y:-5},{x:-7,y:-7},{x:-6,y:-6},{x:-8,y:-6},{x:-5,y:-5},{x:-6,y:-8},{x:-5,y:-7},{x:-4,y:-6},{x:-2,y:-6},{x:-4,y:-8},{x:-7,y:-6},{x:-6,y:-5},{x:-5,y:-6},{x:-7,y:-4},{x:-8,y:-5},{x:-6,y:-7},{x:-5,y:-8},{x:-4,y:-7}],workedTiles:[{x:-6,y:-6},{x:-7,y:-5},{x:-6,y:-8},{x:-5,y:-7},{x:-4,y:-6},{x:-6,y:-4},{x:-8,y:-6},{x:-5,y:-6},{x:-7,y:-4},{x:-8,y:-5},{x:-6,y:-7}]},{location:{x:-9,y:-4},id:f7388e2f-e868-41b2-ab19-4c1e7d0338a5,name:Machu,foundingCiv:Inca,turnAcquired:45,health:275,religion:{religionsAtSomePointAdopted:[Dance of the Aurora],pressures:{The religion of TheLegend27:{class:java.lang.Integer,value:200},Dance of the Aurora:{class:java.lang.Integer,value:700},Sikhism:{class:java.lang.Integer,value:5609},Christianity:{class:java.lang.Integer,value:1251}}},population:{population:2,foodStored:1},cityConstructions:{builtBuildings:[Monument,Amphitheater,Bank,Factory,Market,Stone Works,Colosseum,Observatory,Barracks,Opera House,Aqueduct,Shrine,Temple,University,Public School,Theatre,Castle,Museum,Library,Workshop,Granary,Walls,Armory,Stock Exchange],inProgressConstructions:{Hospital:73},constructionQueue:[Hospital]},expansion:{cultureStored:1401},tiles:[{x:-8,y:-3},{x:-8,y:-4},{x:-6,y:-3},{x:-8,y:-1},{x:-12,y:-2},{x:-9,y:-5},{x:-11,y:-3},{x:-11,y:-2},{x:-11,y:-1},{x:-10,y:-4},{x:-7,y:-2},{x:-10,y:-3},{x:-10,y:-2},{x:-7,y:-3},{x:-9,y:-4},{x:-9,y:-3},{x:-9,y:-2}],workedTiles:[{x:-7,y:-2},{x:-8,y:-4}]},{location:{y:-2},id:906f9a8b-7b46-4076-bc66-15921d8fa82f,name:Ollantaytambo,foundingCiv:Inca,turnAcquired:73,health:300,religion:{religionsAtSomePointAdopted:[Dance of the Aurora,Christianity],pressures:{The religion of TheLegend27:{class:java.lang.Integer,value:400},Dance of the Aurora:{class:java.lang.Integer,value:600},Christianity:{class:java.lang.Integer,value:14305},Sikhism:{class:java.lang.Integer,value:5769}}},population:{population:11,foodStored:135,specialistAllocations:{Scientist:{class:java.lang.Integer,value:3}}},cityConstructions:{builtBuildings:[Monument,Amphitheater,Bank,Harbor,Factory,Lighthouse,Colosseum,Aqueduct,Public School,Arsenal,Windmill,Workshop,Granary,Armory,Stock Exchange,Stable,Military Academy,Market,Barracks,Opera House,Shrine,Temple,University,Theatre,Castle,Museum,Seaport,Library,Walls],inProgressConstructions:{Military Academy:124,Hospital:140},constructionQueue:[Hospital]},expansion:{cultureStored:422},tiles:[{x:-1,y:-2},{x:-1,y:-4},{x:3,y:2},{x:4,y:2},{x:1,y:-1},{x:1,y:-2},{x:1,y:-3},{x:2,y:-1},{x:3,y:1},{x:-2,y:-4},{},{x:2,y:1},{x:-1,y:-3},{x:3},{y:-4},{y:-3},{x:2},{y:-2},{y:-1},{x:1},{x:2,y:-2},{x:3,y:-1},{x:5,y:3},{x:5,y:2}],workedTiles:[{x:1,y:-2},{},{x:1,y:-3},{x:2,y:-1},{y:-3},{x:3,y:1},{x:-2,y:-4},{x:2,y:1}]}],citiesCreated:4,exploredTiles:[{x:4,y:-3},{x:-6,y:-4},{x:-3,y:2},{x:3,y:-2},{x:-4,y:3},{x:3},{x:-1,y:-12},{x:6,y:-4},{x:-6,y:4},{x:-8,y:-6},{x:7,y:5},{x:8,y:6},{x:-7,y:-5},{x:6,y:4},{x:-3},{x:-3,y:-2},{x:-4,y:-3},{x:4,y:3},{x:3,y:2},{x:5,y:4},{x:-6,y:-5},{x:-5,y:-4},{x:5,y:-4},{x:-5,y:4},{x:6,y:5},{x:7,y:6},{x:-7,y:-6},{x:8,y:7},{x:4},{x:-4,y:2},{x:4,y:-2},{y:-1},{x:6,y:-3},{y:1},{x:10,y:5},{x:-8,y:-4},{x:-6,y:-3},{x:6,y:3},{x:8,y:4},{x:-4},{x:-2,y:1},{x:1,y:-8},{x:2,y:-1},{x:-2,y:-1},{x:2,y:1},{x:1,y:8},{x:-1,y:-8},{x:-4,y:-2},{x:4,y:2},{x:-9,y:-5},{x:1,y:9},{x:1,y:-9},{x:-1,y:-9},{x:9,y:5},{x:-5,y:-3},{x:-7,y:-4},{x:5,y:3},{x:7,y:4},{x:-8,y:-5},{x:-5,y:3},{x:1,y:10},{x:5,y:-3},{x:7,y:-4},{x:1,y:-10},{x:8,y:5},{x:-1,y:-10},{x:1,y:11},{x:-1,y:-11},{x:-1,y:-6},{x:-6,y:-2},{x:-3,y:1},{x:3,y:-1},{x:1,y:6},{x:-6,y:2},{x:6,y:-2},{x:8,y:-3},{x:6},{x:-8,y:-3},{x:-6},{x:8,y:3},{x:1,y:-6},{x:-3,y:-1},{x:3,y:1},{x:-2,y:-12},{x:6,y:2},{x:11,y:4},{x:-1,y:-7},{x:-5,y:-2},{x:7,y:3},{x:-5,y:2},{x:5,y:-2},{x:5},{x:7,y:-3},{x:1,y:7},{x:5,y:2},{x:-7,y:-3},{x:-10,y:-4},{x:-5},{x:1,y:-7},{x:-1,y:7},{x:10,y:4},{x:-9,y:-4},{x:9,y:4},{x:-1,y:-4},{x:-4,y:1},{x:4,y:-1},{x:2,y:-8},{x:2,y:8},{x:-2,y:-8},{x:-8,y:-2},{y:8},{x:-8},{x:12,y:3},{x:8,y:2},{x:-1,y:4},{x:1,y:-4},{x:1,y:4},{x:8},{x:8,y:-2},{y:-8},{x:-4,y:-1},{x:4,y:1},{x:3,y:12},{x:2,y:9},{x:-11,y:-3},{y:9},{x:11,y:3},{y:-9},{x:2,y:-9},{x:-2,y:-9},{x:-10,y:-3},{x:-7,y:-2},{x:-1,y:5},{x:1,y:-5},{x:-7},{y:10},{x:10,y:3},{x:1,y:5},{x:-1,y:-5},{x:7},{x:7,y:-2},{y:-10},{x:2,y:10},{x:-2,y:-10},{x:7,y:2},{x:-9,y:-3},{x:2,y:11},{x:9,y:3},{y:-11},{x:-2,y:-11},{x:-1,y:-3},{x:-6,y:-1},{x:-6,y:1},{x:2,y:-6},{x:3,y:-8},{x:-2,y:6},{x:6,y:-1},{y:-6},{x:1,y:3},{y:6},{x:12,y:2},{x:-12,y:-2},{x:-1,y:3},{x:1,y:-3},{x:2,y:6},{x:-2,y:-6},{x:6,y:1},{x:-3,y:-8},{x:3,y:8},{x:3,y:9},{x:-11,y:-2},{x:11},{x:11,y:2},{x:-3,y:-9},{x:-11},{x:2,y:7},{x:-5,y:1},{x:5,y:-1},{x:2,y:-7},{y:-7},{x:10},{x:3,y:10},{x:-2,y:-7},{x:-3,y:-10},{x:5,y:1},{x:-5,y:-1},{x:10,y:2},{x:-10,y:-2},{y:7},{x:-10},{x:-9,y:-2},{x:3,y:11},{x:9},{x:9,y:-2},{x:9,y:2},{x:-3,y:-11},{x:-1,y:-2},{x:4,y:8},{x:-4,y:-8},{x:1},{x:8,y:-1},{x:3,y:-6},{x:-2,y:4},{x:2,y:-4},{x:1,y:2},{y:-4},{x:2,y:4},{x:-2,y:-4},{x:-1},{x:5,y:10},{x:-8,y:-1},{x:8,y:1},{y:4},{x:1,y:-2},{x:-1,y:2},{x:3,y:6},{x:-3,y:-6},{x:4,y:9},{x:-4,y:-9},{x:3,y:7},{x:-7,y:-1},{x:2,y:5},{x:-3,y:-7},{y:-5},{x:7,y:-1},{x:2,y:-5},{x:3,y:-7},{x:-2,y:5},{x:-2,y:-5},{y:5},{x:7,y:1},{x:4,y:10},{x:-4,y:-10},{x:4,y:11},{x:13,y:2},{y:-3},{x:-3,y:-4},{x:-2,y:-3},{x:-2,y:3},{x:2,y:-3},{x:2,y:3},{x:-3,y:4},{x:3,y:-4},{x:4,y:-6},{y:3},{x:12,y:1},{x:-12,y:-1},{x:6,y:8},{x:4,y:6},{x:5,y:7},{x:-5,y:-7},{x:-6,y:-8},{x:3,y:4},{x:-4,y:-6},{x:6,y:9},{x:11,y:1},{x:-11,y:-1},{x:4,y:7},{x:5,y:8},{x:3,y:5},{x:-3,y:-5},{x:-10,y:-1},{x:10,y:-1},{x:3,y:-5},{x:-3,y:5},{x:4,y:-7},{x:10,y:1},{x:-4,y:-7},{x:-5,y:-8},{x:5,y:9},{x:-9,y:-1},{x:9,y:1},{x:9,y:-1},{x:-5,y:-9},{x:-3,y:-3},{},{x:-4,y:-4},{x:4,y:4},{x:-1,y:-1},{x:-4,y:4},{x:5,y:-5},{y:-2},{x:2},{x:1,y:1},{x:-2,y:2},{x:-3,y:3},{x:3,y:-3},{x:4,y:-4},{x:2,y:-2},{x:-2,y:-2},{x:2,y:2},{y:2},{x:-7,y:-7},{x:6,y:6},{x:7,y:7},{x:-6,y:-6},{x:-5,y:-5},{x:5,y:5},{x:-2},{x:-1,y:1},{x:1,y:-1},{x:3,y:3},{x:-5,y:-6},{x:5,y:6},{x:-4,y:-5},{x:6,y:7},{x:-6,y:-7},{x:7,y:8},{x:4,y:5},{x:4,y:-5},{x:-4,y:5},{x:5,y:-6}],hasEverOwnedOriginalCapital:true,totalCultureForContests:26404,totalFaithForContests:6617},{gold:418,civName:Singapore,tech:{scienceOfLast8Turns:[67,67,67,67,67,67,67,67],techsResearched:[Flight,Optics,Steam Power,Acoustics,Mining,Sailing,Education,Archery,Currency,Gunpowder,Fertilizer,Metal Casting,Economics,Construction,Architecture,Banking,Compass,Horseback Riding,Chemistry,Industrialization,Bronze Working,Scientific Theory,Philosophy,Drama and Poetry,Military Science,Printing Press,Mathematics,Guilds,Biology,Machinery,Engineering,Archaeology,Electricity,Dynamite,Pottery,Agriculture,Iron Working,Rifling,Theology,Navigation,Calendar,Masonry,Steel,Physics,Trapping,Chivalry,The Wheel,Animal Husbandry,Writing,Civil Service,Metallurgy,Astronomy],techsToResearch:[Refrigeration],techsInProgress:{Refrigeration:737}},policies:{storedCulture:1602,shouldOpenPolicyPicker:true},civConstructions:{},questManager:{assignedQuests:[{questName:Acquire Great Person,assigner:Singapore,assignee:Inca,assignedOnTurn:82,data1:Great Engineer},{questName:Connect Resource,assigner:Singapore,assignee:Inca,assignedOnTurn:243,data1:Incense}],globalQuestCountdown:1,individualQuestCountdown:{Inca:0,Songhai:0,England:0}},religionManager:{storedFaith:875},goldenAges:{storedHappiness:577,numberOfGoldenAges:4},greatPeople:{},victoryManager:{},ruinsManager:{},diplomacy:{Inca:{otherCivName:Inca,diplomaticStatus:Protector,flagsCountdown:{RecentlyPledgedProtection:2,EverBeenFriends:-1},diplomaticModifiers:{YearsOfPeace:26},influence:33.25},Vancouver:{otherCivName:Vancouver,diplomaticStatus:Peace,diplomaticModifiers:{YearsOfPeace:30},influence:10},Quebec City:{otherCivName:Quebec City,diplomaticStatus:Peace,diplomaticModifiers:{YearsOfPeace:30},influence:10},Songhai:{otherCivName:Songhai,diplomaticStatus:Peace,diplomaticModifiers:{YearsOfPeace:30}},England:{otherCivName:England,diplomaticStatus:Peace,diplomaticModifiers:{YearsOfPeace:30}}},proximity:{Inca:Close,Vancouver:Close,Quebec City:Close,Songhai:Far,Florence:Far,England:Far},cities:[{location:{x:-8,y:1},id:7803ef58-2479-49c2-a9ee-d509ddf34ed0,name:Singapore,foundingCiv:Singapore,health:300,religion:{pressures:{The religion of TheLegend27:{class:java.lang.Integer,value:1500},Sikhism:{class:java.lang.Integer,value:6006},Christianity:{class:java.lang.Integer,value:1376}}},population:{population:18,foodStored:367,specialistAllocations:{Scientist:{class:java.lang.Integer,value:3}}},cityConstructions:{builtBuildings:[Monument,Amphitheater,Bank,Harbor,Lighthouse,Colosseum,Aqueduct,Public School,Arsenal,Windmill,Workshop,Granary,Armory,Stock Exchange,Palace,Mint,Market,Barracks,Opera House,Shrine,Temple,University,Theatre,Castle,Museum,Seaport,Library,Walls],inProgressConstructions:{Hospital:75},constructionQueue:[Hospital]},expansion:{cultureStored:183},tiles:[{x:-8,y:1},{x:-8},{x:-8,y:2},{x:-6,y:3},{x:-8,y:3},{x:-12,y:-1},{x:-11},{x:-7,y:2},{x:-7,y:1},{x:-10,y:-1},{x:-10,y:1},{x:-10},{x:-7,y:3},{x:-7,y:4},{x:-6,y:5},{x:-9},{x:-9,y:1},{x:-9,y:2},{x:-9,y:-1}],workedTiles:[{x:-8},{x:-8,y:2},{x:-6,y:3},{x:-8,y:3},{x:-11},{x:-7,y:1},{x:-10,y:-1},{x:-10,y:1},{x:-10},{x:-7,y:4},{x:-7,y:2},{x:-7,y:3},{x:-9,y:1},{x:-9,y:2},{x:-9}],isOriginalCapital:true}],citiesCreated:1,exploredTiles:[{x:-6,y:2},{x:-6,y:1},{x:-4,y:6},{x:-4,y:3},{x:-3,y:8},{x:-3,y:4},{x:-2,y:6},{x:-6},{y:6},{x:-1,y:6},{x:-12,y:-1},{x:-6,y:-1},{x:-8,y:-3},{x:-12,y:-2},{x:-8,y:3},{x:-6,y:4},{x:-11},{x:-11,y:-1},{x:-11,y:-2},{x:-6,y:5},{x:-10,y:1},{x:-5,y:4},{x:-5,y:2},{x:-5,y:1},{x:-3,y:5},{x:-2,y:7},{x:-4,y:7},{x:-7,y:3},{x:-10},{y:7},{x:-10,y:-4},{x:-1,y:7},{x:-10,y:-1},{x:-10,y:-2},{x:-9,y:-1},{x:-9},{x:-9,y:1},{x:-9,y:2},{x:-9,y:-2},{x:-5,y:5},{x:-8,y:1},{x:-4,y:2},{x:-3,y:6},{x:-3,y:3},{x:-2,y:4},{x:-2,y:8},{x:-4,y:4},{x:-8,y:-1},{x:-8},{x:-1,y:8},{x:-8,y:-2},{y:8},{x:-6,y:3},{x:-8,y:2},{x:-11,y:-3},{x:-1,y:9},{y:9},{x:-2,y:9},{x:-7,y:1},{x:-7},{x:-1,y:5},{x:-5,y:6},{x:-4,y:5},{x:-3,y:7},{x:-2,y:5},{x:-5,y:3},{x:-7,y:2},{x:-7,y:-1},{x:-7,y:4},{x:-10,y:-3},{x:-7,y:-2},{x:-9,y:-3}],hasEverOwnedOriginalCapital:true,totalCultureForContests:1602,totalFaithForContests:875,cityStateResource:Jewelry},{gold:927,civName:Florence,tech:{scienceOfLast8Turns:[46,46,46,58,46,46,46,46],techsResearched:[Flight,Optics,Steam Power,Acoustics,Mining,Sailing,Education,Archery,Currency,Gunpowder,Fertilizer,Metal Casting,Economics,Construction,Architecture,Banking,Compass,Horseback Riding,Chemistry,Industrialization,Bronze Working,Scientific Theory,Philosophy,Drama and Poetry,Military Science,Printing Press,Mathematics,Guilds,Biology,Machinery,Engineering,Archaeology,Electricity,Dynamite,Pottery,Agriculture,Iron Working,Rifling,Theology,Navigation,Calendar,Masonry,Steel,Physics,Trapping,Chivalry,The Wheel,Animal Husbandry,Writing,Civil Service,Metallurgy,Astronomy],techsToResearch:[Radio],techsInProgress:{Radio:518}},policies:{storedCulture:1373,shouldOpenPolicyPicker:true},civConstructions:{},questManager:{assignedQuests:[{questName:Spread Religion,assigner:Florence,assignee:Inca,assignedOnTurn:170,data1:Sikhism,data2:Sikhism},{questName:Connect Resource,assigner:Florence,assignee:Inca,assignedOnTurn:186,data1:Copper},{questName:Contest Technologies,assigner:Florence,assignee:England,assignedOnTurn:281,data1:49},{questName:Contest Technologies,assigner:Florence,assignee:Songhai,assignedOnTurn:281,data1:37},{questName:Contest Technologies,assigner:Florence,assignee:Inca,assignedOnTurn:281,data1:49}],globalQuestCountdown:19,individualQuestCountdown:{Inca:0,Songhai:0,England:0}},religionManager:{storedFaith:400},goldenAges:{storedHappiness:614,numberOfGoldenAges:3},greatPeople:{},victoryManager:{},ruinsManager:{},diplomacy:{Inca:{otherCivName:Inca,diplomaticStatus:Protector,flagsCountdown:{EverBeenFriends:-1},diplomaticModifiers:{OpenBorders:7.875},influence:91},Songhai:{otherCivName:Songhai,diplomaticStatus:Peace,diplomaticModifiers:{YearsOfPeace:30}},England:{otherCivName:England,diplomaticStatus:Peace,flagsCountdown:{WaryOf:-1},diplomaticModifiers:{YearsOfPeace:30,DeclaredWarOnUs:-2.375},influence:-20},Barbarians:{otherCivName:Barbarians,influence:-59}},proximity:{Inca:Close,Vancouver:Far,Singapore:Far,Quebec City:Far,Songhai:Far,England:Neighbors},allyCivName:Inca,naturalWonders:[Krakatoa],flagsCountdown:{RecentlyBullied:0},cities:[{location:{x:-3,y:-9},id:fcf44add-45a1-4c9e-8458-ac9ed7a53f18,name:Florence,foundingCiv:Florence,health:300,religion:{religionsAtSomePointAdopted:[Christianity],pressures:{The religion of TheLegend27:{class:java.lang.Integer,value:1000},Christianity:{class:java.lang.Integer,value:9869},Sikhism:{class:java.lang.Integer,value:3033}}},population:{population:12,foodStored:165,specialistAllocations:{Scientist:{class:java.lang.Integer,value:3}}},cityConstructions:{builtBuildings:[Monument,Amphitheater,Bank,Harbor,Lighthouse,Colosseum,Aqueduct,Public School,Arsenal,Windmill,Workshop,Hospital,Granary,Armory,Stock Exchange,Palace,Military Academy,Market,Barracks,Opera House,Shrine,University,Temple,Theatre,Castle,Museum,Seaport,Library,Walls],inProgressConstructions:{Artillery:79},constructionQueue:[Artillery]},expansion:{cultureStored:600},tiles:[{x:-2,y:-8},{x:-3,y:-8},{x:-1,y:-8},{x:-3,y:-9},{x:-2,y:-9},{x:-4,y:-9},{x:-1,y:-9},{x:-4,y:-10},{x:-3,y:-10},{x:-3,y:-7},{x:-2,y:-7},{x:-2,y:-10},{x:-1,y:-7},{x:-3,y:-11},{x:-5,y:-9},{x:-2,y:-11}],workedTiles:[{x:-3,y:-8},{x:-2,y:-7},{x:-3,y:-7},{x:-3,y:-10},{x:-2,y:-10},{x:-2,y:-9},{x:-1,y:-9},{x:-3,y:-11},{x:-2,y:-11}],isOriginalCapital:true}],citiesCreated:1,exploredTiles:[{x:-1,y:-6},{x:-3,y:-8},{x:-4,y:-6},{x:-5,y:-7},{x:1,y:-6},{x:-6,y:-8},{x:-2,y:-12},{x:-3,y:-4},{x:4,y:-6},{y:-6},{x:2,y:-6},{x:3,y:-8},{x:-1,y:-12},{x:-2,y:-6},{x:-3,y:-9},{x:-2,y:-7},{x:-3,y:-10},{x:-1,y:-7},{x:-4,y:-7},{x:1,y:-7},{x:-5,y:-8},{x:-3,y:-5},{y:-7},{x:2,y:-7},{x:4,y:-7},{x:3,y:-5},{x:-5,y:-9},{x:-3,y:-11},{x:-2,y:-8},{x:-1,y:-8},{x:-1,y:-4},{x:-2,y:-4},{x:-4,y:-8},{y:-8},{x:2,y:-8},{x:3,y:-6},{y:-4},{x:1,y:-8},{x:-3,y:-6},{x:-1,y:-9},{x:-2,y:-9},{x:-4,y:-9},{y:-9},{x:1,y:-9},{x:2,y:-9},{x:-1,y:-10},{x:-2,y:-10},{x:-1,y:-5},{x:-4,y:-10},{x:1,y:-10},{x:1,y:-5},{x:-6,y:-7},{x:-5,y:-6},{x:-2,y:-5},{x:-3,y:-7},{x:-4,y:-5},{y:-10},{x:5,y:-6},{y:-5},{x:3,y:-7},{x:2,y:-5},{x:4,y:-5},{x:-2,y:-11},{x:-1,y:-11},{y:-11}],hasEverOwnedOriginalCapital:true,totalCultureForContests:1373,totalFaithForContests:400},{gold:-611,civName:Vancouver,tech:{scienceOfLast8Turns:[42,42,42,42,42,42,42,42],techsResearched:[Flight,Optics,Steam Power,Acoustics,Mining,Sailing,Education,Archery,Currency,Gunpowder,Fertilizer,Metal Casting,Economics,Construction,Architecture,Banking,Compass,Horseback Riding,Chemistry,Industrialization,Bronze Working,Scientific Theory,Philosophy,Drama and Poetry,Military Science,Printing Press,Mathematics,Guilds,Biology,Machinery,Engineering,Archaeology,Electricity,Dynamite,Pottery,Agriculture,Iron Working,Rifling,Theology,Navigation,Calendar,Masonry,Steel,Physics,Trapping,Chivalry,The Wheel,Animal Husbandry,Writing,Civil Service,Metallurgy,Astronomy],techsToResearch:[Replaceable Parts],techsInProgress:{Replaceable Parts:339}},policies:{storedCulture:1844,shouldOpenPolicyPicker:true},civConstructions:{},questManager:{assignedQuests:[{questName:Conquer City State,assigner:Vancouver,assignee:Inca,assignedOnTurn:120,data1:Quebec City},{questName:Connect Resource,assigner:Vancouver,assignee:Inca,assignedOnTurn:240,data1:Silver},{questName:Contest Technologies,assigner:Vancouver,assignee:England,assignedOnTurn:291,data1:50},{questName:Contest Technologies,assigner:Vancouver,assignee:Songhai,assignedOnTurn:291,data1:38},{questName:Contest Technologies,assigner:Vancouver,assignee:Inca,assignedOnTurn:291,data1:51}],globalQuestCountdown:30,individualQuestCountdown:{Inca:0,Songhai:0,England:0}},religionManager:{storedFaith:649},goldenAges:{storedHappiness:10,numberOfGoldenAges:4,turnsLeftForCurrentGoldenAge:4},greatPeople:{},victoryManager:{},ruinsManager:{},diplomacy:{Inca:{otherCivName:Inca,diplomaticStatus:Protector,flagsCountdown:{EverBeenFriends:-1},diplomaticModifiers:{OpenBorders:1.125},influence:48},Singapore:{otherCivName:Singapore,diplomaticStatus:Peace,diplomaticModifiers:{YearsOfPeace:30}},Quebec City:{otherCivName:Quebec City,diplomaticStatus:Peace,diplomaticModifiers:{YearsOfPeace:30},influence:10},Songhai:{otherCivName:Songhai,diplomaticStatus:Peace,flagsCountdown:{EverBeenFriends:-1},diplomaticModifiers:{YearsOfPeace:30}},England:{otherCivName:England,diplomaticStatus:Peace,diplomaticModifiers:{YearsOfPeace:30},influence:5.5},Barbarians:{otherCivName:Barbarians,influence:-59}},proximity:{Inca:Neighbors,Singapore:Close,Quebec City:Neighbors,Songhai:Far,Florence:Far,England:Far},cities:[{location:{x:-6},id:c813cf06-9c9b-4532-96b0-8ee7e973945f,name:Vancouver,foundingCiv:Vancouver,health:300,religion:{religionsAtSomePointAdopted:[Sikhism],pressures:{The religion of TheLegend27:{class:java.lang.Integer,value:1000},Sikhism:{class:java.lang.Integer,value:5880},Christianity:{class:java.lang.Integer,value:2636}}},population:{population:13,foodStored:136},cityConstructions:{builtBuildings:[Monument,Amphitheater,Bank,Harbor,Lighthouse,Colosseum,Aqueduct,Public School,Arsenal,Workshop,Granary,Armory,Stock Exchange,Palace,Stable,Military Academy,Market,Barracks,Opera House,Shrine,Temple,University,Theatre,Castle,Museum,Seaport,Library,Walls],inProgressConstructions:{Hospital:333},constructionQueue:[Hospital]},expansion:{cultureStored:160},tiles:[{x:-6,y:-1},{x:-6},{x:-6,y:1},{x:-8,y:-2},{x:-4,y:2},{x:-6,y:-2},{x:-4},{x:-6,y:2},{x:-6,y:4},{x:-5,y:5},{x:-7,y:-1},{x:-5,y:1},{x:-5},{x:-7},{x:-5,y:2},{x:-5,y:-1},{x:-5,y:3},{x:-5,y:4},{x:-4,y:5},{x:-3,y:5}],workedTiles:[{x:-8,y:-2},{x:-4},{x:-4,y:2},{x:-6,y:-1},{x:-6,y:-2},{x:-6,y:1},{x:-6,y:2},{x:-5},{x:-5,y:2},{x:-5,y:1},{x:-7,y:-1},{x:-7},{x:-5,y:-1}],isOriginalCapital:true}],citiesCreated:1,exploredTiles:[{x:-6,y:-1},{x:-6,y:-2},{x:-3,y:2},{x:-3,y:1},{x:-2,y:3},{x:-2,y:6},{x:-4,y:-3},{x:-3,y:-2},{x:-3,y:-1},{x:-8,y:3},{x:-6},{x:-3},{x:-8,y:-3},{x:-6,y:1},{x:-4,y:3},{x:-4,y:6},{x:-3,y:4},{x:-6,y:2},{x:-6,y:4},{x:-6,y:-4},{x:-11,y:-2},{x:-5,y:-1},{x:-7,y:-3},{x:-5},{x:-10,y:-2},{x:-10,y:-1},{x:-5,y:1},{x:-3,y:5},{x:-5,y:-4},{x:-5,y:-2},{x:-5,y:2},{x:-5,y:4},{x:-7,y:3},{x:-6,y:5},{x:-9,y:-2},{x:-9,y:-1},{x:-9},{x:-9,y:1},{x:-9,y:2},{x:-4,y:-1},{x:-4},{x:-4,y:1},{x:-2,y:2},{x:-3,y:3},{x:-3,y:6},{x:-2,y:4},{x:-6,y:3},{x:-5,y:5},{x:-8,y:1},{x:-8,y:2},{x:-4,y:-2},{x:-4,y:2},{x:-4,y:4},{x:-8},{x:-2,y:1},{x:-8,y:-2},{x:-8,y:-4},{x:-2},{x:-8,y:-1},{x:-6,y:-3},{x:-11,y:-3},{x:-7,y:-1},{x:-7,y:-4},{x:-10,y:-3},{x:-7,y:-2},{x:-5,y:-3},{x:-7,y:4},{x:-7},{x:-7,y:1},{x:-5,y:6},{x:-7,y:2},{x:-5,y:3},{x:-4,y:5},{x:-2,y:5},{x:-9,y:-3}],hasEverOwnedOriginalCapital:true,totalCultureForContests:1844,totalFaithForContests:649,cityStatePersonality:Hostile},{gold:-205,civName:Quebec City,tech:{scienceOfLast8Turns:[64,64,64,64,42,42,42,42],techsResearched:[Flight,Optics,Steam Power,Acoustics,Mining,Sailing,Education,Archery,Currency,Gunpowder,Fertilizer,Metal Casting,Economics,Construction,Architecture,Banking,Compass,Horseback Riding,Chemistry,Industrialization,Bronze Working,Scientific Theory,Philosophy,Drama and Poetry,Military Science,Printing Press,Mathematics,Guilds,Biology,Machinery,Engineering,Archaeology,Electricity,Dynamite,Pottery,Agriculture,Iron Working,Rifling,Theology,Navigation,Calendar,Masonry,Steel,Physics,Trapping,Chivalry,The Wheel,Animal Husbandry,Writing,Civil Service,Metallurgy,Astronomy],techsToResearch:[Radio],techsInProgress:{Radio:1202}},policies:{storedCulture:1961,shouldOpenPolicyPicker:true},civConstructions:{},questManager:{assignedQuests:[{questName:Route,assigner:Quebec City,assignee:Inca,assignedOnTurn:100},{questName:Connect Resource,assigner:Quebec City,assignee:Inca,assignedOnTurn:207,data1:Silk}],globalQuestCountdown:4,individualQuestCountdown:{Inca:0,Songhai:0,England:0}},religionManager:{storedFaith:627},goldenAges:{storedHappiness:285,numberOfGoldenAges:4},greatPeople:{},victoryManager:{},ruinsManager:{},diplomacy:{Inca:{otherCivName:Inca,diplomaticStatus:Protector,flagsCountdown:{EverBeenFriends:-1},diplomaticModifiers:{OpenBorders:7.5},influence:65.75},Vancouver:{otherCivName:Vancouver,diplomaticStatus:Peace,diplomaticModifiers:{YearsOfPeace:30},influence:10},Singapore:{otherCivName:Singapore,diplomaticStatus:Peace,diplomaticModifiers:{YearsOfPeace:30}},Songhai:{otherCivName:Songhai,diplomaticStatus:Peace,flagsCountdown:{WaryOf:-1},diplomaticModifiers:{YearsOfPeace:30},influence:-20},England:{otherCivName:England,diplomaticStatus:Peace,diplomaticModifiers:{YearsOfPeace:30}},Barbarians:{otherCivName:Barbarians,influence:-59}},proximity:{Inca:Neighbors,Vancouver:Neighbors,Singapore:Close,Songhai:Neighbors,Florence:Far,England:Close},allyCivName:Inca,cities:[{location:{x:-2,y:3},id:292c9f29-2dfd-4b0a-8391-0ae52efa92d6,name:Quebec City,foundingCiv:Quebec City,turnAcquired:1,health:300,religion:{religionsAtSomePointAdopted:[Sikhism],pressures:{The religion of TheLegend27:{class:java.lang.Integer,value:1100},Sikhism:{class:java.lang.Integer,value:6014},Christianity:{class:java.lang.Integer,value:4544}}},population:{population:13,foodStored:186},cityConstructions:{builtBuildings:[Monument,Amphitheater,Bank,Circus,Harbor,Factory,Lighthouse,Colosseum,Aqueduct,Public School,Arsenal,Workshop,Hospital,Granary,Armory,Stock Exchange,Palace,Stable,Military Academy,Market,Barracks,Opera House,Shrine,Temple,University,Theatre,Castle,Museum,Seaport,Library,Walls],constructionQueue:[Science],productionOverflow:35},expansion:{cultureStored:277},tiles:[{x:-3,y:2},{x:-4,y:3},{x:-3,y:3},{x:-4,y:4},{x:-1,y:2},{x:-1,y:3},{x:-1,y:4},{x:-2,y:1},{y:3},{x:-2,y:4},{x:-2,y:3},{x:-2,y:2},{x:-2,y:6},{x:-3,y:1},{x:-4,y:1},{x:-3,y:4},{x:-1,y:5},{x:-2,y:5},{y:5},{x:-1,y:7}],workedTiles:[{x:-4,y:3},{x:-4,y:1},{x:-4,y:4},{x:-1,y:2},{x:-3,y:3},{y:3},{x:-2,y:2},{x:-2,y:6},{x:-3,y:1},{x:-3,y:2},{x:-1,y:3},{x:-2,y:1},{x:-1,y:5}],isOriginalCapital:true}],citiesCreated:1,exploredTiles:[{x:1,y:3},{x:-4,y:3},{x:-1,y:3},{x:-3,y:2},{x:-3,y:8},{x:-2,y:6},{x:-6,y:1},{x:-6,y:4},{x:2,y:6},{x:3,y:8},{y:6},{x:-3},{x:-6,y:-1},{x:3,y:9},{x:-5,y:4},{y:7},{x:-5,y:1},{x:-2,y:7},{x:-6,y:5},{x:2,y:7},{x:-5,y:-1},{x:3,y:10},{x:3,y:11},{x:-6,y:3},{x:-2,y:1},{x:-1,y:8},{x:-3,y:6},{x:-2,y:4},{x:-4,y:2},{x:1,y:2},{x:3,y:6},{y:1},{x:-1},{x:1,y:8},{x:-2,y:-1},{x:2,y:4},{y:4},{x:-1,y:2},{x:-4},{x:-1,y:9},{x:1,y:9},{x:-7,y:4},{x:2,y:5},{x:-5,y:3},{x:-7,y:1},{x:-1,y:10},{x:-2,y:5},{x:-3,y:7},{y:5},{x:1,y:10},{x:3,y:7},{x:1,y:11},{x:-6,y:2},{x:-3,y:1},{x:-1,y:6},{x:-2,y:3},{x:-3,y:4},{x:-4,y:6},{x:1,y:6},{x:2,y:3},{x:-8,y:3},{x:-3,y:-1},{x:-6},{y:3},{x:-7,y:3},{x:-5},{x:-1,y:7},{x:-5,y:2},{x:-3,y:5},{x:-4,y:7},{x:1,y:7},{x:3,y:5},{x:2,y:8},{x:-4,y:1},{x:-3,y:3},{x:-2,y:2},{x:-1,y:4},{x:-2,y:8},{x:-4,y:4},{x:-8,y:2},{x:1,y:1},{x:-5,y:5},{y:2},{x:-2},{x:-4,y:-1},{x:1,y:4},{x:2,y:2},{y:8},{x:-1,y:1},{y:9},{x:2,y:9},{x:-2,y:9},{y:10},{x:-1,y:5},{x:-4,y:5},{x:1,y:5},{x:-5,y:6},{x:-7,y:2},{x:2,y:10},{y:11},{x:2,y:11}],hasEverOwnedOriginalCapital:true,totalCultureForContests:1961,totalFaithForContests:627,cityStatePersonality:Friendly}],barbarians:{},religions:{Dance of the Aurora:{name:Dance of the Aurora,foundingCivName:Inca,followerBeliefs:[Dance of the Aurora]},Sikhism:{name:Sikhism,displayName:Sikhism,foundingCivName:Inca,founderBeliefs:[Pilgrimage,Reliquary],followerBeliefs:[Dance of the Aurora,Religious Art,Swords into Ploughshares]},Desert Folklore:{name:Desert Folklore,foundingCivName:Songhai,followerBeliefs:[Desert Folklore]},Christianity:{name:Christianity,displayName:Christianity,foundingCivName:England,founderBeliefs:[Religious Texts,Ceremonial Burial],followerBeliefs:[Religious Community,God of War,Holy Warriors]},God of War:{name:God of War,foundingCivName:England,followerBeliefs:[God of War]}},difficulty:Prince,tileMap:{mapParameters:{type:Perlin,shape:Rectangular,mapSize:{radius:10,width:23,height:15,name:Tiny},createdWithVersion:3.18.3,seed:1637770344994,temperatureExtremeness:0.59999996},tileList:[{position:{x:13,y:2},baseTerrain:Snow,continent:2},{position:{x:12,y:1},baseTerrain:Snow,continent:2},{position:{x:11},baseTerrain:Snow,continent:2},{position:{x:10,y:-1},baseTerrain:Tundra,terrainFeatures:[Forest],improvement:Lumber mill,continent:2},{position:{x:9,y:-2},baseTerrain:Plains,resource:Horses,resourceAmount:2,improvement:Pasture,continent:2},{position:{x:8,y:-3},baseTerrain:Grassland,resource:Wine,improvement:Plantation,continent:2},{militaryUnit:{owner:England,originalOwner:England,name:Cannon,currentMovement:2,promotions:{promotions:[Accuracy I],numberOfPromotions:2}},civilianUnit:{owner:England,originalOwner:England,name:Great Prophet,currentMovement:2,promotions:{},abilityUsesLeft:{Spread Religion:4},maxAbilityUses:{Spread Religion:4},religion:Christianity},position:{x:7,y:-4},baseTerrain:Plains,resource:Sheep,improvement:Pasture,continent:2},{militaryUnit:{owner:England,originalOwner:England,name:Gatling Gun,currentMovement:2,promotions:{promotions:[Extended Range]}},civilianUnit:{owner:England,originalOwner:England,name:Great Prophet,currentMovement:2,promotions:{},abilityUsesLeft:{Spread Religion:4},maxAbilityUses:{Spread Religion:4},religion:Christianity},position:{x:6,y:-5},baseTerrain:Plains,improvement:City center,roadStatus:Road,continent:2},{militaryUnit:{owner:England,originalOwner:England,name:Lancer,currentMovement:4,promotions:{promotions:[Formation II,Shock I,Formation I,Drill I],numberOfPromotions:2}},civilianUnit:{owner:England,originalOwner:England,name:Great Prophet,currentMovement:2,promotions:{},abilityUsesLeft:{Spread Religion:4},maxAbilityUses:{Spread Religion:4},religion:Christianity},position:{x:5,y:-6},baseTerrain:Plains,terrainFeatures:[Jungle],improvement:Trading post,continent:2},{position:{x:4,y:-7},baseTerrain:Plains,resource:Incense,improvement:Plantation,continent:2},{civilianUnit:{owner:England,originalOwner:England,name:Great Scientist,currentMovement:2,promotions:{}},position:{x:3,y:-8},baseTerrain:Desert,terrainFeatures:[Hill],improvement:Mine,continent:2},{position:{x:2,y:-9},baseTerrain:Desert,terrainFeatures:[Hill],improvement:Mine,continent:2},{position:{x:1,y:-10},baseTerrain:Plains,terrainFeatures:[Hill,Forest],improvement:Lumber mill,continent:2},{position:{y:-11},baseTerrain:Tundra,resource:Dyes,improvement:Plantation,continent:2},{position:{x:-1,y:-12},baseTerrain:Snow,terrainFeatures:[Hill],improvement:Mine,continent:2},{position:{x:12,y:2},baseTerrain:Snow,resource:Uranium,resourceAmount:2,continent:2},{position:{x:11,y:1},baseTerrain:Snow,resource:Copper,improvement:Mine,continent:2},{position:{x:10},baseTerrain:Tundra,improvement:Trading post,continent:2},{position:{x:9,y:-1},baseTerrain:Grassland,resource:Iron,resourceAmount:2,improvement:Mine,continent:2},{civilianUnit:{owner:England,originalOwner:England,name:Great Artist,currentMovement:2,promotions:{}},position:{x:8,y:-2},baseTerrain:Desert,terrainFeatures:[Oasis],continent:2},{position:{x:7,y:-3},baseTerrain:Plains,improvement:Manufactory,continent:2},{civilianUnit:{owner:England,originalOwner:England,name:Great Engineer,currentMovement:4,promotions:{}},position:{x:6,y:-4},baseTerrain:Coast},{civilianUnit:{owner:England,originalOwner:England,name:Great Scientist,currentMovement:4,promotions:{}},position:{x:5,y:-5},baseTerrain:Coast},{civilianUnit:{owner:England,originalOwner:England,name:Worker,currentMovement:2,promotions:{}},position:{x:4,y:-6},baseTerrain:Plains,resource:Aluminum,resourceAmount:3,improvement:Mine,continent:2},{position:{x:3,y:-7},baseTerrain:Coast},{civilianUnit:{owner:England,originalOwner:England,name:Worker,currentMovement:2,promotions:{}},position:{x:2,y:-8},baseTerrain:Desert,terrainFeatures:[Hill],improvement:Mine,continent:2},{militaryUnit:{owner:England,originalOwner:England,name:Cannon,currentMovement:2,promotions:{XP:1,promotions:[Barrage I],numberOfPromotions:2}},position:{x:1,y:-9},baseTerrain:Grassland,resource:Cattle,improvement:City center,roadStatus:Road,continent:2},{militaryUnit:{owner:England,originalOwner:England,name:Rifleman,currentMovement:2,promotions:{}},position:{y:-10},baseTerrain:Snow,terrainFeatures:[Hill],improvement:Mine,continent:2},{position:{x:-1,y:-11},baseTerrain:Snow,continent:2},{position:{x:-2,y:-12},baseTerrain:Snow,terrainFeatures:[Hill],improvement:Mine,continent:2},{position:{x:12,y:3},baseTerrain:Snow,resource:Stone,improvement:Quarry,continent:2},{position:{x:11,y:2},baseTerrain:Snow,continent:2},{position:{x:10,y:1},baseTerrain:Snow,continent:2},{position:{x:9},baseTerrain:Tundra,terrainFeatures:[Forest],improvement:Lumber mill,continent:2},{position:{x:8,y:-1},baseTerrain:Desert,improvement:Farm,continent:2},{position:{x:7,y:-2},baseTerrain:Desert,terrainFeatures:[Hill],improvement:Academy,continent:2},{position:{x:6,y:-3},baseTerrain:Coast,terrainFeatures:[Atoll]},{position:{x:5,y:-4},baseTerrain:Coast},{position:{x:4,y:-5},baseTerrain:Plains,resource:Coal,resourceAmount:3,improvement:Mine,continent:2},{position:{x:3,y:-6},baseTerrain:Coast},{civilianUnit:{owner:England,originalOwner:England,name:Work Boats,currentMovement:6,promotions:{}},position:{x:2,y:-7},baseTerrain:Coast,resource:Oil,resourceAmount:3},{civilianUnit:{owner:England,originalOwner:England,name:Worker,currentMovement:4,promotions:{}},position:{x:1,y:-8},baseTerrain:Coast},{position:{y:-9},baseTerrain:Tundra,terrainFeatures:[Hill],improvement:Mine,continent:2},{position:{x:-1,y:-10},baseTerrain:Snow,continent:2},{militaryUnit:{owner:Florence,originalOwner:Florence,name:Gatling Gun,promotions:{XP:15,promotions:[Barrage I,Accuracy I],numberOfPromotions:2}},position:{x:-2,y:-11},baseTerrain:Snow,terrainFeatures:[Hill],improvement:Mine,continent:2},{militaryUnit:{owner:England,originalOwner:England,name:Cannon,currentMovement:2,promotions:{}},position:{x:11,y:3},baseTerrain:Snow,continent:2},{position:{x:10,y:2},baseTerrain:Snow,improvement:City center,roadStatus:Road,continent:2},{position:{x:9,y:1},baseTerrain:Coast},{position:{x:8},baseTerrain:Grassland,terrainFeatures:[Forest],improvement:Lumber mill,continent:2},{position:{x:7,y:-1},baseTerrain:Desert,terrainFeatures:[Hill],improvement:Academy,continent:2},{militaryUnit:{owner:England,originalOwner:England,name:Gatling Gun,currentMovement:2,promotions:{promotions:[Barrage I],numberOfPromotions:2}},civilianUnit:{owner:England,originalOwner:England,name:Settler,currentMovement:2,promotions:{}},position:{x:6,y:-2},baseTerrain:Plains,terrainFeatures:[Hill],improvement:Landmark,continent:2},{militaryUnit:{owner:England,originalOwner:England,name:Rifleman,currentMovement:2,promotions:{XP:27,promotions:[Ambush I,Shock I],numberOfPromotions:2}},civilianUnit:{owner:England,originalOwner:England,name:Great General,currentMovement:2,promotions:{}},position:{x:5,y:-3},baseTerrain:Plains,resource:Sheep,improvement:Pasture,continent:2},{position:{x:4,y:-4},baseTerrain:Coast},{position:{x:3,y:-5},baseTerrain:Coast},{position:{x:2,y:-6},baseTerrain:Ocean},{position:{x:1,y:-7},baseTerrain:Ocean},{position:{y:-8},baseTerrain:Coast},{position:{x:-1,y:-9},baseTerrain:Snow,terrainFeatures:[Hill],improvement:Mine,continent:2},{civilianUnit:{owner:Florence,originalOwner:Florence,name:Worker,promotions:{}},position:{x:-2,y:-10},baseTerrain:Snow,terrainFeatures:[Hill],improvement:Mine,continent:2},{position:{x:-3,y:-11},baseTerrain:Snow,terrainFeatures:[Hill],improvement:Mine,continent:2},{position:{x:11,y:4},baseTerrain:Snow,continent:2},{position:{x:10,y:3},baseTerrain:Snow,continent:2},{position:{x:9,y:2},baseTerrain:Snow,continent:2},{position:{x:8,y:1},baseTerrain:Coast},{position:{x:7},baseTerrain:Coast},{civilianUnit:{owner:England,originalOwner:England,name:Great General,currentMovement:2,promotions:{}},position:{x:6,y:-1},baseTerrain:Desert,terrainFeatures:[Hill],resource:Sheep,improvement:Pasture,continent:2},{militaryUnit:{owner:England,originalOwner:England,name:Cavalry,currentMovement:4,promotions:{promotions:[Shock I],numberOfPromotions:2}},civilianUnit:{owner:England,originalOwner:England,name:Great Scientist,currentMovement:2,promotions:{}},position:{x:5,y:-2},baseTerrain:Desert,terrainFeatures:[Oasis],continent:2},{militaryUnit:{owner:England,originalOwner:England,name:Cannon,currentMovement:2,promotions:{promotions:[Accuracy I],numberOfPromotions:2}},position:{x:4,y:-3},baseTerrain:Plains,improvement:City center,roadStatus:Road,continent:2},{position:{x:3,y:-4},baseTerrain:Coast},{position:{x:2,y:-5},baseTerrain:Coast},{position:{x:1,y:-6},baseTerrain:Coast},{position:{y:-7},baseTerrain:Ocean},{position:{x:-1,y:-8},baseTerrain:Coast},{militaryUnit:{owner:Florence,originalOwner:Florence,name:Crossbowman,currentMovement:4,promotions:{XP:10,promotions:[Accuracy I],numberOfPromotions:1}},position:{x:-2,y:-9},baseTerrain:Coast,resource:Fish,improvement:Fishing Boats},{militaryUnit:{owner:Florence,originalOwner:Florence,name:Gatling Gun,promotions:{XP:15,promotions:[Volley,Accuracy I],numberOfPromotions:2}},position:{x:-3,y:-10},baseTerrain:Snow,terrainFeatures:[Hill],improvement:Mine,continent:2},{civilianUnit:{owner:Songhai,originalOwner:Songhai,name:Settler,currentMovement:2,promotions:{}},position:{x:10,y:4},baseTerrain:Snow,continent:2},{position:{x:9,y:3},baseTerrain:Tundra,resource:Silk,improvement:Plantation,continent:2},{position:{x:8,y:2},baseTerrain:Coast},{position:{x:7,y:1},baseTerrain:Ocean},{position:{x:6},baseTerrain:Coast},{position:{x:5,y:-1},baseTerrain:Plains,improvement:Farm,continent:2},{position:{x:4,y:-2},baseTerrain:Plains,improvement:Farm,continent:2},{position:{x:3,y:-3},baseTerrain:Grassland,improvement:Farm,continent:2},{civilianUnit:{owner:England,originalOwner:England,name:Great General,currentMovement:4,promotions:{}},position:{x:2,y:-4},baseTerrain:Coast},{militaryUnit:{owner:England,originalOwner:England,name:Cannon,currentMovement:2,promotions:{numberOfPromotions:2}},position:{x:1,y:-5},baseTerrain:Plains,improvement:City center,roadStatus:Road,continent:5},{position:{y:-6},baseTerrain:Coast,terrainFeatures:[Atoll]},{militaryUnit:{owner:Florence,originalOwner:Florence,name:Artillery,promotions:{promotions:[Accuracy II,Accuracy I],numberOfPromotions:2}},position:{x:-1,y:-7},baseTerrain:Ocean},{position:{x:-2,y:-8},baseTerrain:Coast},{position:{x:-3,y:-9},baseTerrain:Tundra,resource:Deer,improvement:City center,roadStatus:Road,continent:2},{position:{x:-4,y:-10},baseTerrain:Coast},{position:{x:10,y:5},baseTerrain:Coast,resource:Fish,improvement:Fishing Boats},{position:{x:9,y:4},baseTerrain:Coast},{militaryUnit:{owner:Inca,originalOwner:Inca,name:Crossbowman,currentMovement:4,promotions:{XP:5,promotions:[Cover I],numberOfPromotions:1}},position:{x:8,y:3},baseTerrain:Coast},{position:{x:7,y:2},baseTerrain:Ocean},{position:{x:6,y:1},baseTerrain:Coast},{position:{x:5},baseTerrain:Grassland,improvement:Farm,continent:2},{position:{x:4,y:-1},baseTerrain:Plains,resource:Ivory,improvement:Camp,continent:2},{position:{x:3,y:-2},baseTerrain:Plains,improvement:Farm,continent:2},{position:{x:2,y:-3},baseTerrain:Coast},{position:{x:1,y:-4},baseTerrain:Coast},{position:{y:-5},baseTerrain:Coast},{position:{x:-1,y:-6},baseTerrain:Ocean},{position:{x:-2,y:-7},baseTerrain:Coast,resource:Whales,improvement:Fishing Boats},{militaryUnit:{owner:Florence,originalOwner:Florence,name:Rifleman,promotions:{XP:5,promotions:[Ambush I],numberOfPromotions:1}},position:{x:-3,y:-8},baseTerrain:Coast,resource:Fish,improvement:Fishing Boats},{militaryUnit:{owner:Florence,originalOwner:Florence,name:Galleass,promotions:{XP:9,promotions:[Bombardment I],numberOfPromotions:1}},position:{x:-4,y:-9},baseTerrain:Coast},{position:{x:9,y:5},baseTerrain:Coast},{position:{x:8,y:4},baseTerrain:Ocean},{position:{x:7,y:3},baseTerrain:Ocean},{position:{x:6,y:2},baseTerrain:Ocean},{position:{x:5,y:1},baseTerrain:Coast},{position:{x:4},baseTerrain:Coast},{position:{x:3,y:-1},baseTerrain:Coast},{position:{x:2,y:-2},baseTerrain:Coast},{position:{x:1,y:-3},baseTerrain:Plains,terrainFeatures:[Jungle],improvement:Trading post,continent:4},{position:{y:-4},baseTerrain:Coast},{position:{x:-1,y:-5},baseTerrain:Ocean},{militaryUnit:{owner:Inca,originalOwner:Inca,name:Caravel,currentMovement:5,promotions:{}},position:{x:-2,y:-6},baseTerrain:Coast},{position:{x:-3,y:-7},baseTerrain:Mountain,naturalWonder:Krakatoa},{militaryUnit:{owner:Inca,originalOwner:Inca,name:Crossbowman,currentMovement:4,promotions:{}},position:{x:-4,y:-8},baseTerrain:Coast},{position:{x:-5,y:-9},baseTerrain:Ocean},{position:{x:9,y:6},baseTerrain:Snow,improvement:City center,roadStatus:Road,continent:0},{position:{x:8,y:5},baseTerrain:Coast},{position:{x:7,y:4},baseTerrain:Ocean},{position:{x:6,y:3},baseTerrain:Ocean},{position:{x:5,y:2},baseTerrain:Ocean},{position:{x:4,y:1},baseTerrain:Ocean},{position:{x:3},baseTerrain:Ocean},{position:{x:2,y:-1},baseTerrain:Coast,terrainFeatures:[Atoll]},{position:{x:1,y:-2},baseTerrain:Grassland,resource:Cattle,improvement:Pasture,continent:4},{militaryUnit:{owner:Inca,originalOwner:Inca,name:Artillery,currentMovement:2,promotions:{promotions:[Accuracy II,Accuracy I],numberOfPromotions:2}},civilianUnit:{owner:Inca,originalOwner:Inca,name:Great General,currentMovement:2,promotions:{}},position:{y:-3},baseTerrain:Plains,terrainFeatures:[Forest],improvement:Lumber mill,continent:4},{position:{x:-1,y:-4},baseTerrain:Coast},{position:{x:-2,y:-5},baseTerrain:Ocean},{position:{x:-3,y:-6},baseTerrain:Coast},{position:{x:-4,y:-7},baseTerrain:Coast},{position:{x:-5,y:-8},baseTerrain:Ocean},{position:{x:8,y:6},baseTerrain:Snow,resource:Copper,improvement:Mine,continent:0},{position:{x:7,y:5},baseTerrain:Coast},{position:{x:6,y:4},baseTerrain:Ocean},{position:{x:5,y:3},baseTerrain:Ocean},{position:{x:4,y:2},baseTerrain:Ocean},{position:{x:3,y:1},baseTerrain:Ocean},{position:{x:2},baseTerrain:Ocean},{position:{x:1,y:-1},baseTerrain:Coast},{militaryUnit:{owner:Inca,originalOwner:Inca,name:Gatling Gun,currentMovement:2,promotions:{XP:2,promotions:[Slinger Withdraw]}},position:{y:-2},baseTerrain:Plains,improvement:City center,roadStatus:Road,continent:4},{position:{x:-1,y:-3},baseTerrain:Coast},{position:{x:-2,y:-4},baseTerrain:Ocean},{position:{x:-3,y:-5},baseTerrain:Ocean},{position:{x:-4,y:-6},baseTerrain:Ocean},{position:{x:-5,y:-7},baseTerrain:Ocean},{position:{x:-6,y:-8},baseTerrain:Ocean},{position:{x:8,y:7},baseTerrain:Snow,continent:0},{position:{x:7,y:6},baseTerrain:Coast},{position:{x:6,y:5},baseTerrain:Coast},{position:{x:5,y:4},baseTerrain:Coast},{position:{x:4,y:3},baseTerrain:Ocean},{position:{x:3,y:2},baseTerrain:Ocean},{position:{x:2,y:1},baseTerrain:Ocean},{position:{x:1},baseTerrain:Coast},{position:{y:-1},baseTerrain:Coast},{position:{x:-1,y:-2},baseTerrain:Coast},{position:{x:-2,y:-3},baseTerrain:Coast},{position:{x:-3,y:-4},baseTerrain:Coast},{position:{x:-4,y:-5},baseTerrain:Coast,resource:Fish,improvement:Fishing Boats},{position:{x:-5,y:-6},baseTerrain:Coast,resource:Pearls,improvement:Fishing Boats},{position:{x:-6,y:-7},baseTerrain:Ocean},{position:{x:7,y:7},baseTerrain:Snow,continent:0},{position:{x:6,y:6},baseTerrain:Coast},{position:{x:5,y:5},baseTerrain:Tundra,resource:Deer,improvement:Camp,continent:0},{position:{x:4,y:4},baseTerrain:Coast,terrainFeatures:[Atoll]},{position:{x:3,y:3},baseTerrain:Coast},{position:{x:2,y:2},baseTerrain:Ocean},{position:{x:1,y:1},baseTerrain:Coast},{baseTerrain:Grassland,terrainFeatures:[Hill],resource:Gems,improvement:Mine,continent:0},{position:{x:-1,y:-1},baseTerrain:Coast},{position:{x:-2,y:-2},baseTerrain:Plains,improvement:Farm,continent:0},{position:{x:-3,y:-3},baseTerrain:Coast,terrainFeatures:[Atoll]},{position:{x:-4,y:-4},baseTerrain:Grassland,improvement:Farm,continent:0},{position:{x:-5,y:-5},baseTerrain:Tundra,improvement:Trading post,continent:0},{position:{x:-6,y:-6},baseTerrain:Coast,terrainFeatures:[Atoll]},{position:{x:-7,y:-7},baseTerrain:Coast,terrainFeatures:[Ice]},{position:{x:7,y:8},baseTerrain:Snow,continent:0},{position:{x:6,y:7},baseTerrain:Snow,improvement:City center,roadStatus:Road,continent:0},{position:{x:5,y:6},baseTerrain:Tundra,improvement:Trading post,continent:0},{position:{x:4,y:5},baseTerrain:Plains,improvement:Academy,continent:0},{civilianUnit:{owner:Songhai,originalOwner:Songhai,name:Worker,currentMovement:2,promotions:{}},position:{x:3,y:4},baseTerrain:Plains,improvement:Farm,continent:0},{position:{x:2,y:3},baseTerrain:Coast},{position:{x:1,y:2},baseTerrain:Coast},{position:{y:1},baseTerrain:Plains,terrainFeatures:[Hill],improvement:Academy,continent:0},{position:{x:-1},baseTerrain:Mountain},{militaryUnit:{owner:Inca,originalOwner:Inca,name:Cavalry,currentMovement:4,promotions:{promotions:[Morale,Shock I,Drill I],numberOfPromotions:2}},civilianUnit:{owner:Inca,originalOwner:Inca,name:Great Prophet,currentMovement:2,promotions:{},abilityUsesLeft:{Spread Religion:4},maxAbilityUses:{Spread Religion:4},religion:Sikhism},position:{x:-2,y:-1},baseTerrain:Grassland,improvement:Farm,continent:0},{militaryUnit:{owner:Inca,originalOwner:Inca,name:Gatling Gun,currentMovement:2,promotions:{promotions:[Slinger Withdraw]}},civilianUnit:{owner:Inca,originalOwner:Inca,name:Great Prophet,currentMovement:2,promotions:{},abilityUsesLeft:{Spread Religion:4},maxAbilityUses:{Spread Religion:4},religion:Sikhism},position:{x:-3,y:-2},baseTerrain:Grassland,terrainFeatures:[Hill],improvement:City center,roadStatus:Road,continent:0},{militaryUnit:{owner:Inca,originalOwner:Inca,name:Rifleman,currentMovement:2,promotions:{XP:5,promotions:[Cover I,Morale],numberOfPromotions:1}},civilianUnit:{owner:Inca,originalOwner:Inca,name:Great Prophet,currentMovement:2,promotions:{},abilityUsesLeft:{Spread Religion:4},maxAbilityUses:{Spread Religion:4},religion:Sikhism},position:{x:-4,y:-3},baseTerrain:Grassland,terrainFeatures:[Hill],resource:Sheep,improvement:Pasture,continent:0},{position:{x:-5,y:-4},baseTerrain:Grassland,improvement:Farm,continent:0},{militaryUnit:{owner:Inca,originalOwner:Inca,name:Rifleman,currentMovement:2,promotions:{XP:5,promotions:[Drill I],numberOfPromotions:1}},position:{x:-6,y:-5},baseTerrain:Tundra,improvement:Trading post,continent:0},{militaryUnit:{owner:Inca,originalOwner:Inca,name:Gatling Gun,currentMovement:2,promotions:{promotions:[Slinger Withdraw]}},position:{x:-7,y:-6},baseTerrain:Snow,improvement:City center,roadStatus:Road,continent:0},{position:{x:6,y:8},baseTerrain:Snow,continent:0},{position:{x:5,y:7},baseTerrain:Snow,continent:0},{civilianUnit:{owner:Songhai,originalOwner:Songhai,name:Worker,currentMovement:2,promotions:{}},position:{x:4,y:6},baseTerrain:Tundra,improvement:Trading post,continent:0},{position:{x:3,y:5},baseTerrain:Grassland,improvement:Farm,continent:0},{position:{x:2,y:4},baseTerrain:Plains,improvement:Customs house,continent:0},{position:{x:1,y:3},baseTerrain:Plains,continent:0},{position:{y:2},baseTerrain:Grassland,terrainFeatures:[Forest],continent:0},{position:{x:-1,y:1},baseTerrain:Grassland,terrainFeatures:[Hill],improvement:Customs house,continent:0},{position:{x:-2},baseTerrain:Grassland,terrainFeatures:[Hill],improvement:Academy,continent:0},{civilianUnit:{owner:Inca,originalOwner:Inca,name:Great Prophet,currentMovement:2,promotions:{},abilityUsesLeft:{Spread Religion:4},maxAbilityUses:{Spread Religion:4},religion:Sikhism},position:{x:-3,y:-1},baseTerrain:Plains,terrainFeatures:[Hill],resource:Sheep,improvement:Pasture,continent:0},{position:{x:-4,y:-2},baseTerrain:Desert,resource:Gold Ore,improvement:Mine,continent:0},{position:{x:-5,y:-3},baseTerrain:Grassland,terrainFeatures:[Hill],improvement:Mine,continent:0},{position:{x:-6,y:-4},baseTerrain:Tundra,terrainFeatures:[Hill],improvement:Terrace farm,continent:0},{militaryUnit:{owner:Inca,originalOwner:Inca,name:Cavalry,currentMovement:4,promotions:{promotions:[Shock I,Drill I],numberOfPromotions:2}},position:{x:-7,y:-5},baseTerrain:Tundra,terrainFeatures:[Hill],improvement:Mine,continent:0},{position:{x:-8,y:-6},baseTerrain:Snow,improvement:Farm,continent:0},{position:{x:6,y:9},baseTerrain:Snow,resource:Copper,improvement:Mine,continent:0},{civilianUnit:{owner:Songhai,originalOwner:Songhai,name:Great General,promotions:{}},position:{x:5,y:8},baseTerrain:Snow,continent:0},{position:{x:4,y:7},baseTerrain:Snow,continent:0},{civilianUnit:{owner:Songhai,originalOwner:Songhai,name:Worker,currentMovement:2,promotions:{}},position:{x:3,y:6},baseTerrain:Tundra,terrainFeatures:[Forest],improvement:Lumber mill,continent:0},{civilianUnit:{owner:Songhai,originalOwner:Songhai,name:Great Prophet,currentMovement:2,promotions:{},abilityUsesLeft:{Spread Religion:4},maxAbilityUses:{Spread Religion:4},religion:Desert Folklore},position:{x:2,y:5},baseTerrain:Plains,resource:Sheep,improvement:Pasture,continent:0},{position:{x:1,y:4},baseTerrain:Desert,resource:Wheat,improvement:Farm,continent:0},{position:{y:3},baseTerrain:Grassland,improvement:Farm,continent:0},{position:{x:-1,y:2},baseTerrain:Grassland,terrainFeatures:[Hill],resource:Aluminum,resourceAmount:3,improvement:Mine,continent:0},{position:{x:-2,y:1},baseTerrain:Grassland,terrainFeatures:[Hill],improvement:Mine,continent:0},{position:{x:-3},baseTerrain:Grassland,improvement:Farm,continent:0},{militaryUnit:{owner:Inca,originalOwner:Inca,name:Artillery,currentMovement:2,promotions:{}},position:{x:-4,y:-1},baseTerrain:Grassland,terrainFeatures:[Hill],improvement:Academy,continent:0},{position:{x:-5,y:-2},baseTerrain:Plains,terrainFeatures:[Hill],improvement:Academy,continent:0},{position:{x:-6,y:-3},baseTerrain:Tundra,improvement:Trading post,continent:0},{position:{x:-7,y:-4},baseTerrain:Snow,terrainFeatures:[Hill],improvement:Mine,continent:0},{position:{x:-8,y:-5},baseTerrain:Lakes},{position:{x:5,y:9},baseTerrain:Snow,continent:0},{position:{x:4,y:8},baseTerrain:Snow,continent:0},{civilianUnit:{owner:Songhai,originalOwner:Songhai,name:Worker,currentMovement:2,promotions:{}},position:{x:3,y:7},baseTerrain:Snow,continent:0},{civilianUnit:{owner:Songhai,originalOwner:Songhai,name:Great Prophet,currentMovement:2,promotions:{},abilityUsesLeft:{Spread Religion:4},maxAbilityUses:{Spread Religion:4},religion:Desert Folklore},position:{x:2,y:6},baseTerrain:Grassland,resource:Sugar,improvement:City center,roadStatus:Road,continent:0},{civilianUnit:{owner:Songhai,originalOwner:Songhai,name:Great Prophet,currentMovement:2,promotions:{},abilityUsesLeft:{Spread Religion:4},maxAbilityUses:{Spread Religion:4},religion:Desert Folklore},position:{x:1,y:5},baseTerrain:Desert,improvement:Farm,continent:0},{civilianUnit:{owner:Songhai,originalOwner:Songhai,name:Great Merchant,currentMovement:2,promotions:{}},position:{y:4},baseTerrain:Desert,terrainFeatures:[Oasis],continent:0},{militaryUnit:{owner:Quebec City,originalOwner:Quebec City,name:Privateer,promotions:{XP:15,promotions:[Coastal Raider I,Boarding Party I],numberOfPromotions:2}},position:{x:-1,y:3},baseTerrain:Coast,terrainFeatures:[Atoll]},{position:{x:-2,y:2},baseTerrain:Grassland,improvement:Farm,continent:0},{position:{x:-3,y:1},baseTerrain:Grassland,improvement:Farm,continent:0},{position:{x:-4},baseTerrain:Plains,improvement:Farm,continent:0},{position:{x:-5,y:-1},baseTerrain:Desert,improvement:Farm,continent:0},{position:{x:-6,y:-2},baseTerrain:Plains,terrainFeatures:[Hill],improvement:Mine,continent:0},{civilianUnit:{owner:Inca,originalOwner:Inca,name:Worker,currentMovement:2,promotions:{}},position:{x:-7,y:-3},baseTerrain:Tundra,improvement:Trading post,continent:0},{militaryUnit:{owner:Inca,originalOwner:Inca,name:Rifleman,currentMovement:2,promotions:{promotions:[Ambush I,Cover I],numberOfPromotions:2}},civilianUnit:{owner:Inca,originalOwner:Inca,name:Worker,currentMovement:2,promotions:{}},position:{x:-8,y:-4},baseTerrain:Snow,terrainFeatures:[Hill],improvement:Mine,continent:0},{militaryUnit:{owner:Inca,originalOwner:Inca,name:Artillery,currentMovement:2,promotions:{}},civilianUnit:{owner:Inca,originalOwner:Inca,name:Worker,currentMovement:2,promotions:{}},position:{x:-9,y:-5},baseTerrain:Snow,resource:Stone,improvement:Quarry,continent:0},{position:{x:5,y:10},baseTerrain:Snow,improvement:City center,roadStatus:Road,continent:0},{position:{x:4,y:9},baseTerrain:Snow,continent:0},{position:{x:3,y:8},baseTerrain:Snow,continent:0},{civilianUnit:{owner:Songhai,originalOwner:Songhai,name:Settler,currentMovement:2,promotions:{}},position:{x:2,y:7},baseTerrain:Tundra,resource:Deer,improvement:Camp,continent:0},{civilianUnit:{owner:Songhai,originalOwner:Songhai,name:Great Prophet,currentMovement:3,promotions:{},abilityUsesLeft:{Spread Religion:4},maxAbilityUses:{Spread Religion:4},religion:Desert Folklore},position:{x:1,y:6},baseTerrain:Coast},{position:{y:5},baseTerrain:Coast},{position:{x:-1,y:4},baseTerrain:Coast},{militaryUnit:{owner:Quebec City,originalOwner:Quebec City,name:Cavalry,promotions:{XP:15,promotions:[Shock I],numberOfPromotions:2}},position:{x:-2,y:3},baseTerrain:Grassland,terrainFeatures:[Hill],improvement:City center,roadStatus:Road,continent:0},{position:{x:-3,y:2},baseTerrain:Grassland,terrainFeatures:[Hill],improvement:Mine,continent:0},{militaryUnit:{owner:Quebec City,originalOwner:Quebec City,name:Cavalry,promotions:{promotions:[Shock I,Drill I],numberOfPromotions:2}},position:{x:-4,y:1},baseTerrain:Grassland,terrainFeatures:[Hill],resource:Coal,resourceAmount:3,improvement:Mine,continent:0},{position:{x:-5},baseTerrain:Grassland,resource:Cattle,improvement:Pasture,continent:0},{position:{x:-6,y:-1},baseTerrain:Desert,terrainFeatures:[Hill],improvement:Mine,continent:0},{position:{x:-7,y:-2},baseTerrain:Tundra,terrainFeatures:[Hill,Forest],improvement:Lumber mill,continent:0},{position:{x:-8,y:-3},baseTerrain:Mountain},{militaryUnit:{owner:Inca,originalOwner:Inca,name:Gatling Gun,currentMovement:2,promotions:{promotions:[Slinger Withdraw]}},civilianUnit:{owner:Inca,originalOwner:Inca,name:Worker,currentMovement:2,promotions:{}},position:{x:-9,y:-4},baseTerrain:Snow,terrainFeatures:[Hill],improvement:City center,roadStatus:Road,continent:0},{position:{x:4,y:10},baseTerrain:Snow,continent:0},{position:{x:3,y:9},baseTerrain:Snow,terrainFeatures:[Hill],improvement:Mine,continent:0},{position:{x:2,y:8},baseTerrain:Tundra,improvement:Trading post,continent:0},{position:{x:1,y:7},baseTerrain:Coast},{position:{y:6},baseTerrain:Ocean},{position:{x:-1,y:5},baseTerrain:Ocean},{militaryUnit:{owner:Quebec City,originalOwner:Quebec City,name:Cannon,currentMovement:4,promotions:{promotions:[Barrage I],numberOfPromotions:2}},position:{x:-2,y:4},baseTerrain:Coast},{militaryUnit:{owner:Quebec City,originalOwner:Quebec City,name:Gatling Gun,promotions:{XP:15,promotions:[Cover I,Accuracy I],numberOfPromotions:2}},position:{x:-3,y:3},baseTerrain:Grassland,terrainFeatures:[Hill],resource:Horses,resourceAmount:2,improvement:Pasture,continent:0},{civilianUnit:{owner:Vancouver,originalOwner:Vancouver,name:Worker,promotions:{}},position:{x:-4,y:2},baseTerrain:Grassland,terrainFeatures:[Forest],improvement:Lumber mill,continent:0},{position:{x:-5,y:1},baseTerrain:Plains,terrainFeatures:[Hill],improvement:Mine,continent:0},{position:{x:-6},baseTerrain:Desert,terrainFeatures:[Hill],improvement:City center,roadStatus:Road,continent:0},{position:{x:-7,y:-1},baseTerrain:Coast},{position:{x:-8,y:-2},baseTerrain:Coast,resource:Crab,improvement:Fishing Boats},{position:{x:-9,y:-3},baseTerrain:Mountain},{position:{x:-10,y:-4},baseTerrain:Mountain},{position:{x:4,y:11},baseTerrain:Coast,resource:Fish},{position:{x:3,y:10},baseTerrain:Snow,continent:0},{position:{x:2,y:9},baseTerrain:Coast},{position:{x:1,y:8},baseTerrain:Coast},{position:{y:7},baseTerrain:Ocean},{position:{x:-1,y:6},baseTerrain:Ocean},{position:{x:-2,y:5},baseTerrain:Ocean},{position:{x:-3,y:4},baseTerrain:Coast},{civilianUnit:{owner:Quebec City,originalOwner:Quebec City,name:Worker,promotions:{}},position:{x:-4,y:3},baseTerrain:Plains,resource:Cotton,improvement:Plantation,continent:0},{position:{x:-5,y:2},baseTerrain:Grassland,improvement:Farm,continent:0},{position:{x:-6,y:1},baseTerrain:Coast},{position:{x:-7},baseTerrain:Coast},{position:{x:-8,y:-1},baseTerrain:Ocean},{position:{x:-9,y:-2},baseTerrain:Coast},{position:{x:-10,y:-3},baseTerrain:Snow,terrainFeatures:[Hill],continent:3},{position:{x:3,y:11},baseTerrain:Coast},{position:{x:2,y:10},baseTerrain:Coast},{position:{x:1,y:9},baseTerrain:Ocean},{position:{y:8},baseTerrain:Ocean},{position:{x:-1,y:7},baseTerrain:Ocean},{position:{x:-2,y:6},baseTerrain:Ocean},{position:{x:-3,y:5},baseTerrain:Ocean},{militaryUnit:{owner:Quebec City,originalOwner:Quebec City,name:Rifleman,promotions:{XP:15,promotions:[Ambush I,Drill I],numberOfPromotions:2}},position:{x:-4,y:4},baseTerrain:Coast,resource:Fish,improvement:Fishing Boats},{position:{x:-5,y:3},baseTerrain:Coast},{position:{x:-6,y:2},baseTerrain:Coast},{position:{x:-7,y:1},baseTerrain:Coast,resource:Fish,improvement:Fishing Boats},{militaryUnit:{owner:Singapore,originalOwner:Singapore,name:Caravel,promotions:{XP:5,promotions:[Coastal Raider I],numberOfPromotions:1}},position:{x:-8},baseTerrain:Coast,resource:Fish,improvement:Fishing Boats},{position:{x:-9,y:-1},baseTerrain:Coast},{position:{x:-10,y:-2},baseTerrain:Snow,resource:Stone,continent:3},{position:{x:-11,y:-3},baseTerrain:Snow,continent:3},{position:{x:3,y:12},baseTerrain:Ocean},{position:{x:2,y:11},baseTerrain:Ocean},{militaryUnit:{owner:England,originalOwner:England,name:Musketman,currentMovement:4,promotions:{XP:5,promotions:[Cover I],numberOfPromotions:1}},position:{x:1,y:10},baseTerrain:Coast},{position:{y:9},baseTerrain:Coast},{position:{x:-1,y:8},baseTerrain:Coast,terrainFeatures:[Atoll]},{position:{x:-2,y:7},baseTerrain:Ocean},{position:{x:-3,y:6},baseTerrain:Ocean},{position:{x:-4,y:5},baseTerrain:Ocean},{position:{x:-5,y:4},baseTerrain:Ocean},{position:{x:-6,y:3},baseTerrain:Ocean},{position:{x:-7,y:2},baseTerrain:Coast},{militaryUnit:{owner:Singapore,originalOwner:Singapore,name:Gatling Gun,currentMovement:2,promotions:{XP:2}},civilianUnit:{owner:Singapore,originalOwner:Singapore,name:Worker,promotions:{}},position:{x:-8,y:1},baseTerrain:Grassland,improvement:City center,roadStatus:Road,continent:3},{militaryUnit:{owner:Singapore,originalOwner:Singapore,name:Pikeman,promotions:{}},position:{x:-9},baseTerrain:Tundra,terrainFeatures:[Hill],improvement:Mine,continent:3},{position:{x:-10,y:-1},baseTerrain:Tundra,improvement:Farm,continent:3},{position:{x:-11,y:-2},baseTerrain:Snow,continent:3},{position:{x:2,y:12},baseTerrain:Coast},{position:{x:1,y:11},baseTerrain:Coast},{militaryUnit:{owner:England,originalOwner:England,name:Gatling Gun,currentMovement:2,promotions:{XP:17,promotions:[Barrage I,Extended Range],numberOfPromotions:1}},position:{y:10},baseTerrain:Snow,improvement:City center,roadStatus:Road,continent:1},{position:{x:-1,y:9},baseTerrain:Grassland,resource:Uranium,resourceAmount:2,continent:1},{position:{x:-2,y:8},baseTerrain:Coast},{position:{x:-3,y:7},baseTerrain:Ocean},{militaryUnit:{owner:Quebec City,originalOwner:Quebec City,name:Privateer,currentMovement:2,promotions:{XP:15,promotions:[Mobility,Coastal Raider I],numberOfPromotions:2}},position:{x:-4,y:6},baseTerrain:Ocean},{position:{x:-5,y:5},baseTerrain:Ocean},{position:{x:-6,y:4},baseTerrain:Ocean},{position:{x:-7,y:3},baseTerrain:Coast},{position:{x:-8,y:2},baseTerrain:Desert,resource:Silver,improvement:Mine,continent:3},{position:{x:-9,y:1},baseTerrain:Grassland,resource:Oil,resourceAmount:3,improvement:Oil well,continent:3},{position:{x:-10},baseTerrain:Tundra,improvement:Farm,continent:3},{position:{x:-11,y:-1},baseTerrain:Lakes},{position:{x:-12,y:-2},baseTerrain:Snow,resource:Iron,resourceAmount:2,continent:3},{position:{x:2,y:13},baseTerrain:Coast,terrainFeatures:[Ice]},{position:{x:1,y:12},baseTerrain:Snow,continent:1},{position:{y:11},baseTerrain:Snow,continent:1},{position:{x:-1,y:10},baseTerrain:Tundra,resource:Furs,continent:1},{position:{x:-2,y:9},baseTerrain:Plains,terrainFeatures:[Forest],resource:Deer,continent:1},{position:{x:-3,y:8},baseTerrain:Coast},{position:{x:-4,y:7},baseTerrain:Ocean},{position:{x:-5,y:6},baseTerrain:Ocean},{militaryUnit:{owner:Singapore,originalOwner:Singapore,name:Privateer,promotions:{promotions:[Mobility,Coastal Raider I],numberOfPromotions:2}},position:{x:-6,y:5},baseTerrain:Ocean},{position:{x:-7,y:4},baseTerrain:Ocean},{position:{x:-8,y:3},baseTerrain:Coast},{militaryUnit:{owner:Singapore,originalOwner:Singapore,name:Spearman,promotions:{XP:18,numberOfPromotions:1}},position:{x:-9,y:2},baseTerrain:Plains,terrainFeatures:[Forest],resource:Deer,improvement:Camp,continent:3},{position:{x:-10,y:1},baseTerrain:Tundra,improvement:Farm,continent:3},{position:{x:-11},baseTerrain:Lakes},{position:{x:-12,y:-1},baseTerrain:Lakes}],startingLocations:[]},gameParameters:{gameSpeed:Quick,players:[{playerType:Human},{},{}],numberOfCityStates:4,religionEnabled:true,victoryTypes:[Domination,Scientific,Cultural]},turns:300,currentPlayer:England,currentTurnStartTime:1637770666921,gameId:13329e21-0b05-4ae5-8576-f5ae27bcaf0e} diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/StarryNight.jpg b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/example_assets/StarryNight.jpg similarity index 100% rename from android/assets/scripting/enginefiles/python/unciv_scripting_examples/StarryNight.jpg rename to android/assets/scripting/enginefiles/python/unciv_scripting_examples/example_assets/StarryNight.jpg diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/TurboRainbow.png b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/example_assets/TurboRainbow.png similarity index 100% rename from android/assets/scripting/enginefiles/python/unciv_scripting_examples/TurboRainbow.png rename to android/assets/scripting/enginefiles/python/unciv_scripting_examples/example_assets/TurboRainbow.png diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/WheatField.jpg b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/example_assets/WheatField.jpg similarity index 100% rename from android/assets/scripting/enginefiles/python/unciv_scripting_examples/WheatField.jpg rename to android/assets/scripting/enginefiles/python/unciv_scripting_examples/example_assets/WheatField.jpg diff --git a/android/assets/scripting/enginefiles/qjs/main.js b/android/assets/scripting/enginefiles/qjs/main.js index 4a983f08d7197..1a0d8058a9dc2 100644 --- a/android/assets/scripting/enginefiles/qjs/main.js +++ b/android/assets/scripting/enginefiles/qjs/main.js @@ -2,6 +2,9 @@ function motd() { return "\nThis backend is HIGHLY EXPERIMENTAL. It does not implement any API bindings yet, and it may not be stable. Use it at your own risk!\n\n" } +// So... cashapp/zipline is clearly the best JS library to use for this (which in turn means that embedded QuickJS, and not Webview V8 or anything will indeed be the engine). +// Maybe LiquidCore? But I assume that's way heavier. + ` Due to the basic design of Python, any Python environment that you lock down enough to be safe will also be nearly useless. diff --git a/core/Module.md b/core/Module.md index b08fdb458d218..49a29cafec63f 100644 --- a/core/Module.md +++ b/core/Module.md @@ -210,6 +210,7 @@ Some action types, data formats, and expected response types and data formats fo 'exec': String -> 'exec_response': String //REPL print. + //Response may include 'Exception' flag. (Not implemented.) ``` ``` diff --git a/core/src/com/unciv/scripting/ScriptingBackend.kt b/core/src/com/unciv/scripting/ScriptingBackend.kt index e749b9e698e3e..57d17813ae5f4 100644 --- a/core/src/com/unciv/scripting/ScriptingBackend.kt +++ b/core/src/com/unciv/scripting/ScriptingBackend.kt @@ -26,6 +26,10 @@ import java.util.* data class AutocompleteResults(val matches: List = listOf(), val isHelpText: Boolean = false, val helpText: String = "") +data class ExecResult(val resultPrint: String, val isException: Boolean = false) +//TODO: Use this. + + /** * Base class for required companion objects of ScriptingBackend implementations. * @@ -84,6 +88,10 @@ interface ScriptingBackend { * @return null on successful termination, an Exception() otherwise. */ fun terminate(): Exception? { + // The same reasoning for returning instead of throwing an Exception() as for Blackbox.stop() also applies here. + // Namely: Some errors may be expected, and thus not exceptional enough to warrant exceptional flow control via try/catch. Let those be propagated and handled or ignored more easily as return values. + // This protects the distinction between errors that are an expected and predictable part of normal operation (Network down, zombie process, etc), which don't cause executed code blocks to be broken out of at arbitrary points, and legitimately exceptional runtime errors where the program is doing something it shouldn't be, which break normal control flow by breaking out of code blocks mid-execution. + // So if an exception is raised, it can be caught and turned into a return value at the point where it happens, without having to wrap a try{} block around the entire call stack between there and the point where it's eventually handled. return null } diff --git a/core/src/com/unciv/scripting/ScriptingScope.kt b/core/src/com/unciv/scripting/ScriptingScope.kt index ba4de6920500c..47e5e767982e7 100644 --- a/core/src/com/unciv/scripting/ScriptingScope.kt +++ b/core/src/com/unciv/scripting/ScriptingScope.kt @@ -6,6 +6,7 @@ import com.badlogic.gdx.graphics.PixmapIO import com.badlogic.gdx.utils.Base64Coder import com.unciv.UncivGame import com.unciv.logic.GameInfo +import com.unciv.logic.GameSaver import com.unciv.logic.civilization.CivilizationInfo import com.unciv.scripting.utils.ScriptingApiEnums import com.unciv.scripting.utils.InstanceFactories @@ -19,6 +20,9 @@ import java.io.ByteArrayOutputStream // TODO: Move this, and all nested objects and classes, to api/? +// TODO: Search core code for Transient lazy init caches (E.G. natural wonders saved in TileMap apparently, and TileMap.resources), and add functions to refresh them. + + /** * Holds references to all internal game data that scripting backends have access to. * @@ -47,6 +51,8 @@ class ScriptingScope( //val _availableNames = listOf("civInfo", "gameInfo", "uncivGame", "worldScreen", "apiHelpers") // Nope. Annotate instead. ) { + val GameSaver = com.unciv.logic.GameSaver //TODO: Organize. + val apiHelpers = ApiHelpers(this) class ApiHelpers(val scriptingScope: ScriptingScope) { @@ -59,8 +65,8 @@ class ScriptingScope( val registeredInstances = InstanceRegistry() //Debug/dev identity function for both Kotlin and scripts. Check if value survives serialization, force something to be added to ScriptingProtocol.instanceSaver, etc. fun unchanged(obj: Any?) = obj - fun printLn(msg: Any?) = println(msg) - //fun readLn() + fun printLine(msg: Any?) = println(msg) // Different name from Kotlin's is deliberate, to abstract for scripts. + //fun readLine() //Return a line from the main game process's STDIN. fun toString(obj: Any?) = obj.toString() fun copyToClipboard(value: Any?) { diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index f739e2786c8ef..5e32bd7f896b7 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -77,6 +77,11 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr return motd } + fun spawnBackendAndReturnMotd(backendtype: ScriptingBackendType, switchTo: Boolean = true) { + // TODO + // The script manager/handler dispatcher for mods is going to want to keep track of which backends belong to which mods. + } + fun switchToBackend(index: Int) { scriptingBackends.enforceValidIndex(index) activeBackend = index @@ -85,6 +90,7 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr fun switchToBackend(backend: ScriptingBackendBase) { for ((i, b) in scriptingBackends.withIndex()) { // TODO: Apparently there's a bunch of extensions like .withIndex(), .indices, and .lastIndex that I can use to replace a lot of stuff currently done with .size. + // TODO: Why didn't I use indexOf? if (b == backend) { switchToBackend(index = i) } diff --git a/core/src/com/unciv/scripting/reflection/Reflection.kt b/core/src/com/unciv/scripting/reflection/Reflection.kt index cc323706e4735..66cbfcdd59071 100644 --- a/core/src/com/unciv/scripting/reflection/Reflection.kt +++ b/core/src/com/unciv/scripting/reflection/Reflection.kt @@ -3,12 +3,14 @@ package com.unciv.scripting.reflection import com.unciv.scripting.utils.TokenizingJson import kotlin.collections.ArrayList import kotlin.reflect.KCallable -//import kotlin.reflect.KClass import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KParameter import kotlin.reflect.KProperty1 +import kotlin.reflect.KType import kotlin.reflect.full.isSubclassOf +import kotlin.reflect.full.isSubtypeOf import kotlin.reflect.full.isSuperclassOf +import kotlin.reflect.full.isSupertypeOf import kotlin.reflect.jvm.jvmErasure import kotlinx.serialization.Serializable import java.util.* @@ -22,6 +24,7 @@ object Reflection { val property = instance::class.members .first { it.name == propertyName } as KProperty1 // If scripting member access performance becomes an issue, memoizing this could be a potential first step. + // TODO: Throw more helpful error on failure. return property.get(instance) as R? } @@ -35,10 +38,17 @@ object Reflection { * * @property instance The receiver on which to find and call a method. * @property methodName The name of the method to resolve and call. - * @property matchNumbersLeniently Whether to treat all numeric types as the same. Useful for E.G. untyped deserialized data. - * @property matchClassesQualnames Whether to treat classes as the same if they have the same qualifiedName. Useful for E.G. ignoring the invariant arrays representing vararg parameters. + * @property matchNumbersLeniently Whether to treat all numeric types as the same. Useful for E.G. untyped deserialized data. Adds small extra step to most calls. + * @property matchClassesQualnames Whether to treat classes as the same if they have the same qualifiedName. Useful for E.G. ignoring the invariant arrays representing vararg parameters. Adds small extra step to many calls. + * @property resolveAmbiguousSpecificity Whether to try to resolve multiple ambigous matching signatures by finding one that strictly subtypes all others. Rules for this are documented under getMostSpecificCallable. No impact when not needed; Increases function domain properly handled but does not decrease performance in other uses. */ - class InstanceMethodDispatcher(val instance: Any, val methodName: String, val matchNumbersLeniently: Boolean = false, val matchClassesQualnames: Boolean = false) { + class InstanceMethodDispatcher( + val instance: Any, + val methodName: String, + val matchNumbersLeniently: Boolean = false, + val matchClassesQualnames: Boolean = false, + val resolveAmbiguousSpecificity: Boolean = false + ) { // This isn't just a nice-to-have feature. Before I implemented it, identical calls from demo scripts to methods with multiple versions (E.G. ArrayList().add()) would rarely but randomly fail because the member/signature that was found would change between runs or compilations. // TODO: This is going to need unit tests. @@ -50,19 +60,23 @@ object Reflection { /** * Lazily evaluated list of KCallables for every method that matches the given name. */ - val methods: List> by lazy { instance::class.members.filter{ it.name == methodName }.map{ it as KCallable } } + val methods: List> by lazy { instance::class.members.filter{ it.name == methodName }.map{ it as KCallable } } //Filter down to is KCallable if name collisions with properties are a possible issue. /** * @return Useful representative text. */ override fun toString() = """${this::class.simpleName}(instance=${this.instance::class.simpleName}(), methodName="${this.methodName}") with ${this.methods.size} dispatch candidates""" + // Used by "docstring" packet action in ScriptingProtocol, which is in turn exposed in interpeters as help text. TODO: Could move to an extension method in ScriptingProtocol, I suppose. /** * @return Whether a given argument value can be cast to the type of a given KParameter. */ - private fun checkParameterMatches(kparam: KParameter, arg: Any?): Boolean { + private fun checkParameterMatches(kparam: KParameter, arg: Any?, paramKtypeAppend: ArrayList): Boolean { // TODO: If performance becomes an issue, try inlining these. Then again, the JVM presumably optimizes it at runtime already (and there's far more calls than this containing function). + // val ktype = kparam.type // Necessary. For some reason, accessing kparam.ktype here seems to cause kparam.ktype.isMarkedNullable to be incorrectly false in the first "if" block. + // val isMarkedNullable = ktype.isMarkedNullable + paramKtypeAppend.add(kparam.type) if (arg == null) { // Multiple dispatch of null between Any and Any? seems ambiguous in Kotlin even without reflection. // Here, I'm resolving it myself, so it seems fine. @@ -71,8 +85,8 @@ object Reflection { // I suppose it's not a problem here as it seems broken in Kotlin generally. return kparam.type.isMarkedNullable } - val argcls = arg::class val kparamcls = kparam.type.jvmErasure + val argcls = arg::class if (matchNumbersLeniently && argcls.isSubclassOf(Number::class) && kparamcls.isSubclassOf(Number::class)) { // I think/hope this basically causes Java-style implicit conversions (or Kotlin implicit casts?). // NOTE: However, doesn't correctly forbid unconvertible types. @@ -82,55 +96,71 @@ object Reflection { return kparamcls.isSuperclassOf(argcls) // Seems to also work for generics, I guess. || (matchClassesQualnames && kparamcls.qualifiedName != null && argcls.qualifiedName != null && kparamcls.qualifiedName == argcls.qualifiedName) - // Lets different types be matched to invariants, such as for vararg arrays. + // Lets more types be matched to invariants, such as for vararg arrays. // However, the JVM still throws its own error in that case, so leaving this disabled for now. } /** * @return Whether a given KCallable's signature might be compatible with a given Array of arguments. */ - private fun checkCallableMatches(callable: KCallable, arguments: Array): Boolean { + private fun checkCallableMatches(callable: KCallable, arguments: Array, paramKtypeAppends: HashMap, ArrayList>): Boolean { // I'm not aware of any situation where this function's behaviour will deviate from Kotlin, but that doesn't mean there aren't any. Wait, no. I do know that runtime checking and resolution of erased generics will probably be looser than at compile time. They seem to act like Any(?). + val ktypeappend = arrayListOf() + paramKtypeAppends[callable] = ktypeappend val params = callable.parameters return params.size == arguments.size + 1 // Check that the parameters size is same as arguments size plus one for the receiver. && (params.slice(1..arguments.size) zip arguments).all { // Check argument classes match parameter types, skipping the receiver. - (kparam, arg) -> checkParameterMatches(kparam, arg) + (kparam, arg) -> checkParameterMatches(kparam, arg, paramKtypeAppend=ktypeappend) } - /* TODO: Dead code. - when { - useVararg -> { // Checking for varargs is somewhat more complex/slower, so hide it behind a flag. - for ((kparam, arg) in params.slice(1..params.size-1) zip arguments) { - // Check each argument's class matches its coresponding parameter type, skipping the receiver. - if (kparam.isVararg) { - // Wait, varargs probably need to be an array in KCallable.call(), which means using them would require reformatting the arguments, which, to do efficiently, would require bubbling reults from this check out all the way out to this.call(). (Could do something with nullable array return, I guess.) - return true - } - if (!checkParameterMatches(kparam, arg)) { - return false - } - } - if (params.size != arguments.size + 1) { - // Check that the parameters size is same as arguments size plus one for the receiver. - // Because the entire parameter list has to be iterated through to handle vararg, this has to come at the end. - return false - } - return true - } - else -> { - return params.size == arguments.size + 1 // Check that the parameters size is same as arguments size plus one for the receiver. - && (params.slice(1..arguments.size) zip arguments).all { // Check argument classes match parameter types, skipping the receiver. - (kparam, arg) -> checkParameterMatches(kparam, arg) - } - } - } - */ } /** * @return A list containing a KCallable for every version of this dispatcher's method that has a signature which may be compatible with a given Array of arguments. */ - fun getMatchingCallables(arguments: Array): List> { - return methods.filter { checkCallableMatches(it, arguments) } + fun getMatchingCallables(arguments: Array, paramKtypeAppends: HashMap, ArrayList>): List> { + return methods.filter { checkCallableMatches(it, arguments, paramKtypeAppends) } + } + + // Given a List of KCallables and a Map of their parameters' types, try to find one KCallable the signature of which is a subtype of all of the others. + + // For a KCallable to be returned, the following criteria must be true: + // Every relevant parameter type in it must be either the same as the corresponding parameter type in every other KCallable or a subtype of the corresponding parameter type in every other KCallable. + // At least one parameter type in it must be a strict subtype of the corresponding parameter type for every other KCallable. + + // + fun getMostSpecificCallable(matches: List>, paramKtypes: Map, ArrayList>): KCallable? { + // Should only be called when multiple/ambiguous signatures have been found. + return matches.firstOrNull { // Check all signatures. + val checkcallable = it // The signature we are currently checking for specificity. + val checkparamktypes = paramKtypes[checkcallable]!! + matches.all { // Compare currently checked signature to all other signatures. It must be a strict subtype of all other signatures. + val othercallable = it + if (checkcallable == othercallable) { + // Don't check against itself. + return@all true + } + var subtypefound = false + var supertypefound = false + for ((checkktype, otherktype) in checkparamktypes zip paramKtypes[othercallable]!!) { + // Compare all parameter types of currently checked signature to all parameter types of the other signature we are currently comparing it to. + if (checkktype == otherktype) { + // Identical types have no implications. + continue + } + if (!subtypefound && checkktype.isSubtypeOf(otherktype)) { + // At least one strict subtype is needed. + subtypefound = true + } + if (checkktype.isSupertypeOf(otherktype)) { + // No strict supertypes are allowed. + supertypefound = true + break + } + } + (subtypefound && !supertypefound) + // I did something similar for Cython once. Well, specifically, someone else had done something similar, and like this, it was running in exponential time or something, so I made it faster by building an index. + } + } } /** @@ -146,19 +176,31 @@ object Reflection { // gameInfo.civilizations.add(1, civInfo) // gameInfo.civilizations.add(civInfo) // Both need to work. - val matches = getMatchingCallables(arguments) + val callableparamktypes = hashMapOf, ArrayList>() + // Map of all traversed KCallables to lists of their parameters' KTypes. + // Only parameters, and not arguments, are saved, though both are traversed. + // KCallables that don't match the call arguments should only have as many parameter KTypes saved as it took to find out they don't match. + val matches = getMatchingCallables(arguments, paramKtypeAppends=callableparamktypes) + var match: KCallable? = null if (matches.size < 1) { throw IllegalArgumentException("No matching signatures found for calling ${instance::class?.simpleName}.${methodName} with given arguments: (${arguments.map{if (it == null) "null" else it::class?.simpleName ?: "null"}.joinToString(", ")})") //FIXME: A lot of non-null assertions and null checks (not here, but generally in the codebase) can probably be replaced with safe calls. } if (matches.size > 1) { - //I guess could also allow this, producing ambiguous behaviour and leaving it up to the methods to avoid creating such scenarios. Actually no, that sounds like a terrible idea reading it back. - //Could try to choose most specific signatures based on inheritance hierarchy. Does Kotlin do that? Probably not. So shouldn't do it here, then, if that's the case. - throw IllegalArgumentException("Multiple matching signatures found for calling ${methodName}:\n\t${matches.map{it.toString()}.joinToString("\n\t")}") + if (resolveAmbiguousSpecificity) { + //Kotlin seems to choose the most specific signatures based on inheritance hierarchy. + //E.G. UncivGame.setScreen(), which uses a more specific parameter type than its GDX base class and thus creates a different, semi-ambiguous (sub-)signature, but still gets resolved. + match = getMostSpecificCallable(matches, paramKtypes = callableparamktypes) + } + if (match == null) { + throw IllegalArgumentException("Multiple matching signatures found for calling ${methodName} with given arguments:\n\t(${arguments.map{if (it == null) "null" else it::class?.simpleName ?: "null"}.joinToString(", ")})\n\t${matches.map{it.toString()}.joinToString("\n\t")}") + } + } else { + match = matches[0]!! } - return matches[0]!!.call( - instance, - *arguments + return match.call( + instance, + *arguments ) } } @@ -335,7 +377,13 @@ object Reflection { try { obj = readInstanceProperty(obj!!, element.name) } catch (e: ClassCastException) { - obj = InstanceMethodDispatcher(obj!!, element.name, matchNumbersLeniently = true, matchClassesQualnames = false) + obj = InstanceMethodDispatcher( + obj!!, + element.name, + matchNumbersLeniently = true, + matchClassesQualnames = false, + resolveAmbiguousSpecificity = true + ) } } PathElementType.Key -> { @@ -348,6 +396,8 @@ object Reflection { ) } PathElementType.Call -> { + // TODO: Handle invoke operator. Easy enough, just recurse to access the .invoke. + // Maybe TODO: Handle lambdas. E.G. WorldScreen.nextTurnAction. Honestly, it may be better to just expose wrapping non-lambdas. obj = (obj as InstanceMethodDispatcher).call( ( if (element.doEval) diff --git a/core/src/com/unciv/scripting/utils/Blackbox.kt b/core/src/com/unciv/scripting/utils/Blackbox.kt index 54ce46d52dedb..811ebf38b1365 100644 --- a/core/src/com/unciv/scripting/utils/Blackbox.kt +++ b/core/src/com/unciv/scripting/utils/Blackbox.kt @@ -13,10 +13,12 @@ interface Blackbox { /** * Try to shut down this black box. * - * Because there might be normal situations where a "black box" isn't viable to cleanly shut down, I'm thinking that letting exceptions be returned will let those situations be distinguished from actual errors. + * Because there might be normal situations where a "black box" isn't viable to cleanly shut down, I'm thinking that letting exceptions be returned instead of thrown will let those situations be distinguished from actual errors. * * E.G.: Making an invalid API call to a requests library should still throw the Exception as usual, but making the right call only to find out that the network's down or a process is frozen would be a more "normal" and uncontrollable situation, so in that case the exception should be a return value instead of being thrown. * + * This way, the entire call stack between where a predictable error happens and where it's eventually handled doesn't have to be wrapped in an overly broad try{} block. + * * @return null on success, or an Exception() on failure. */ fun stop(): Exception? = null diff --git a/core/src/com/unciv/scripting/utils/InstanceFactories.kt b/core/src/com/unciv/scripting/utils/InstanceFactories.kt index 2a4351015a80b..68597ed019806 100644 --- a/core/src/com/unciv/scripting/utils/InstanceFactories.kt +++ b/core/src/com/unciv/scripting/utils/InstanceFactories.kt @@ -1,5 +1,8 @@ package com.unciv.scripting.utils +import com.unciv.MainMenuScreen +import com.unciv.ui.mapeditor.MapEditorScreen +import com.unciv.logic.map.TileMap import com.badlogic.gdx.math.Vector2 @@ -10,13 +13,17 @@ import com.badlogic.gdx.math.Vector2 */ object InstanceFactories { //This, and possible ApiHelpers itself, need better nested namespaces. - object Math { + val Game = object { } - object Rulesets { + val Math = object { } - object Kotlin { + val Rulesets = object { } - object GUI { + val Kotlin = object { + } + val Gui = object { + fun MainMenuScreen() = com.unciv.MainMenuScreen() + fun MapEditorScreen(map: TileMap) = com.unciv.ui.mapeditor.MapEditorScreen(map) } fun arrayOf(elements: Collection): Array<*> = elements.toTypedArray() fun arrayOfAny(elements: Collection): Array = elements.toTypedArray() diff --git a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt index 06dd41747afc5..fd20e7974e919 100644 --- a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt @@ -139,7 +139,7 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un fun openConsole() { game.setScreen(this) - keyPressDispatcher.install(stage) + keyPressDispatcher.install(stage) //TODO: Can this be moved to UncivGame.setScreen? stage.setKeyboardFocus(inputField) inputField.getOnscreenKeyboard().show(true) this.isOpen = true diff --git a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt index 3c2f40c8142fd..9d1ea3cf2a826 100644 --- a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt +++ b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt @@ -81,7 +81,7 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas private val diplomacyButtonHolder = Table() private val fogOfWarButton = createFogOfWarButton() private val nextTurnButton = createNextTurnButton() - private var nextTurnAction: () -> Unit = {} + private var nextTurnAction: () -> Unit = {} // TODO: Expose to scripting API. Also: Break up and expose individual actions, checking for order validity. Status: Lambdas not supported in Reflection.kt. private val tutorialTaskTable = Table().apply { background = ImageGetter.getBackground(ImageGetter.getBlue().lerp(Color.BLACK, 0.5f)) } private val notificationsScroll: NotificationsScroll diff --git a/tests/src/com/unciv/scripting/ScriptedTests.kt b/tests/src/com/unciv/scripting/ScriptedTests.kt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/tests/src/com/unciv/scripting/reflection/InstanceMethodDispatcherTests.kt b/tests/src/com/unciv/scripting/reflection/InstanceMethodDispatcherTests.kt new file mode 100644 index 0000000000000..e69de29bb2d1d From 0313aa7f9a4ae893f0c0244f7597a8943b2b05bc Mon Sep 17 00:00:00 2001 From: will-ca Date: Sun, 28 Nov 2021 16:39:22 +0000 Subject: [PATCH 56/93] Docs? --- .gitignore | 1 + .../assets/scripting/enginefiles/python/PythonScripting.md | 2 +- .../python/unciv_scripting_examples/MapEditingMacros.py | 2 +- .../enginefiles/python/unciv_scripting_examples/Tests.py | 4 ++-- core/Module.md | 6 +++--- core/src/com/unciv/scripting/ScriptingState.kt | 1 + core/src/com/unciv/scripting/utils/TokenizingJson.kt | 1 - .../com/unciv/ui/consolescreen/IConsoleScreenAccessible.kt | 1 + 8 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index bb6c17052d778..47324598c0c77 100644 --- a/.gitignore +++ b/.gitignore @@ -146,6 +146,7 @@ android/assets/music/ maps/ mods/ SaveFiles/ +SaveFiles GameSettings.json # Python diff --git a/android/assets/scripting/enginefiles/python/PythonScripting.md b/android/assets/scripting/enginefiles/python/PythonScripting.md index eff582b6194ff..188bd7cd0d321 100644 --- a/android/assets/scripting/enginefiles/python/PythonScripting.md +++ b/android/assets/scripting/enginefiles/python/PythonScripting.md @@ -441,7 +441,7 @@ except ForeignError: The only major caveat to the robustness of this error handling is that it does not protect against valid Kotlin/JVM actions that lead to unexpected states which then cause exceptions in later use by unrelated game code. Assigning an inappropriate value to a Kotlin/JVM member, or deleting a key-value pair where it is required by internal game code, for example, will likely cause the core game to crash the next time the invalid value is used. -``` +```python3 >>> gameInfo.tileMap.values[0].naturalWonder = "Crash" # Executes and sets .naturalWonder to "Crash" successfully. # But the game crashes when you click on the changed tile because there aren't any textures or stats for the "Crash" natural wonder. diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py index 82d7fab253f7a..b053199ed05a7 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py @@ -17,7 +17,7 @@ from . import Utils -# If you modify this file, please add any new functions to the build tests. +# If you modify this file, please add any new functions to Tests.py. t = re.sub("//.*", "", re.sub('/\*.*\*/', "", unciv.apiHelpers.assetFileString("jsons/Civ V - Gods & Kings/Terrains.json"), flags=re.DOTALL)) diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py index 90624d96d939e..19c68445947b3 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py @@ -117,9 +117,9 @@ def _print(*args, **kwargs): @TestRunner.Test(runwith=InGame) -def NewGameTest(): +def LoadGameTest(): """Example test. Explicitly tests that the InGame context manager is working.""" - # Everything below is the same, just explicitly passing existing functions to the registration function instead of using it as a decorator. + # The other tests below are all set up the same, just by explicitly passing existing functions to the registration function instead of using it as a decorator. assert unciv.apiHelpers.isInGame for v in (unciv.gameInfo, unciv.civInfo, unciv.worldScreen): assert unciv_pyhelpers.real(v) is not None diff --git a/core/Module.md b/core/Module.md index 49a29cafec63f..9222e73e58218 100644 --- a/core/Module.md +++ b/core/Module.md @@ -14,15 +14,15 @@ ## Design Principles -**The Kotlin/JVM code should neither know nor care about the language running on the other end of its scripting API.** If a behaviour is specific to a particular language, then it's also too messy and complex to try to take special account for from the other side of an IPC channel. Instead, the complexity of each specific scripting language should be handled entirely within that language itself, such that the only thing exposed to the Kotlin code is a common interface built around structures that exist in most computer programming languages (like command strings, attributes, keys, calls, assignments, collections, etc). This not only keeps the scripting protocols and interfaces compatible with multiple backends, it also serves as a test that helps keep their design relatively clean and maintainable by forcing messy or complicated behaviours to be implemented in more appropriate places. +**The Kotlin/JVM code should neither know nor care about the language running on the other end of its scripting API.** If a behaviour is specific to a particular language, then it's also too messy and complex to try to take special account for from the other side of an IPC channel. Instead, the complexity of each specific scripting language should be handled entirely within that language itself, such that the only thing exposed to the Kotlin code is a common interface built around structures that exist in most computer programming languages (like code strings, attributes, keys, calls, assignments, collections, etc). This not only keeps the scripting protocols and interfaces compatible with multiple backends; It also serves as a test that helps keep their design relatively clean and maintainable by forcing messy or complicated behaviours to be implemented in more appropriate places than the IPC interface. **Parts should be kept as modular and interchangeable as possible.** Each component type should have a somewhat well-defined job, and should not contain or be inseparably entwined with code that does things aside from that job. If a base class's primary role is to expose an REPL, for example, then extra features like command history or implementation details like running a subprocess can be moved into either another class or a subclass. If an interface's job is just to propagate code strings or resolve request packets into arbitrary Kotlin objects, then TODO: Finish this sentence. Again, IMO this both makes it easy to support versatile configurations and helps with keeping a reasonably neat codebase and architecture. **Different levels of execution/evaluation should not mix.** The IPC protocol defines the packet structures, types, and communication order that are for *implementing* scripting language semantics for accessing Kotlin/JVM data. Therefore, the IPC protocol should not itself become a *part of* scripting language semantics; No user/mod script in any language should ever have to manually create and send or receive and parse IPC packets. Certain API functions have been defined to provide additional capabilities that are *accessible through* scripting language semantics (in class ApiHelpers). Therefore, those functions should never be used in *implementing* scripting language semantics; No overloaded operator presented to a user script as part of the core Unciv API should ever implicitly call such a method as part of its basic functionality. The entrypoints for the scripting system have the roles of taking code strings (from user input, from mods, etc) and returning a result string (to print out, log, etc) (and possibly an exception Boolean flag). Therefore, they should never have to understand, or even be able to use, any data aside from opaque strings (such as IPC packets or structured return results). -**In an API meant for dynamic scripting languages, dynamic behaviours are better than static ones.** The Unciv Kotlin codebase was around 60k lines when I started on this scripting API. By using reflection in the JVM and operator overloading in scripting languages, nearly all of the classes and structures defined in there can be mirrored directly in the scripting environment without having to write or maintain a single line of hardcoded API. Because API endpoints exposed in scripting languages are already all dynamically generated at runtime, when the class structure in Kotlin changes, the attributes and methods available from all scripting backends also immediately match the new Kotlin code. +**In an API meant for dynamic scripting languages, dynamic behaviours are better than static ones.** The Unciv Kotlin codebase was around 60k lines when I started on this scripting API. By using reflection in the JVM and operator overloading in scripting languages, nearly all of the classes and structures defined in there can be mirrored directly in the scripting environment without having to write or maintain a single line of hardcoded API. Because API endpoints exposed in scripting languages are already all dynamically generated at runtime, when the class structure in Kotlin changes, the attributes and methods available from all scripting backends also immediately match the new Kotlin code without requiring any effort to update. -**In the IPC mechanisms, the specification and architecture come before implementation.** IPC actions aren't statically checked like the Kotlin code is. They aren't even syntax-checked like Python code. I do have them spitting out exceptions showing the offending packets in both Kotlin and Python in cases of obvious desync, but even that's breakable. The only thing really keeping them working is simultaneous adherence on both ends to a common protocol, which is easier when the protocol is fairly simple. If implementing a particular syntax for a scripting language would require adding a new packet type or changing the REPL loop's control flow, consider whether the use case for it would be better served by adding an API-level helper function instead. +**In the IPC mechanisms, the specification and architecture come before implementation.** IPC actions aren't statically checked like the Kotlin code is. They aren't even syntax-checked like Python code. I do have them spitting out exceptions showing the offending packets in both Kotlin and Python in cases of obvious desync, but even that's breakable. The only thing really keeping them working is simultaneous adherence on both ends to a common protocol, which is easier when the protocol is fairly simple. If implementing a particular syntax for a scripting language would require adding a new packet type or changing the REPL loop's control flow, consider whether the use case for it would be better served by adding an API-level helper function instead. (I.E. Consider adding something to ScriptingScope.ApiHelpers and letting scripts handle it themselves, instead of adding something to ScriptingProtocol and E.G. a magic method in wrapping.py.) ## Class Overview diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index 5e32bd7f896b7..fa18a01571179 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -116,6 +116,7 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr } fun hasBackend(): Boolean { + // TODO: Any check like this can be replaced with .isEmpty(). return scriptingBackends.size > 0 } diff --git a/core/src/com/unciv/scripting/utils/TokenizingJson.kt b/core/src/com/unciv/scripting/utils/TokenizingJson.kt index 7934e659e450c..64ae7c52bbf4d 100644 --- a/core/src/com/unciv/scripting/utils/TokenizingJson.kt +++ b/core/src/com/unciv/scripting/utils/TokenizingJson.kt @@ -30,7 +30,6 @@ object TokenizingJson { */ object TokenizingSerializer: KSerializer { - override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Any?") /** diff --git a/core/src/com/unciv/ui/consolescreen/IConsoleScreenAccessible.kt b/core/src/com/unciv/ui/consolescreen/IConsoleScreenAccessible.kt index 5a9d4bbe4d4f0..a56b567080811 100644 --- a/core/src/com/unciv/ui/consolescreen/IConsoleScreenAccessible.kt +++ b/core/src/com/unciv/ui/consolescreen/IConsoleScreenAccessible.kt @@ -28,6 +28,7 @@ interface IConsoleScreenAccessible { //Defaults to setting the game's screen to this instance. Can also use a lambda, for E.G. WorldScreen and UncivGame.setWorldScreen(). fun BaseScreen.setConsoleScreenCloseAction(closeAction: (() -> Unit)? = null) { + // TODO: This can probably be combined with setOpenConsoleScreenHotkey. this.consoleScreen.closeAction = closeAction ?: { this.game.setScreen(this) } } From c329a0a8eaa6f1ff2e220eb6f48ba61e663907ff Mon Sep 17 00:00:00 2001 From: will-ca Date: Tue, 30 Nov 2021 16:57:53 +0000 Subject: [PATCH 57/93] Examples. Docs. Experimenting with fleshing out script-available factories. --- CodeGenerators.py | 239 ++++++++++++++ .../enginefiles/python/PythonScripting.md | 25 +- .../enginefiles/python/unciv_lib/api.py | 2 +- .../ProceduralTechtree.py | 28 +- .../UniqueInjection.py | 6 + core/Module.md | 6 +- core/src/com/unciv/UncivGame.kt | 1 + .../com/unciv/scripting/ScriptingBackend.kt | 2 +- .../src/com/unciv/scripting/ScriptingScope.kt | 42 ++- .../unciv/scripting/api/ScriptingApiEnums.kt | 133 ++++++++ .../scripting/api/ScriptingApiFactories.kt | 302 ++++++++++++++++++ .../ScriptingApiInstanceRegistry.kt} | 4 +- .../scripting/{utils => protocol}/Blackbox.kt | 2 +- .../scripting/protocol/ScriptingProtocol.kt | 3 +- .../protocol/ScriptingReplManager.kt | 8 +- .../scripting/protocol/SubprocessBlackbox.kt | 1 - .../unciv/scripting/reflection/Reflection.kt | 4 + .../scripting/utils/InstanceFactories.kt | 35 -- .../scripting/utils/InstanceTokenizer.kt | 7 +- .../scripting/utils/ScriptingApiAccessible.kt | 2 +- .../scripting/utils/ScriptingApiEnums.kt | 16 - .../unciv/scripting/utils/TokenizingJson.kt | 2 +- .../unciv/ui/consolescreen/ConsoleScreen.kt | 2 +- 23 files changed, 795 insertions(+), 77 deletions(-) create mode 100644 CodeGenerators.py create mode 100644 android/assets/scripting/enginefiles/python/unciv_scripting_examples/UniqueInjection.py create mode 100644 core/src/com/unciv/scripting/api/ScriptingApiEnums.kt create mode 100644 core/src/com/unciv/scripting/api/ScriptingApiFactories.kt rename core/src/com/unciv/scripting/{utils/InstanceRegistry.kt => api/ScriptingApiInstanceRegistry.kt} (94%) rename core/src/com/unciv/scripting/{utils => protocol}/Blackbox.kt (98%) delete mode 100644 core/src/com/unciv/scripting/utils/InstanceFactories.kt delete mode 100644 core/src/com/unciv/scripting/utils/ScriptingApiEnums.kt diff --git a/CodeGenerators.py b/CodeGenerators.py new file mode 100644 index 0000000000000..971ed0c4c9e18 --- /dev/null +++ b/CodeGenerators.py @@ -0,0 +1,239 @@ +import os, re, string, math + + +# TODO: Honestly, this has gotten to the point where it will be easier to rewrite in Kotlin and use an AST. Though, the Kotlin parser is apparently quite difficult and possibly fragile to use too. +# Kotlin/grammar-tools is official? +# https://jitinsharma.com/posts/parsing-kotlin-using-code-kotlin/ + +# Deprecated. Use reflection to get classes at runtime instead. + + +BASE_REPO_DIR = os.path.dirname(__file__) + +ALPHANUMERIC = string.ascii_letters + string.digits + +def relPath(path="core"): + return os.path.join(BASE_REPO_DIR, path) + +def starGlob(path="core"): + for directory, subdirs, files in os.walk(relPath(path)): + for d in subdirs: + yield os.path.join(directory, d, "") + for f in files: + yield os.path.join(directory, f) + +DEFAULT_SOURCE_FILES = tuple(starGlob("core")) + + +# Obviously a proper parser would be better than this. + +def stripComment(code): + stringed = code.split('"') + for i in range(0, len(stringed), 2): + if "//" in stringed[i]: + stringed[i] = stringed[i].partition("//")[0] + stringed = stringed[0:i+1] + break + return re.sub('/\*.*\*/', '', '"'.join(stringed)) + +def partitionNextName(code): + code = code.strip() + split = re.search(f"[^_{ALPHANUMERIC}]", code) + if split is None: + return code + start = split.start() + return code[:start], code[start:] + +def partitionClassSig(code): + # TODO: Strip keywords here? + name, sig = partitionNextName(code) + sig = sig.strip(' {}\n\t') + typesig = sig.rpartition(':') + #typesig_first = re.search(f'[^ \t{ALPHANUMERIC}\.]', typesig[2]) + #if typesig_first and typesig[2][typesig_first.start()] == '(': + # I think round brackets in arguments should exist only in default values, so if the first strange character after the last colon is an opening round bracket, then the last colon probably means inheritance. + # Could also check for equal number of closing and opening brackets. But that could break with specific string default arguments. + # Right. Inheritance can have angular brackets from type parameters. Nvm. + # Also, this would break with function types anyway. + if typesig[2].count('(') == typesig[2].count(')'): + sig, typesig = typesig[0].strip(), typesig[2].strip() + if len(sig) > 2 and sig[0] == '(' and sig[-1] == ')': + sigargs = [] + currarg = [] + bracklevel = 0 + last_c = None + for c in sig[1:-1]: + if (not bracklevel) and c == ',': + sigargs.append(''.join(currarg).strip()) + currarg.clear() + continue + currarg.append(c) + if c in '<([': + bracklevel += 1 + elif c in ')]': + bracklevel -= 1 + elif c == '>' and last_c != '-': + # Function type arrows. + bracklevel -=1 + last_c = c + sigargs.append(''.join(currarg).strip()) + # Type params can have commas. + del currarg, bracklevel + sig = tuple((name.strip().rpartition(' ')[2], params.partition('=')[0].strip()) for arg in sigargs if arg for name, _, params in [arg.partition(':')]) + # Skip empties to handle trailing commas. + else: + sig = () + return name, sig, typesig + +class KDef: + def __init__(self, packagepath, basepath, name, sig): + self.packagepath = packagepath + self.basepath = basepath + self.name = name + self.sig = sig + def __repr__(self): + return f"{self.__class__.__name__!s}({self.basepath!r}, {self.name!r}, {self.sig!r})" + def getQualifiedPath(self): + return '.'.join(i for l in (self.packagepath, self.basepath, (self.name,)) for i in l) + + +def scrapeClassDefs(keyword='enum class ', filepaths=DEFAULT_SOURCE_FILES, *, sort=lambda d: d.getQualifiedPath()): + assert keyword in {'enum class ', 'class '} + defs = [] + for p in filepaths: + if not p.endswith('.kt'): + continue + with open(p, 'r') as file: + iscommented = False + i = 0 + def nextl(): + nonlocal iscommented + nonlocal i + l = file.readline() + i += 1 + if not l: + raise StopIteration() + l = stripComment(l) + if '*/' in l: + iscommented = False + l = l.partition('*/')[2] + if '/*' in l: + iscommented = True + l = l.partition('/*')[0] + elif iscommented: + return nextl() + return l + try: + package = None + while not package: + # Find package name. + package = nextl().partition('package ')[2].rstrip() + basepath = [] + indent = 0 + privatelevel = math.inf + setprivate = False + while True: + line = nextl() + stripped = line.lstrip() + newscope = None + if 'private class ' in line: + newscope = 'private class ' + setprivate = keyword == 'class ' + elif 'fun ' in line: + newscope = 'fun ' + setprivate = keyword == 'class ' + elif 'class ' in line: + newscope = 'class ' # Set keyword for scope's name. + elif 'object ' in line: + newscope = 'object ' + setprivate = keyword == 'class ' # Mostly broken because classes in _ScriptingConstantsClasses need each other, I think. + if newscope: + indent = (len(line)-len(stripped)) // 4 # Spaces 🙄. + basepath = basepath[:indent] # Trim higher levels. + basepath.append(partitionNextName(stripped.partition(newscope)[2])[0]) + if setprivate: + privatelevel = min(privatelevel, indent) + elif indent <= privatelevel: + privatelevel = math.inf + while line.count('(') != line.count(')'): + line += ' ' + nextl().strip() + # Horrible way of handling multi-line argument declarations. + # I mean, continuing if the current expression is invalid is also how a lot of languages do it. The issue is failure to account for comments and strings, but comments are at least already implemented elsewhere. + if privatelevel == math.inf and stripped.startswith(keyword): # Implies a newscope was also True. + name, sig, typesig = partitionClassSig(line.partition(keyword)[2]) + defs.append(KDef( + (package,), # Package name. + tuple(p for p in basepath[:indent] if p), # Clip nesting level to indent level. Companions produce empty names, so skip those. + name, + sig + )) + except StopIteration: + pass + return defs if sort is None else sorted(defs, key=sort) + +def scrapeImportStatements(filepaths=DEFAULT_SOURCE_FILES): + statements = set() + for p in filepaths: + if not p.endswith('.kt'): + continue + with open(p, 'r') as file: + while line := file.readline(): + line = stripComment(line).strip() + if line.startswith('import '): + statements.add(line) + return sorted(statements) + + +GENERATED_COMMENT_BEGIN = "// **THE BELOW CODE WAS AUTOMATICALLY GENERATED WITH A SCRIPT**" +GENERATED_COMMENT_END = "// **THE ABOVE CODE WAS AUTOMATICALLY GENERATED WITH A SCRIPT**" + +def markGeneratedComment(func): + def _markGeneratedComment(*args, **kwargs): + return GENERATED_COMMENT_BEGIN+'\n'+func(*args, **kwargs)+'\n'+GENERATED_COMMENT_END + return _markGeneratedComment + +def copyToClipboard(text): + import tkinter + tk = tkinter.Tk() + tk.withdraw() + tk.clipboard_clear() + tk.clipboard_append(text) + + +@markGeneratedComment +def genEnumImports(filepaths=DEFAULT_SOURCE_FILES): + return "\n".join(f'import {e.getQualifiedPath()}' for e in scrapeClassDefs('enum class ', filepaths)) + +@markGeneratedComment +def genEnumMaps(filepaths=DEFAULT_SOURCE_FILES): + return "\n".join(f' val {e.name} = enumToMap<{e.getQualifiedPath()}>()' for e in scrapeClassDefs('enum class ', filepaths)) + + +@markGeneratedComment +def genClassImports(filepaths=DEFAULT_SOURCE_FILES): + classes = scrapeClassDefs('class ', filepaths) + otherimports = scrapeImportStatements(filepaths) + otherimportsmapping = {s.rpartition('.')[-1]: s for s in otherimports} + basetypes = {kdef.name for kdef in classes} + neededtypes = {a[1] for kdef in classes for a in kdef.sig} + classpaths = [f'import {e.getQualifiedPath()}' for e in classes] + argpaths = [otherimportsmapping[t] for t in neededtypes if t not in basetypes and t in otherimportsmapping] + universals = {*(s for s in otherimports if s.endswith('.*')), *(''.join((*s.rpartition('.')[:2], '*')) for l in (classpaths, argpaths) for s in l)} + return "\n".join(sorted(universals)) + +factory_bins = { + '': "Rulesets", + 'com.unciv.ui': "" +} + +factory_blacklist = { + 'UncivSound', # Private constructor. + 'TileGroupMap', # Type params. + 'UncivTooltip', # Type params. + 'Simulation', # Requires Experimental opt-in. + 'TechPickerScreen' # For some reason Technology? uniquely fails to resolve as an argument type. Could try qualified path, but not worth it. +} + +@markGeneratedComment +def genClassFactories(filepaths=DEFAULT_SOURCE_FILES): + return "\n".join(f'fun {e.name}({", ".join(": ".join(a) for a in e.sig)}) = {e.getQualifiedPath()}({", ".join(f"{a[0]}={a[0]}" for a in e.sig)})' for e in scrapeClassDefs('class ', filepaths) if e.name not in factory_blacklist) diff --git a/android/assets/scripting/enginefiles/python/PythonScripting.md b/android/assets/scripting/enginefiles/python/PythonScripting.md index 188bd7cd0d321..14c94faba1698 100644 --- a/android/assets/scripting/enginefiles/python/PythonScripting.md +++ b/android/assets/scripting/enginefiles/python/PythonScripting.md @@ -412,6 +412,27 @@ for e in real(civInfo.cities[0].cityStats.currentCityStats.values): Because the elements yielded this way do not have equivalent paths in the Kotlin/JVM namespace, and are not foreign object wrappers, any complex objects will have to be assigned as token strings to a concrete path in order to do anything with them. +When using every value from a Kotlin/JVM container, iterating over its wrapper object is also likely to be slower than iterating over its resolved value. This is because iteration over wrappers is implemented on the Python side by creating a new wrapper at the next index for every item, so every use of the yielded value requires another IPC call, while evaluating the container itself means that the object being iterated over is a real container deserialized from a JSON array. + +``` +def slow(): + for name in civInfo.naturalWonders: + print("We have the natural wonder: " + name) + # Uses IPC call on every loop, as name is a foreign wrapper. + # Equivalent to accessing real(civInfo.naturalWonders[i]) on every loop. + +def fast(): + for name in real(civInfo.naturalWonders): + print("We have the natural wonder: " + name) + # Uses only one IPC call at the start of the loop, and iterates over resulting JSON array. + # Name is a real Python string. + +def alsofastish(): + for name in civInfo.naturalWonders: + print("We have a natural wonder!") + # Even though name is a foreign object wrapper here too, it's never used, so no extra IPC calls are generated. +``` + --- ## Error Handling @@ -448,10 +469,10 @@ The only major caveat to the robustness of this error handling is that it does n >>> del gameInfo.ruleSet.technologies["Sailing"] # Executes and removes "Sailing" technology from tech tree successfully. -# But the game crashes if you try to select any techs that required "Sailing", because they still have "Sailing" in their dependencies. +# But the game crashes if you try to select any techs that required "Sailing", because they still have "Sailing" in their prerequisites. >>> civInfo.cities[0].tiles.add("Crash") -# Executes and adds "Crash" string to set containing capital's tiles' coordinates. +# Executes and adds "Crash" string to the set containing your capital's tiles' coordinates. # But the game breaks when you press "Next Turn", because the JVM thread processing the turn tries to use the string "Crash" as a Vector2. ``` diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/api.py b/android/assets/scripting/enginefiles/python/unciv_lib/api.py index 1ac2b2adfffde..ea9e366401e02 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/api.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/api.py @@ -33,7 +33,7 @@ def _expose(obj): def get_keys(obj): """Get keys of object. Fail silently if it has no keys. Used to let PyAutocompleter work with ForeignObject.""" try: - return obj.keys() + return obj.keys() # FIXME: This results in actual calls over IPC since hiding the .keys() IPC protcol-based method for non-mappings in ForeignObject. except (AttributeError, ipc.ForeignError): return () diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/ProceduralTechtree.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/ProceduralTechtree.py index 5b083d0768d47..f57c6cef50430 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/ProceduralTechtree.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/ProceduralTechtree.py @@ -93,16 +93,40 @@ def _getInvalidTechs(): pass +#t=gameInfo.ruleSet.technologies['Mining']; t.row -= 1; t.column.techs.remove(t); b=gameInfo.ruleSet.technologies['Masonry']; b.column.techs.add(t); t.column=b.column + def extendTechTree(iterations=1): raise NotImplementedError() pass def clearTechTree(*, safe=True): + """Clear all items on the tech tree that haven't yet been researched by any civilizations. Pass safe=False to also clear technologies that have already been researched.""" for name in techtree().keys(): if (not safe) or not any(name in civinfo.tech.techsResearched or name in civinfo.tech.techsInProgress or name == civinfo.tech.currentTechnologyName() for civinfo in gameInfo.civilizations): del techtree()[name] def scrambleTechTree(): - raise NotImplementedError() - pass + """Randomly shuffle the order of all items on the tech tree.""" + # from unciv_scripting_examples.ProceduralTechtree import *; scrambleTechTree() + technames = [*techtree().keys()] + random.shuffle(technames) + techpositions = {n:n for n in technames} + originalpreqs = {n:real(techtree()[n].prerequisites) for n in technames} + for tname in technames: + oname = random.sample(technames, 1)[0] + tech, other = (techtree()[n] for n in (tname, oname)) + for t in (tech, other): + t.column.techs.remove(t) + tech.column, other.column = real(other.column), real(tech.column) + for t in (tech, other): + t.column.techs.add(t) + tech.row, other.row = real(other.row), real(tech.row) + techpositions[tname], techpositions[oname] = techpositions[oname], techpositions[tname] + techreplacements = {v:k for k, v in techpositions.items()} + assert len(techreplacements) == len(techpositions) + for tname in technames: + tech = techtree()[tname] + tech.prerequisites.clear() + tech.prerequisites.addAll([techreplacements[ot] for ot in originalpreqs[techpositions[tname]]]) + # toprereqs, oprereqs = (real(t.prerequisites) for t in (tech, other)) diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/UniqueInjection.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/UniqueInjection.py new file mode 100644 index 0000000000000..2b7f49b30cf75 --- /dev/null +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/UniqueInjection.py @@ -0,0 +1,6 @@ +""" +Demonstration of injecting uniques outside of the ruleset into a +""" + +# Precedent for injected unique: "Requires a [] in all cities". +# Since uniques are transient and such, probably inject into promotions. (Except that checks against the ruleset.) diff --git a/core/Module.md b/core/Module.md index 9222e73e58218..7b0c26e02cbff 100644 --- a/core/Module.md +++ b/core/Module.md @@ -153,7 +153,7 @@ Example Kotlin-side instance requested by script interpreter: SomeKotlinInstance@M3mAdDr ``` -Example response packet to send script interpreter this instance: +Example response packet to send this instance to the script interpreter: ```JSON { @@ -316,7 +316,7 @@ Some action types, data formats, and expected response types and data formats fo //Map of dispatchable signatures as strings to lists of pairs of names and types of arguments accepted by a function. //Response must be String if sent with Exception flag. //Currently just used by Python autocompleter to generate help text. - //Could also be used to control functions in scripting environment. If so, then names of types should be standardized. + //Could also be used to control function signatures in scripting environment. If so, then names of types should be standardized. ``` ``` @@ -336,7 +336,7 @@ Flags are string values for communicating extra information that doesn't need a ``` 'PassMic' - //Indicates that the sending side will now begin listening instead, and the receiving side should send the next request. + //Indicates that the sending side has no more requests to make, and that the receiving side should either send the next request or expect a response to an open request. //Sent by Kotlin side at start of script engine startup/MOTD, autocompletion, and execution to allow script to request values, and should be sent by script interpreter immediately before sending response with results of execution. ``` diff --git a/core/src/com/unciv/UncivGame.kt b/core/src/com/unciv/UncivGame.kt index a3b1a33e4e197..54424897a6c78 100644 --- a/core/src/com/unciv/UncivGame.kt +++ b/core/src/com/unciv/UncivGame.kt @@ -50,6 +50,7 @@ class UncivGame(parameters: UncivGameParameters) : Game() { var viewEntireMapForDebug = false /** For when you need to test something in an advanced game and don't have time to faff around */ var superchargedForDebug = false + var printScriptingPacketsForDebug = false /** Simulate until this turn on the first "Next turn" button press. * Does not update World View changes until finished. diff --git a/core/src/com/unciv/scripting/ScriptingBackend.kt b/core/src/com/unciv/scripting/ScriptingBackend.kt index 57d17813ae5f4..5a2711dfce213 100644 --- a/core/src/com/unciv/scripting/ScriptingBackend.kt +++ b/core/src/com/unciv/scripting/ScriptingBackend.kt @@ -4,12 +4,12 @@ import com.badlogic.gdx.Gdx import com.badlogic.gdx.files.FileHandle import com.unciv.scripting.reflection.Reflection +import com.unciv.scripting.protocol.Blackbox import com.unciv.scripting.protocol.ScriptingReplManager import com.unciv.scripting.protocol.ScriptingProtocolReplManager import com.unciv.scripting.protocol.ScriptingRawReplManager import com.unciv.scripting.protocol.SubprocessBlackbox import com.unciv.scripting.utils.ApiSpecGenerator -import com.unciv.scripting.utils.Blackbox import com.unciv.scripting.utils.SourceManager import com.unciv.scripting.utils.SyntaxHighlighter import kotlin.reflect.full.* diff --git a/core/src/com/unciv/scripting/ScriptingScope.kt b/core/src/com/unciv/scripting/ScriptingScope.kt index 47e5e767982e7..1317a58d42d55 100644 --- a/core/src/com/unciv/scripting/ScriptingScope.kt +++ b/core/src/com/unciv/scripting/ScriptingScope.kt @@ -8,9 +8,9 @@ import com.unciv.UncivGame import com.unciv.logic.GameInfo import com.unciv.logic.GameSaver import com.unciv.logic.civilization.CivilizationInfo -import com.unciv.scripting.utils.ScriptingApiEnums -import com.unciv.scripting.utils.InstanceFactories -import com.unciv.scripting.utils.InstanceRegistry +import com.unciv.scripting.api.ScriptingApiEnums +import com.unciv.scripting.api.ScriptingApiFactories +import com.unciv.scripting.api.ScriptingApiInstanceRegistry import com.unciv.ui.mapeditor.MapEditorScreen import com.unciv.ui.utils.ImageGetter import com.unciv.ui.utils.toPixmap @@ -55,14 +55,16 @@ class ScriptingScope( val apiHelpers = ApiHelpers(this) + //var modApiHelpers: ModApiHelpers? + class ApiHelpers(val scriptingScope: ScriptingScope) { // This could probably eventually include ways for scripts to create and inject their own UI elements too. Create, populate, show even popups for mods, inject buttons that execute script strings for macros. // TODO: The vast majority of these don't need scriptingScope access, and thus can be put on singletons. val isInGame: Boolean get() = (scriptingScope.civInfo != null && scriptingScope.gameInfo != null && scriptingScope.uncivGame != null) - val Factories = InstanceFactories + val Factories = ScriptingApiFactories val Enums = ScriptingApiEnums - val registeredInstances = InstanceRegistry() + val registeredInstances = ScriptingApiInstanceRegistry() //Debug/dev identity function for both Kotlin and scripts. Check if value survives serialization, force something to be added to ScriptingProtocol.instanceSaver, etc. fun unchanged(obj: Any?) = obj fun printLine(msg: Any?) = println(msg) // Different name from Kotlin's is deliberate, to abstract for scripts. @@ -100,9 +102,37 @@ class ScriptingScope( // fun applyProperties(instance: Any, properties: Map) { // } //setTimeout? - fun lambdifyScript(code: String ): () -> Unit = fun(){ scriptingScope.uncivGame!!.scriptingState.exec(code); return } // FIXME: Requires awareness of which scriptingState and which backend to use. + //fun lambdifyScript(code: String ): () -> Unit = fun(){ scriptingScope.uncivGame!!.scriptingState.exec(code); return } // FIXME: Requires awareness of which scriptingState and which backend to use. //Directly invoking the resulting lambda from a running script will almost certainly break the REPL loop/IPC protocol. } } + +// Does having one state manage multiple backends that all share the same scope really make sense? Mod handler dispatch, callbacks, etc might all be easier if the multi-backend functionality of ScriptingState were implemented only for ConsoleScreen. + + +/* +//class ModApiHelpers { + var handlerContext: NamedTuple? + // Why not just use a map? String keys will be clearer in scripts than integers anyway. + // Collection that gets replaced with any contextual parameters when running script handlers. E.G. Unit moved, city founded, tech researched, construction finished. +} + + +//class NamedTuple(val map: Map) { + //Default order-preserving implementation of Map is important. + //SortedMap? + val keys = map.keys.toList() + //Default Set implementation also preserver order, so hopefully fine. + fun contains(vararg args: Any?) { + throw UnsupportedOperationException() + //Check against values? Keys? + } + fun get() { + if (is Int) { + } + } +} +*/ + //worldScreen.bottomUnitTable.selectedCity.cityConstructions.purchaseConstruction("Missionary", -1, False, apiHelpers.Enums.Stat.statsUsableToBuy[4]) diff --git a/core/src/com/unciv/scripting/api/ScriptingApiEnums.kt b/core/src/com/unciv/scripting/api/ScriptingApiEnums.kt new file mode 100644 index 0000000000000..8e3ca2f168d31 --- /dev/null +++ b/core/src/com/unciv/scripting/api/ScriptingApiEnums.kt @@ -0,0 +1,133 @@ +package com.unciv.scripting.api + +// **THE BELOW CODE WAS AUTOMATICALLY GENERATED WITH A SCRIPT** +import com.unciv.logic.automation.ThreatLevel +import com.unciv.logic.battle.CombatAction +import com.unciv.logic.city.CityFlags +import com.unciv.logic.city.RejectionReason +import com.unciv.logic.civilization.AlertType +import com.unciv.logic.civilization.CityStatePersonality +import com.unciv.logic.civilization.CityStateType +import com.unciv.logic.civilization.CivFlags +import com.unciv.logic.civilization.PlayerType +import com.unciv.logic.civilization.Proximity +import com.unciv.logic.civilization.ReligionState +import com.unciv.logic.civilization.diplomacy.DiplomacyFlags +import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers +import com.unciv.logic.civilization.diplomacy.DiplomaticStatus +import com.unciv.logic.civilization.diplomacy.RelationshipLevel +import com.unciv.logic.map.MapSize +import com.unciv.logic.map.MapUnit.DoubleMovementTerrainTarget +import com.unciv.logic.map.RoadStatus +import com.unciv.logic.map.TileMap.AssignContinentsMode +import com.unciv.logic.map.mapgenerator.MapRegions.ImpactType +import com.unciv.logic.map.mapgenerator.RiverGenerator.RiverCoordinate.BottomRightOrLeft +import com.unciv.logic.trade.TradeType +import com.unciv.logic.trade.TradeType.TradeTypeNumberType +import com.unciv.models.Tutorial +import com.unciv.models.UnitActionType +import com.unciv.models.metadata.BaseRuleset +import com.unciv.models.metadata.GameSpeed +import com.unciv.models.metadata.LocaleCode +import com.unciv.models.ruleset.BeliefType +import com.unciv.models.ruleset.Policy.PolicyBranchType +import com.unciv.models.ruleset.QuestName +import com.unciv.models.ruleset.QuestType +import com.unciv.models.ruleset.Ruleset.RulesetErrorSeverity +import com.unciv.models.ruleset.VictoryType +import com.unciv.models.ruleset.tile.ResourceType +import com.unciv.models.ruleset.tile.TerrainType +import com.unciv.models.ruleset.unique.UniqueFlag +import com.unciv.models.ruleset.unique.UniqueParameterType +import com.unciv.models.ruleset.unique.UniqueTarget +import com.unciv.models.ruleset.unique.UniqueType +import com.unciv.models.ruleset.unique.UniqueType.UniqueComplianceErrorSeverity +import com.unciv.models.ruleset.unit.UnitLayer +import com.unciv.models.ruleset.unit.UnitMovementType +import com.unciv.models.stats.Stat +import com.unciv.scripting.ScriptingBackendType +import com.unciv.scripting.protocol.ScriptingProtocol.KnownFlag +import com.unciv.scripting.reflection.Reflection.PathElementType +import com.unciv.scripting.utils.ScriptingApiExposure +import com.unciv.ui.audio.MusicTrackChooserFlags +import com.unciv.ui.audio.MusicTrackController.State +import com.unciv.ui.civilopedia.CivilopediaCategories +import com.unciv.ui.civilopedia.FormattedLine.IconDisplay +import com.unciv.ui.civilopedia.FormattedLine.LinkType +import com.unciv.ui.consolescreen.ConsoleScreen.SetTextCursorMode +import com.unciv.ui.pickerscreens.ModManagementOptions.SortType +import com.unciv.ui.utils.UncivTooltip.TipState +import com.unciv.ui.victoryscreen.RankingType +// **THE ABOVE CODE WAS AUTOMATICALLY GENERATED WITH A SCRIPT** + + +inline fun > enumToMap() = enumValues().associateBy{ it.name } + +//Could try automatically finding all enums instead. + +/** + * For use in ScriptingScope. Allows interpreted scripts to access Unciv Enum constants. + * + * Currently exposes enum values as maps. + */ +object ScriptingApiEnums { +// **THE BELOW CODE WAS AUTOMATICALLY GENERATED WITH A SCRIPT** + val ThreatLevel = enumToMap() + val CombatAction = enumToMap() + val CityFlags = enumToMap() + val RejectionReason = enumToMap() + val AlertType = enumToMap() + val CityStatePersonality = enumToMap() + val CityStateType = enumToMap() + val CivFlags = enumToMap() + val PlayerType = enumToMap() + val Proximity = enumToMap() + val ReligionState = enumToMap() + val DiplomacyFlags = enumToMap() + val DiplomaticModifiers = enumToMap() + val DiplomaticStatus = enumToMap() + val RelationshipLevel = enumToMap() + val MapSize = enumToMap() + val DoubleMovementTerrainTarget = enumToMap() + val RoadStatus = enumToMap() + val AssignContinentsMode = enumToMap() + val ImpactType = enumToMap() + val BottomRightOrLeft = enumToMap() + val TradeType = enumToMap() + val TradeTypeNumberType = enumToMap() + val Tutorial = enumToMap() + val UnitActionType = enumToMap() + val BaseRuleset = enumToMap() + val GameSpeed = enumToMap() + val LocaleCode = enumToMap() + val BeliefType = enumToMap() + val PolicyBranchType = enumToMap() + val QuestName = enumToMap() + val QuestType = enumToMap() + val RulesetErrorSeverity = enumToMap() + val VictoryType = enumToMap() + val ResourceType = enumToMap() + val TerrainType = enumToMap() + val UniqueFlag = enumToMap() + val UniqueParameterType = enumToMap() + val UniqueTarget = enumToMap() + val UniqueType = enumToMap() + val UniqueComplianceErrorSeverity = enumToMap() + val UnitLayer = enumToMap() + val UnitMovementType = enumToMap() + val Stat = enumToMap() + val ScriptingBackendType = enumToMap() + val KnownFlag = enumToMap() + val PathElementType = enumToMap() + val ScriptingApiExposure = enumToMap() + val MusicTrackChooserFlags = enumToMap() + val State = enumToMap() + val CivilopediaCategories = enumToMap() + val IconDisplay = enumToMap() + val LinkType = enumToMap() + val SetTextCursorMode = enumToMap() + val SortType = enumToMap() + val TipState = enumToMap() + val RankingType = enumToMap() +// **THE ABOVE CODE WAS AUTOMATICALLY GENERATED WITH A SCRIPT** +} diff --git a/core/src/com/unciv/scripting/api/ScriptingApiFactories.kt b/core/src/com/unciv/scripting/api/ScriptingApiFactories.kt new file mode 100644 index 0000000000000..bcdd418c8dcfc --- /dev/null +++ b/core/src/com/unciv/scripting/api/ScriptingApiFactories.kt @@ -0,0 +1,302 @@ +package com.unciv.scripting.api + +//import com.unciv.MainMenuScreen +//import com.unciv.ui.mapeditor.MapEditorScreen +//import com.unciv.logic.map.TileMap +import com.badlogic.gdx.math.Vector2 + + +// **THE BELOW CODE WAS AUTOMATICALLY GENERATED WITH A SCRIPT** +import com.badlogic.gdx.graphics.* +import com.badlogic.gdx.scenes.scene2d.* +import com.badlogic.gdx.scenes.scene2d.ui.* +import com.unciv.* +import com.unciv.logic.* +import com.unciv.logic.automation.* +import com.unciv.logic.battle.* +import com.unciv.logic.city.* +import com.unciv.logic.civilization.* +import com.unciv.logic.civilization.RuinsManager.* +import com.unciv.logic.civilization.diplomacy.* +import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers.* +import com.unciv.logic.map.* +import com.unciv.logic.map.mapgenerator.* +import com.unciv.logic.trade.* +import com.unciv.logic.trade.TradeType.* +import com.unciv.models.* +import com.unciv.models.metadata.* +import com.unciv.models.ruleset.* +import com.unciv.models.ruleset.tech.* +import com.unciv.models.ruleset.tile.* +import com.unciv.models.ruleset.unique.* +import com.unciv.models.ruleset.unique.UniqueType.* +import com.unciv.models.ruleset.unit.* +import com.unciv.models.simulation.* +import com.unciv.models.tilesets.* +import com.unciv.models.translations.* +import com.unciv.scripting.* +import com.unciv.scripting.ScriptingScope.* +import com.unciv.scripting.api.* +import com.unciv.scripting.protocol.* +import com.unciv.ui.* +import com.unciv.ui.audio.* +import com.unciv.ui.cityscreen.* +import com.unciv.ui.civilopedia.* +import com.unciv.ui.consolescreen.* +import com.unciv.ui.map.* +import com.unciv.ui.mapeditor.* +import com.unciv.ui.mapeditor.MapEditorMenuPopup.* +import com.unciv.ui.newgamescreen.* +import com.unciv.ui.overviewscreen.* +import com.unciv.ui.pickerscreens.* +import com.unciv.ui.saves.* +import com.unciv.ui.tilegroups.* +import com.unciv.ui.trade.* +import com.unciv.ui.tutorials.* +import com.unciv.ui.utils.* +import com.unciv.ui.victoryscreen.* +import com.unciv.ui.worldscreen.* +import com.unciv.ui.worldscreen.WorldMapHolder.* +import com.unciv.ui.worldscreen.bottombar.* +import com.unciv.ui.worldscreen.mainmenu.* +import com.unciv.ui.worldscreen.unit.* +import java.io.* +import java.util.* +import kotlin.math.* +import kotlin.reflect.full.* +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* +// **THE ABOVE CODE WAS AUTOMATICALLY GENERATED WITH A SCRIPT** + + +// Honestly, see if you can just instantiate them by string/name instead. +// Class.forName("com.unciv.etc").newInstance(), or Class.forName("com.unciv.etc").kotlin, or something. + +/** + * For use in ScriptingScope. Allows interpreted scripts to make new instances of Kotlin/JVM classes. + */ +object ScriptingApiFactories { + //This, and possible ApiHelpers itself, need better nested namespaces. + val Game = object { + } + val Math = object { + } + val Rulesets = object { + } + val Kotlin = object { + } + val Gui = object { + fun MainMenuScreen() = com.unciv.MainMenuScreen() + fun MapEditorScreen(map: TileMap) = com.unciv.ui.mapeditor.MapEditorScreen(map) + } + //Class.forName("java.lang.String").kotlin.primaryConstructor.call + fun test(qualName: String) = Class.forName(qualName) + fun test2(qualName: String) = Class.forName(qualName).kotlin //Not accessible. Probably extension/compiler-made? + fun instanceFromQualname(qualName: String, args: List) = Class.forName(qualName).kotlin.primaryConstructor!!.call(*args.toTypedArray()) + // TODO: Use generalized InstanceMethodDispatcher to find right callable in KClass.constructors if no primary constructor. + + // apiHelpers.Factories.instanceFromQualname('java.lang.String', []) + // apiHelpers.Factories.instanceFromQualname('com.unciv.logic.map.MapUnit', []) + // apiHelpers.Factories.instanceFromQualname('com.unciv.logic.city.CityStats', [civInfo.cities[0]]) + // Fails: apiHelpers.Factories.instanceFromQualname('com.badlogic.gdx.math.Vector2', [1.5, 2.5]) + + // Refer: https://stackoverflow.com/questions/40672880/creating-a-new-instance-of-a-kclass + + // See https://stackoverflow.com/questions/59936471/kotlin-reflect-package-and-get-all-classes. Build structured map of all classes? + + // apiHelpers.registeredInstances['x'] = apiHelpers.Factories.test2('com.badlogic.gdx.math.Vector2') + // apiHelpers.registeredInstances['x'].constructors[1].call(apiHelpers.Factories.arrayOf([1, 2])) + // apiHelpers.registeredInstances['y'] = apiHelpers.Factories.test('com.badlogic.gdx.math.Vector2') + fun arrayOf(elements: Collection): Array<*> = elements.toTypedArray() + fun arrayOfAny(elements: Collection): Array = elements.toTypedArray() + fun arrayOfString(elements: Collection): Array = elements.toTypedArray() + fun Vector2(x: Number, y: Number) = com.badlogic.gdx.math.Vector2(x.toFloat(), y.toFloat()) +// fun MapUnit() = "NotImplemented" +// fun Technology() = "NotImplemented" + +// **THE BELOW CODE WAS AUTOMATICALLY GENERATED WITH A SCRIPT** +fun JsonParser() = com.unciv.JsonParser() +fun MainMenuScreen() = com.unciv.MainMenuScreen() +fun UncivGame(parameters: UncivGameParameters) = com.unciv.UncivGame(parameters=parameters) +fun UncivGameParameters(version: String, crashReportSender: CrashReportSender?, cancelDiscordEvent: (() -> Unit)?, fontImplementation: NativeFontImplementation?, consoleMode: Boolean, customSaveLocationHelper: CustomSaveLocationHelper?, limitOrientationsHelper: LimitOrientationsHelper?) = com.unciv.UncivGameParameters(version=version, crashReportSender=crashReportSender, cancelDiscordEvent=cancelDiscordEvent, fontImplementation=fontImplementation, consoleMode=consoleMode, customSaveLocationHelper=customSaveLocationHelper, limitOrientationsHelper=limitOrientationsHelper) +fun BarbarianManager() = com.unciv.logic.BarbarianManager() +fun GameInfo() = com.unciv.logic.GameInfo() +fun UncivShowableException(missingMods: String) = com.unciv.logic.UncivShowableException(missingMods=missingMods) +fun BarbarianAutomation(civInfo: CivilizationInfo) = com.unciv.logic.automation.BarbarianAutomation(civInfo=civInfo) +fun ConstructionAutomation(cityConstructions: CityConstructions) = com.unciv.logic.automation.ConstructionAutomation(cityConstructions=cityConstructions) +fun BattleDamageModifier(vs: String, modificationAmount: Float) = com.unciv.logic.battle.BattleDamageModifier(vs=vs, modificationAmount=modificationAmount) +fun CityCombatant(city: CityInfo) = com.unciv.logic.battle.CityCombatant(city=city) +fun MapUnitCombatant(unit: MapUnit) = com.unciv.logic.battle.MapUnitCombatant(unit=unit) +fun CityConstructions() = com.unciv.logic.city.CityConstructions() +fun CityExpansionManager() = com.unciv.logic.city.CityExpansionManager() +fun CityInfo() = com.unciv.logic.city.CityInfo() +fun CityInfoConquestFunctions(city: CityInfo) = com.unciv.logic.city.CityInfoConquestFunctions(city=city) +fun CityInfoReligionManager() = com.unciv.logic.city.CityInfoReligionManager() +fun CityStats(cityInfo: CityInfo) = com.unciv.logic.city.CityStats(cityInfo=cityInfo) +fun PopulationManager() = com.unciv.logic.city.PopulationManager() +fun CapitalConnectionsFinder(civInfo: CivilizationInfo) = com.unciv.logic.civilization.CapitalConnectionsFinder(civInfo=civInfo) +fun CityStateFunctions(civInfo: CivilizationInfo) = com.unciv.logic.civilization.CityStateFunctions(civInfo=civInfo) +fun CivConstructions() = com.unciv.logic.civilization.CivConstructions() +fun CivInfoStats(civInfo: CivilizationInfo) = com.unciv.logic.civilization.CivInfoStats(civInfo=civInfo) +fun CivInfoTransientUpdater(civInfo: CivilizationInfo) = com.unciv.logic.civilization.CivInfoTransientUpdater(civInfo=civInfo) +fun CivilizationInfo() = com.unciv.logic.civilization.CivilizationInfo() +fun GoldenAgeManager() = com.unciv.logic.civilization.GoldenAgeManager() +fun GreatPersonManager() = com.unciv.logic.civilization.GreatPersonManager() +fun PolicyManager() = com.unciv.logic.civilization.PolicyManager() +fun PopupAlert() = com.unciv.logic.civilization.PopupAlert() +fun QuestManager() = com.unciv.logic.civilization.QuestManager() +fun ReligionManager() = com.unciv.logic.civilization.ReligionManager() +fun RuinsManager() = com.unciv.logic.civilization.RuinsManager.RuinsManager() +fun TechManager() = com.unciv.logic.civilization.TechManager() +fun VictoryManager() = com.unciv.logic.civilization.VictoryManager() +fun DiplomacyManager() = com.unciv.logic.civilization.diplomacy.DiplomacyManager() +fun BFS(startingPoint: TileInfo, predicate: (TileInfo) -> Boolean) = com.unciv.logic.map.BFS(startingPoint=startingPoint, predicate=predicate) +fun MapSizeNew() = com.unciv.logic.map.MapSizeNew() +fun MapUnit() = com.unciv.logic.map.MapUnit() +fun TileMap() = com.unciv.logic.map.TileMap() +fun UnitMovementAlgorithms(unit: MapUnit) = com.unciv.logic.map.UnitMovementAlgorithms(unit=unit) +fun UnitPromotions() = com.unciv.logic.map.UnitPromotions() +fun MapGenerator(ruleset: Ruleset) = com.unciv.logic.map.mapgenerator.MapGenerator(ruleset=ruleset) +fun MapLandmassGenerator(ruleset: Ruleset, randomness: MapGenerationRandomness) = com.unciv.logic.map.mapgenerator.MapLandmassGenerator(ruleset=ruleset, randomness=randomness) +fun MapRegions(ruleset: Ruleset) = com.unciv.logic.map.mapgenerator.MapRegions(ruleset=ruleset) +fun NaturalWonderGenerator(ruleset: Ruleset, randomness: MapGenerationRandomness) = com.unciv.logic.map.mapgenerator.NaturalWonderGenerator(ruleset=ruleset, randomness=randomness) +fun RiverGenerator(tileMap: TileMap, randomness: MapGenerationRandomness) = com.unciv.logic.map.mapgenerator.RiverGenerator(tileMap=tileMap, randomness=randomness) +fun Trade() = com.unciv.logic.trade.Trade() +fun TradeEvaluation() = com.unciv.logic.trade.TradeEvaluation() +fun TradeLogic(ourCivilization: CivilizationInfo, otherCivilization: CivilizationInfo) = com.unciv.logic.trade.TradeLogic(ourCivilization=ourCivilization, otherCivilization=otherCivilization) +fun TradeOffersList() = com.unciv.logic.trade.TradeOffersList() +fun AttackableTile(tileToAttackFrom: TileInfo, tileToAttack: TileInfo, movementLeftAfterMovingToAttackTile: Float) = com.unciv.models.AttackableTile(tileToAttackFrom=tileToAttackFrom, tileToAttack=tileToAttack, movementLeftAfterMovingToAttackTile=movementLeftAfterMovingToAttackTile) +fun Religion() = com.unciv.models.Religion() +fun GameParameters() = com.unciv.models.metadata.GameParameters() +fun GameSettings() = com.unciv.models.metadata.GameSettings() +fun GameSetupInfo(gameParameters: GameParameters, mapParameters: MapParameters) = com.unciv.models.metadata.GameSetupInfo(gameParameters=gameParameters, mapParameters=mapParameters) +fun Player(chosenCiv: String) = com.unciv.models.metadata.Player(chosenCiv=chosenCiv) +fun Belief() = com.unciv.models.ruleset.Belief() +fun Building() = com.unciv.models.ruleset.Building() +fun Difficulty() = com.unciv.models.ruleset.Difficulty() +fun Era() = com.unciv.models.ruleset.Era() +fun Nation() = com.unciv.models.ruleset.Nation() +fun PolicyBranch() = com.unciv.models.ruleset.PolicyBranch() +fun Quest() = com.unciv.models.ruleset.Quest() +fun RuinReward() = com.unciv.models.ruleset.RuinReward() +fun TechColumn() = com.unciv.models.ruleset.tech.TechColumn() +fun Technology() = com.unciv.models.ruleset.tech.Technology() +fun Terrain() = com.unciv.models.ruleset.tile.Terrain() +fun TileImprovement() = com.unciv.models.ruleset.tile.TileImprovement() +fun TileResource() = com.unciv.models.ruleset.tile.TileResource() +fun Unique(text: String, sourceObjectType: UniqueTarget?, sourceObjectName: String?) = com.unciv.models.ruleset.unique.Unique(text=text, sourceObjectType=sourceObjectType, sourceObjectName=sourceObjectName) +fun BaseUnit() = com.unciv.models.ruleset.unit.BaseUnit() +fun Promotion() = com.unciv.models.ruleset.unit.Promotion() +fun UnitType() = com.unciv.models.ruleset.unit.UnitType() +fun MutableInt(value: Int) = com.unciv.models.simulation.MutableInt(value=value) +fun SimulationStep(gameInfo: GameInfo) = com.unciv.models.simulation.SimulationStep(gameInfo=gameInfo) +fun TileSetConfig() = com.unciv.models.tilesets.TileSetConfig() +fun TranslationEntry(entry: String) = com.unciv.models.translations.TranslationEntry(entry=entry) +fun Translations() = com.unciv.models.translations.Translations() +fun ScriptingScope(civInfo: CivilizationInfo?, gameInfo: GameInfo?, uncivGame: UncivGame?, worldScreen: WorldScreen?, mapEditorScreen: MapEditorScreen?) = com.unciv.scripting.ScriptingScope(civInfo=civInfo, gameInfo=gameInfo, uncivGame=uncivGame, worldScreen=worldScreen, mapEditorScreen=mapEditorScreen) +fun ApiHelpers(scriptingScope: ScriptingScope) = com.unciv.scripting.ScriptingScope.ApiHelpers(scriptingScope=scriptingScope) +fun ScriptingApiInstanceRegistry() = com.unciv.scripting.api.ScriptingApiInstanceRegistry() +fun ScriptingRawReplManager(scriptingScope: ScriptingScope, blackbox: Blackbox) = com.unciv.scripting.protocol.ScriptingRawReplManager(scriptingScope=scriptingScope, blackbox=blackbox) +fun SubprocessBlackbox(processCmd: Array) = com.unciv.scripting.protocol.SubprocessBlackbox(processCmd=processCmd) +fun AddMultiplayerGameScreen(backScreen: MultiplayerScreen) = com.unciv.ui.AddMultiplayerGameScreen(backScreen=backScreen) +fun EditMultiplayerGameInfoScreen(gameInfo: GameInfoPreview?, gameName: String, backScreen: MultiplayerScreen) = com.unciv.ui.EditMultiplayerGameInfoScreen(gameInfo=gameInfo, gameName=gameName, backScreen=backScreen) +fun LanguagePickerScreen() = com.unciv.ui.LanguagePickerScreen() +fun MultiplayerScreen(previousScreen: BaseScreen) = com.unciv.ui.MultiplayerScreen(previousScreen=previousScreen) +fun MusicController() = com.unciv.ui.audio.MusicController() +fun MusicTrackController(volume: Float) = com.unciv.ui.audio.MusicTrackController(volume=volume) +fun CityConstructionsTable(cityScreen: CityScreen) = com.unciv.ui.cityscreen.CityConstructionsTable(cityScreen=cityScreen) +fun CityInfoTable(cityScreen: CityScreen) = com.unciv.ui.cityscreen.CityInfoTable(cityScreen=cityScreen) +fun CityReligionInfoTable(religionManager: CityInfoReligionManager, showMajority: Boolean) = com.unciv.ui.cityscreen.CityReligionInfoTable(religionManager=religionManager, showMajority=showMajority) +fun CityScreen(city: CityInfo, selectedConstruction: IConstruction?, selectedTile: TileInfo?) = com.unciv.ui.cityscreen.CityScreen(city=city, selectedConstruction=selectedConstruction, selectedTile=selectedTile) +fun CityScreenCityPickerTable(cityScreen: CityScreen) = com.unciv.ui.cityscreen.CityScreenCityPickerTable(cityScreen=cityScreen) +fun CityScreenTileTable(cityScreen: CityScreen) = com.unciv.ui.cityscreen.CityScreenTileTable(cityScreen=cityScreen) +fun CityStatsTable(cityScreen: CityScreen) = com.unciv.ui.cityscreen.CityStatsTable(cityScreen=cityScreen) +fun CityTileGroup(city: CityInfo, tileInfo: TileInfo, tileSetStrings: TileSetStrings) = com.unciv.ui.cityscreen.CityTileGroup(city=city, tileInfo=tileInfo, tileSetStrings=tileSetStrings) +fun ConstructionInfoTable(cityScreen: CityScreen) = com.unciv.ui.cityscreen.ConstructionInfoTable(cityScreen=cityScreen) +fun SpecialistAllocationTable(cityScreen: CityScreen) = com.unciv.ui.cityscreen.SpecialistAllocationTable(cityScreen=cityScreen) +fun YieldGroup() = com.unciv.ui.cityscreen.YieldGroup() +fun CivilopediaScreen(ruleset: Ruleset, previousScreen: BaseScreen, category: CivilopediaCategories, link: String) = com.unciv.ui.civilopedia.CivilopediaScreen(ruleset=ruleset, previousScreen=previousScreen, category=category, link=link) +fun FormattedLine(text: String, link: String, icon: String, extraImage: String, imageSize: Float, size: Int, header: Int, indent: Int, padding: Float, color: String, separator: Boolean, starred: Boolean, centered: Boolean, iconCrossed: Boolean) = com.unciv.ui.civilopedia.FormattedLine(text=text, link=link, icon=icon, extraImage=extraImage, imageSize=imageSize, size=size, header=header, indent=indent, padding=padding, color=color, separator=separator, starred=starred, centered=centered, iconCrossed=iconCrossed) +fun ConsoleScreen(scriptingState: ScriptingState, closeAction: () -> Unit) = com.unciv.ui.consolescreen.ConsoleScreen(scriptingState=scriptingState, closeAction=closeAction) +fun EditorMapHolder(mapEditorScreen: MapEditorScreen, tileMap: TileMap) = com.unciv.ui.mapeditor.EditorMapHolder(mapEditorScreen=mapEditorScreen, tileMap=tileMap) +fun GameParametersScreen(mapEditorScreen: MapEditorScreen) = com.unciv.ui.mapeditor.GameParametersScreen(mapEditorScreen=mapEditorScreen) +fun MapEditorMenuPopup(mapEditorScreen: MapEditorScreen) = com.unciv.ui.mapeditor.MapEditorMenuPopup(mapEditorScreen=mapEditorScreen) +fun MapEditorRulesetPopup(mapEditorScreen: MapEditorScreen) = com.unciv.ui.mapeditor.MapEditorMenuPopup.MapEditorRulesetPopup(mapEditorScreen=mapEditorScreen) +fun MapEditorOptionsTable(mapEditorScreen: MapEditorScreen) = com.unciv.ui.mapeditor.MapEditorOptionsTable(mapEditorScreen=mapEditorScreen) +fun MapEditorScreen() = com.unciv.ui.mapeditor.MapEditorScreen() +fun NewMapScreen(mapParameters: MapParameters) = com.unciv.ui.mapeditor.NewMapScreen(mapParameters=mapParameters) +fun SaveAndLoadMapScreen(mapToSave: TileMap?, save: Boolean, previousScreen: BaseScreen) = com.unciv.ui.mapeditor.SaveAndLoadMapScreen(mapToSave=mapToSave, save=save, previousScreen=previousScreen) +fun GameOptionsTable(previousScreen: IPreviousScreen, isPortrait: Boolean, updatePlayerPickerTable: (desiredCiv:String)->Unit) = com.unciv.ui.newgamescreen.GameOptionsTable(previousScreen=previousScreen, isPortrait=isPortrait, updatePlayerPickerTable=updatePlayerPickerTable) +fun MapOptionsTable(newGameScreen: NewGameScreen) = com.unciv.ui.newgamescreen.MapOptionsTable(newGameScreen=newGameScreen) +fun MapParametersTable(mapParameters: MapParameters, isEmptyMapAllowed: Boolean) = com.unciv.ui.newgamescreen.MapParametersTable(mapParameters=mapParameters, isEmptyMapAllowed=isEmptyMapAllowed) +fun ModCheckboxTable(mods: LinkedHashSet, baseRuleset: String, screen: BaseScreen, isPortrait: Boolean, onUpdate: (String) -> Unit) = com.unciv.ui.newgamescreen.ModCheckboxTable(mods=mods, baseRuleset=baseRuleset, screen=screen, isPortrait=isPortrait, onUpdate=onUpdate) +fun NationTable(nation: Nation, width: Float, minHeight: Float, ruleset: Ruleset?) = com.unciv.ui.newgamescreen.NationTable(nation=nation, width=width, minHeight=minHeight, ruleset=ruleset) +fun NewGameScreen(previousScreen: BaseScreen, _gameSetupInfo: GameSetupInfo?) = com.unciv.ui.newgamescreen.NewGameScreen(previousScreen=previousScreen, _gameSetupInfo=_gameSetupInfo) +fun PlayerPickerTable(previousScreen: IPreviousScreen, gameParameters: GameParameters, blockWidth: Float) = com.unciv.ui.newgamescreen.PlayerPickerTable(previousScreen=previousScreen, gameParameters=gameParameters, blockWidth=blockWidth) +fun CityOverviewTable(viewingPlayer: CivilizationInfo, overviewScreen: EmpireOverviewScreen) = com.unciv.ui.overviewscreen.CityOverviewTable(viewingPlayer=viewingPlayer, overviewScreen=overviewScreen) +fun DiplomacyOverviewTable(viewingPlayer: CivilizationInfo, overviewScreen: EmpireOverviewScreen) = com.unciv.ui.overviewscreen.DiplomacyOverviewTable(viewingPlayer=viewingPlayer, overviewScreen=overviewScreen) +fun EmpireOverviewScreen(viewingPlayer: CivilizationInfo, defaultPage: String) = com.unciv.ui.overviewscreen.EmpireOverviewScreen(viewingPlayer=viewingPlayer, defaultPage=defaultPage) +fun ReligionOverviewTable(viewingPlayer: CivilizationInfo, overviewScreen: EmpireOverviewScreen) = com.unciv.ui.overviewscreen.ReligionOverviewTable(viewingPlayer=viewingPlayer, overviewScreen=overviewScreen) +fun ResourcesOverviewTable(viewingPlayer: CivilizationInfo, overviewScreen: EmpireOverviewScreen) = com.unciv.ui.overviewscreen.ResourcesOverviewTable(viewingPlayer=viewingPlayer, overviewScreen=overviewScreen) +fun StatsOverviewTable(viewingPlayer: CivilizationInfo, overviewScreen: EmpireOverviewScreen) = com.unciv.ui.overviewscreen.StatsOverviewTable(viewingPlayer=viewingPlayer, overviewScreen=overviewScreen) +fun TradesOverviewTable(viewingPlayer: CivilizationInfo, overviewScreen: EmpireOverviewScreen) = com.unciv.ui.overviewscreen.TradesOverviewTable(viewingPlayer=viewingPlayer, overviewScreen=overviewScreen) +fun UnitOverviewTable(viewingPlayer: CivilizationInfo, overviewScreen: EmpireOverviewScreen) = com.unciv.ui.overviewscreen.UnitOverviewTable(viewingPlayer=viewingPlayer, overviewScreen=overviewScreen) +fun WonderOverviewTable(viewingPlayer: CivilizationInfo, overviewScreen: EmpireOverviewScreen) = com.unciv.ui.overviewscreen.WonderOverviewTable(viewingPlayer=viewingPlayer, overviewScreen=overviewScreen) +fun DiplomaticVotePickerScreen(votingCiv: CivilizationInfo) = com.unciv.ui.pickerscreens.DiplomaticVotePickerScreen(votingCiv=votingCiv) +fun DiplomaticVoteResultScreen(votesCast: HashMap, viewingCiv: CivilizationInfo) = com.unciv.ui.pickerscreens.DiplomaticVoteResultScreen(votesCast=votesCast, viewingCiv=viewingCiv) +fun GreatPersonPickerScreen(civInfo: CivilizationInfo) = com.unciv.ui.pickerscreens.GreatPersonPickerScreen(civInfo=civInfo) +fun ImprovementPickerScreen(tileInfo: TileInfo, unit: MapUnit, onAccept: ()->Unit) = com.unciv.ui.pickerscreens.ImprovementPickerScreen(tileInfo=tileInfo, unit=unit, onAccept=onAccept) +fun ModManagementOptions(modManagementScreen: ModManagementScreen) = com.unciv.ui.pickerscreens.ModManagementOptions(modManagementScreen=modManagementScreen) +fun ModManagementScreen(previousInstalledMods: HashMap?, previousOnlineMods: HashMap?) = com.unciv.ui.pickerscreens.ModManagementScreen(previousInstalledMods=previousInstalledMods, previousOnlineMods=previousOnlineMods) +fun PantheonPickerScreen(choosingCiv: CivilizationInfo, gameInfo: GameInfo) = com.unciv.ui.pickerscreens.PantheonPickerScreen(choosingCiv=choosingCiv, gameInfo=gameInfo) +fun PolicyPickerScreen(worldScreen: WorldScreen, civInfo: CivilizationInfo) = com.unciv.ui.pickerscreens.PolicyPickerScreen(worldScreen=worldScreen, civInfo=civInfo) +fun PromotionPickerScreen(unit: MapUnit) = com.unciv.ui.pickerscreens.PromotionPickerScreen(unit=unit) +fun ReligiousBeliefsPickerScreen(choosingCiv: CivilizationInfo, gameInfo: GameInfo, beliefsToChoose: Counter, pickIconAndName: Boolean) = com.unciv.ui.pickerscreens.ReligiousBeliefsPickerScreen(choosingCiv=choosingCiv, gameInfo=gameInfo, beliefsToChoose=beliefsToChoose, pickIconAndName=pickIconAndName) +fun TechButton(techName: String, techManager: TechManager, isWorldScreen: Boolean) = com.unciv.ui.pickerscreens.TechButton(techName=techName, techManager=techManager, isWorldScreen=isWorldScreen) +fun LoadGameScreen(previousScreen: BaseScreen) = com.unciv.ui.saves.LoadGameScreen(previousScreen=previousScreen) +fun SaveGameScreen(gameInfo: GameInfo) = com.unciv.ui.saves.SaveGameScreen(gameInfo=gameInfo) +fun CityButton(city: CityInfo, tileGroup: WorldTileGroup) = com.unciv.ui.tilegroups.CityButton(city=city, tileGroup=tileGroup) +fun TileGroupIcons(tileGroup: TileGroup) = com.unciv.ui.tilegroups.TileGroupIcons(tileGroup=tileGroup) +fun TileSetStrings() = com.unciv.ui.tilegroups.TileSetStrings() +fun WorldTileGroup(worldScreen: WorldScreen, tileInfo: TileInfo, tileSetStrings: TileSetStrings) = com.unciv.ui.tilegroups.WorldTileGroup(worldScreen=worldScreen, tileInfo=tileInfo, tileSetStrings=tileSetStrings) +fun DiplomacyScreen(viewingCiv: CivilizationInfo) = com.unciv.ui.trade.DiplomacyScreen(viewingCiv=viewingCiv) +fun LeaderIntroTable(civInfo: CivilizationInfo, hello: String) = com.unciv.ui.trade.LeaderIntroTable(civInfo=civInfo, hello=hello) +fun OfferColumnsTable(tradeLogic: TradeLogic, screen: DiplomacyScreen, onChange: () -> Unit) = com.unciv.ui.trade.OfferColumnsTable(tradeLogic=tradeLogic, screen=screen, onChange=onChange) +fun OffersListScroll(persistenceID: String, onOfferClicked: (TradeOffer) -> Unit) = com.unciv.ui.trade.OffersListScroll(persistenceID=persistenceID, onOfferClicked=onOfferClicked) +fun TradeTable(otherCivilization: CivilizationInfo, stage: DiplomacyScreen) = com.unciv.ui.trade.TradeTable(otherCivilization=otherCivilization, stage=stage) +fun TutorialController(screen: BaseScreen) = com.unciv.ui.tutorials.TutorialController(screen=screen) +fun TutorialRender(screen: BaseScreen) = com.unciv.ui.tutorials.TutorialRender(screen=screen) +fun AskNumberPopup(screen: BaseScreen, label: String, icon: IconCircleGroup, defaultText: String, amountButtons: List, bounds: IntRange, errorText: String, validate: (input: Int) -> Boolean, actionOnOk: (input: Int) -> Unit) = com.unciv.ui.utils.AskNumberPopup(screen=screen, label=label, icon=icon, defaultText=defaultText, amountButtons=amountButtons, bounds=bounds, errorText=errorText, validate=validate, actionOnOk=actionOnOk) +fun AskTextPopup(screen: BaseScreen, label: String, icon: IconCircleGroup, defaultText: String, errorText: String, maxLength: Int, validate: (input: String) -> Boolean, actionOnOk: (input: String) -> Unit) = com.unciv.ui.utils.AskTextPopup(screen=screen, label=label, icon=icon, defaultText=defaultText, errorText=errorText, maxLength=maxLength, validate=validate, actionOnOk=actionOnOk) +fun ExitGamePopup(screen: BaseScreen, force: Boolean) = com.unciv.ui.utils.ExitGamePopup(screen=screen, force=force) +fun ExpanderTab(title: String, fontSize: Int, icon: Actor?, startsOutOpened: Boolean, defaultPad: Float, headerPad: Float, expanderWidth: Float, persistenceID: String?, onChange: (() -> Unit)?, initContent: ((Table) -> Unit)?) = com.unciv.ui.utils.ExpanderTab(title=title, fontSize=fontSize, icon=icon, startsOutOpened=startsOutOpened, defaultPad=defaultPad, headerPad=headerPad, expanderWidth=expanderWidth, persistenceID=persistenceID, onChange=onChange, initContent=initContent) +fun IconCircleGroup(size: Float, actor: Actor, resizeActor: Boolean, color: Color) = com.unciv.ui.utils.IconCircleGroup(size=size, actor=actor, resizeActor=resizeActor, color=color) +fun TabbedPager(minimumWidth: Float, maximumWidth: Float, minimumHeight: Float, maximumHeight: Float, headerFontSize: Int, headerFontColor: Color, highlightColor: Color, backgroundColor: Color, headerPadding: Float, capacity: Int) = com.unciv.ui.utils.TabbedPager(minimumWidth=minimumWidth, maximumWidth=maximumWidth, minimumHeight=minimumHeight, maximumHeight=maximumHeight, headerFontSize=headerFontSize, headerFontColor=headerFontColor, highlightColor=highlightColor, backgroundColor=backgroundColor, headerPadding=headerPadding, capacity=capacity) +fun ToastPopup(message: String, screen: BaseScreen, time: Long) = com.unciv.ui.utils.ToastPopup(message=message, screen=screen, time=time) +fun UncivSlider(min: Float, max: Float, step: Float, vertical: Boolean, plusMinus: Boolean, initial: Float, sound: UncivSound, getTipText: ((Float) -> String)?, onChange: ((Float) -> Unit)?) = com.unciv.ui.utils.UncivSlider(min=min, max=max, step=step, vertical=vertical, plusMinus=plusMinus, initial=initial, sound=sound, getTipText=getTipText, onChange=onChange) +fun UnitGroup(unit: MapUnit, size: Float) = com.unciv.ui.utils.UnitGroup(unit=unit, size=size) +fun WrappableLabel(text: String, expectedWidth: Float, fontColor: Color, fontSize: Int) = com.unciv.ui.utils.WrappableLabel(text=text, expectedWidth=expectedWidth, fontColor=fontColor, fontSize=fontSize) +fun VictoryScreen(worldScreen: WorldScreen) = com.unciv.ui.victoryscreen.VictoryScreen(worldScreen=worldScreen) +fun AlertPopup(worldScreen: WorldScreen, popupAlert: PopupAlert) = com.unciv.ui.worldscreen.AlertPopup(worldScreen=worldScreen, popupAlert=popupAlert) +fun Minimap(mapHolder: WorldMapHolder, minimapSize: Int) = com.unciv.ui.worldscreen.Minimap(mapHolder=mapHolder, minimapSize=minimapSize) +fun NotificationsScroll(worldScreen: WorldScreen) = com.unciv.ui.worldscreen.NotificationsScroll(worldScreen=worldScreen) +fun PlayerReadyScreen(gameInfo: GameInfo, currentPlayerCiv: CivilizationInfo) = com.unciv.ui.worldscreen.PlayerReadyScreen(gameInfo=gameInfo, currentPlayerCiv=currentPlayerCiv) +fun TradePopup(worldScreen: WorldScreen) = com.unciv.ui.worldscreen.TradePopup(worldScreen=worldScreen) +fun WorldMapHolder(worldScreen: WorldScreen, tileMap: TileMap) = com.unciv.ui.worldscreen.WorldMapHolder(worldScreen=worldScreen, tileMap=tileMap) +fun MoveHereButtonDto(unitToTurnsToDestination: HashMap, tileInfo: TileInfo) = com.unciv.ui.worldscreen.WorldMapHolder.MoveHereButtonDto(unitToTurnsToDestination=unitToTurnsToDestination, tileInfo=tileInfo) +fun SwapWithButtonDto(unit: MapUnit, tileInfo: TileInfo) = com.unciv.ui.worldscreen.WorldMapHolder.SwapWithButtonDto(unit=unit, tileInfo=tileInfo) +fun WorldScreen(gameInfo: GameInfo, viewingCiv: CivilizationInfo) = com.unciv.ui.worldscreen.WorldScreen(gameInfo=gameInfo, viewingCiv=viewingCiv) +fun WorldScreenTopBar(worldScreen: WorldScreen) = com.unciv.ui.worldscreen.WorldScreenTopBar(worldScreen=worldScreen) +fun BattleTable(worldScreen: WorldScreen) = com.unciv.ui.worldscreen.bottombar.BattleTable(worldScreen=worldScreen) +fun TileInfoTable(viewingCiv: CivilizationInfo) = com.unciv.ui.worldscreen.bottombar.TileInfoTable(viewingCiv=viewingCiv) +fun OptionsPopup(previousScreen: BaseScreen) = com.unciv.ui.worldscreen.mainmenu.OptionsPopup(previousScreen=previousScreen) +fun WorldScreenCommunityPopup(worldScreen: WorldScreen) = com.unciv.ui.worldscreen.mainmenu.WorldScreenCommunityPopup(worldScreen=worldScreen) +fun WorldScreenMenuPopup(worldScreen: WorldScreen) = com.unciv.ui.worldscreen.mainmenu.WorldScreenMenuPopup(worldScreen=worldScreen) +fun IdleUnitButton(unitTable: UnitTable, tileMapHolder: WorldMapHolder, previous: Boolean) = com.unciv.ui.worldscreen.unit.IdleUnitButton(unitTable=unitTable, tileMapHolder=tileMapHolder, previous=previous) +fun UnitActionsTable(worldScreen: WorldScreen) = com.unciv.ui.worldscreen.unit.UnitActionsTable(worldScreen=worldScreen) +fun UnitTable(worldScreen: WorldScreen) = com.unciv.ui.worldscreen.unit.UnitTable(worldScreen=worldScreen) +// **THE ABOVE CODE WAS AUTOMATICALLY GENERATED WITH A SCRIPT** + +} + diff --git a/core/src/com/unciv/scripting/utils/InstanceRegistry.kt b/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt similarity index 94% rename from core/src/com/unciv/scripting/utils/InstanceRegistry.kt rename to core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt index c7e63eecd52d5..a676cdf002b14 100644 --- a/core/src/com/unciv/scripting/utils/InstanceRegistry.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt @@ -1,4 +1,4 @@ -package com.unciv.scripting.utils +package com.unciv.scripting.api /** @@ -8,7 +8,7 @@ package com.unciv.scripting.utils * Currently all it does is throw an exception on an assignment colliding with an existing key, and reads and removals for non-existent keys.. * Was going to have functions named to fit creating and freeing fields, but so far Python's item assignment and deletion syntaxes are plenty clean */ -class InstanceRegistry(): MutableMap { +class ScriptingApiInstanceRegistry(): MutableMap { private val backingMap = mutableMapOf() override val entries get() = backingMap.entries diff --git a/core/src/com/unciv/scripting/utils/Blackbox.kt b/core/src/com/unciv/scripting/protocol/Blackbox.kt similarity index 98% rename from core/src/com/unciv/scripting/utils/Blackbox.kt rename to core/src/com/unciv/scripting/protocol/Blackbox.kt index 811ebf38b1365..2399e4fa4596c 100644 --- a/core/src/com/unciv/scripting/utils/Blackbox.kt +++ b/core/src/com/unciv/scripting/protocol/Blackbox.kt @@ -1,4 +1,4 @@ -package com.unciv.scripting.utils +package com.unciv.scripting.protocol /** diff --git a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt index a99399019f310..6ba254cf62bbf 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt @@ -359,7 +359,8 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = ) try { leaf as Map - //Ensure same behaviour as "keys" action. + // Ensure same behaviour as "keys" action. + // TODO: Make this and other key operations work with operator overloading. Though Map is already an interface that anything can implement, so maybe not. data = TokenizingJson.getJsonElement(true) } catch (e: Exception) { data = TokenizingJson.getJsonElement(false) diff --git a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt index a2b9c764d5332..960e8fbc253d3 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt @@ -1,11 +1,11 @@ package com.unciv.scripting.protocol +import com.unciv.UncivGame // For debug packet print only. import com.unciv.scripting.AutocompleteResults import com.unciv.scripting.ScriptingBackend import com.unciv.scripting.ScriptingScope import com.unciv.scripting.protocol.ScriptingPacket import com.unciv.scripting.protocol.ScriptingProtocol -import com.unciv.scripting.utils.Blackbox abstract class ScriptingReplManager(val scriptingScope: ScriptingScope, val blackbox: Blackbox): ScriptingBackend { @@ -48,7 +48,7 @@ class ScriptingRawReplManager(scriptingScope: ScriptingScope, blackbox: Blackbox */ class ScriptingProtocolReplManager(scriptingScope: ScriptingScope, blackbox: Blackbox): ScriptingReplManager(scriptingScope, blackbox) { -// TODO: scriptingScope can be an Any. Hm. "scriptingScope" is more readable within Unciv, but Any communicate a cleaner design and cleaner design constraints. Hm. That means "scriptingScope" communicates the wrong thing with its readability. It's called "scope" in ScriptingProtocol, which should be plently clear enough. +// TODO: scriptingScope can be an Any. Hm. "scriptingScope" is more readable within Unciv, but Any communicates a cleaner design and cleaner design constraints. Hm. That means "scriptingScope" communicates the wrong thing with its readability. It's called "scope" in ScriptingProtocol, which should be plenty clear enough. /** * ScriptingProtocol puts references to pre-tokenized returned objects in here. @@ -65,9 +65,11 @@ class ScriptingProtocolReplManager(scriptingScope: ScriptingScope, blackbox: Bla //TODO: Doc fun getRequestResponse(packetToSend: ScriptingPacket, enforceValidity: Boolean = true, execLoop: () -> Unit = fun(){}): ScriptingPacket { // Please update the specifications in Module.md if you change the basic structure of this REPL loop. + if (UncivGame.Current.printScriptingPacketsForDebug) println("\nSending: ${packetToSend}") // TODO: Add debug flag. blackbox.write(packetToSend.toJson() + "\n") execLoop() val response = ScriptingPacket.fromJson(blackbox.read(block=true)) + if (UncivGame.Current.printScriptingPacketsForDebug) println("\nReceived: ${response}") if (enforceValidity) { ScriptingProtocol.enforceIsResponse(packetToSend, response) } @@ -82,8 +84,10 @@ class ScriptingProtocolReplManager(scriptingScope: ScriptingScope, blackbox: Bla fun foreignExecLoop() { while (true) { val request = ScriptingPacket.fromJson(blackbox.read(block=true)) + if (UncivGame.Current.printScriptingPacketsForDebug) println("\nReceived: ${request}") if (request.action != null) { val response = scriptingProtocol.makeActionResponse(request) + if (UncivGame.Current.printScriptingPacketsForDebug) println("\nSending: ${response}") blackbox.write(response.toJson() + "\n") } if (request.hasFlag(ScriptingProtocol.KnownFlag.PassMic)) { diff --git a/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt b/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt index c0e6ad67572bf..660e6d68edc27 100644 --- a/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt +++ b/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt @@ -1,6 +1,5 @@ package com.unciv.scripting.protocol -import com.unciv.scripting.utils.Blackbox import java.io.* diff --git a/core/src/com/unciv/scripting/reflection/Reflection.kt b/core/src/com/unciv/scripting/reflection/Reflection.kt index 66cbfcdd59071..f544ff43a8097 100644 --- a/core/src/com/unciv/scripting/reflection/Reflection.kt +++ b/core/src/com/unciv/scripting/reflection/Reflection.kt @@ -49,6 +49,9 @@ object Reflection { val matchClassesQualnames: Boolean = false, val resolveAmbiguousSpecificity: Boolean = false ) { + // TODO: Allow manually matching to supplied KCallables, E.G., KClass.constructors. + // Probably: Move actual resolution into a singleton, and prepend the receiver instance to the arguments here instead of skipping it its params during resolution. + // This isn't just a nice-to-have feature. Before I implemented it, identical calls from demo scripts to methods with multiple versions (E.G. ArrayList().add()) would rarely but randomly fail because the member/signature that was found would change between runs or compilations. // TODO: This is going to need unit tests. @@ -207,6 +210,7 @@ object Reflection { fun readInstanceItem(instance: Any, keyOrIndex: Any): Any? { + // TODO: Make this work with operator overloading. Though Map is already an interface that anything can implement, so maybe not. if (keyOrIndex is Int) { return (instance as List)[keyOrIndex] } else { diff --git a/core/src/com/unciv/scripting/utils/InstanceFactories.kt b/core/src/com/unciv/scripting/utils/InstanceFactories.kt deleted file mode 100644 index 68597ed019806..0000000000000 --- a/core/src/com/unciv/scripting/utils/InstanceFactories.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.unciv.scripting.utils - -import com.unciv.MainMenuScreen -import com.unciv.ui.mapeditor.MapEditorScreen -import com.unciv.logic.map.TileMap -import com.badlogic.gdx.math.Vector2 - - -//TODO: Rename to ScriptingApiFactories? Move to separate package with other API-focused things? - -/** - * For use in ScriptingScope. Allows interpreted scripts to make new instances of Kotlin/JVM classes. - */ -object InstanceFactories { - //This, and possible ApiHelpers itself, need better nested namespaces. - val Game = object { - } - val Math = object { - } - val Rulesets = object { - } - val Kotlin = object { - } - val Gui = object { - fun MainMenuScreen() = com.unciv.MainMenuScreen() - fun MapEditorScreen(map: TileMap) = com.unciv.ui.mapeditor.MapEditorScreen(map) - } - fun arrayOf(elements: Collection): Array<*> = elements.toTypedArray() - fun arrayOfAny(elements: Collection): Array = elements.toTypedArray() - fun arrayOfString(elements: Collection): Array = elements.toTypedArray() - fun Vector2(x: Number, y: Number) = com.badlogic.gdx.math.Vector2(x.toFloat(), y.toFloat()) - fun MapUnit() = "NotImplemented" - fun Technology() = "NotImplemented" -} - diff --git a/core/src/com/unciv/scripting/utils/InstanceTokenizer.kt b/core/src/com/unciv/scripting/utils/InstanceTokenizer.kt index cb1b357779786..f41c3207df6bb 100644 --- a/core/src/com/unciv/scripting/utils/InstanceTokenizer.kt +++ b/core/src/com/unciv/scripting/utils/InstanceTokenizer.kt @@ -47,7 +47,12 @@ object InstanceTokenizer { * @return Token string. */ private fun tokenFromInstance(value: Any?): String { - var stringified = value.toString() + var stringified: String + try { // Because this can be overridden, it can fail. E.G. MapUnit.toString() depends on a lateinit. + stringified = value.toString() + } catch (e: Exception) { + stringified = "?" // TODO: Use exception text? + } if (stringified.length > tokenMaxLength) { stringified = stringified.slice(0..tokenMaxLength-4) + "..." } diff --git a/core/src/com/unciv/scripting/utils/ScriptingApiAccessible.kt b/core/src/com/unciv/scripting/utils/ScriptingApiAccessible.kt index 085f6151716ec..fdc7e3f6cee8f 100644 --- a/core/src/com/unciv/scripting/utils/ScriptingApiAccessible.kt +++ b/core/src/com/unciv/scripting/utils/ScriptingApiAccessible.kt @@ -3,7 +3,7 @@ package com.unciv.scripting.utils enum class ScriptingApiExposure { All, Player, Cheats, Mods -} +} // Mods should have more restrictive access to system-facing memebrs than everything else, since they're the only untrusted code. @Retention(AnnotationRetention.RUNTIME) annotation class ScriptingApiAccessible( diff --git a/core/src/com/unciv/scripting/utils/ScriptingApiEnums.kt b/core/src/com/unciv/scripting/utils/ScriptingApiEnums.kt deleted file mode 100644 index 185acd46f676f..0000000000000 --- a/core/src/com/unciv/scripting/utils/ScriptingApiEnums.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.unciv.scripting.utils - -import com.unciv.models.stats.Stat - - -inline fun > enumToMap() = enumValues().associateBy{ it.name } - - -/** - * For use in ScriptingScope. Allows interpreted scripts to access Unciv Enum constants. - * - * Currently exposes enum values as maps. - */ -object ScriptingApiEnums { - val Stat = enumToMap() -} diff --git a/core/src/com/unciv/scripting/utils/TokenizingJson.kt b/core/src/com/unciv/scripting/utils/TokenizingJson.kt index 64ae7c52bbf4d..91ed6af865f4f 100644 --- a/core/src/com/unciv/scripting/utils/TokenizingJson.kt +++ b/core/src/com/unciv/scripting/utils/TokenizingJson.kt @@ -14,7 +14,7 @@ import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.decodeFromJsonElement import kotlinx.serialization.modules.SerializersModule - +// TODO: Move to serialization package with InstanceTokenizer.kt /** * Json serialization that accepts Any?, and converts non-primitive values to string keys using InstanceTokenizer. diff --git a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt index fd20e7974e919..e7a955ceeca04 100644 --- a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt @@ -238,7 +238,7 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un } } setText(minmatch + original.slice(cursorpos..original.length-1), SetTextCursorMode.Insert) - // Splice the longest starting substring with the text after the cursor, to let autocomplete implementations work on the middle of current input. + // TODO: Splice the longest starting substring with the text after the cursor, to let autocomplete implementations work on the middle of current input. } } From 5f34138ef7c17cc2487f2167c5d41eef162c8588 Mon Sep 17 00:00:00 2001 From: will-ca Date: Wed, 1 Dec 2021 20:56:23 +0000 Subject: [PATCH 58/93] =?UTF-8?q?Generalize=20multiple=20method=20dispatch?= =?UTF-8?q?=20to=20not=20automatically=20add=20receiver=E2=80=94=20Allows?= =?UTF-8?q?=20Java=20constructors=20to=20be=20also=20be=20resolved.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Expose all Class constructors and Enums in scripting API by qualifiedName. Build for Android. Kotlin-side parsed loops/mapping. --- CodeGenerators.py | 239 -------------- .../enginefiles/python/PythonScripting.md | 17 + .../enginefiles/python/unciv_lib/api.py | 2 +- .../python/unciv_lib/autocompletion.py | 3 + .../enginefiles/python/unciv_lib/wrapping.py | 11 + .../python/unciv_scripting_examples/Tests.py | 42 ++- android/build.gradle.kts | 5 + core/src/com/unciv/MainMenuScreen.kt | 1 - .../com/unciv/scripting/ScriptingBackend.kt | 7 +- .../src/com/unciv/scripting/ScriptingScope.kt | 15 +- .../src/com/unciv/scripting/ScriptingState.kt | 8 +- core/src/com/unciv/scripting/api/LazyMap.kt | 34 ++ .../unciv/scripting/api/ScriptingApiEnums.kt | 134 +------- .../scripting/api/ScriptingApiFactories.kt | 303 +++--------------- .../api/ScriptingApiInstanceRegistry.kt | 9 +- .../scripting/api/ScriptingApiMappers.kt | 50 +++ .../com/unciv/scripting/protocol/Blackbox.kt | 2 +- .../scripting/protocol/ScriptingProtocol.kt | 36 +-- .../protocol/ScriptingReplManager.kt | 4 +- .../scripting/protocol/SubprocessBlackbox.kt | 4 +- .../reflection/FunctionDispatcher.kt | 183 +++++++++++ .../unciv/scripting/reflection/Reflection.kt | 234 +++----------- .../unciv/scripting/utils/ApiSpecGenerator.kt | 7 +- .../scripting/utils/InstanceTokenizer.kt | 6 +- .../scripting/utils/ScriptingApiAccessible.kt | 2 +- .../unciv/scripting/utils/SourceManager.kt | 6 +- .../scripting/utils/StringifyException.kt | 7 +- .../unciv/scripting/utils/TokenizingJson.kt | 27 +- .../unciv/ui/consolescreen/ConsoleScreen.kt | 15 +- 29 files changed, 525 insertions(+), 888 deletions(-) delete mode 100644 CodeGenerators.py create mode 100644 core/src/com/unciv/scripting/api/LazyMap.kt create mode 100644 core/src/com/unciv/scripting/api/ScriptingApiMappers.kt create mode 100644 core/src/com/unciv/scripting/reflection/FunctionDispatcher.kt diff --git a/CodeGenerators.py b/CodeGenerators.py deleted file mode 100644 index 971ed0c4c9e18..0000000000000 --- a/CodeGenerators.py +++ /dev/null @@ -1,239 +0,0 @@ -import os, re, string, math - - -# TODO: Honestly, this has gotten to the point where it will be easier to rewrite in Kotlin and use an AST. Though, the Kotlin parser is apparently quite difficult and possibly fragile to use too. -# Kotlin/grammar-tools is official? -# https://jitinsharma.com/posts/parsing-kotlin-using-code-kotlin/ - -# Deprecated. Use reflection to get classes at runtime instead. - - -BASE_REPO_DIR = os.path.dirname(__file__) - -ALPHANUMERIC = string.ascii_letters + string.digits - -def relPath(path="core"): - return os.path.join(BASE_REPO_DIR, path) - -def starGlob(path="core"): - for directory, subdirs, files in os.walk(relPath(path)): - for d in subdirs: - yield os.path.join(directory, d, "") - for f in files: - yield os.path.join(directory, f) - -DEFAULT_SOURCE_FILES = tuple(starGlob("core")) - - -# Obviously a proper parser would be better than this. - -def stripComment(code): - stringed = code.split('"') - for i in range(0, len(stringed), 2): - if "//" in stringed[i]: - stringed[i] = stringed[i].partition("//")[0] - stringed = stringed[0:i+1] - break - return re.sub('/\*.*\*/', '', '"'.join(stringed)) - -def partitionNextName(code): - code = code.strip() - split = re.search(f"[^_{ALPHANUMERIC}]", code) - if split is None: - return code - start = split.start() - return code[:start], code[start:] - -def partitionClassSig(code): - # TODO: Strip keywords here? - name, sig = partitionNextName(code) - sig = sig.strip(' {}\n\t') - typesig = sig.rpartition(':') - #typesig_first = re.search(f'[^ \t{ALPHANUMERIC}\.]', typesig[2]) - #if typesig_first and typesig[2][typesig_first.start()] == '(': - # I think round brackets in arguments should exist only in default values, so if the first strange character after the last colon is an opening round bracket, then the last colon probably means inheritance. - # Could also check for equal number of closing and opening brackets. But that could break with specific string default arguments. - # Right. Inheritance can have angular brackets from type parameters. Nvm. - # Also, this would break with function types anyway. - if typesig[2].count('(') == typesig[2].count(')'): - sig, typesig = typesig[0].strip(), typesig[2].strip() - if len(sig) > 2 and sig[0] == '(' and sig[-1] == ')': - sigargs = [] - currarg = [] - bracklevel = 0 - last_c = None - for c in sig[1:-1]: - if (not bracklevel) and c == ',': - sigargs.append(''.join(currarg).strip()) - currarg.clear() - continue - currarg.append(c) - if c in '<([': - bracklevel += 1 - elif c in ')]': - bracklevel -= 1 - elif c == '>' and last_c != '-': - # Function type arrows. - bracklevel -=1 - last_c = c - sigargs.append(''.join(currarg).strip()) - # Type params can have commas. - del currarg, bracklevel - sig = tuple((name.strip().rpartition(' ')[2], params.partition('=')[0].strip()) for arg in sigargs if arg for name, _, params in [arg.partition(':')]) - # Skip empties to handle trailing commas. - else: - sig = () - return name, sig, typesig - -class KDef: - def __init__(self, packagepath, basepath, name, sig): - self.packagepath = packagepath - self.basepath = basepath - self.name = name - self.sig = sig - def __repr__(self): - return f"{self.__class__.__name__!s}({self.basepath!r}, {self.name!r}, {self.sig!r})" - def getQualifiedPath(self): - return '.'.join(i for l in (self.packagepath, self.basepath, (self.name,)) for i in l) - - -def scrapeClassDefs(keyword='enum class ', filepaths=DEFAULT_SOURCE_FILES, *, sort=lambda d: d.getQualifiedPath()): - assert keyword in {'enum class ', 'class '} - defs = [] - for p in filepaths: - if not p.endswith('.kt'): - continue - with open(p, 'r') as file: - iscommented = False - i = 0 - def nextl(): - nonlocal iscommented - nonlocal i - l = file.readline() - i += 1 - if not l: - raise StopIteration() - l = stripComment(l) - if '*/' in l: - iscommented = False - l = l.partition('*/')[2] - if '/*' in l: - iscommented = True - l = l.partition('/*')[0] - elif iscommented: - return nextl() - return l - try: - package = None - while not package: - # Find package name. - package = nextl().partition('package ')[2].rstrip() - basepath = [] - indent = 0 - privatelevel = math.inf - setprivate = False - while True: - line = nextl() - stripped = line.lstrip() - newscope = None - if 'private class ' in line: - newscope = 'private class ' - setprivate = keyword == 'class ' - elif 'fun ' in line: - newscope = 'fun ' - setprivate = keyword == 'class ' - elif 'class ' in line: - newscope = 'class ' # Set keyword for scope's name. - elif 'object ' in line: - newscope = 'object ' - setprivate = keyword == 'class ' # Mostly broken because classes in _ScriptingConstantsClasses need each other, I think. - if newscope: - indent = (len(line)-len(stripped)) // 4 # Spaces 🙄. - basepath = basepath[:indent] # Trim higher levels. - basepath.append(partitionNextName(stripped.partition(newscope)[2])[0]) - if setprivate: - privatelevel = min(privatelevel, indent) - elif indent <= privatelevel: - privatelevel = math.inf - while line.count('(') != line.count(')'): - line += ' ' + nextl().strip() - # Horrible way of handling multi-line argument declarations. - # I mean, continuing if the current expression is invalid is also how a lot of languages do it. The issue is failure to account for comments and strings, but comments are at least already implemented elsewhere. - if privatelevel == math.inf and stripped.startswith(keyword): # Implies a newscope was also True. - name, sig, typesig = partitionClassSig(line.partition(keyword)[2]) - defs.append(KDef( - (package,), # Package name. - tuple(p for p in basepath[:indent] if p), # Clip nesting level to indent level. Companions produce empty names, so skip those. - name, - sig - )) - except StopIteration: - pass - return defs if sort is None else sorted(defs, key=sort) - -def scrapeImportStatements(filepaths=DEFAULT_SOURCE_FILES): - statements = set() - for p in filepaths: - if not p.endswith('.kt'): - continue - with open(p, 'r') as file: - while line := file.readline(): - line = stripComment(line).strip() - if line.startswith('import '): - statements.add(line) - return sorted(statements) - - -GENERATED_COMMENT_BEGIN = "// **THE BELOW CODE WAS AUTOMATICALLY GENERATED WITH A SCRIPT**" -GENERATED_COMMENT_END = "// **THE ABOVE CODE WAS AUTOMATICALLY GENERATED WITH A SCRIPT**" - -def markGeneratedComment(func): - def _markGeneratedComment(*args, **kwargs): - return GENERATED_COMMENT_BEGIN+'\n'+func(*args, **kwargs)+'\n'+GENERATED_COMMENT_END - return _markGeneratedComment - -def copyToClipboard(text): - import tkinter - tk = tkinter.Tk() - tk.withdraw() - tk.clipboard_clear() - tk.clipboard_append(text) - - -@markGeneratedComment -def genEnumImports(filepaths=DEFAULT_SOURCE_FILES): - return "\n".join(f'import {e.getQualifiedPath()}' for e in scrapeClassDefs('enum class ', filepaths)) - -@markGeneratedComment -def genEnumMaps(filepaths=DEFAULT_SOURCE_FILES): - return "\n".join(f' val {e.name} = enumToMap<{e.getQualifiedPath()}>()' for e in scrapeClassDefs('enum class ', filepaths)) - - -@markGeneratedComment -def genClassImports(filepaths=DEFAULT_SOURCE_FILES): - classes = scrapeClassDefs('class ', filepaths) - otherimports = scrapeImportStatements(filepaths) - otherimportsmapping = {s.rpartition('.')[-1]: s for s in otherimports} - basetypes = {kdef.name for kdef in classes} - neededtypes = {a[1] for kdef in classes for a in kdef.sig} - classpaths = [f'import {e.getQualifiedPath()}' for e in classes] - argpaths = [otherimportsmapping[t] for t in neededtypes if t not in basetypes and t in otherimportsmapping] - universals = {*(s for s in otherimports if s.endswith('.*')), *(''.join((*s.rpartition('.')[:2], '*')) for l in (classpaths, argpaths) for s in l)} - return "\n".join(sorted(universals)) - -factory_bins = { - '': "Rulesets", - 'com.unciv.ui': "" -} - -factory_blacklist = { - 'UncivSound', # Private constructor. - 'TileGroupMap', # Type params. - 'UncivTooltip', # Type params. - 'Simulation', # Requires Experimental opt-in. - 'TechPickerScreen' # For some reason Technology? uniquely fails to resolve as an argument type. Could try qualified path, but not worth it. -} - -@markGeneratedComment -def genClassFactories(filepaths=DEFAULT_SOURCE_FILES): - return "\n".join(f'fun {e.name}({", ".join(": ".join(a) for a in e.sig)}) = {e.getQualifiedPath()}({", ".join(f"{a[0]}={a[0]}" for a in e.sig)})' for e in scrapeClassDefs('class ', filepaths) if e.name not in factory_blacklist) diff --git a/android/assets/scripting/enginefiles/python/PythonScripting.md b/android/assets/scripting/enginefiles/python/PythonScripting.md index 14c94faba1698..64e3a4b5ec552 100644 --- a/android/assets/scripting/enginefiles/python/PythonScripting.md +++ b/android/assets/scripting/enginefiles/python/PythonScripting.md @@ -366,6 +366,23 @@ def faster(): # Saves 5 Python object instantiations with every loop! ``` +There are additionally a number of Kotlin/JVM helper functions that can speed up scripts by applying simple operations to or reading simple values from lots of Kotlin/JVM instances at once. These functions accept and return lists and mappings that can be serialized as JSON arrays and objects, allowing hundreds or thousands of operations to be performed with only a single IPC request. + +```python3 +# TODO +def slow(): + for t in gameInfo.tileMap.values: + if t.militaryUnit is not None or t.civilianUnit is not None: + t.terrainFeatures.add("Fallout") + +def fast(): + tileproperties = apiHelpers.mapPathCodes(gameInfo.tileMap.values, ('.militaryUnit', '.civilianUnit')) + apiHelpers.applyPathCodes({tile:{'.terrainFeatures.add("Fallout")'} for tile in tileproperties if tileproperties['.militaryunit'] is None or tileproperties['.civilianUnit'] is None}) + +def also_slow(): + +``` + Every element in the path sent by a wrapper object to Kotlin/the JVM also requires the Kotlin side to perform an additional reflective member resolution step. ```python3 diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/api.py b/android/assets/scripting/enginefiles/python/unciv_lib/api.py index ea9e366401e02..0d207fbaae78a 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/api.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/api.py @@ -33,7 +33,7 @@ def _expose(obj): def get_keys(obj): """Get keys of object. Fail silently if it has no keys. Used to let PyAutocompleter work with ForeignObject.""" try: - return obj.keys() # FIXME: This results in actual calls over IPC since hiding the .keys() IPC protcol-based method for non-mappings in ForeignObject. + return obj.keys() # FIXME: This results in function calls over IPC since hiding the .keys() IPC protcol-based method for non-mappings in ForeignObject. except (AttributeError, ipc.ForeignError): return () diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py b/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py index aa000188742a4..8e3b154626060 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py @@ -59,6 +59,9 @@ def GetAutocomplete(self, command): class PyAutocompleteManager(AutocompleteManager): """Advanced autocompleter. Returns keys when accessing mappings. Implements API that returns docstrings as help text for callables. Adds opening round and square brackets to autocomplete matches to show callables and mappings.""" + # FIXME: Dot after a mapping fails. + # FIXME: Also fails: apiHelpers.Enums.enumMapsByQualname["com.unciv.logic.automation.ThreatLevel"] + # It's the whitespaceadjusted_workingcode.rpartition('.'), I think. def Evaled(self, path): assert ')' not in path, f"Closing brackets not currently allowed in autocomplete eval: {path}" return eval(path, self.scope, self.scope) diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py index d344243863cd2..f241a5e7beda8 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py @@ -305,6 +305,17 @@ def __dir__(self): # Implemented and works, but disabled for now. See ScriptingProtocol.kt and Module.md. @ForeignRequestMethod def __call__(self, *args): + # From an IPC and protocol perspective, there isn't anything wrong with supporting directly accessing a value after a call. + # The problems are 1. Not knowing when/if to reify, 2. Reified and unrefied things behaving differently, 3. Implicit calls and static-emulating behaviour becoming unpredictable. + # E.G.: `a = civInfo.addGold(5); del a` and `civInfo.addGold(5)` would be different from `apiHelpers.printLine(civInfo.addGold(5))`. + # That was a deliberate concern and design decision at first, at think. + # But after writing some more example scripts, docs, and fleshing out the API, I'm wondering if it might be better to let scripts construct paths however they want while requiring them to explicitly reify foreign objects in most cases, instead of having to assign to apiHelpers.registeredInstances so often. + # That might work better in a typed language, I think. But it could also be a bit strange in Python, and implicit conversion could be more confusing with it. Right now foreign wrappers are basically interchangeable in most cases with real values, forbidding calls lets them be safely used with lazy resolution, and scripts don't really have to think about the path list. + # E.G.: `v = civInfo.someFunction().x; v+5; print(v)` would call `civInfo.someFunction()` every time that `v` is used, because `.x` is still a wrapper that includes a call buried in its path. + # Hm. And while avoiding assignments to registeredInstances might improve performance, having to make sure there aren't any 'type':'Call's buried deep in each wrapper's path before every implicit use would eat some of those gains right back up, in addition to being an opaque error-prone mess from the perspective of normal Python code. + # It would also be much easier to write Python code that accidentally has abysmal performance (in addition to unexpected side effects) due to implicitly re-calling an expensive Kotlin function every time a variable assigned from an attribute is used, when by all normal Python semantics an attribute should not behave like that. + # Point is: Function calls do not have static, access-safe values like properties or keys. So wrapping them up in a dynamic ForeignObject that pretends to be static, the way I have properties and keys, would make language semantics wildly deviate from language behaviour. + # Yeah, I forced calls to terminate wrappers for a reason. return ({ 'action': 'read', 'data': { diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py index 19c68445947b3..e544f8b616054 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py @@ -8,7 +8,6 @@ Pass debugprint=False if running from Kotlin as part of build tests, because running scripts' STDOUT is already captured by the Python REPL and sent to Kotlin code, which should then check for the presence of the 'Exception' IPC packet flag. """ - import os import unciv, unciv_pyhelpers#, unciv_lib @@ -17,10 +16,11 @@ # from unciv_scripting_examples.Tests import *; TestRunner.run_tests() +# from unciv_scripting_examples.Tests import *; InMapEditor.__enter__() try: assert False - # Can also check __debug__. + # Can also check __debug__. Meh. Explicit(ly using the behaviour) is better here than implicit(ly relying on related behaviour). except: pass else: @@ -37,7 +37,7 @@ def getTestGame(): return unciv.GameSaver.gameInfoFromString(Elizabeth300) def goToMainMenu(): - unciv.uncivGame.setScreen(unciv.apiHelpers.Factories.Gui.MainMenuScreen()) + unciv.uncivGame.setScreen(unciv.apiHelpers.Factories.constructorByQualname['com.unciv.MainMenuScreen']())#unciv.apiHelpers.Factories.Gui.MainMenuScreen()) @Utils.singleton() @@ -53,7 +53,10 @@ class InMapEditor: """Context manager object that loads a test map in the map editor on entrance and returns to the main menu on exit.""" def __enter__(self): with Utils.TokensAsWrappers(getTestGame()) as (gameinfo,): - unciv.uncivGame.setScreen(unciv.apiHelpers.Factories.Gui.MapEditorScreen(gameinfo.tileMap)) + unciv.uncivGame.setScreen( + unciv.apiHelpers.Factories.constructorByQualname['com.unciv.ui.mapeditor.MapEditorScreen'](gameinfo.tileMap) + # SetScreen doesn't seem to be needed here. But that seems like a glitch in the core Unciv code. + )#unciv.apiHelpers.Factories.Gui.MapEditorScreen(gameinfo.tileMap)) def __exit__(self, *exc): goToMainMenu() @@ -65,7 +68,7 @@ class TestRunner: def __init__(self): self._tests = [] class _TestCls: - """Class to define and run a single test. Accepts the function to test, a human-readable name for the test, a context manager with which to run it, and args and kwargs with which to call the funciton.""" + """Class to define and run a single test. Accepts the function to test, a human-readable name for the test, a context manager with which to run it, and args and kwargs with which to call the function.""" def __init__(self, func, name=None, runwith=None, args=(), kwargs={}): self.func = func self.name = getattr(func, '__name__', None) if name is None else name @@ -78,6 +81,10 @@ def __call__(self): else: with self.runwith: self.func(*self.args, **self.kwargs) + def keys(self): + return [t.name for t in self._tests] + def __getitem__(self, key): + return next(t for t in self._tests if t.name == key) def Test(self, *args, **kwargs): """Return a decorator that registers a function to be run as a test, and then returns it unchanged. Accepts the same configuration arguments as _TestCls.""" # Return values aren't checked. A call that completes is considered a pass. A call that raises an exception is considered a fail. @@ -126,6 +133,31 @@ def LoadGameTest(): assert unciv_pyhelpers.isForeignToken(v) +@TestRunner.Test(runwith=InGame, name="NoPrivatesTest-InGame", args=(unciv, 2)) +@TestRunner.Test(runwith=InMapEditor, name="NoPrivatesTest-InMapEditor", args=(unciv, 2)) +def NoPrivatesTest(start, maxdepth, *, _depth=0, _failures=None, _namestack=None): + # Would have to differentiate between unitialized properties and the like, and privates. + if _failures is None: + _failures = [] + if _namestack is None: + _namestack = () + try: + names = dir(start) + except: + _failures.append('.'.join(_namestack)) + else: + for name in dir(start): + namestack = (*_namestack, name) + try: + v = getattr(start, name) + except: + _failures.append('.'.join(namestack)) + else: + if _depth < maxdepth: + NoPrivatesTest(v, maxdepth, _depth=_depth+1, _failures=_failures, _namestack=namestack) + if _depth == 0: + assert not _failures, _failures + # Tests for PlayerMacros.py. TestRunner.Test(runwith=InGame, args=(unciv.civInfo.cities, 0.5))( diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 3ea8261147cdf..704271143f784 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -30,6 +30,10 @@ android { versionName = BuildConfig.appVersion base.archivesBaseName = "Unciv" + + multiDexEnabled = true + // As of the first attempts to build for Android with the scripting API, the project is too large to fit in one DEX file (71052 > 65536). + // Only needed with minSdk below 21: https://developer.android.com/studio/build/multidex } // necessary for Android Work lib @@ -125,4 +129,5 @@ dependencies { // run `./gradlew build --scan` to see details implementation("androidx.core:core-ktx:1.6.0") implementation("androidx.work:work-runtime-ktx:2.6.0") + implementation("androidx.multidex:multidex:2.0.1") } diff --git a/core/src/com/unciv/MainMenuScreen.kt b/core/src/com/unciv/MainMenuScreen.kt index 8579c3d42ade8..a51ddbb2cc9ef 100644 --- a/core/src/com/unciv/MainMenuScreen.kt +++ b/core/src/com/unciv/MainMenuScreen.kt @@ -1,7 +1,6 @@ package com.unciv import com.badlogic.gdx.Gdx -import com.badlogic.gdx.Input import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.scenes.scene2d.Touchable import com.badlogic.gdx.scenes.scene2d.actions.Actions diff --git a/core/src/com/unciv/scripting/ScriptingBackend.kt b/core/src/com/unciv/scripting/ScriptingBackend.kt index 5a2711dfce213..f0b8d4a0eccad 100644 --- a/core/src/com/unciv/scripting/ScriptingBackend.kt +++ b/core/src/com/unciv/scripting/ScriptingBackend.kt @@ -1,6 +1,6 @@ package com.unciv.scripting -import com.badlogic.gdx.Gdx +//import com.badlogic.gdx.Gdx import com.badlogic.gdx.files.FileHandle import com.unciv.scripting.reflection.Reflection @@ -13,7 +13,7 @@ import com.unciv.scripting.utils.ApiSpecGenerator import com.unciv.scripting.utils.SourceManager import com.unciv.scripting.utils.SyntaxHighlighter import kotlin.reflect.full.* -import java.util.* +//import java.util.* /** @@ -624,9 +624,10 @@ enum class ScriptingBackendType(val metadata: ScriptingBackend_metadata) { SystemLua(SluaScriptingBackend), // DevTools(DevToolsScriptingBackend), //For running ApiSpecGenerator. Comment in releases. Uncomment if needed. + // TODO: Have .new function? } - +// TODO: Lowercase name. Actually, no. Just get rid of this. fun SpawnNamedScriptingBackend(backendtype: ScriptingBackendType, scriptingScope: ScriptingScope): ScriptingBackendBase { // Seems unnecessary? return backendtype.metadata.new(scriptingScope) diff --git a/core/src/com/unciv/scripting/ScriptingScope.kt b/core/src/com/unciv/scripting/ScriptingScope.kt index 1317a58d42d55..08aaf1846dfb9 100644 --- a/core/src/com/unciv/scripting/ScriptingScope.kt +++ b/core/src/com/unciv/scripting/ScriptingScope.kt @@ -6,11 +6,13 @@ import com.badlogic.gdx.graphics.PixmapIO import com.badlogic.gdx.utils.Base64Coder import com.unciv.UncivGame import com.unciv.logic.GameInfo -import com.unciv.logic.GameSaver +//import com.unciv.logic.GameSaver import com.unciv.logic.civilization.CivilizationInfo import com.unciv.scripting.api.ScriptingApiEnums import com.unciv.scripting.api.ScriptingApiFactories import com.unciv.scripting.api.ScriptingApiInstanceRegistry +import com.unciv.scripting.api.ScriptingApiMappers +import com.unciv.scripting.reflection.Reflection import com.unciv.ui.mapeditor.MapEditorScreen import com.unciv.ui.utils.ImageGetter import com.unciv.ui.utils.toPixmap @@ -58,22 +60,24 @@ class ScriptingScope( //var modApiHelpers: ModApiHelpers? class ApiHelpers(val scriptingScope: ScriptingScope) { + // TODO: Move this into its own file. // This could probably eventually include ways for scripts to create and inject their own UI elements too. Create, populate, show even popups for mods, inject buttons that execute script strings for macros. // TODO: The vast majority of these don't need scriptingScope access, and thus can be put on singletons. val isInGame: Boolean get() = (scriptingScope.civInfo != null && scriptingScope.gameInfo != null && scriptingScope.uncivGame != null) val Factories = ScriptingApiFactories val Enums = ScriptingApiEnums + val Mappers = ScriptingApiMappers val registeredInstances = ScriptingApiInstanceRegistry() //Debug/dev identity function for both Kotlin and scripts. Check if value survives serialization, force something to be added to ScriptingProtocol.instanceSaver, etc. - fun unchanged(obj: Any?) = obj - fun printLine(msg: Any?) = println(msg) // Different name from Kotlin's is deliberate, to abstract for scripts. + fun echo(obj: Any?) = obj // TODO: Rename back to echo. + fun printLine(msg: Any?) = println(msg.toString()) // Different name from Kotlin's is deliberate, to abstract for scripts. //fun readLine() //Return a line from the main game process's STDIN. fun toString(obj: Any?) = obj.toString() fun copyToClipboard(value: Any?) { //Better than scripts potentially doing it themselves. In Python, for example, a way to do this would involve setting up an invisible TKinter window. - Gdx.app.clipboard.contents = value.toString(); + Gdx.app.clipboard.contents = value.toString() } //fun typeOf(obj: Any?) = obj::class.simpleName //fun typeOfQualified(obj: Any?) = obj::class.qualifiedName @@ -99,8 +103,7 @@ class ScriptingScope( exporter.dispose() // This one should be called automatically by GC anyway. return String(Base64Coder.encode(fakepng.toByteArray())) } -// fun applyProperties(instance: Any, properties: Map) { -// } + //setTimeout? //fun lambdifyScript(code: String ): () -> Unit = fun(){ scriptingScope.uncivGame!!.scriptingState.exec(code); return } // FIXME: Requires awareness of which scriptingState and which backend to use. //Directly invoking the resulting lambda from a running script will almost certainly break the REPL loop/IPC protocol. diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index fa18a01571179..7f83562852af9 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -1,9 +1,9 @@ package com.unciv.scripting -import com.unciv.logic.GameInfo -import com.unciv.logic.civilization.CivilizationInfo -import com.unciv.UncivGame -import com.unciv.ui.worldscreen.WorldScreen +//import com.unciv.logic.GameInfo +//import com.unciv.logic.civilization.CivilizationInfo +//import com.unciv.UncivGame +//import com.unciv.ui.worldscreen.WorldScreen import kotlin.collections.ArrayList import kotlin.math.max import kotlin.math.min diff --git a/core/src/com/unciv/scripting/api/LazyMap.kt b/core/src/com/unciv/scripting/api/LazyMap.kt new file mode 100644 index 0000000000000..cbe75fc8b934a --- /dev/null +++ b/core/src/com/unciv/scripting/api/LazyMap.kt @@ -0,0 +1,34 @@ +package com.unciv.scripting.api + + +// Lazy Map of indeterminate size. + +// Memoizes a single-argument function. +// Generates values using the provided function the first time each key is encountered. +// Implements both invocation and indexing. + +// @property func The function that returns the value for a given key. +// @property exposeState Whether to expose the content-specific members of the backing map. +class LazyMap(val func: (K) -> V, val exposeState: Boolean = false): Map { + // Benefit of a Map over a function is that because mapping access can be safely assumed by scripting language bindings to have no side effects, it's semantically easier for scripting language bindings to let the returned value be immediately called, autocompleted, indexed/attribute-read, etc. + private val backingMap = hashMapOf() + override fun get(key: K): V { + val result: V + if (key !in backingMap) { + result = func(key) + backingMap[key] = result + } else { + result = backingMap[key]!! + } + return result + } + fun invoke(key: K): V = get(key) + private fun noStateError(): Nothing = throw(UnsupportedOperationException("Cannot access backing state of ${this::class.simpleName} by default.")) + override val entries get() = if (exposeState) backingMap.entries else noStateError() + override val keys get() = if (exposeState) backingMap.keys else noStateError() + override val values get() = if (exposeState) backingMap.values else noStateError() + override val size get() = if (exposeState) backingMap.size else noStateError() + override fun containsKey(key: K) = if (exposeState) backingMap.containsKey(key) else noStateError() + override fun containsValue(value: V) = if (exposeState) backingMap.containsValue(value) else noStateError() + override fun isEmpty() = if (exposeState) backingMap.isEmpty() else noStateError() +} diff --git a/core/src/com/unciv/scripting/api/ScriptingApiEnums.kt b/core/src/com/unciv/scripting/api/ScriptingApiEnums.kt index 8e3ca2f168d31..93c99e2107d21 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiEnums.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiEnums.kt @@ -1,69 +1,15 @@ package com.unciv.scripting.api -// **THE BELOW CODE WAS AUTOMATICALLY GENERATED WITH A SCRIPT** -import com.unciv.logic.automation.ThreatLevel -import com.unciv.logic.battle.CombatAction -import com.unciv.logic.city.CityFlags -import com.unciv.logic.city.RejectionReason -import com.unciv.logic.civilization.AlertType -import com.unciv.logic.civilization.CityStatePersonality -import com.unciv.logic.civilization.CityStateType -import com.unciv.logic.civilization.CivFlags -import com.unciv.logic.civilization.PlayerType -import com.unciv.logic.civilization.Proximity -import com.unciv.logic.civilization.ReligionState -import com.unciv.logic.civilization.diplomacy.DiplomacyFlags -import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers -import com.unciv.logic.civilization.diplomacy.DiplomaticStatus -import com.unciv.logic.civilization.diplomacy.RelationshipLevel -import com.unciv.logic.map.MapSize -import com.unciv.logic.map.MapUnit.DoubleMovementTerrainTarget -import com.unciv.logic.map.RoadStatus -import com.unciv.logic.map.TileMap.AssignContinentsMode -import com.unciv.logic.map.mapgenerator.MapRegions.ImpactType -import com.unciv.logic.map.mapgenerator.RiverGenerator.RiverCoordinate.BottomRightOrLeft -import com.unciv.logic.trade.TradeType -import com.unciv.logic.trade.TradeType.TradeTypeNumberType -import com.unciv.models.Tutorial -import com.unciv.models.UnitActionType -import com.unciv.models.metadata.BaseRuleset -import com.unciv.models.metadata.GameSpeed -import com.unciv.models.metadata.LocaleCode -import com.unciv.models.ruleset.BeliefType -import com.unciv.models.ruleset.Policy.PolicyBranchType -import com.unciv.models.ruleset.QuestName -import com.unciv.models.ruleset.QuestType -import com.unciv.models.ruleset.Ruleset.RulesetErrorSeverity -import com.unciv.models.ruleset.VictoryType -import com.unciv.models.ruleset.tile.ResourceType -import com.unciv.models.ruleset.tile.TerrainType -import com.unciv.models.ruleset.unique.UniqueFlag -import com.unciv.models.ruleset.unique.UniqueParameterType -import com.unciv.models.ruleset.unique.UniqueTarget -import com.unciv.models.ruleset.unique.UniqueType -import com.unciv.models.ruleset.unique.UniqueType.UniqueComplianceErrorSeverity -import com.unciv.models.ruleset.unit.UnitLayer -import com.unciv.models.ruleset.unit.UnitMovementType -import com.unciv.models.stats.Stat -import com.unciv.scripting.ScriptingBackendType -import com.unciv.scripting.protocol.ScriptingProtocol.KnownFlag -import com.unciv.scripting.reflection.Reflection.PathElementType -import com.unciv.scripting.utils.ScriptingApiExposure -import com.unciv.ui.audio.MusicTrackChooserFlags -import com.unciv.ui.audio.MusicTrackController.State -import com.unciv.ui.civilopedia.CivilopediaCategories -import com.unciv.ui.civilopedia.FormattedLine.IconDisplay -import com.unciv.ui.civilopedia.FormattedLine.LinkType -import com.unciv.ui.consolescreen.ConsoleScreen.SetTextCursorMode -import com.unciv.ui.pickerscreens.ModManagementOptions.SortType -import com.unciv.ui.utils.UncivTooltip.TipState -import com.unciv.ui.victoryscreen.RankingType -// **THE ABOVE CODE WAS AUTOMATICALLY GENERATED WITH A SCRIPT** +// Convert an Enum type parameter into a Map of its constants by their names. +inline fun > enumToMap() = enumValues().associateBy{ it.name } +fun enumQualnameToMap(qualName: String) = Class.forName(qualName).enumConstants.associateBy{ (it as Enum<*>).name } +// Always return a built-in Map class instance here, so its gets serialized as JSON object instead of tokenized, and scripts can refer directly to its items. +// I cast to Enum<*> fully expecting it would crash because it felt metaclass-y. But apparently it's just a base class, so it works? -inline fun > enumToMap() = enumValues().associateBy{ it.name } +// TODO (Later): Use ClassGraph to automatically find all relevant classes on build. +// https://github.com/classgraph/classgraph/wiki/Build-Time-Scanning -//Could try automatically finding all enums instead. /** * For use in ScriptingScope. Allows interpreted scripts to access Unciv Enum constants. @@ -71,63 +17,11 @@ inline fun > enumToMap() = enumValues().associateBy{ it.na * Currently exposes enum values as maps. */ object ScriptingApiEnums { -// **THE BELOW CODE WAS AUTOMATICALLY GENERATED WITH A SCRIPT** - val ThreatLevel = enumToMap() - val CombatAction = enumToMap() - val CityFlags = enumToMap() - val RejectionReason = enumToMap() - val AlertType = enumToMap() - val CityStatePersonality = enumToMap() - val CityStateType = enumToMap() - val CivFlags = enumToMap() - val PlayerType = enumToMap() - val Proximity = enumToMap() - val ReligionState = enumToMap() - val DiplomacyFlags = enumToMap() - val DiplomaticModifiers = enumToMap() - val DiplomaticStatus = enumToMap() - val RelationshipLevel = enumToMap() - val MapSize = enumToMap() - val DoubleMovementTerrainTarget = enumToMap() - val RoadStatus = enumToMap() - val AssignContinentsMode = enumToMap() - val ImpactType = enumToMap() - val BottomRightOrLeft = enumToMap() - val TradeType = enumToMap() - val TradeTypeNumberType = enumToMap() - val Tutorial = enumToMap() - val UnitActionType = enumToMap() - val BaseRuleset = enumToMap() - val GameSpeed = enumToMap() - val LocaleCode = enumToMap() - val BeliefType = enumToMap() - val PolicyBranchType = enumToMap() - val QuestName = enumToMap() - val QuestType = enumToMap() - val RulesetErrorSeverity = enumToMap() - val VictoryType = enumToMap() - val ResourceType = enumToMap() - val TerrainType = enumToMap() - val UniqueFlag = enumToMap() - val UniqueParameterType = enumToMap() - val UniqueTarget = enumToMap() - val UniqueType = enumToMap() - val UniqueComplianceErrorSeverity = enumToMap() - val UnitLayer = enumToMap() - val UnitMovementType = enumToMap() - val Stat = enumToMap() - val ScriptingBackendType = enumToMap() - val KnownFlag = enumToMap() - val PathElementType = enumToMap() - val ScriptingApiExposure = enumToMap() - val MusicTrackChooserFlags = enumToMap() - val State = enumToMap() - val CivilopediaCategories = enumToMap() - val IconDisplay = enumToMap() - val LinkType = enumToMap() - val SetTextCursorMode = enumToMap() - val SortType = enumToMap() - val TipState = enumToMap() - val RankingType = enumToMap() -// **THE ABOVE CODE WAS AUTOMATICALLY GENERATED WITH A SCRIPT** + val enumMapsByQualname = LazyMap(::enumQualnameToMap) + + // apiHelpers.Enums.enumMapsByQualname["com.unciv.logic.automation.ThreatLevel"]['VeryLow'] + + //fun getEnumValue(qualName: String, value: String) = getEnumMap(qualName)[value] + // The return from getEnumMap should be a real serialized thing anyway, right? + //val testThreatLevel = enumQualnameToMap("com.unciv.logic.automation.ThreatLevel") } diff --git a/core/src/com/unciv/scripting/api/ScriptingApiFactories.kt b/core/src/com/unciv/scripting/api/ScriptingApiFactories.kt index bcdd418c8dcfc..8f3b69c6ce7c6 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiFactories.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiFactories.kt @@ -1,78 +1,14 @@ package com.unciv.scripting.api -//import com.unciv.MainMenuScreen -//import com.unciv.ui.mapeditor.MapEditorScreen -//import com.unciv.logic.map.TileMap -import com.badlogic.gdx.math.Vector2 +import com.unciv.scripting.reflection.FunctionDispatcher +import com.unciv.scripting.reflection.makeFunctionDispatcher +import kotlin.reflect.full.primaryConstructor -// **THE BELOW CODE WAS AUTOMATICALLY GENERATED WITH A SCRIPT** -import com.badlogic.gdx.graphics.* -import com.badlogic.gdx.scenes.scene2d.* -import com.badlogic.gdx.scenes.scene2d.ui.* -import com.unciv.* -import com.unciv.logic.* -import com.unciv.logic.automation.* -import com.unciv.logic.battle.* -import com.unciv.logic.city.* -import com.unciv.logic.civilization.* -import com.unciv.logic.civilization.RuinsManager.* -import com.unciv.logic.civilization.diplomacy.* -import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers.* -import com.unciv.logic.map.* -import com.unciv.logic.map.mapgenerator.* -import com.unciv.logic.trade.* -import com.unciv.logic.trade.TradeType.* -import com.unciv.models.* -import com.unciv.models.metadata.* -import com.unciv.models.ruleset.* -import com.unciv.models.ruleset.tech.* -import com.unciv.models.ruleset.tile.* -import com.unciv.models.ruleset.unique.* -import com.unciv.models.ruleset.unique.UniqueType.* -import com.unciv.models.ruleset.unit.* -import com.unciv.models.simulation.* -import com.unciv.models.tilesets.* -import com.unciv.models.translations.* -import com.unciv.scripting.* -import com.unciv.scripting.ScriptingScope.* -import com.unciv.scripting.api.* -import com.unciv.scripting.protocol.* -import com.unciv.ui.* -import com.unciv.ui.audio.* -import com.unciv.ui.cityscreen.* -import com.unciv.ui.civilopedia.* -import com.unciv.ui.consolescreen.* -import com.unciv.ui.map.* -import com.unciv.ui.mapeditor.* -import com.unciv.ui.mapeditor.MapEditorMenuPopup.* -import com.unciv.ui.newgamescreen.* -import com.unciv.ui.overviewscreen.* -import com.unciv.ui.pickerscreens.* -import com.unciv.ui.saves.* -import com.unciv.ui.tilegroups.* -import com.unciv.ui.trade.* -import com.unciv.ui.tutorials.* -import com.unciv.ui.utils.* -import com.unciv.ui.victoryscreen.* -import com.unciv.ui.worldscreen.* -import com.unciv.ui.worldscreen.WorldMapHolder.* -import com.unciv.ui.worldscreen.bottombar.* -import com.unciv.ui.worldscreen.mainmenu.* -import com.unciv.ui.worldscreen.unit.* -import java.io.* -import java.util.* -import kotlin.math.* -import kotlin.reflect.full.* -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* -// **THE ABOVE CODE WAS AUTOMATICALLY GENERATED WITH A SCRIPT** +// TODO (Later): Use ClassGraph to automatically find all relevant classes on build. +// https://github.com/classgraph/classgraph/wiki/Build-Time-Scanning -// Honestly, see if you can just instantiate them by string/name instead. -// Class.forName("com.unciv.etc").newInstance(), or Class.forName("com.unciv.etc").kotlin, or something. - /** * For use in ScriptingScope. Allows interpreted scripts to make new instances of Kotlin/JVM classes. */ @@ -87,23 +23,49 @@ object ScriptingApiFactories { val Kotlin = object { } val Gui = object { - fun MainMenuScreen() = com.unciv.MainMenuScreen() - fun MapEditorScreen(map: TileMap) = com.unciv.ui.mapeditor.MapEditorScreen(map) } - //Class.forName("java.lang.String").kotlin.primaryConstructor.call - fun test(qualName: String) = Class.forName(qualName) - fun test2(qualName: String) = Class.forName(qualName).kotlin //Not accessible. Probably extension/compiler-made? - fun instanceFromQualname(qualName: String, args: List) = Class.forName(qualName).kotlin.primaryConstructor!!.call(*args.toTypedArray()) - // TODO: Use generalized InstanceMethodDispatcher to find right callable in KClass.constructors if no primary constructor. + +// fun kotlinClassFromQualname(qualName: String) = memoizedQualnameToKclass(qualName) // For debug. Probably comment out in builds. // TODO: Check is disabled in unit tests. Actually nah. May as well leave this around. + + val kotlinClassByQualname= LazyMap({ qualName: String -> Class.forName(qualName).kotlin }, exposeState = true) // Mostly for debug. Generally scripts shouldn't be using this. + + val constructorByQualname = LazyMap({ qualName: String -> makeFunctionDispatcher(Class.forName(qualName).kotlin.constructors) +// val cls = Class.forName(qualName).kotlin +// val cons = cls.primaryConstructor +// if (false && cons != null) +// makeFunctionDispatcher(listOf(cons::call)) +// // Seems that empty constructors take vararg arrays? E.G. MapEditorScreen, MainMenuScreen +// // Actually, it seems like .primaryConstructor is a lie. +// else makeFunctionDispatcher(cls.constructors) + }, exposeState = true) + +// fun instanceFromQualname(qualName: String, args: List): Any? { +// // TODO: Deprecate. +// val cls = Class.forName(qualName).kotlin +// val cons = cls.primaryConstructor +// return if (cons != null) +// cons.call(*args.toTypedArray()) +// else FunctionDispatcher( +// functions = cls.constructors, +// matchNumbersLeniently = true, +// matchClassesQualnames = false, +// resolveAmbiguousSpecificity = true +// ).call(args.toTypedArray()) +// } +// // TODO: Use generalized InstanceMethodDispatcher to find right callable in KClass.constructors if no primary constructor. // apiHelpers.Factories.instanceFromQualname('java.lang.String', []) // apiHelpers.Factories.instanceFromQualname('com.unciv.logic.map.MapUnit', []) // apiHelpers.Factories.instanceFromQualname('com.unciv.logic.city.CityStats', [civInfo.cities[0]]) - // Fails: apiHelpers.Factories.instanceFromQualname('com.badlogic.gdx.math.Vector2', [1.5, 2.5]) + // apiHelpers.Factories.instanceFromQualname('com.badlogic.gdx.math.Vector2', [1, 2]) // Refer: https://stackoverflow.com/questions/40672880/creating-a-new-instance-of-a-kclass // See https://stackoverflow.com/questions/59936471/kotlin-reflect-package-and-get-all-classes. Build structured map of all classes? + // https://stackoverflow.com/questions/3845823/getting-list-of-fully-qualified-names-from-a-simple-name + // https://stackoverflow.com/questions/52573605/kotlin-can-i-get-all-objects-that-implements-specific-interface + + // The JAR for Reflections is only a couple hundred KB. It also doesn't work on Android. // apiHelpers.registeredInstances['x'] = apiHelpers.Factories.test2('com.badlogic.gdx.math.Vector2') // apiHelpers.registeredInstances['x'].constructors[1].call(apiHelpers.Factories.arrayOf([1, 2])) @@ -111,192 +73,5 @@ object ScriptingApiFactories { fun arrayOf(elements: Collection): Array<*> = elements.toTypedArray() fun arrayOfAny(elements: Collection): Array = elements.toTypedArray() fun arrayOfString(elements: Collection): Array = elements.toTypedArray() - fun Vector2(x: Number, y: Number) = com.badlogic.gdx.math.Vector2(x.toFloat(), y.toFloat()) -// fun MapUnit() = "NotImplemented" -// fun Technology() = "NotImplemented" - -// **THE BELOW CODE WAS AUTOMATICALLY GENERATED WITH A SCRIPT** -fun JsonParser() = com.unciv.JsonParser() -fun MainMenuScreen() = com.unciv.MainMenuScreen() -fun UncivGame(parameters: UncivGameParameters) = com.unciv.UncivGame(parameters=parameters) -fun UncivGameParameters(version: String, crashReportSender: CrashReportSender?, cancelDiscordEvent: (() -> Unit)?, fontImplementation: NativeFontImplementation?, consoleMode: Boolean, customSaveLocationHelper: CustomSaveLocationHelper?, limitOrientationsHelper: LimitOrientationsHelper?) = com.unciv.UncivGameParameters(version=version, crashReportSender=crashReportSender, cancelDiscordEvent=cancelDiscordEvent, fontImplementation=fontImplementation, consoleMode=consoleMode, customSaveLocationHelper=customSaveLocationHelper, limitOrientationsHelper=limitOrientationsHelper) -fun BarbarianManager() = com.unciv.logic.BarbarianManager() -fun GameInfo() = com.unciv.logic.GameInfo() -fun UncivShowableException(missingMods: String) = com.unciv.logic.UncivShowableException(missingMods=missingMods) -fun BarbarianAutomation(civInfo: CivilizationInfo) = com.unciv.logic.automation.BarbarianAutomation(civInfo=civInfo) -fun ConstructionAutomation(cityConstructions: CityConstructions) = com.unciv.logic.automation.ConstructionAutomation(cityConstructions=cityConstructions) -fun BattleDamageModifier(vs: String, modificationAmount: Float) = com.unciv.logic.battle.BattleDamageModifier(vs=vs, modificationAmount=modificationAmount) -fun CityCombatant(city: CityInfo) = com.unciv.logic.battle.CityCombatant(city=city) -fun MapUnitCombatant(unit: MapUnit) = com.unciv.logic.battle.MapUnitCombatant(unit=unit) -fun CityConstructions() = com.unciv.logic.city.CityConstructions() -fun CityExpansionManager() = com.unciv.logic.city.CityExpansionManager() -fun CityInfo() = com.unciv.logic.city.CityInfo() -fun CityInfoConquestFunctions(city: CityInfo) = com.unciv.logic.city.CityInfoConquestFunctions(city=city) -fun CityInfoReligionManager() = com.unciv.logic.city.CityInfoReligionManager() -fun CityStats(cityInfo: CityInfo) = com.unciv.logic.city.CityStats(cityInfo=cityInfo) -fun PopulationManager() = com.unciv.logic.city.PopulationManager() -fun CapitalConnectionsFinder(civInfo: CivilizationInfo) = com.unciv.logic.civilization.CapitalConnectionsFinder(civInfo=civInfo) -fun CityStateFunctions(civInfo: CivilizationInfo) = com.unciv.logic.civilization.CityStateFunctions(civInfo=civInfo) -fun CivConstructions() = com.unciv.logic.civilization.CivConstructions() -fun CivInfoStats(civInfo: CivilizationInfo) = com.unciv.logic.civilization.CivInfoStats(civInfo=civInfo) -fun CivInfoTransientUpdater(civInfo: CivilizationInfo) = com.unciv.logic.civilization.CivInfoTransientUpdater(civInfo=civInfo) -fun CivilizationInfo() = com.unciv.logic.civilization.CivilizationInfo() -fun GoldenAgeManager() = com.unciv.logic.civilization.GoldenAgeManager() -fun GreatPersonManager() = com.unciv.logic.civilization.GreatPersonManager() -fun PolicyManager() = com.unciv.logic.civilization.PolicyManager() -fun PopupAlert() = com.unciv.logic.civilization.PopupAlert() -fun QuestManager() = com.unciv.logic.civilization.QuestManager() -fun ReligionManager() = com.unciv.logic.civilization.ReligionManager() -fun RuinsManager() = com.unciv.logic.civilization.RuinsManager.RuinsManager() -fun TechManager() = com.unciv.logic.civilization.TechManager() -fun VictoryManager() = com.unciv.logic.civilization.VictoryManager() -fun DiplomacyManager() = com.unciv.logic.civilization.diplomacy.DiplomacyManager() -fun BFS(startingPoint: TileInfo, predicate: (TileInfo) -> Boolean) = com.unciv.logic.map.BFS(startingPoint=startingPoint, predicate=predicate) -fun MapSizeNew() = com.unciv.logic.map.MapSizeNew() -fun MapUnit() = com.unciv.logic.map.MapUnit() -fun TileMap() = com.unciv.logic.map.TileMap() -fun UnitMovementAlgorithms(unit: MapUnit) = com.unciv.logic.map.UnitMovementAlgorithms(unit=unit) -fun UnitPromotions() = com.unciv.logic.map.UnitPromotions() -fun MapGenerator(ruleset: Ruleset) = com.unciv.logic.map.mapgenerator.MapGenerator(ruleset=ruleset) -fun MapLandmassGenerator(ruleset: Ruleset, randomness: MapGenerationRandomness) = com.unciv.logic.map.mapgenerator.MapLandmassGenerator(ruleset=ruleset, randomness=randomness) -fun MapRegions(ruleset: Ruleset) = com.unciv.logic.map.mapgenerator.MapRegions(ruleset=ruleset) -fun NaturalWonderGenerator(ruleset: Ruleset, randomness: MapGenerationRandomness) = com.unciv.logic.map.mapgenerator.NaturalWonderGenerator(ruleset=ruleset, randomness=randomness) -fun RiverGenerator(tileMap: TileMap, randomness: MapGenerationRandomness) = com.unciv.logic.map.mapgenerator.RiverGenerator(tileMap=tileMap, randomness=randomness) -fun Trade() = com.unciv.logic.trade.Trade() -fun TradeEvaluation() = com.unciv.logic.trade.TradeEvaluation() -fun TradeLogic(ourCivilization: CivilizationInfo, otherCivilization: CivilizationInfo) = com.unciv.logic.trade.TradeLogic(ourCivilization=ourCivilization, otherCivilization=otherCivilization) -fun TradeOffersList() = com.unciv.logic.trade.TradeOffersList() -fun AttackableTile(tileToAttackFrom: TileInfo, tileToAttack: TileInfo, movementLeftAfterMovingToAttackTile: Float) = com.unciv.models.AttackableTile(tileToAttackFrom=tileToAttackFrom, tileToAttack=tileToAttack, movementLeftAfterMovingToAttackTile=movementLeftAfterMovingToAttackTile) -fun Religion() = com.unciv.models.Religion() -fun GameParameters() = com.unciv.models.metadata.GameParameters() -fun GameSettings() = com.unciv.models.metadata.GameSettings() -fun GameSetupInfo(gameParameters: GameParameters, mapParameters: MapParameters) = com.unciv.models.metadata.GameSetupInfo(gameParameters=gameParameters, mapParameters=mapParameters) -fun Player(chosenCiv: String) = com.unciv.models.metadata.Player(chosenCiv=chosenCiv) -fun Belief() = com.unciv.models.ruleset.Belief() -fun Building() = com.unciv.models.ruleset.Building() -fun Difficulty() = com.unciv.models.ruleset.Difficulty() -fun Era() = com.unciv.models.ruleset.Era() -fun Nation() = com.unciv.models.ruleset.Nation() -fun PolicyBranch() = com.unciv.models.ruleset.PolicyBranch() -fun Quest() = com.unciv.models.ruleset.Quest() -fun RuinReward() = com.unciv.models.ruleset.RuinReward() -fun TechColumn() = com.unciv.models.ruleset.tech.TechColumn() -fun Technology() = com.unciv.models.ruleset.tech.Technology() -fun Terrain() = com.unciv.models.ruleset.tile.Terrain() -fun TileImprovement() = com.unciv.models.ruleset.tile.TileImprovement() -fun TileResource() = com.unciv.models.ruleset.tile.TileResource() -fun Unique(text: String, sourceObjectType: UniqueTarget?, sourceObjectName: String?) = com.unciv.models.ruleset.unique.Unique(text=text, sourceObjectType=sourceObjectType, sourceObjectName=sourceObjectName) -fun BaseUnit() = com.unciv.models.ruleset.unit.BaseUnit() -fun Promotion() = com.unciv.models.ruleset.unit.Promotion() -fun UnitType() = com.unciv.models.ruleset.unit.UnitType() -fun MutableInt(value: Int) = com.unciv.models.simulation.MutableInt(value=value) -fun SimulationStep(gameInfo: GameInfo) = com.unciv.models.simulation.SimulationStep(gameInfo=gameInfo) -fun TileSetConfig() = com.unciv.models.tilesets.TileSetConfig() -fun TranslationEntry(entry: String) = com.unciv.models.translations.TranslationEntry(entry=entry) -fun Translations() = com.unciv.models.translations.Translations() -fun ScriptingScope(civInfo: CivilizationInfo?, gameInfo: GameInfo?, uncivGame: UncivGame?, worldScreen: WorldScreen?, mapEditorScreen: MapEditorScreen?) = com.unciv.scripting.ScriptingScope(civInfo=civInfo, gameInfo=gameInfo, uncivGame=uncivGame, worldScreen=worldScreen, mapEditorScreen=mapEditorScreen) -fun ApiHelpers(scriptingScope: ScriptingScope) = com.unciv.scripting.ScriptingScope.ApiHelpers(scriptingScope=scriptingScope) -fun ScriptingApiInstanceRegistry() = com.unciv.scripting.api.ScriptingApiInstanceRegistry() -fun ScriptingRawReplManager(scriptingScope: ScriptingScope, blackbox: Blackbox) = com.unciv.scripting.protocol.ScriptingRawReplManager(scriptingScope=scriptingScope, blackbox=blackbox) -fun SubprocessBlackbox(processCmd: Array) = com.unciv.scripting.protocol.SubprocessBlackbox(processCmd=processCmd) -fun AddMultiplayerGameScreen(backScreen: MultiplayerScreen) = com.unciv.ui.AddMultiplayerGameScreen(backScreen=backScreen) -fun EditMultiplayerGameInfoScreen(gameInfo: GameInfoPreview?, gameName: String, backScreen: MultiplayerScreen) = com.unciv.ui.EditMultiplayerGameInfoScreen(gameInfo=gameInfo, gameName=gameName, backScreen=backScreen) -fun LanguagePickerScreen() = com.unciv.ui.LanguagePickerScreen() -fun MultiplayerScreen(previousScreen: BaseScreen) = com.unciv.ui.MultiplayerScreen(previousScreen=previousScreen) -fun MusicController() = com.unciv.ui.audio.MusicController() -fun MusicTrackController(volume: Float) = com.unciv.ui.audio.MusicTrackController(volume=volume) -fun CityConstructionsTable(cityScreen: CityScreen) = com.unciv.ui.cityscreen.CityConstructionsTable(cityScreen=cityScreen) -fun CityInfoTable(cityScreen: CityScreen) = com.unciv.ui.cityscreen.CityInfoTable(cityScreen=cityScreen) -fun CityReligionInfoTable(religionManager: CityInfoReligionManager, showMajority: Boolean) = com.unciv.ui.cityscreen.CityReligionInfoTable(religionManager=religionManager, showMajority=showMajority) -fun CityScreen(city: CityInfo, selectedConstruction: IConstruction?, selectedTile: TileInfo?) = com.unciv.ui.cityscreen.CityScreen(city=city, selectedConstruction=selectedConstruction, selectedTile=selectedTile) -fun CityScreenCityPickerTable(cityScreen: CityScreen) = com.unciv.ui.cityscreen.CityScreenCityPickerTable(cityScreen=cityScreen) -fun CityScreenTileTable(cityScreen: CityScreen) = com.unciv.ui.cityscreen.CityScreenTileTable(cityScreen=cityScreen) -fun CityStatsTable(cityScreen: CityScreen) = com.unciv.ui.cityscreen.CityStatsTable(cityScreen=cityScreen) -fun CityTileGroup(city: CityInfo, tileInfo: TileInfo, tileSetStrings: TileSetStrings) = com.unciv.ui.cityscreen.CityTileGroup(city=city, tileInfo=tileInfo, tileSetStrings=tileSetStrings) -fun ConstructionInfoTable(cityScreen: CityScreen) = com.unciv.ui.cityscreen.ConstructionInfoTable(cityScreen=cityScreen) -fun SpecialistAllocationTable(cityScreen: CityScreen) = com.unciv.ui.cityscreen.SpecialistAllocationTable(cityScreen=cityScreen) -fun YieldGroup() = com.unciv.ui.cityscreen.YieldGroup() -fun CivilopediaScreen(ruleset: Ruleset, previousScreen: BaseScreen, category: CivilopediaCategories, link: String) = com.unciv.ui.civilopedia.CivilopediaScreen(ruleset=ruleset, previousScreen=previousScreen, category=category, link=link) -fun FormattedLine(text: String, link: String, icon: String, extraImage: String, imageSize: Float, size: Int, header: Int, indent: Int, padding: Float, color: String, separator: Boolean, starred: Boolean, centered: Boolean, iconCrossed: Boolean) = com.unciv.ui.civilopedia.FormattedLine(text=text, link=link, icon=icon, extraImage=extraImage, imageSize=imageSize, size=size, header=header, indent=indent, padding=padding, color=color, separator=separator, starred=starred, centered=centered, iconCrossed=iconCrossed) -fun ConsoleScreen(scriptingState: ScriptingState, closeAction: () -> Unit) = com.unciv.ui.consolescreen.ConsoleScreen(scriptingState=scriptingState, closeAction=closeAction) -fun EditorMapHolder(mapEditorScreen: MapEditorScreen, tileMap: TileMap) = com.unciv.ui.mapeditor.EditorMapHolder(mapEditorScreen=mapEditorScreen, tileMap=tileMap) -fun GameParametersScreen(mapEditorScreen: MapEditorScreen) = com.unciv.ui.mapeditor.GameParametersScreen(mapEditorScreen=mapEditorScreen) -fun MapEditorMenuPopup(mapEditorScreen: MapEditorScreen) = com.unciv.ui.mapeditor.MapEditorMenuPopup(mapEditorScreen=mapEditorScreen) -fun MapEditorRulesetPopup(mapEditorScreen: MapEditorScreen) = com.unciv.ui.mapeditor.MapEditorMenuPopup.MapEditorRulesetPopup(mapEditorScreen=mapEditorScreen) -fun MapEditorOptionsTable(mapEditorScreen: MapEditorScreen) = com.unciv.ui.mapeditor.MapEditorOptionsTable(mapEditorScreen=mapEditorScreen) -fun MapEditorScreen() = com.unciv.ui.mapeditor.MapEditorScreen() -fun NewMapScreen(mapParameters: MapParameters) = com.unciv.ui.mapeditor.NewMapScreen(mapParameters=mapParameters) -fun SaveAndLoadMapScreen(mapToSave: TileMap?, save: Boolean, previousScreen: BaseScreen) = com.unciv.ui.mapeditor.SaveAndLoadMapScreen(mapToSave=mapToSave, save=save, previousScreen=previousScreen) -fun GameOptionsTable(previousScreen: IPreviousScreen, isPortrait: Boolean, updatePlayerPickerTable: (desiredCiv:String)->Unit) = com.unciv.ui.newgamescreen.GameOptionsTable(previousScreen=previousScreen, isPortrait=isPortrait, updatePlayerPickerTable=updatePlayerPickerTable) -fun MapOptionsTable(newGameScreen: NewGameScreen) = com.unciv.ui.newgamescreen.MapOptionsTable(newGameScreen=newGameScreen) -fun MapParametersTable(mapParameters: MapParameters, isEmptyMapAllowed: Boolean) = com.unciv.ui.newgamescreen.MapParametersTable(mapParameters=mapParameters, isEmptyMapAllowed=isEmptyMapAllowed) -fun ModCheckboxTable(mods: LinkedHashSet, baseRuleset: String, screen: BaseScreen, isPortrait: Boolean, onUpdate: (String) -> Unit) = com.unciv.ui.newgamescreen.ModCheckboxTable(mods=mods, baseRuleset=baseRuleset, screen=screen, isPortrait=isPortrait, onUpdate=onUpdate) -fun NationTable(nation: Nation, width: Float, minHeight: Float, ruleset: Ruleset?) = com.unciv.ui.newgamescreen.NationTable(nation=nation, width=width, minHeight=minHeight, ruleset=ruleset) -fun NewGameScreen(previousScreen: BaseScreen, _gameSetupInfo: GameSetupInfo?) = com.unciv.ui.newgamescreen.NewGameScreen(previousScreen=previousScreen, _gameSetupInfo=_gameSetupInfo) -fun PlayerPickerTable(previousScreen: IPreviousScreen, gameParameters: GameParameters, blockWidth: Float) = com.unciv.ui.newgamescreen.PlayerPickerTable(previousScreen=previousScreen, gameParameters=gameParameters, blockWidth=blockWidth) -fun CityOverviewTable(viewingPlayer: CivilizationInfo, overviewScreen: EmpireOverviewScreen) = com.unciv.ui.overviewscreen.CityOverviewTable(viewingPlayer=viewingPlayer, overviewScreen=overviewScreen) -fun DiplomacyOverviewTable(viewingPlayer: CivilizationInfo, overviewScreen: EmpireOverviewScreen) = com.unciv.ui.overviewscreen.DiplomacyOverviewTable(viewingPlayer=viewingPlayer, overviewScreen=overviewScreen) -fun EmpireOverviewScreen(viewingPlayer: CivilizationInfo, defaultPage: String) = com.unciv.ui.overviewscreen.EmpireOverviewScreen(viewingPlayer=viewingPlayer, defaultPage=defaultPage) -fun ReligionOverviewTable(viewingPlayer: CivilizationInfo, overviewScreen: EmpireOverviewScreen) = com.unciv.ui.overviewscreen.ReligionOverviewTable(viewingPlayer=viewingPlayer, overviewScreen=overviewScreen) -fun ResourcesOverviewTable(viewingPlayer: CivilizationInfo, overviewScreen: EmpireOverviewScreen) = com.unciv.ui.overviewscreen.ResourcesOverviewTable(viewingPlayer=viewingPlayer, overviewScreen=overviewScreen) -fun StatsOverviewTable(viewingPlayer: CivilizationInfo, overviewScreen: EmpireOverviewScreen) = com.unciv.ui.overviewscreen.StatsOverviewTable(viewingPlayer=viewingPlayer, overviewScreen=overviewScreen) -fun TradesOverviewTable(viewingPlayer: CivilizationInfo, overviewScreen: EmpireOverviewScreen) = com.unciv.ui.overviewscreen.TradesOverviewTable(viewingPlayer=viewingPlayer, overviewScreen=overviewScreen) -fun UnitOverviewTable(viewingPlayer: CivilizationInfo, overviewScreen: EmpireOverviewScreen) = com.unciv.ui.overviewscreen.UnitOverviewTable(viewingPlayer=viewingPlayer, overviewScreen=overviewScreen) -fun WonderOverviewTable(viewingPlayer: CivilizationInfo, overviewScreen: EmpireOverviewScreen) = com.unciv.ui.overviewscreen.WonderOverviewTable(viewingPlayer=viewingPlayer, overviewScreen=overviewScreen) -fun DiplomaticVotePickerScreen(votingCiv: CivilizationInfo) = com.unciv.ui.pickerscreens.DiplomaticVotePickerScreen(votingCiv=votingCiv) -fun DiplomaticVoteResultScreen(votesCast: HashMap, viewingCiv: CivilizationInfo) = com.unciv.ui.pickerscreens.DiplomaticVoteResultScreen(votesCast=votesCast, viewingCiv=viewingCiv) -fun GreatPersonPickerScreen(civInfo: CivilizationInfo) = com.unciv.ui.pickerscreens.GreatPersonPickerScreen(civInfo=civInfo) -fun ImprovementPickerScreen(tileInfo: TileInfo, unit: MapUnit, onAccept: ()->Unit) = com.unciv.ui.pickerscreens.ImprovementPickerScreen(tileInfo=tileInfo, unit=unit, onAccept=onAccept) -fun ModManagementOptions(modManagementScreen: ModManagementScreen) = com.unciv.ui.pickerscreens.ModManagementOptions(modManagementScreen=modManagementScreen) -fun ModManagementScreen(previousInstalledMods: HashMap?, previousOnlineMods: HashMap?) = com.unciv.ui.pickerscreens.ModManagementScreen(previousInstalledMods=previousInstalledMods, previousOnlineMods=previousOnlineMods) -fun PantheonPickerScreen(choosingCiv: CivilizationInfo, gameInfo: GameInfo) = com.unciv.ui.pickerscreens.PantheonPickerScreen(choosingCiv=choosingCiv, gameInfo=gameInfo) -fun PolicyPickerScreen(worldScreen: WorldScreen, civInfo: CivilizationInfo) = com.unciv.ui.pickerscreens.PolicyPickerScreen(worldScreen=worldScreen, civInfo=civInfo) -fun PromotionPickerScreen(unit: MapUnit) = com.unciv.ui.pickerscreens.PromotionPickerScreen(unit=unit) -fun ReligiousBeliefsPickerScreen(choosingCiv: CivilizationInfo, gameInfo: GameInfo, beliefsToChoose: Counter, pickIconAndName: Boolean) = com.unciv.ui.pickerscreens.ReligiousBeliefsPickerScreen(choosingCiv=choosingCiv, gameInfo=gameInfo, beliefsToChoose=beliefsToChoose, pickIconAndName=pickIconAndName) -fun TechButton(techName: String, techManager: TechManager, isWorldScreen: Boolean) = com.unciv.ui.pickerscreens.TechButton(techName=techName, techManager=techManager, isWorldScreen=isWorldScreen) -fun LoadGameScreen(previousScreen: BaseScreen) = com.unciv.ui.saves.LoadGameScreen(previousScreen=previousScreen) -fun SaveGameScreen(gameInfo: GameInfo) = com.unciv.ui.saves.SaveGameScreen(gameInfo=gameInfo) -fun CityButton(city: CityInfo, tileGroup: WorldTileGroup) = com.unciv.ui.tilegroups.CityButton(city=city, tileGroup=tileGroup) -fun TileGroupIcons(tileGroup: TileGroup) = com.unciv.ui.tilegroups.TileGroupIcons(tileGroup=tileGroup) -fun TileSetStrings() = com.unciv.ui.tilegroups.TileSetStrings() -fun WorldTileGroup(worldScreen: WorldScreen, tileInfo: TileInfo, tileSetStrings: TileSetStrings) = com.unciv.ui.tilegroups.WorldTileGroup(worldScreen=worldScreen, tileInfo=tileInfo, tileSetStrings=tileSetStrings) -fun DiplomacyScreen(viewingCiv: CivilizationInfo) = com.unciv.ui.trade.DiplomacyScreen(viewingCiv=viewingCiv) -fun LeaderIntroTable(civInfo: CivilizationInfo, hello: String) = com.unciv.ui.trade.LeaderIntroTable(civInfo=civInfo, hello=hello) -fun OfferColumnsTable(tradeLogic: TradeLogic, screen: DiplomacyScreen, onChange: () -> Unit) = com.unciv.ui.trade.OfferColumnsTable(tradeLogic=tradeLogic, screen=screen, onChange=onChange) -fun OffersListScroll(persistenceID: String, onOfferClicked: (TradeOffer) -> Unit) = com.unciv.ui.trade.OffersListScroll(persistenceID=persistenceID, onOfferClicked=onOfferClicked) -fun TradeTable(otherCivilization: CivilizationInfo, stage: DiplomacyScreen) = com.unciv.ui.trade.TradeTable(otherCivilization=otherCivilization, stage=stage) -fun TutorialController(screen: BaseScreen) = com.unciv.ui.tutorials.TutorialController(screen=screen) -fun TutorialRender(screen: BaseScreen) = com.unciv.ui.tutorials.TutorialRender(screen=screen) -fun AskNumberPopup(screen: BaseScreen, label: String, icon: IconCircleGroup, defaultText: String, amountButtons: List, bounds: IntRange, errorText: String, validate: (input: Int) -> Boolean, actionOnOk: (input: Int) -> Unit) = com.unciv.ui.utils.AskNumberPopup(screen=screen, label=label, icon=icon, defaultText=defaultText, amountButtons=amountButtons, bounds=bounds, errorText=errorText, validate=validate, actionOnOk=actionOnOk) -fun AskTextPopup(screen: BaseScreen, label: String, icon: IconCircleGroup, defaultText: String, errorText: String, maxLength: Int, validate: (input: String) -> Boolean, actionOnOk: (input: String) -> Unit) = com.unciv.ui.utils.AskTextPopup(screen=screen, label=label, icon=icon, defaultText=defaultText, errorText=errorText, maxLength=maxLength, validate=validate, actionOnOk=actionOnOk) -fun ExitGamePopup(screen: BaseScreen, force: Boolean) = com.unciv.ui.utils.ExitGamePopup(screen=screen, force=force) -fun ExpanderTab(title: String, fontSize: Int, icon: Actor?, startsOutOpened: Boolean, defaultPad: Float, headerPad: Float, expanderWidth: Float, persistenceID: String?, onChange: (() -> Unit)?, initContent: ((Table) -> Unit)?) = com.unciv.ui.utils.ExpanderTab(title=title, fontSize=fontSize, icon=icon, startsOutOpened=startsOutOpened, defaultPad=defaultPad, headerPad=headerPad, expanderWidth=expanderWidth, persistenceID=persistenceID, onChange=onChange, initContent=initContent) -fun IconCircleGroup(size: Float, actor: Actor, resizeActor: Boolean, color: Color) = com.unciv.ui.utils.IconCircleGroup(size=size, actor=actor, resizeActor=resizeActor, color=color) -fun TabbedPager(minimumWidth: Float, maximumWidth: Float, minimumHeight: Float, maximumHeight: Float, headerFontSize: Int, headerFontColor: Color, highlightColor: Color, backgroundColor: Color, headerPadding: Float, capacity: Int) = com.unciv.ui.utils.TabbedPager(minimumWidth=minimumWidth, maximumWidth=maximumWidth, minimumHeight=minimumHeight, maximumHeight=maximumHeight, headerFontSize=headerFontSize, headerFontColor=headerFontColor, highlightColor=highlightColor, backgroundColor=backgroundColor, headerPadding=headerPadding, capacity=capacity) -fun ToastPopup(message: String, screen: BaseScreen, time: Long) = com.unciv.ui.utils.ToastPopup(message=message, screen=screen, time=time) -fun UncivSlider(min: Float, max: Float, step: Float, vertical: Boolean, plusMinus: Boolean, initial: Float, sound: UncivSound, getTipText: ((Float) -> String)?, onChange: ((Float) -> Unit)?) = com.unciv.ui.utils.UncivSlider(min=min, max=max, step=step, vertical=vertical, plusMinus=plusMinus, initial=initial, sound=sound, getTipText=getTipText, onChange=onChange) -fun UnitGroup(unit: MapUnit, size: Float) = com.unciv.ui.utils.UnitGroup(unit=unit, size=size) -fun WrappableLabel(text: String, expectedWidth: Float, fontColor: Color, fontSize: Int) = com.unciv.ui.utils.WrappableLabel(text=text, expectedWidth=expectedWidth, fontColor=fontColor, fontSize=fontSize) -fun VictoryScreen(worldScreen: WorldScreen) = com.unciv.ui.victoryscreen.VictoryScreen(worldScreen=worldScreen) -fun AlertPopup(worldScreen: WorldScreen, popupAlert: PopupAlert) = com.unciv.ui.worldscreen.AlertPopup(worldScreen=worldScreen, popupAlert=popupAlert) -fun Minimap(mapHolder: WorldMapHolder, minimapSize: Int) = com.unciv.ui.worldscreen.Minimap(mapHolder=mapHolder, minimapSize=minimapSize) -fun NotificationsScroll(worldScreen: WorldScreen) = com.unciv.ui.worldscreen.NotificationsScroll(worldScreen=worldScreen) -fun PlayerReadyScreen(gameInfo: GameInfo, currentPlayerCiv: CivilizationInfo) = com.unciv.ui.worldscreen.PlayerReadyScreen(gameInfo=gameInfo, currentPlayerCiv=currentPlayerCiv) -fun TradePopup(worldScreen: WorldScreen) = com.unciv.ui.worldscreen.TradePopup(worldScreen=worldScreen) -fun WorldMapHolder(worldScreen: WorldScreen, tileMap: TileMap) = com.unciv.ui.worldscreen.WorldMapHolder(worldScreen=worldScreen, tileMap=tileMap) -fun MoveHereButtonDto(unitToTurnsToDestination: HashMap, tileInfo: TileInfo) = com.unciv.ui.worldscreen.WorldMapHolder.MoveHereButtonDto(unitToTurnsToDestination=unitToTurnsToDestination, tileInfo=tileInfo) -fun SwapWithButtonDto(unit: MapUnit, tileInfo: TileInfo) = com.unciv.ui.worldscreen.WorldMapHolder.SwapWithButtonDto(unit=unit, tileInfo=tileInfo) -fun WorldScreen(gameInfo: GameInfo, viewingCiv: CivilizationInfo) = com.unciv.ui.worldscreen.WorldScreen(gameInfo=gameInfo, viewingCiv=viewingCiv) -fun WorldScreenTopBar(worldScreen: WorldScreen) = com.unciv.ui.worldscreen.WorldScreenTopBar(worldScreen=worldScreen) -fun BattleTable(worldScreen: WorldScreen) = com.unciv.ui.worldscreen.bottombar.BattleTable(worldScreen=worldScreen) -fun TileInfoTable(viewingCiv: CivilizationInfo) = com.unciv.ui.worldscreen.bottombar.TileInfoTable(viewingCiv=viewingCiv) -fun OptionsPopup(previousScreen: BaseScreen) = com.unciv.ui.worldscreen.mainmenu.OptionsPopup(previousScreen=previousScreen) -fun WorldScreenCommunityPopup(worldScreen: WorldScreen) = com.unciv.ui.worldscreen.mainmenu.WorldScreenCommunityPopup(worldScreen=worldScreen) -fun WorldScreenMenuPopup(worldScreen: WorldScreen) = com.unciv.ui.worldscreen.mainmenu.WorldScreenMenuPopup(worldScreen=worldScreen) -fun IdleUnitButton(unitTable: UnitTable, tileMapHolder: WorldMapHolder, previous: Boolean) = com.unciv.ui.worldscreen.unit.IdleUnitButton(unitTable=unitTable, tileMapHolder=tileMapHolder, previous=previous) -fun UnitActionsTable(worldScreen: WorldScreen) = com.unciv.ui.worldscreen.unit.UnitActionsTable(worldScreen=worldScreen) -fun UnitTable(worldScreen: WorldScreen) = com.unciv.ui.worldscreen.unit.UnitTable(worldScreen=worldScreen) -// **THE ABOVE CODE WAS AUTOMATICALLY GENERATED WITH A SCRIPT** - } diff --git a/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt b/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt index a676cdf002b14..dc5bbce560a46 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt @@ -5,10 +5,11 @@ package com.unciv.scripting.api * Namespace in ScriptingScope().apiHelpers, for scripts to do their own memory management by keeping references to objects alive. * * Wraps a MutableMap<>(). - * Currently all it does is throw an exception on an assignment colliding with an existing key, and reads and removals for non-existent keys.. - * Was going to have functions named to fit creating and freeing fields, but so far Python's item assignment and deletion syntaxes are plenty clean + * + * @throws IllegalArgumentException On an attempted assignment colliding with an existing key. + * @throws NoSuchElementException For reads and removals at non-existent keys. */ -class ScriptingApiInstanceRegistry(): MutableMap { +class ScriptingApiInstanceRegistry: MutableMap { private val backingMap = mutableMapOf() override val entries get() = backingMap.entries @@ -20,7 +21,7 @@ class ScriptingApiInstanceRegistry(): MutableMap { get() = backingMap.size override fun containsKey(key: String) = backingMap.containsKey(key) override fun containsValue(value: Any?) = backingMap.containsValue(value) - override fun get(key: String): Any? { + override fun get(key: String): Any? { //FIXME: Operator modifiers? if (key !in this) { throw NoSuchElementException("\"${key}\" not in ${this}.") } diff --git a/core/src/com/unciv/scripting/api/ScriptingApiMappers.kt b/core/src/com/unciv/scripting/api/ScriptingApiMappers.kt new file mode 100644 index 0000000000000..58fe8976398d6 --- /dev/null +++ b/core/src/com/unciv/scripting/api/ScriptingApiMappers.kt @@ -0,0 +1,50 @@ +package com.unciv.scripting.api + +import com.unciv.scripting.reflection.Reflection + +// TODO: Rename this. + +object ScriptingApiMappers { + + //// Some ways to access or assign the same property(s) on a lot of instances at once, using only one IPC call. Maybe use parseKotlinPath? Probably preserve order in return instead of mapping from each instance, since script must already have list of tokens anyway (ideally also from a single IPC call). Or preserve mapping, since order could be messy, and to fit with the assignment function? + fun mapPathCodes(instances: List, pathcodes: Collection): List> { + val pathElementLists = pathcodes.associateWith{ Reflection.parseKotlinPath(it) } + return instances.map { + val instance = it + pathElementLists.mapValues{ + Reflection.resolveInstancePath(instance, it.value) + } + } + } + + fun getPathCodesFrom(instance: Any, pathcodes: Collection) = pathcodes.associateWith{ Reflection.resolveInstancePath(instance, Reflection.parseKotlinPath(it)) } + + fun getPathCodes(instancesAndPathcodes: Map>): Map> { + val pathElementLists = LazyMap>(Reflection::parseKotlinPath) + return instancesAndPathcodes.mapValues{ (instance, pathcodes) -> + pathcodes.associateWith{ + Reflection.resolveInstancePath(instance, pathElementLists[it]) + } + } + } + + fun applyPathCodesTo(instance: Any, pathcodesAndValues: Map): Any { + for ((pathcode, value) in pathcodesAndValues) { + Reflection.setInstancePath(instance, Reflection.parseKotlinPath(pathcode), value) + } + return instance + } + + fun applyPathCodes(instancesPathcodesAndValues: Map>) { + val pathElementLists = LazyMap>(Reflection::parseKotlinPath) + for ((instance, assignments) in instancesPathcodesAndValues) { + for ((pathcode, value) in assignments) { + Reflection.setInstancePath(instance, pathElementLists[pathcode], value) + } + } + } +} +// st=time.time(); [real(t.baseTerrain) for t in gameInfo.tileMap.values]; print(time.time()-st) +// st=time.time(); apiHelpers.Mappers.mapPathCodes(gameInfo.tileMap.values, ['baseTerrain']); print(time.time()-st) +// FIXME: Gets slower each time run. +// Actually around the same speed. Or wait: Is that the InstanceTokenizer slowdown? diff --git a/core/src/com/unciv/scripting/protocol/Blackbox.kt b/core/src/com/unciv/scripting/protocol/Blackbox.kt index 2399e4fa4596c..7189ee369df9d 100644 --- a/core/src/com/unciv/scripting/protocol/Blackbox.kt +++ b/core/src/com/unciv/scripting/protocol/Blackbox.kt @@ -4,7 +4,7 @@ package com.unciv.scripting.protocol /** * Unified interface for anything that receives and responds to input without any access to or relevance for its internal states. * - * Should be able to wrap STDIN/STDOUT, pipes, JNI, NDK, network sockets, external processes, embeded code, etc, and make them all interchangeable. + * Should be able to wrap STDIN/STDOUT, pipes, JNI, NDK, network sockets, external processes, embedded code, etc, and make them all interchangeable. */ interface Blackbox { diff --git a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt index 6ba254cf62bbf..f30d1bb4ad151 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt @@ -1,6 +1,7 @@ package com.unciv.scripting.protocol import com.unciv.scripting.AutocompleteResults +import com.unciv.scripting.reflection.FunctionDispatcher import com.unciv.scripting.reflection.Reflection import com.unciv.scripting.utils.stringifyException import com.unciv.scripting.utils.TokenizingJson @@ -8,7 +9,6 @@ import kotlin.random.Random import kotlinx.serialization.Serializable import kotlinx.serialization.encodeToString import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonNull @@ -233,7 +233,7 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = * @return Response packet. */ fun makeActionResponse(packet: ScriptingPacket): ScriptingPacket { - val action = ScriptingProtocol.responseTypes[packet.action]!! + val action = responseTypes[packet.action]!! var data: JsonElement? = null val flags = mutableListOf() when (packet.action) { @@ -249,7 +249,7 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = ) )) } catch (e: Exception) { - data = JsonPrimitive(stringifyException(e)) + data = JsonPrimitive(e.stringifyException()) flags.add(KnownFlag.Exception.value) } } @@ -261,7 +261,7 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = TokenizingJson.getJsonReal((packet.data as JsonObject)["value"]!!) ) } catch (e: Exception) { - data = JsonPrimitive(stringifyException(e)) + data = JsonPrimitive(e.stringifyException()) flags.add(KnownFlag.Exception.value) } } @@ -272,7 +272,7 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) ) } catch (e: Exception) { - data = JsonPrimitive(stringifyException(e)) + data = JsonPrimitive(e.stringifyException()) flags.add(KnownFlag.Exception.value) } } @@ -282,9 +282,9 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = scope, TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) ) - data = TokenizingJson.getJsonElement(leaf!!::class.members.map{it.name}) + data = TokenizingJson.getJsonElement(leaf!!::class.members.map{it.name}) // TODO: Honestly, probably restrict to non-privates and other members that are actually accessible. Test from Python. } catch (e: Exception) { - data = JsonPrimitive(stringifyException(e)) + data = JsonPrimitive(e.stringifyException()) flags.add(KnownFlag.Exception.value) } } @@ -296,7 +296,7 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = // ) // data = TokenizingJson.getJsonElement(leaf.hashCode()) // } catch (e: Exception) { -// data = JsonPrimitive(stringifyException(e)) +// data = JsonPrimitive(e.stringifyException()) // flags.add(KnownFlag.Exception.value) // } // } @@ -309,7 +309,7 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = ) data = TokenizingJson.getJsonElement((leaf as Map).keys) } catch (e: Exception) { - data = JsonPrimitive(stringifyException(e)) + data = JsonPrimitive(e.stringifyException()) flags.add(KnownFlag.Exception.value) } } @@ -330,7 +330,7 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = } } } catch (e: Exception) { - data = JsonPrimitive(stringifyException(e)) + data = JsonPrimitive(e.stringifyException()) flags.add(KnownFlag.Exception.value) } } @@ -347,7 +347,7 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = data = TokenizingJson.getJsonElement(_check in (leaf as Collection)) } } catch (e: Exception) { - data = JsonPrimitive(stringifyException(e)) + data = JsonPrimitive(e.stringifyException()) flags.add(KnownFlag.Exception.value) } } @@ -359,14 +359,14 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = ) try { leaf as Map - // Ensure same behaviour as "keys" action. + // Ensure same behaviour as "keys" action. IK It's probably/hopefully the same as using the is operator, but I'm not sure. // TODO: Make this and other key operations work with operator overloading. Though Map is already an interface that anything can implement, so maybe not. data = TokenizingJson.getJsonElement(true) } catch (e: Exception) { data = TokenizingJson.getJsonElement(false) } } catch (e: Exception) { - data = JsonPrimitive(stringifyException(e)) + data = JsonPrimitive(e.stringifyException()) flags.add(KnownFlag.Exception.value) } } @@ -376,9 +376,9 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = scope, TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) ) - data = TokenizingJson.getJsonElement(leaf is Reflection.InstanceMethodDispatcher) + data = TokenizingJson.getJsonElement(leaf is FunctionDispatcher) } catch (e: Exception) { - data = JsonPrimitive(stringifyException(e)) + data = JsonPrimitive(e.stringifyException()) flags.add(KnownFlag.Exception.value) } } @@ -390,14 +390,14 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = ) data = TokenizingJson.getJsonElement( mapOf>>( - *((leaf as Reflection.InstanceMethodDispatcher).methods.map { + *((leaf as FunctionDispatcher).functions.map { it.toString() to it.parameters.map{ listOf(it.name?.toString(), it.type.toString()) } }).toTypedArray() // The innermost listOf should semantically be a Pair as per the spec in Module.md, but a List seems safer to serialize. ) ) } catch (e: Exception) { - data = JsonPrimitive(stringifyException(e)) + data = JsonPrimitive(e.stringifyException()) flags.add(KnownFlag.Exception.value) } } @@ -409,7 +409,7 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = ) data = TokenizingJson.getJsonElement(leaf.toString()) } catch (e: Exception) { - data = JsonPrimitive(stringifyException(e)) + data = JsonPrimitive(e.stringifyException()) flags.add(KnownFlag.Exception.value) } } diff --git a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt index 960e8fbc253d3..b9320c8c00a8c 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt @@ -4,8 +4,8 @@ import com.unciv.UncivGame // For debug packet print only. import com.unciv.scripting.AutocompleteResults import com.unciv.scripting.ScriptingBackend import com.unciv.scripting.ScriptingScope -import com.unciv.scripting.protocol.ScriptingPacket -import com.unciv.scripting.protocol.ScriptingProtocol +//import com.unciv.scripting.protocol.ScriptingPacket +//import com.unciv.scripting.protocol.ScriptingProtocol abstract class ScriptingReplManager(val scriptingScope: ScriptingScope, val blackbox: Blackbox): ScriptingBackend { diff --git a/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt b/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt index 660e6d68edc27..3c0add4fab964 100644 --- a/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt +++ b/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt @@ -13,7 +13,7 @@ class SubprocessBlackbox(val processCmd: Array): Blackbox { /** * The wrapped process. */ - var process: java.lang.Process? = null + var process: Process? = null /** * STDOUT of the wrapped process, or null. @@ -30,7 +30,7 @@ class SubprocessBlackbox(val processCmd: Array): Blackbox { var processLaunchFail: String? = null override val isAlive: Boolean - get() = process != null && process!!.isAlive() + get() = process != null && process!!.isAlive() // TODO: API Level 26. But also, it's not like using subprocesses is actually planned for Android. override val readyForWrite: Boolean get() = isAlive diff --git a/core/src/com/unciv/scripting/reflection/FunctionDispatcher.kt b/core/src/com/unciv/scripting/reflection/FunctionDispatcher.kt new file mode 100644 index 0000000000000..dd7668f0e5028 --- /dev/null +++ b/core/src/com/unciv/scripting/reflection/FunctionDispatcher.kt @@ -0,0 +1,183 @@ +package com.unciv.scripting.reflection + +import kotlin.reflect.KCallable +import kotlin.reflect.KParameter +import kotlin.reflect.KType +import kotlin.reflect.full.isSubclassOf +import kotlin.reflect.full.isSubtypeOf +import kotlin.reflect.full.isSuperclassOf +import kotlin.reflect.full.isSupertypeOf +import kotlin.reflect.jvm.jvmErasure + +// I'm choosing to define this as a class to avoid having to pass the three configuration parameters through every function, but I do suspect that instantiating the class for every dynamic function call may be measurably slower than making the whole class a singleton object and passing the configurations through function arguments instead. + +// Return a [FunctionDispatcher]() with consistent settings for the scripting API. +fun makeFunctionDispatcher(functions: Collection>) = FunctionDispatcher( + functions = functions, + matchNumbersLeniently = true, + matchClassesQualnames = false, + resolveAmbiguousSpecificity = true +) + +/** + * Dynamic dispatch to one of multiple KCallables. + * + * Uses reflection to narrow down functions to the one(s) that have the correct signature for a given array of arguments + * + * Varargs can be used, but they must be supplied as a single correctly typed array instead of as separate arguments. + * + * @property functions List of functions against which to resolve calls. + * @property matchNumbersLeniently Whether to treat all numeric types as the same. Useful for E.G. untyped deserialized data. Adds small extra step to most calls. + * @property matchClassesQualnames Whether to treat classes as the same if they have the same qualifiedName. Useful for E.G. ignoring the invariant arrays representing vararg parameters. Adds small extra step to some calls. + * @property resolveAmbiguousSpecificity Whether to try to resolve multiple ambiguous matching signatures by finding one that strictly subtypes all others. Rules for this are documented under getMostSpecificCallable. Does not add any extra steps unless needed; Increases function domain properly handled but does not decrease performance in other uses. + */ +open class FunctionDispatcher( + val functions: Collection>, + val matchNumbersLeniently: Boolean = false, + val matchClassesQualnames: Boolean = false, + val resolveAmbiguousSpecificity: Boolean = false +) { + + // Could try to implement KCallable interface. But not sure it's be worth it or map closely enough— What do lambdas do? I guess isOpen, isAbstract, etc should just all be False? + + // Not supporting varargs for now. Doing so would require rebuilding the arguments array to move all vararg arguments into a new array for KCallable.call(). + + /** + * @return Whether a given argument value can be cast to the type of a given KParameter. + */ + private fun checkParameterMatches(kparam: KParameter, arg: Any?, paramKtypeAppend: ArrayList): Boolean { + // TODO: If performance becomes an issue, try inlining these. Then again, the JVM presumably optimizes it at runtime already (and there's far more calls than this containing function). + paramKtypeAppend.add(kparam.type) + if (arg == null) { + // Multiple dispatch of null between Any and Any? seems ambiguous in Kotlin even without reflection. + // Here, I'm resolving it myself, so it seems fine. + // However, with generics, even if I find the right KCallable, it seems that a nullable argument T? will usually (but not always, depending on each time you compile) be sent to the non-nullable T version of the function if one has been defined. + // KCallable.toString() shows the nullable signature, and KParam.name shows the argument name from the nullable version. But an exception is still thrown on .call() with null, and its text will use the argument name from the non-nullable version. + // I suppose it's not a problem here as it seems broken in Kotlin generally. + return kparam.type.isMarkedNullable + } + val kparamcls = kparam.type.jvmErasure + val argcls = arg::class + if (matchNumbersLeniently && argcls.isSubclassOf(Number::class) && kparamcls.isSubclassOf(Number::class)) { + // I think/hope this basically causes Java-style implicit conversions (or Kotlin implicit casts?). + // NOTE: However, doesn't correctly forbid unconvertible types. E.G. Doubles match against Floats. + // Info: https://docs.oracle.com/javase/specs/jls/se7/html/jls-5.html#jls-5.1.2 + return true + } + return kparamcls.isSuperclassOf(argcls) + // Seems to also work for generics, I guess. + || (matchClassesQualnames && kparamcls.qualifiedName != null && argcls.qualifiedName != null && kparamcls.qualifiedName == argcls.qualifiedName) + // Lets more types be matched to invariants, such as for vararg arrays. + // However, the JVM still throws its own error in that case, so leaving this disabled for now. + } + + /** + * @return Whether a given KCallable's signature might be compatible with a given Array of arguments. + */ + private fun checkCallableMatches(callable: KCallable, arguments: Array, paramKtypeAppends: HashMap, ArrayList>): Boolean { + // I'm not aware of any situation where this function's behaviour will deviate from Kotlin, but that doesn't mean there aren't any. Wait, no. I do know that runtime checking and resolution of erased generics will probably be looser than at compile time. They seem to act like Any(?). + val ktypeappend = arrayListOf() + paramKtypeAppends[callable] = ktypeappend + val params = callable.parameters + return params.size == arguments.size + && (params zip arguments).all { // Check argument classes match parameter types, skipping the receiver. + (kparam, arg) -> checkParameterMatches(kparam, arg, paramKtypeAppend=ktypeappend) + } + } + + /** + * @return The KCallables that have a signature which may be compatible with a given Array of arguments. + */ + private fun getMatchingCallables(arguments: Array, paramKtypeAppends: HashMap, ArrayList>): List> { + // Private because subclasses may choose to modify the arguments passed to call(). + return functions.filter { checkCallableMatches(it, arguments, paramKtypeAppends) } + } + + // Given a List of KCallables and a Map of their parameters' types, try to find one KCallable the signature of which is a subtype of all of the others. + + // For a KCallable to be returned, the following criteria must be true: + // Every relevant parameter type in it must be either the same as the corresponding parameter type in every other KCallable or a subtype of the corresponding parameter type in every other KCallable. + // At least one parameter type in it must be a strict subtype of the corresponding parameter type for every other KCallable. + + // + private fun getMostSpecificCallable(matches: List>, paramKtypes: Map, ArrayList>): KCallable? { + // Private because subclasses may choose to modify the arguments passed to call(). + // Should only be called when multiple/ambiguous signatures have been found. + return matches.firstOrNull { // Check all signatures. + val checkcallable = it // The signature we are currently checking for specificity. + val checkparamktypes = paramKtypes[checkcallable]!! + matches.all { // Compare currently checked signature to all other signatures. It must be a strict subtype of all other signatures. + val othercallable = it + if (checkcallable == othercallable) { + // Don't check against itself. + return@all true + } + var subtypefound = false + var supertypefound = false + for ((checkktype, otherktype) in checkparamktypes zip paramKtypes[othercallable]!!) { + // Compare all parameter types of currently checked signature to all parameter types of the other signature we are currently comparing it to. + if (checkktype == otherktype) { + // Identical types that neither allow nor forbid a match. + continue + } + if (!subtypefound && checkktype.isSubtypeOf(otherktype)) { + // At least one strict subtype is needed. + subtypefound = true + } + if (checkktype.isSupertypeOf(otherktype)) { + // No strict supertypes are allowed. + supertypefound = true + break + } + } + (subtypefound && !supertypefound) + // I did something similar for Cython once. Well, specifically, someone else had done something similar, and like this, it was running in exponential time or something, so I made it faster by building an index. + } + } + } + + /** + * Call the correct function for a given array of arguments. + * + * @param arguments The arguments with which to call the function. + * @return The result from dispatching the given arguments to the function definition with a compatible signature. + * @throws IllegalArgumentException If no compatible signature was found, or if more than one compatible signature was found. + */ + open fun call(arguments: Array): Any? { + // KCallable's .call() takes varargs instead of an array object. But spreads are expensive, so I'm not doing that. + // To test from Python: + // gameInfo.civilizations.add(1, civInfo) + // gameInfo.civilizations.add(civInfo) + // Both need to work. + val callableparamktypes = hashMapOf, ArrayList>() + // Map of all traversed KCallables to lists of their parameters' KTypes. + // Only parameters, and not arguments, are saved, though both are traversed. + // KCallables that don't match the call arguments should only have as many parameter KTypes saved as it took to find out they don't match. + val matches = getMatchingCallables(arguments, paramKtypeAppends=callableparamktypes) + var match: KCallable? = null + if (matches.size < 1) { + throw IllegalArgumentException("No matching signatures found for calling ${nounifyFunctions()} with given arguments: (${arguments.map{if (it == null) "null" else it::class?.simpleName ?: "null"}.joinToString(", ")})") + //FIXME: A lot of non-null assertions and null checks (not here, but generally in the codebase) can probably be replaced with safe calls. + } + if (matches.size > 1) { + if (resolveAmbiguousSpecificity) { + //Kotlin seems to choose the most specific signatures based on inheritance hierarchy. + //E.G. UncivGame.setScreen(), which uses a more specific parameter type than its GDX base class and thus creates a different, semi-ambiguous (sub-)signature, but still gets resolved. + match = getMostSpecificCallable(matches, paramKtypes = callableparamktypes) + } + if (match == null) { + throw IllegalArgumentException("Multiple matching signatures found for calling ${nounifyFunctions()} with given arguments:\n\t(${arguments.map{if (it == null) "null" else it::class?.simpleName ?: "null"}.joinToString(", ")})\n\t${matches.map{it.toString()}.joinToString("\n\t")}") + } + } else { + match = matches[0]!! + } + return match.call( + *arguments + ) + } + + /** + * @return A short, human-readable string that describes the target functions collectively. + */ + open fun nounifyFunctions() = "${functions.size} functions" +} diff --git a/core/src/com/unciv/scripting/reflection/Reflection.kt b/core/src/com/unciv/scripting/reflection/Reflection.kt index f544ff43a8097..88b20fe1f0d30 100644 --- a/core/src/com/unciv/scripting/reflection/Reflection.kt +++ b/core/src/com/unciv/scripting/reflection/Reflection.kt @@ -4,16 +4,8 @@ import com.unciv.scripting.utils.TokenizingJson import kotlin.collections.ArrayList import kotlin.reflect.KCallable import kotlin.reflect.KMutableProperty1 -import kotlin.reflect.KParameter import kotlin.reflect.KProperty1 -import kotlin.reflect.KType -import kotlin.reflect.full.isSubclassOf -import kotlin.reflect.full.isSubtypeOf -import kotlin.reflect.full.isSuperclassOf -import kotlin.reflect.full.isSupertypeOf -import kotlin.reflect.jvm.jvmErasure import kotlinx.serialization.Serializable -import java.util.* object Reflection { @@ -28,184 +20,54 @@ object Reflection { return property.get(instance) as R? } + // Return an [InstanceMethodDispatcher]() with consistent settings for the scripting API. + fun makeInstanceMethodDispatcher(instance: Any, methodName: String) = InstanceMethodDispatcher( + instance = instance, + methodName = methodName, + matchNumbersLeniently = true, + matchClassesQualnames = false, + resolveAmbiguousSpecificity = true + ) /** * Dynamic multiple dispatch for Any Kotlin instances by methodName. * - * Uses reflection to first find all members matching the expected method name, and then to narrow them down to members that have the correct signature for a given array of arguments. + * Uses reflection to first find all members matching the expected method name, and then to call the correct method for given arguments. * - * Varargs can be used, but they must be supplied as a single correctly typed array instead of separate arguments. + * See the [FunctionDispatcher] superclass for details on the method resolution strategy and configuration parameters. * * @property instance The receiver on which to find and call a method. * @property methodName The name of the method to resolve and call. - * @property matchNumbersLeniently Whether to treat all numeric types as the same. Useful for E.G. untyped deserialized data. Adds small extra step to most calls. - * @property matchClassesQualnames Whether to treat classes as the same if they have the same qualifiedName. Useful for E.G. ignoring the invariant arrays representing vararg parameters. Adds small extra step to many calls. - * @property resolveAmbiguousSpecificity Whether to try to resolve multiple ambigous matching signatures by finding one that strictly subtypes all others. Rules for this are documented under getMostSpecificCallable. No impact when not needed; Increases function domain properly handled but does not decrease performance in other uses. */ class InstanceMethodDispatcher( val instance: Any, val methodName: String, - val matchNumbersLeniently: Boolean = false, - val matchClassesQualnames: Boolean = false, - val resolveAmbiguousSpecificity: Boolean = false + matchNumbersLeniently: Boolean = false, + matchClassesQualnames: Boolean = false, + resolveAmbiguousSpecificity: Boolean = false + ) : FunctionDispatcher( + functions = instance::class.members.filter{ it.name == methodName }.map{ it as KCallable }, + matchNumbersLeniently = matchNumbersLeniently, + matchClassesQualnames = matchClassesQualnames, + resolveAmbiguousSpecificity = resolveAmbiguousSpecificity ) { - // TODO: Allow manually matching to supplied KCallables, E.G., KClass.constructors. - // Probably: Move actual resolution into a singleton, and prepend the receiver instance to the arguments here instead of skipping it its params during resolution. // This isn't just a nice-to-have feature. Before I implemented it, identical calls from demo scripts to methods with multiple versions (E.G. ArrayList().add()) would rarely but randomly fail because the member/signature that was found would change between runs or compilations. // TODO: This is going to need unit tests. - // Could try to implement KCallable interface. But not sure it's be worth it or map closely enough— What do lambdas do? I guess isOpen, isAbstract, etc should just all be False? - - // Not supporting varargs for now. Doing so would require rebuilding the arguments array to move all vararg arguments into a new array for KCallable.call(). - /** - * Lazily evaluated list of KCallables for every method that matches the given name. + * @return Helpful representative text. */ - val methods: List> by lazy { instance::class.members.filter{ it.name == methodName }.map{ it as KCallable } } - //Filter down to is KCallable if name collisions with properties are a possible issue. - - /** - * @return Useful representative text. - */ - override fun toString() = """${this::class.simpleName}(instance=${this.instance::class.simpleName}(), methodName="${this.methodName}") with ${this.methods.size} dispatch candidates""" + override fun toString() = """${this::class.simpleName}(instance=${this.instance::class.simpleName}(), methodName="${this.methodName}") with ${this.functions.size} dispatch candidates""" // Used by "docstring" packet action in ScriptingProtocol, which is in turn exposed in interpeters as help text. TODO: Could move to an extension method in ScriptingProtocol, I suppose. - /** - * @return Whether a given argument value can be cast to the type of a given KParameter. - */ - private fun checkParameterMatches(kparam: KParameter, arg: Any?, paramKtypeAppend: ArrayList): Boolean { - // TODO: If performance becomes an issue, try inlining these. Then again, the JVM presumably optimizes it at runtime already (and there's far more calls than this containing function). - // val ktype = kparam.type // Necessary. For some reason, accessing kparam.ktype here seems to cause kparam.ktype.isMarkedNullable to be incorrectly false in the first "if" block. - // val isMarkedNullable = ktype.isMarkedNullable - paramKtypeAppend.add(kparam.type) - if (arg == null) { - // Multiple dispatch of null between Any and Any? seems ambiguous in Kotlin even without reflection. - // Here, I'm resolving it myself, so it seems fine. - // However, with generics, even if I find the right KCallable, it seems that a nullable argument T? will usually (but not always, depending on each time you compile) be sent to the non-nullable T version of the function if one has been defined. - // KCallable.toString() shows the nullable signature, and KParam.name shows the argument name from the nullable version. But an exception is still thrown on .call() with null, and its text will use the argument name from the non-nullable version. - // I suppose it's not a problem here as it seems broken in Kotlin generally. - return kparam.type.isMarkedNullable - } - val kparamcls = kparam.type.jvmErasure - val argcls = arg::class - if (matchNumbersLeniently && argcls.isSubclassOf(Number::class) && kparamcls.isSubclassOf(Number::class)) { - // I think/hope this basically causes Java-style implicit conversions (or Kotlin implicit casts?). - // NOTE: However, doesn't correctly forbid unconvertible types. - // Info: https://docs.oracle.com/javase/specs/jls/se7/html/jls-5.html#jls-5.1.2 - return true - } - return kparamcls.isSuperclassOf(argcls) - // Seems to also work for generics, I guess. - || (matchClassesQualnames && kparamcls.qualifiedName != null && argcls.qualifiedName != null && kparamcls.qualifiedName == argcls.qualifiedName) - // Lets more types be matched to invariants, such as for vararg arrays. - // However, the JVM still throws its own error in that case, so leaving this disabled for now. - } - - /** - * @return Whether a given KCallable's signature might be compatible with a given Array of arguments. - */ - private fun checkCallableMatches(callable: KCallable, arguments: Array, paramKtypeAppends: HashMap, ArrayList>): Boolean { - // I'm not aware of any situation where this function's behaviour will deviate from Kotlin, but that doesn't mean there aren't any. Wait, no. I do know that runtime checking and resolution of erased generics will probably be looser than at compile time. They seem to act like Any(?). - val ktypeappend = arrayListOf() - paramKtypeAppends[callable] = ktypeappend - val params = callable.parameters - return params.size == arguments.size + 1 // Check that the parameters size is same as arguments size plus one for the receiver. - && (params.slice(1..arguments.size) zip arguments).all { // Check argument classes match parameter types, skipping the receiver. - (kparam, arg) -> checkParameterMatches(kparam, arg, paramKtypeAppend=ktypeappend) - } - } - - /** - * @return A list containing a KCallable for every version of this dispatcher's method that has a signature which may be compatible with a given Array of arguments. - */ - fun getMatchingCallables(arguments: Array, paramKtypeAppends: HashMap, ArrayList>): List> { - return methods.filter { checkCallableMatches(it, arguments, paramKtypeAppends) } - } - - // Given a List of KCallables and a Map of their parameters' types, try to find one KCallable the signature of which is a subtype of all of the others. - - // For a KCallable to be returned, the following criteria must be true: - // Every relevant parameter type in it must be either the same as the corresponding parameter type in every other KCallable or a subtype of the corresponding parameter type in every other KCallable. - // At least one parameter type in it must be a strict subtype of the corresponding parameter type for every other KCallable. - - // - fun getMostSpecificCallable(matches: List>, paramKtypes: Map, ArrayList>): KCallable? { - // Should only be called when multiple/ambiguous signatures have been found. - return matches.firstOrNull { // Check all signatures. - val checkcallable = it // The signature we are currently checking for specificity. - val checkparamktypes = paramKtypes[checkcallable]!! - matches.all { // Compare currently checked signature to all other signatures. It must be a strict subtype of all other signatures. - val othercallable = it - if (checkcallable == othercallable) { - // Don't check against itself. - return@all true - } - var subtypefound = false - var supertypefound = false - for ((checkktype, otherktype) in checkparamktypes zip paramKtypes[othercallable]!!) { - // Compare all parameter types of currently checked signature to all parameter types of the other signature we are currently comparing it to. - if (checkktype == otherktype) { - // Identical types have no implications. - continue - } - if (!subtypefound && checkktype.isSubtypeOf(otherktype)) { - // At least one strict subtype is needed. - subtypefound = true - } - if (checkktype.isSupertypeOf(otherktype)) { - // No strict supertypes are allowed. - supertypefound = true - break - } - } - (subtypefound && !supertypefound) - // I did something similar for Cython once. Well, specifically, someone else had done something similar, and like this, it was running in exponential time or something, so I made it faster by building an index. - } - } + override fun call(arguments: Array): Any? { + return super.call(arrayOf(instance, *arguments)) + // Add receiver to arguments. } - /** - * Call the correct version of the method for a given array of arguments. - * - * @param arguments The arguments with which to call the method. - * @return The result from dispatching the given arguments to the method definition with a compatible signature. - * @throws IllegalArgumentException If no compatible signature was found, or if more than one compatible signature was found. - */ - fun call(arguments: Array): Any? { - // KCallable's .call() takes varargs instead of an array object. But spreads are expensive, so I'm not doing that. - // To test from Python: - // gameInfo.civilizations.add(1, civInfo) - // gameInfo.civilizations.add(civInfo) - // Both need to work. - val callableparamktypes = hashMapOf, ArrayList>() - // Map of all traversed KCallables to lists of their parameters' KTypes. - // Only parameters, and not arguments, are saved, though both are traversed. - // KCallables that don't match the call arguments should only have as many parameter KTypes saved as it took to find out they don't match. - val matches = getMatchingCallables(arguments, paramKtypeAppends=callableparamktypes) - var match: KCallable? = null - if (matches.size < 1) { - throw IllegalArgumentException("No matching signatures found for calling ${instance::class?.simpleName}.${methodName} with given arguments: (${arguments.map{if (it == null) "null" else it::class?.simpleName ?: "null"}.joinToString(", ")})") - //FIXME: A lot of non-null assertions and null checks (not here, but generally in the codebase) can probably be replaced with safe calls. - } - if (matches.size > 1) { - if (resolveAmbiguousSpecificity) { - //Kotlin seems to choose the most specific signatures based on inheritance hierarchy. - //E.G. UncivGame.setScreen(), which uses a more specific parameter type than its GDX base class and thus creates a different, semi-ambiguous (sub-)signature, but still gets resolved. - match = getMostSpecificCallable(matches, paramKtypes = callableparamktypes) - } - if (match == null) { - throw IllegalArgumentException("Multiple matching signatures found for calling ${methodName} with given arguments:\n\t(${arguments.map{if (it == null) "null" else it::class?.simpleName ?: "null"}.joinToString(", ")})\n\t${matches.map{it.toString()}.joinToString("\n\t")}") - } - } else { - match = matches[0]!! - } - return match.call( - instance, - *arguments - ) - } + override fun nounifyFunctions() = "${instance::class?.simpleName}.${methodName}" } @@ -219,13 +81,13 @@ object Reflection { } - fun setInstanceProperty(instance: Any, propertyName: String, value: T?): Unit { + fun setInstanceProperty(instance: Any, propertyName: String, value: T?) { val property = instance::class.members .first { it.name == propertyName } as KMutableProperty1 property.set(instance, value) } - fun setInstanceItem(instance: Any, keyOrIndex: Any, value: Any?): Unit { + fun setInstanceItem(instance: Any, keyOrIndex: Any, value: Any?) { if (keyOrIndex is Int) { (instance as MutableList)[keyOrIndex] = value } else { @@ -233,7 +95,7 @@ object Reflection { } } - fun removeInstanceItem(instance: Any, keyOrIndex: Any): Unit { + fun removeInstanceItem(instance: Any, keyOrIndex: Any) { if (keyOrIndex is Int) { (instance as MutableList).removeAt(keyOrIndex) } else { @@ -242,10 +104,10 @@ object Reflection { } - enum class PathElementType() { - Property(), - Key(), - Call() + enum class PathElementType { + Property, + Key, + Call } @Serializable @@ -277,7 +139,7 @@ object Reflection { fun parseKotlinPath(code: String): List { var path: MutableList = ArrayList() - var curr_type = PathElementType.Property + //var curr_type = PathElementType.Property var curr_name = ArrayList() var curr_brackets = "" var curr_bracketdepth = 0 @@ -381,12 +243,9 @@ object Reflection { try { obj = readInstanceProperty(obj!!, element.name) } catch (e: ClassCastException) { - obj = InstanceMethodDispatcher( + obj = makeInstanceMethodDispatcher( obj!!, - element.name, - matchNumbersLeniently = true, - matchClassesQualnames = false, - resolveAmbiguousSpecificity = true + element.name ) } } @@ -402,7 +261,8 @@ object Reflection { PathElementType.Call -> { // TODO: Handle invoke operator. Easy enough, just recurse to access the .invoke. // Maybe TODO: Handle lambdas. E.G. WorldScreen.nextTurnAction. Honestly, it may be better to just expose wrapping non-lambdas. - obj = (obj as InstanceMethodDispatcher).call( + obj = (obj as FunctionDispatcher).call( + // Undocumented implicit behaviour: Using the last object means that this should work with explicitly created FunctionDispatcher()s. ( if (element.doEval) splitToplevelExprs(element.name).map{ evalKotlinString(instance!!, it) } @@ -411,9 +271,9 @@ object Reflection { ).toTypedArray() ) } - else -> { - throw UnsupportedOperationException("Unknown path element type: ${element.type}") - } +// else -> { +// throw UnsupportedOperationException("Unknown path element type: ${element.type}") +// } } } return obj @@ -446,7 +306,7 @@ object Reflection { } - fun setInstancePath(instance: Any, path: List, value: Any?): Unit { + fun setInstancePath(instance: Any, path: List, value: Any?) { val leafobj = resolveInstancePath(instance, path.slice(0..path.size-2)) val leafelement = path[path.size - 1] when (leafelement.type) { @@ -466,13 +326,13 @@ object Reflection { PathElementType.Call -> { throw UnsupportedOperationException("Cannot assign to function call.") } - else -> { - throw UnsupportedOperationException("Unknown path element type: ${leafelement.type}") - } +// else -> { +// throw UnsupportedOperationException("Unknown path element type: ${leafelement.type}") +// } } } - fun removeInstancePath(instance: Any, path: List): Unit { + fun removeInstancePath(instance: Any, path: List) { val leafobj = resolveInstancePath(instance, path.slice(0..path.size-2)) val leafelement = path[path.size - 1] when (leafelement.type) { @@ -491,9 +351,9 @@ object Reflection { PathElementType.Call -> { throw UnsupportedOperationException("Cannot remove function call.") } - else -> { - throw UnsupportedOperationException("Unknown path element type: ${leafelement.type}") - } +// else -> { +// throw UnsupportedOperationException("Unknown path element type: ${leafelement.type}") +// } } } } diff --git a/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt b/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt index a8fd237b2fc9f..55058002db32f 100644 --- a/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt +++ b/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt @@ -4,8 +4,8 @@ import com.unciv.scripting.ScriptingScope import kotlin.reflect.KCallable import kotlin.reflect.KClass import kotlin.reflect.KFunction -import kotlin.reflect.KProperty1 -import com.badlogic.gdx.utils.Json +//import kotlin.reflect.KProperty1 +//import com.badlogic.gdx.utils.Json // Automatically running this should probably be a part of the build process. @@ -14,6 +14,9 @@ import com.badlogic.gdx.utils.Json // TODO: See UniqueDocsWriter.kt. https://github.com/yairm210/Unciv/commit/4617bc21a70f4f4bd29dc80ba7d648349c9fc3f8 +// Honestly, probably just deprecate this whole thing. Recent experiments with factories and enums shows how hard and messy parsing/reflecting Kotlin/JVM code structure can be, and the dynamically generated, lazily evaluated wrappers model used in the Python API works better and more flexibly than I ever expected anything using this system to. + + data class ApiSpecDef( var path: String, var isIterable: Boolean, diff --git a/core/src/com/unciv/scripting/utils/InstanceTokenizer.kt b/core/src/com/unciv/scripting/utils/InstanceTokenizer.kt index f41c3207df6bb..fe3d66695912e 100644 --- a/core/src/com/unciv/scripting/utils/InstanceTokenizer.kt +++ b/core/src/com/unciv/scripting/utils/InstanceTokenizer.kt @@ -1,7 +1,7 @@ package com.unciv.scripting.utils import com.unciv.scripting.ScriptingConstants -import kotlin.math.min +//import kotlin.math.min import java.lang.ref.WeakReference import java.util.UUID @@ -35,7 +35,7 @@ object InstanceTokenizer { /** * Length to clip generated token strings to. Here in case token string generation uses the instance's .toString(), which it currently does. */ - private val tokenMaxLength = 100 + private const val tokenMaxLength = 100 /** * Generate a distinctive token string to represent a Kotlin/JVM object. @@ -70,7 +70,7 @@ object InstanceTokenizer { /** * Remove all tokens and WeakReferences whose instances have already been garbage-collected. */ - fun clean(): Unit { + fun clean() { //FIXME (if I become a problem): Because a new unique token is currently generated even if the instance is already tokenized as something else, this will eventually get slower over time if a script makes lots of requests that result in new instance tokens for objects that last a long time (E.G. uncivGame). And since any stored instances should ideally be WeakReferences to prevent garbage collection from being broken for *all* instances, fixing that may not be as simple as checking for existing tokens to reuse them. //TODO: Probably keep another map of instance hashes to weakrefs and their existing token? The hashes will have to have counts instead of just containment, since otherwise a hash collision would cause the earlier token to become inaccessible. val badtokens = mutableListOf() diff --git a/core/src/com/unciv/scripting/utils/ScriptingApiAccessible.kt b/core/src/com/unciv/scripting/utils/ScriptingApiAccessible.kt index fdc7e3f6cee8f..5a2f77bb50cef 100644 --- a/core/src/com/unciv/scripting/utils/ScriptingApiAccessible.kt +++ b/core/src/com/unciv/scripting/utils/ScriptingApiAccessible.kt @@ -3,7 +3,7 @@ package com.unciv.scripting.utils enum class ScriptingApiExposure { All, Player, Cheats, Mods -} // Mods should have more restrictive access to system-facing memebrs than everything else, since they're the only untrusted code. +} // Mods should have more restrictive access to system-facing members than everything else, since they're the only untrusted code. @Retention(AnnotationRetention.RUNTIME) annotation class ScriptingApiAccessible( diff --git a/core/src/com/unciv/scripting/utils/SourceManager.kt b/core/src/com/unciv/scripting/utils/SourceManager.kt index 6adbf3b958484..4f459d9db98d1 100644 --- a/core/src/com/unciv/scripting/utils/SourceManager.kt +++ b/core/src/com/unciv/scripting/utils/SourceManager.kt @@ -1,8 +1,8 @@ package com.unciv.scripting.utils -import com.badlogic.gdx.Gdx +//import com.badlogic.gdx.Gdx import com.badlogic.gdx.files.FileHandle -import com.unciv.JsonParser +//import com.unciv.JsonParser import com.unciv.scripting.ScriptingConstants //import com.unciv.scripting.ScriptingConstants import kotlin.concurrent.thread @@ -34,7 +34,7 @@ object SourceManager { val enginedir = getEngineLibraries(engine) val outdir = FileHandle.tempDirectory("unciv-${engine}_") fun addfile(sourcedir: FileHandle, path: String) { - var target = outdir.child(path) + val target = outdir.child(path) if (path.endsWith("/")) { target.mkdirs() } else { diff --git a/core/src/com/unciv/scripting/utils/StringifyException.kt b/core/src/com/unciv/scripting/utils/StringifyException.kt index 1a8391fcb725b..00c08dd76d033 100644 --- a/core/src/com/unciv/scripting/utils/StringifyException.kt +++ b/core/src/com/unciv/scripting/utils/StringifyException.kt @@ -2,12 +2,11 @@ package com.unciv.scripting.utils /** - * @param exception Any Exception. * @return String of exception preceded by entire stack trace. */ -fun stringifyException(exception: Exception): String { +fun Exception.stringifyException(): String { val causes = arrayListOf() - var cause: Throwable? = exception + var cause: Throwable? = this while (cause != null) { causes.add(cause) cause = cause.cause @@ -15,7 +14,7 @@ fun stringifyException(exception: Exception): String { } return listOf( "\n", - *exception.getStackTrace(), + *this.stackTrace, "\n", *causes.asReversed().map{ it.toString() }.toTypedArray() ).joinToString("\n") diff --git a/core/src/com/unciv/scripting/utils/TokenizingJson.kt b/core/src/com/unciv/scripting/utils/TokenizingJson.kt index 91ed6af865f4f..6a6b4033016b4 100644 --- a/core/src/com/unciv/scripting/utils/TokenizingJson.kt +++ b/core/src/com/unciv/scripting/utils/TokenizingJson.kt @@ -1,7 +1,7 @@ package com.unciv.scripting.utils import kotlinx.serialization.* -import kotlinx.serialization.builtins.serializer +//import kotlinx.serialization.builtins.serializer import kotlinx.serialization.descriptors.* import kotlinx.serialization.encoding.* import kotlinx.serialization.json.Json @@ -11,8 +11,8 @@ import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonNull import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.decodeFromJsonElement -import kotlinx.serialization.modules.SerializersModule +//import kotlinx.serialization.json.decodeFromJsonElement +//import kotlinx.serialization.modules.SerializersModule // TODO: Move to serialization package with InstanceTokenizer.kt @@ -115,21 +115,26 @@ object TokenizingJson { return value } if (!isTokenizationMandatory(value)) { - if (value is Map<*, *>) { //TODO: Decide what to do with non-string keys... I guess I could tokenize them? - return JsonObject( (value as Map).mapValues{ getJsonElement(it.value) } ) + if (value is Map<*, *>) { + return JsonObject( (value as Map).entries.associate { + json.encodeToString(getJsonElement(it.key)) to getJsonElement(it.value) + // TODO: Currently, this means that string keys are encoded with quotes as part of the string. + // Serialized keys are additionally different from Kotlin/JVM keys exposed reflectively. + // Treating keys as their own JSON strings may be the only way to let all types of values be represented unambiguously in keys, though. + // TODO: More testing/documentation is needed. + } ) } if (value is Iterable<*>) { // Apparently ::class.java.isArray can be used to check for primitive arrays, but it breaks return JsonArray(value.map{ getJsonElement(it) }) } if (value is Sequence<*>) { - var v = (value as Sequence).toList() - return getJsonElement(v) + return getJsonElement((value as Sequence).toList()) } if (value is String) { return JsonPrimitive(value as String) } - if (value is Int || value is Long || value is Float || value is Double) { + if (value is Number) {//(value is Int || value is Long || value is Float || value is Double) { return JsonPrimitive(value as Number) } if (value is Boolean) { //TODO: Arrays and primitive arrays? @@ -156,7 +161,9 @@ object TokenizingJson { return (value as List).map{ getJsonReal(it) }// as List } if (value is JsonObject) { - return (value as Map).mapValues{ getJsonReal(it.value) }// as Map + return (value as Map).entries.associate{ + InstanceTokenizer.getReal(it.key) to getJsonReal(it.value) + }// as Map } if (value is JsonPrimitive) { val v = value as JsonPrimitive @@ -164,6 +171,8 @@ object TokenizingJson { return InstanceTokenizer.getReal(v.content) } else { return v.content.toIntOrNull() + ?: v.content.toLongOrNull() + ?: v.content.toFloatOrNull() // NOTE: This may prevent .toDoubleOrNull() from ever being used. I think the implicit number type conflation in FunctionDispatcher means that Floats can still be used where Doubles are expected, though. ?: v.content.toDoubleOrNull() ?: v.content.toBooleanStrictOrNull() ?: v.content diff --git a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt index e7a955ceeca04..2158a188f17b0 100644 --- a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt @@ -2,8 +2,6 @@ package com.unciv.ui.consolescreen import com.badlogic.gdx.Input import com.badlogic.gdx.graphics.Color -import com.badlogic.gdx.scenes.scene2d.Actor -import com.badlogic.gdx.scenes.scene2d.ui.Cell import com.badlogic.gdx.scenes.scene2d.ui.Image import com.badlogic.gdx.scenes.scene2d.ui.Label import com.badlogic.gdx.scenes.scene2d.ui.SplitPane @@ -11,7 +9,6 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.TextButton import com.badlogic.gdx.scenes.scene2d.ui.TextField import com.unciv.Constants -import com.unciv.scripting.ScriptingBackendBase import com.unciv.scripting.ScriptingBackendType import com.unciv.scripting.ScriptingState import com.unciv.ui.utils.* @@ -286,12 +283,12 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un } } - enum class SetTextCursorMode() { - End(), - Unchanged(), - Insert(), - SelectAll(), - SelectAfter() + enum class SetTextCursorMode { + End, + Unchanged, + Insert, + SelectAll, + SelectAfter } } From 1cb4e3c52e73a60907381ac3b91b669f141bfed9 Mon Sep 17 00:00:00 2001 From: will-ca Date: Tue, 7 Dec 2021 00:36:24 +0000 Subject: [PATCH 59/93] Comments, notes, style. Add many TODOs. More reflective API helpers for JVM/Kotlin. Statics, top levels. --- .../enginefiles/python/PythonScripting.md | 20 +++- .../scripting/enginefiles/python/main.py | 4 + .../enginefiles/python/unciv_lib/api.py | 5 + .../python/unciv_lib/autocompletion.py | 5 + .../enginefiles/python/unciv_lib/ipc.py | 51 --------- .../enginefiles/python/unciv_lib/wrapping.py | 4 +- .../unciv_scripting_examples/LifesOut.py | 36 ++++++ .../python/unciv_scripting_examples/Tests.py | 11 +- .../assets/scripting/enginefiles/qjs/main.js | 58 +++++++++- .../com/unciv/scripting/ScriptingBackend.kt | 39 ++++--- .../src/com/unciv/scripting/ScriptingScope.kt | 22 +++- .../src/com/unciv/scripting/ScriptingState.kt | 27 +++-- .../scripting/api/{LazyMap.kt => FakeMaps.kt} | 18 ++- .../unciv/scripting/api/ScriptingApiEnums.kt | 8 +- .../scripting/api/ScriptingApiFactories.kt | 107 +++++++++--------- .../api/ScriptingApiInstanceRegistry.kt | 39 ++++++- .../scripting/api/ScriptingApiMappers.kt | 10 +- .../scripting/protocol/ScriptingProtocol.kt | 8 +- .../scripting/protocol/SubprocessBlackbox.kt | 16 ++- .../reflection/FunctionDispatcher.kt | 10 +- .../unciv/scripting/reflection/Reflection.kt | 35 +++--- .../unciv/scripting/utils/ApiSpecGenerator.kt | 10 +- .../scripting/utils/InstanceTokenizer.kt | 10 +- .../unciv/scripting/utils/SourceManager.kt | 1 + .../scripting/utils/StringifyException.kt | 2 +- .../unciv/scripting/utils/TokenizingJson.kt | 8 +- .../unciv/ui/consolescreen/ConsoleScreen.kt | 13 ++- .../consolescreen/IConsoleScreenAccessible.kt | 7 +- .../com/unciv/ui/worldscreen/WorldScreen.kt | 2 +- .../ui/worldscreen/mainmenu/OptionsPopup.kt | 7 +- 30 files changed, 391 insertions(+), 202 deletions(-) create mode 100644 android/assets/scripting/enginefiles/python/unciv_scripting_examples/LifesOut.py rename core/src/com/unciv/scripting/api/{LazyMap.kt => FakeMaps.kt} (69%) diff --git a/android/assets/scripting/enginefiles/python/PythonScripting.md b/android/assets/scripting/enginefiles/python/PythonScripting.md index 64e3a4b5ec552..25acd1b3d86a5 100644 --- a/android/assets/scripting/enginefiles/python/PythonScripting.md +++ b/android/assets/scripting/enginefiles/python/PythonScripting.md @@ -17,7 +17,19 @@ There are basically two types of Python objects in this API: A **wrapper** is an object that stores a list of attribute names, keys, and function call parameters corresponding to a path in the Kotlin/JVM namespace. E.G.: `"civInfo.civilizations[0].population.setPopulation"` is a string representation of a wrapped path that begins with two attribute accesses, followed by one array index, and two more attribute names. -A wrapper object does not store any more values than that. When it is evaluated, it uses a simple IPC protocol to request an up-to-date real value from the game's Kotlin code. The `unciv_lib.api.real()`/`unciv_pyhelpers.real()` function can be used to manually get a real Python value from a foreign instance wrapper. +```python3 +[ + {'type': 'Property', 'name': 'someAttribute', 'params': []}, + {'type': 'Key', 'name': , 'params': [5]}, + {'type': 'Call', 'name': , 'params': ["arg1", "arg2"]} +] +# A path such as those used internally by wrapper objects. + +Scope.someAttribute[5]("arg1", "arg2") +# Equivalent expression of the path. +``` + +A wrapper object does not store any more values than that. It is basically an unsent packet that contains only the path where the value it represents can be found, and not the value itself. When it is evaluated, it uses a simple IPC protocol to request an up-to-date real value from the game's Kotlin code. The `unciv_lib.api.real()`/`unciv_pyhelpers.real()` function can be used to manually get a real Python value from a foreign instance wrapper. However, because the wrapper class implements many Magic Methods that automatically call its evaluation method, many programming idioms common in Python are possible with them even without manual evaluation. Comparisons, equality, arithmetic, concatenation, in-place operations and more are all supported. @@ -164,6 +176,8 @@ To get around this, you can use the foreign token to assign the object it repres The `apiHelpers.registeredInstances` helper object can be used for this: +TODO: In most cases, instancesAsInstances is better than registeredInstances. registeredInstances is needed only when persisting a Kotlin data structure between script executions (…Which— Why would you do?). + ```python3 token = civInfo.cities[0].getCenterTile() # Token string representing a `TileInfo()` instance. @@ -495,6 +509,10 @@ The only major caveat to the robustness of this error handling is that it does n --- +Top-level functions are translated by the Kotlin compiler into static methods in JVM classes named by appending 'Kt' to the package plus name of the file in which they're defined. TODO. + +--- + ## Other Languages The Python-specific behaviour is not meant as a hard standard, in that it doesn't have to be copied exactly in any other languages. Some other design may be more suited for ECMAScript, Lua, and other possible backends. If implementing another language, I think some attempt should still be made to keep a similar API and feature equivalence, though. diff --git a/android/assets/scripting/enginefiles/python/main.py b/android/assets/scripting/enginefiles/python/main.py index 7d39fea2988ad..0ffc68c71ccdb 100644 --- a/android/assets/scripting/enginefiles/python/main.py +++ b/android/assets/scripting/enginefiles/python/main.py @@ -2,6 +2,10 @@ # Example: https://lwn.net/Articles/574215/ # Even if Python's sandboxed, the full reflective access on the Kotlin/JVM side isn't. +# Huh: https://stackoverflow.com/questions/15093663/packaging-linux-binary-in-android-apk + +#"""Due to the massive standard library and tbird-party libraries available to Python, due to the similarly heavy footprint of the CPython interpreter, the recommended use cases of this scripting backend are user automation, prototyping, and experimentation or research. For mods, use the JS backend instead.""" + try: import os with open(os.path.join(os.path.dirname(__file__), "PythonScripting.md"), 'r') as f: diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/api.py b/android/assets/scripting/enginefiles/python/unciv_lib/api.py index 0d207fbaae78a..172482cd202ac 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/api.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/api.py @@ -85,6 +85,11 @@ def isForeignToken(obj): resolved = real(obj) return isinstance(resolved, str) and resolved.startswith(apiconstants['kotlinInstanceTokenPrefix']) +@expose() +def pathcodeFromWrapper(wrapper): + return wrapping.stringPathList(wrapper._getpath_()) + # TODO + class UncivReplTransceiver(ipc.ForeignActionReceiver, ipc.ForeignActionSender): """Class that implements the Unciv IPC and scripting protocol by receiving and responding to its packets. See Module.md.""" diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py b/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py index 8e3b154626060..efe107089bee4 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py @@ -57,6 +57,11 @@ def GetAutocomplete(self, command): """Return either a sequence of full autocomplete matches or a help string for a given command.""" +# class AstAutocompleteManager(AutocompleteManager): + # pass + # TODO. Or not. Somehow sounds messier than string parsing. + + class PyAutocompleteManager(AutocompleteManager): """Advanced autocompleter. Returns keys when accessing mappings. Implements API that returns docstrings as help text for callables. Adds opening round and square brackets to autocomplete matches to show callables and mappings.""" # FIXME: Dot after a mapping fails. diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/ipc.py b/android/assets/scripting/enginefiles/python/unciv_lib/ipc.py index 36f106ad2eec7..57475908225ac 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/ipc.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/ipc.py @@ -2,11 +2,6 @@ stdout = sys.stdout -#def ResolvePath(path, scope): -## return eval(path, scope, scope) -# raise NotImplementedError() - # Needed if access to Python internals from Kotlin is ever implemented. But that would go against the current execution model. (See Module.md/REPL Loop.) - class IpcJsonEncoder(json.JSONEncoder): """JSONEncoder that lets classes define a special ._ipcjson_() method to control how they'll be serialized. Used by ForeignObject to send its resolved value.""" def default(self, obj): @@ -121,49 +116,3 @@ def __enter__(self): return self.fakeout def __exit__(self, *exc): sys.stdout = self.stdout - -#class ForeignActionBindingSender(ForeignActionSender): -# #Probably easier to just define these as needed in the classes that call them. -# def SendForeignCall(self, path, args, kwargs): -# return self.GetForeignActionResponse({'action': ('call', 'data': {'path':path, 'args':args, 'kwargs':kwargs}}, 'call_response') -# def SendForeignRead(self, path): -# identifier = self.MakeUniqueID() -# return self.GetForeignActionResponse({'action': 'read', 'data': {'path':path}}, 'read_response') -# def SendForeignAssign(self, path, value): -# return self.GetForeignActionResponse({'action': 'assign', 'data': {'path':path, 'value':value}}) -# def MakeForeignFunction(self, callpath, callargs): -# pass - -#class ForeignActionBindingReceiver(ForeignActionReceiver): -# # This is nice to have, but basically useless, right? In the current model, there shouldn't be any circumstances where the Kotlin code explicitly changes or is even aware of the state of the script interpreter, since the Kotlin side has to deal with any number of languages, all the data lives on the Kotlin side and it's thus the script interpreter's job to request what it needs, and all communication is through standardized requests for either Kotlin-side reflection or a handful of REPL functions and raw code eval on the scripting side -# def foreignCallEvaluator(func): -# def _foreignCallEvaluator(self, *args, **kwargs): -# try: -# pass -# except: -# pass -# def ResolvePath(self, path): -# return eval(path, self.scope, self.scope) -# # NOTE: This should be replaced with recursive parsing into `getattr()` and `[]`/`.__getitem__`/`.__getindex__` if arbitrary code has the chance to be passed. -# def EvalForeignCall(self, packet): -# packet.enforce_type('call') -# return self.ResolvePath(packet.data['path'])(*packet.data['args'], **packet.data['kwargs']) -# def EvalForeignRead(self, packet): -# packet.enforce_type('read') -# return self.ResolvePath(packet.data['path']) -# def ExecForeignAssign(self, packet): -# packet.enforce_type('assign') -# path = packet.data['path'] -# value = packet.data['value'] -# if path[-1] == ']': -# spath = path.rpartition('[') -# self.ResolvePath(spath[0])[spath[-1][:-1]] = value -# else: -# spath = path.rpartition('.') -# if spath[0]: -# setattr(self.ResolvePath(spath[0]), spath[-1], value) -# else: -# self.scope[spath[-1]] = value - - - diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py index f241a5e7beda8..623f0f1b35787 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py @@ -50,7 +50,7 @@ def _reversedop(a, b, *args, **kwargs): _reversedop.__doc__ = f"{func.__doc__ or name + ' operator.'}\n\nReversed version." return _reversedop -def InplaceMethod(func): +def InplaceMethod(func): # TODO: Lower-case these names? """Return a wrapped a function that calls ._setvalue_() on its first self argument with its original result.""" def _inplacemethod(self, *args, **kwargs): self._setvalue_(func(self, *args, **kwargs)) @@ -79,7 +79,7 @@ def stringPathList(pathlist): if p['type'] == 'Property': items.append(f".{p['name']}") if p['type'] == 'Key': - items.append(f"[{p['params'][0]}]") + items.append(f"[{json.dumps(p['params'][0], cls=ipc.IpcJsonEncoder)}]") if p['type'] == 'Call': items.append(f"({', '.join(p['params'])}])") return "".join(items) diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/LifesOut.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/LifesOut.py new file mode 100644 index 0000000000000..aee27f38685f0 --- /dev/null +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/LifesOut.py @@ -0,0 +1,36 @@ +""" +Demo and experimental mod for script-created and script-controlled GUI elements. + +Adds combination Lights Out and Conways Game of Life minigame. + + +Call showPopup() to start playing. + +Call injectAnywhere() to create a UI button to launch the game. + + +Due to performance and stability concerns, it's not recommended for a mod to write a lot of code like this. + +But it can be done. +""" + + +# Actually, a Tic-Tac-Toe game with open trade deals for stake would be a better and easier demo. + +#get apiHelpers.instancesAsInstances[apiHelpers.Factories.constructorByQualname["com.unciv.ui.utils.Popup"](uncivGame.consoleScreen)].open(False) + +def showPopup(): + pass + + +def injectWorldScreen(): + pass + +def injectMainMenu(): + pass + +def injectGeneric(): + pass + +def injectAnywhere(): + pass diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py index e544f8b616054..61b47aba1bb1b 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py @@ -56,7 +56,7 @@ def __enter__(self): unciv.uncivGame.setScreen( unciv.apiHelpers.Factories.constructorByQualname['com.unciv.ui.mapeditor.MapEditorScreen'](gameinfo.tileMap) # SetScreen doesn't seem to be needed here. But that seems like a glitch in the core Unciv code. - )#unciv.apiHelpers.Factories.Gui.MapEditorScreen(gameinfo.tileMap)) + ) def __exit__(self, *exc): goToMainMenu() @@ -112,7 +112,14 @@ def _print(*args, **kwargs): _print(f"Python test PASSED: {test.name}") _print("\n") if failures: - exc = AssertionError(f"{len(failures)} Python tests FAILED: {[test.name for test in failures]}\n\n") + failcounts = {} + for exc in failures.values(): + exc_name = exc.__class__.__name__ + if exc_name not in failcounts: + failcounts[exc_name] = 0 + failcounts[exc_name] += 1 + del exc_name + exc = AssertionError(f"{len(failures)} Python tests FAILED: {[test.name for test in failures]}\nFailure types: {failcounts}\n\n") _print(exc) raise exc else: diff --git a/android/assets/scripting/enginefiles/qjs/main.js b/android/assets/scripting/enginefiles/qjs/main.js index 1a0d8058a9dc2..fe19633498511 100644 --- a/android/assets/scripting/enginefiles/qjs/main.js +++ b/android/assets/scripting/enginefiles/qjs/main.js @@ -1,3 +1,6 @@ +`The recommended use case of this backend is for modding. For user automation, debug, prototyping, or experimentation, the Python backend may provide more features.` + + function motd() { return "\nThis backend is HIGHLY EXPERIMENTAL. It does not implement any API bindings yet, and it may not be stable. Use it at your own risk!\n\n" } @@ -6,6 +9,9 @@ function motd() { // Maybe LiquidCore? But I assume that's way heavier. +// QuickJS, like CPython, has a C API for native modules. But that probably shouldn't be used here. + + ` Due to the basic design of Python, any Python environment that you lock down enough to be safe will also be nearly useless. @@ -20,12 +26,58 @@ Instead, CPython can be the favoured interpreter for developer tools and user sc So JS and Lua can be made highly portable/lightweight, and safely sandboxed to run mods. Meanwhile, CPython, if it's installed on the user's system, can be used as a richer scripting environment for developer/modder tools and user customization. ` +function ContextManager() { +} +Object.assign(ContextManager.prototype, { + enter: function() { + return this; + }, + exit: function(exception) { + return false; + }, + withRun: function(callfunc) { + let value = this.enter(); + let error = null; + let result = undefined; + try { + result = callfunc(value); + } catch (e) { + error = e; + } + if (this.exit(error)) { + throw error; + } + return result; + } +}); + +function FakeStdOut() { +} +FakeStdOut.prototype = Object.assign(Object.create(ContextManager.prototype), { + enter: function() { + this.fakeout = [] + }, + exit: function() { + } +}); + +function makeScopeProxy() { + +} + +//let handlers={get: (target, prop, receiver) => prop == 'real' ? target : new Proxy([...target, prop], handlers)}; let p=new Proxy([], handlers) +// Chrome, Node, and SpiderMonkey can print this fine. QuickJS and Deno use inspection in their REPL. +// TODO: Implement JS bindings. + +// https://stackoverflow.com/questions/9781285/specify-scope-for-eval-in-javascript +// https://www.figma.com/blog/how-we-built-the-figma-plugin-system/#attempt-3-realms +// https://stackoverflow.com/questions/37010237/android-how-to-use-isolatedprocess try { - while (true) { + while (false) { let line = std.in.getline(); if (line === null) { - // std.in.getline() returns null in case of IOError, broken pipes. So this check prevents it from eating 100% CPU in a loop, which could previously happen if you closed Unciv with the window button. + // std.in.getline() returns null in case of error. So this check prevents it from eating 100% CPU in a loop, which could previously happen if you closed Unciv with the window button. throw Error("Null on STDIN.") } let out = `qjs > ${line}\n`; @@ -40,5 +92,5 @@ try { } } catch (e) { } finally { - std.exit() + //std.exit() } diff --git a/core/src/com/unciv/scripting/ScriptingBackend.kt b/core/src/com/unciv/scripting/ScriptingBackend.kt index f0b8d4a0eccad..8eb5a74d65000 100644 --- a/core/src/com/unciv/scripting/ScriptingBackend.kt +++ b/core/src/com/unciv/scripting/ScriptingBackend.kt @@ -12,7 +12,7 @@ import com.unciv.scripting.protocol.SubprocessBlackbox import com.unciv.scripting.utils.ApiSpecGenerator import com.unciv.scripting.utils.SourceManager import com.unciv.scripting.utils.SyntaxHighlighter -import kotlin.reflect.full.* +import kotlin.reflect.full.companionObjectInstance //import java.util.* @@ -43,14 +43,11 @@ abstract class ScriptingBackend_metadata { abstract fun new(scriptingScope: ScriptingScope): ScriptingBackendBase abstract val displayName: String val syntaxHighlighting: SyntaxHighlighter? = null - // Putting the syntax highlighters here makes the most sense semantically as they should be singletons. - // But it'd also be nice to let subclasses define ways of generating new their syntax highlighters based on their other parameters. (E.G.: Read a JSON of REGEXs, based on EnvironmentedScriptingBackend().engine. - // Hm. I think the solution in that case is to subclass ScriptingBackend_metadata, and put those properties in the companion, which I will now do. } abstract class EnvironmentedScriptBackend_metadata: ScriptingBackend_metadata() { abstract val engine: String - //Why did I put this here? There was probably a reason, because it was a lot of trouble. + // Why did I put this here? There was probably a reason, because it was a lot of trouble. } @@ -97,6 +94,8 @@ interface ScriptingBackend { } +// TODO: Rename ScriptingBackend to ScriptingImplementation. +// TODO: Add .userTerminable flag and per-instance display string to ScriptingBackendBase. Let mod command histories be seen on ConsoleScreen? open class ScriptingBackendBase(val scriptingScope: ScriptingScope): ScriptingBackend { //A little bit confusing. @@ -119,7 +118,7 @@ open class ScriptingBackendBase(val scriptingScope: ScriptingScope): ScriptingBa * Let the companion object of the correct subclass be accessed in subclass instances. */ open val metadata - get(): ScriptingBackend_metadata = this::class.companionObjectInstance as ScriptingBackend_metadata + get() = this::class.companionObjectInstance as ScriptingBackend_metadata } @@ -264,7 +263,7 @@ class HardcodedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacken val isnull = obj == null appendOut( if (detailed) - "Type: ${if (isnull) null else obj!!::class.qualifiedName}\n\nValue: ${obj}\n\nMembers: ${if (isnull) null else obj!!::class.members.map{it.name}}\n" + "Type: ${if (isnull) null else obj!!::class.qualifiedName}\n\nValue: ${obj}\n\nMembers: ${if (isnull) null else obj!!::class.members.map {it.name}}\n" else "${obj}" ) @@ -347,7 +346,11 @@ class ReflectiveScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacke "set 2000 worldScreen.bottomUnitTable.selectedUnit.promotions.XP", "get worldScreen.bottomUnitTable.selectedCity.population.setPopulation(25)", "set \"Cattle\" worldScreen.mapHolder.selectedTile.resource", - "set \"Krakatoa\" worldScreen.mapHolder.selectedTile.naturalWonder" + "set \"Krakatoa\" worldScreen.mapHolder.selectedTile.naturalWonder", + "get civInfo.addGold(civInfo.tech.techsResearched.size)", + "get uncivGame.setScreen(apiHelpers.Factories.constructorByQualname[\"com.unciv.ui.mapeditor.MapEditorScreen\"](gameInfo.tileMap))", + "get apiHelpers.Factories.constructorByQualname[\"com.unciv.ui.utils.ToastPopup\"](\"This is a popup!\", uncivGame.consoleScreen, 2000)" + //apiHelpers.Factories.constructorByQualname["com.unciv.ui.worldscreen.AlertPopup"](worldScreen, apiHelpers.Factories.constructorByQualname["com.unciv.logic.civilization.PopupAlert"](apiHelpers.Enums.enumMapsByQualname["com.unciv.logic.civilization.AlertType"]["FirstContact"], "Carthage")) ) override fun motd(): String { @@ -356,7 +359,7 @@ class ReflectiveScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacke override fun autocomplete(command: String, cursorPos: Int?): AutocompleteResults { try { - var comm = commandparams.keys.find{ command.startsWith(it+" ") } + var comm = commandparams.keys.find { command.startsWith(it+" ") } if (comm != null) { val params = command.drop(comm.length+1).split(' ', limit=commandparams[comm]!!) //val prefix @@ -364,7 +367,7 @@ class ReflectiveScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacke //val suffix val workingcode = params[params.size-1] val workingpath = Reflection.parseKotlinPath(workingcode) - if (workingpath.any{ it.type == Reflection.PathElementType.Call }) { + if (workingpath.any { it.type == Reflection.PathElementType.Call }) { return AutocompleteResults(listOf(), true, "No autocomplete available for function calls.") } val leafname = if (workingpath.size > 0) workingpath[workingpath.size - 1].name else "" @@ -372,12 +375,12 @@ class ReflectiveScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacke val branchobj = Reflection.resolveInstancePath(scriptingScope, workingpath.slice(0..workingpath.size-2)) return AutocompleteResults( branchobj!!::class.members - .map{ it.name } - .filter{ it.startsWith(leafname) } - .map{ prefix + it } + .map { it.name } + .filter { it.startsWith(leafname) } + .map { prefix + it } ) } else { - return AutocompleteResults(commandparams.keys.filter{ it.startsWith(command) }.map{ it+" " }) + return AutocompleteResults(commandparams.keys.filter { it.startsWith(command) }.map { it+" " }) } } catch (e: Exception) { return AutocompleteResults(listOf(), true, "Could not get autocompletion: ${e}") @@ -440,7 +443,9 @@ abstract class EnvironmentedScriptingBackend(scriptingScope: ScriptingScope): Sc val folderHandle: FileHandle by lazy { SourceManager.setupInterpreterEnvironment(metadata.engine) } // This requires the overridden values for engine, so setting it in the constructor causes a null error... May be fixed since moving engine to the companions. - // Also, BlackboxScriptingBackend inherits from this, but not all subclasses of BlackboxScriptingBackend might need it. So as long as it's not accessed, it won't be intialized. + // Also, BlackboxScriptingBackend inherits from this, but not all subclasses of BlackboxScriptingBackend might need it. So as long as it's not accessed, it won't be initialized. + + // TODO: Probably implement a the Disposable interface method here to clean up the folder. } @@ -586,9 +591,9 @@ class DevToolsScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackend This tool is meant to help update code files. Available commands: - """.trimIndent()+"\n"+commands.map{ "> ${it}" }.joinToString("\n")+"\n\n" + """.trimIndent()+"\n"+commands.map { "> ${it}" }.joinToString("\n")+"\n\n" - override fun autocomplete(command: String, cursorPos: Int?) = AutocompleteResults(commands.filter{ it.startsWith(command) }) + override fun autocomplete(command: String, cursorPos: Int?) = AutocompleteResults(commands.filter { it.startsWith(command) }) override fun exec(command: String): String { val commv = command.split(' ', limit=2) diff --git a/core/src/com/unciv/scripting/ScriptingScope.kt b/core/src/com/unciv/scripting/ScriptingScope.kt index 08aaf1846dfb9..98ace38306aa8 100644 --- a/core/src/com/unciv/scripting/ScriptingScope.kt +++ b/core/src/com/unciv/scripting/ScriptingScope.kt @@ -8,6 +8,7 @@ import com.unciv.UncivGame import com.unciv.logic.GameInfo //import com.unciv.logic.GameSaver import com.unciv.logic.civilization.CivilizationInfo +import com.unciv.scripting.api.FakeMap import com.unciv.scripting.api.ScriptingApiEnums import com.unciv.scripting.api.ScriptingApiFactories import com.unciv.scripting.api.ScriptingApiInstanceRegistry @@ -49,7 +50,8 @@ class ScriptingScope( var gameInfo: GameInfo? = null, var uncivGame: UncivGame? = null, var worldScreen: WorldScreen? = null, - var mapEditorScreen: MapEditorScreen? = null + var mapEditorScreen: MapEditorScreen? = null, + //var scriptingBackend: ScriptingBackend? = null // TODO: Currently executing backend. //val _availableNames = listOf("civInfo", "gameInfo", "uncivGame", "worldScreen", "apiHelpers") // Nope. Annotate instead. ) { @@ -60,15 +62,24 @@ class ScriptingScope( //var modApiHelpers: ModApiHelpers? class ApiHelpers(val scriptingScope: ScriptingScope) { + // This, and the classes of its members, should try to implement only the minimum number of helper functions that are needed for each type of functionality otherwise not possible in scripts. E.G. Don't add special "loadGame" functions or whatever here, but do expose the existing methods of UncivGame. E.G. Don't add factories to speed up making alert popups, because all the required constructors can already be called through constructorByQualname anyway. Let the rest of the codebase and the scripts themselves do the work— Maintenance of the API itself will be easier if all it does is expose existing Kotlin code to dynamic Python/JS/Lua code. // TODO: Move this into its own file. // This could probably eventually include ways for scripts to create and inject their own UI elements too. Create, populate, show even popups for mods, inject buttons that execute script strings for macros. // TODO: The vast majority of these don't need scriptingScope access, and thus can be put on singletons. val isInGame: Boolean get() = (scriptingScope.civInfo != null && scriptingScope.gameInfo != null && scriptingScope.uncivGame != null) + @Suppress("PropertyName") val Factories = ScriptingApiFactories + @Suppress("PropertyName") val Enums = ScriptingApiEnums + @Suppress("PropertyName") val Mappers = ScriptingApiMappers val registeredInstances = ScriptingApiInstanceRegistry() + val instancesAsInstances = FakeMap() // TODO: Rename this. + // Scripting language bindings work by keeping track of the paths to values, and making Kotlin/the JVM resolve them only when needed. + // This creates a dilemma: Resolving a path into a Kotlin value too early means that no further paths (E.G. attribute, keys, calls) can be built on top of it. But resolving it late means that expected side effects may not happen (E.G. function calls probably shouldn't be deferred). And values that *must* be resolved, like the results of function calls, cannot have their own members and method accessed until they themselves are assigned to a path, because they're just kinda floating around as far as the scripting-exposed semantics are concerned. + // So this fake Map works around that, by providing a way for any random object to appear to have a path. + //Debug/dev identity function for both Kotlin and scripts. Check if value survives serialization, force something to be added to ScriptingProtocol.instanceSaver, etc. fun echo(obj: Any?) = obj // TODO: Rename back to echo. fun printLine(msg: Any?) = println(msg.toString()) // Different name from Kotlin's is deliberate, to abstract for scripts. @@ -94,7 +105,7 @@ class ScriptingScope( // To test in Python: // import PIL.Image, io, base64; PIL.Image.open(io.BytesIO(base64.b64decode(apiHelpers.assetImage("StatIcons/Resistance")))).show() val fakepng = ByteArrayOutputStream() - //Close this steam? Well, the docs say doing so "has no effect", and it should clearly get GC'd anyway. + //Close this stream? Well, the docs say doing so "has no effect", and it should clearly get GC'd anyway. val pixmap = ImageGetter.getDrawable(path).getRegion().toPixmap() val exporter = PixmapIO.PNG() // Could be kept and "reused to encode multiple PNGs with minimal allocation", according to the docs. I don't see it as a sufficient bottleneck yet to necesarily justify the complexity and risk, though. exporter.setFlipY(false) @@ -112,13 +123,15 @@ class ScriptingScope( } // Does having one state manage multiple backends that all share the same scope really make sense? Mod handler dispatch, callbacks, etc might all be easier if the multi-backend functionality of ScriptingState were implemented only for ConsoleScreen. - +// ScriptingState also helps separate , keep the shared ScriptingScope between all of them (such that it only needs to be updated once on game context changes), and update /* //class ModApiHelpers { var handlerContext: NamedTuple? // Why not just use a map? String keys will be clearer in scripts than integers anyway. // Collection that gets replaced with any contextual parameters when running script handlers. E.G. Unit moved, city founded, tech researched, construction finished. +//fun showScriptedChoicePopup(title: String, body: String, options: List, callback: String?) // Actually, no. Popup() seems to work on its own. + // TODO: Mods blacklist, for security threats. } @@ -138,4 +151,5 @@ class ScriptingScope( } */ -//worldScreen.bottomUnitTable.selectedCity.cityConstructions.purchaseConstruction("Missionary", -1, False, apiHelpers.Enums.Stat.statsUsableToBuy[4]) + + diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index 7f83562852af9..7f1193306f99f 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -8,6 +8,7 @@ import kotlin.collections.ArrayList import kotlin.math.max import kotlin.math.min +// TODO: Move to ExtensionFunctions.kt. fun ArrayList.clipIndexToBounds(index: Int, extendsize: Int = 0): Int { return max(0, min(this.size-1+extendsize, index)) @@ -19,6 +20,10 @@ fun ArrayList.clipIndexToBounds(index: Int, extendsize: Int = 0): Int { // TODO: Replace Exception types with Throwable? Wait, no. Apparently that just includes "serious problems that a reasonable application should not try to catch." +// TODO: There's probably some public vars that can/should be private set. + +// TODO: Mods blacklist. + // See https://github.com/yairm210/Unciv/pull/5592/commits/a1f51e08ab782ab46bda220e0c4aaae2e8ba21a4 for example of running locking operation in separate thread. /** @@ -45,12 +50,13 @@ fun ArrayList.enforceValidIndex(index: Int) { * @property scriptingScope ScriptingScope instance at the root of all scripting API. */ //TODO: Probably deprecate/remove initialBackendType. -class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: ScriptingBackendType? = null){ +//TODO: Actually, probably should be only one instance in game, since various context changes set various ScriptingScope properties through it, and being able to view mod command history will also be useful. +class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: ScriptingBackendType? = null) { - val scriptingBackends: ArrayList = ArrayList() + val scriptingBackends = ArrayList() - private val outputHistory: ArrayList = ArrayList() - private val commandHistory: ArrayList = ArrayList() + private val outputHistory = ArrayList() + private val commandHistory = ArrayList() var activeBackend: Int = 0 @@ -92,16 +98,19 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr // TODO: Apparently there's a bunch of extensions like .withIndex(), .indices, and .lastIndex that I can use to replace a lot of stuff currently done with .size. // TODO: Why didn't I use indexOf? if (b == backend) { - switchToBackend(index = i) + return switchToBackend(index = i) } } throw IllegalArgumentException("Could not find scripting backend base: ${backend}") } - fun switchToBackend(displayname: String) { + fun switchToBackend(displayName: String) { //TODO } + fun switchToBackend(backendType: ScriptingBackendType) { + } + fun termBackend(index: Int): Exception? { scriptingBackends.enforceValidIndex(index) val result = scriptingBackends[index].terminate() @@ -150,7 +159,8 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr } } - fun exec(command: String): String { + fun exec(command: String): String { // TODO: Allow "passing" args that get assigned to something under ScriptingScope here. + //scriptingScope.scriptingBackend = if (command.length > 0) { commandHistory.add(command) // TODO: Also don't add duplicates. while (commandHistory.size > maxCommandHistory) { @@ -162,11 +172,13 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr activeCommandHistory = 0 var out: String if (hasBackend()) { + // TODO: Ternary. out = getActiveBackend().exec(command) } else { out = "" } echo(out) + //scriptingScope.scriptingBackend = null // TODO return out } @@ -174,6 +186,7 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr // scriptingScope.worldScreen?.isPlayersTurn = false //TODO //Not perfect. I think scriptingScope also exposes mutating the GUI itself, and many things that aren't protected by this? Then again, a script that *wants* to cause a crash/ANR will always be able to do so by just assigning an invalid value or deleting a required node somewhere. Could make mod handlers outside of worldScreen blocking, with written stipulations on (dis)recommended size, and then + //https://github.com/yairm210/Unciv/pull/5592/commits/a1f51e08ab782ab46bda220e0c4aaae2e8ba21a4 // } // fun releaseUiLock() { diff --git a/core/src/com/unciv/scripting/api/LazyMap.kt b/core/src/com/unciv/scripting/api/FakeMaps.kt similarity index 69% rename from core/src/com/unciv/scripting/api/LazyMap.kt rename to core/src/com/unciv/scripting/api/FakeMaps.kt index cbe75fc8b934a..30b0feecf3fbc 100644 --- a/core/src/com/unciv/scripting/api/LazyMap.kt +++ b/core/src/com/unciv/scripting/api/FakeMaps.kt @@ -1,6 +1,17 @@ package com.unciv.scripting.api +abstract class StatelessMap: Map { + protected fun noStateError(): Nothing = throw(UnsupportedOperationException("Cannot access backing state of ${this::class.simpleName} by default.")) + override val entries: MutableSet> get() = noStateError() + override val keys: MutableSet get() = noStateError() + override val values: MutableCollection get() = noStateError() + override val size: Int get() = noStateError() + override fun containsKey(key: K): Boolean = noStateError() + override fun containsValue(value: V): Boolean = noStateError() + override fun isEmpty(): Boolean = noStateError() +} + // Lazy Map of indeterminate size. // Memoizes a single-argument function. @@ -9,7 +20,7 @@ package com.unciv.scripting.api // @property func The function that returns the value for a given key. // @property exposeState Whether to expose the content-specific members of the backing map. -class LazyMap(val func: (K) -> V, val exposeState: Boolean = false): Map { +class LazyMap(val func: (K) -> V, val exposeState: Boolean = false): StatelessMap() { // Benefit of a Map over a function is that because mapping access can be safely assumed by scripting language bindings to have no side effects, it's semantically easier for scripting language bindings to let the returned value be immediately called, autocompleted, indexed/attribute-read, etc. private val backingMap = hashMapOf() override fun get(key: K): V { @@ -23,7 +34,6 @@ class LazyMap(val func: (K) -> V, val exposeState: Boolean = false): Map(val func: (K) -> V, val exposeState: Boolean = false): Map() { + override fun get(key: Any?) = key +} diff --git a/core/src/com/unciv/scripting/api/ScriptingApiEnums.kt b/core/src/com/unciv/scripting/api/ScriptingApiEnums.kt index 93c99e2107d21..72acc1688f449 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiEnums.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiEnums.kt @@ -1,9 +1,9 @@ package com.unciv.scripting.api // Convert an Enum type parameter into a Map of its constants by their names. -inline fun > enumToMap() = enumValues().associateBy{ it.name } +inline fun > enumToMap() = enumValues().associateBy { it.name } -fun enumQualnameToMap(qualName: String) = Class.forName(qualName).enumConstants.associateBy{ (it as Enum<*>).name } +fun enumQualnameToMap(qualName: String) = Class.forName(qualName).enumConstants.associateBy { (it as Enum<*>).name } // Always return a built-in Map class instance here, so its gets serialized as JSON object instead of tokenized, and scripts can refer directly to its items. // I cast to Enum<*> fully expecting it would crash because it felt metaclass-y. But apparently it's just a base class, so it works? @@ -20,8 +20,4 @@ object ScriptingApiEnums { val enumMapsByQualname = LazyMap(::enumQualnameToMap) // apiHelpers.Enums.enumMapsByQualname["com.unciv.logic.automation.ThreatLevel"]['VeryLow'] - - //fun getEnumValue(qualName: String, value: String) = getEnumMap(qualName)[value] - // The return from getEnumMap should be a real serialized thing anyway, right? - //val testThreatLevel = enumQualnameToMap("com.unciv.logic.automation.ThreatLevel") } diff --git a/core/src/com/unciv/scripting/api/ScriptingApiFactories.kt b/core/src/com/unciv/scripting/api/ScriptingApiFactories.kt index 8f3b69c6ce7c6..f708b37b8a9ab 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiFactories.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiFactories.kt @@ -1,75 +1,70 @@ package com.unciv.scripting.api import com.unciv.scripting.reflection.FunctionDispatcher +import com.unciv.scripting.reflection.Reflection import com.unciv.scripting.reflection.makeFunctionDispatcher +import kotlin.reflect.KCallable import kotlin.reflect.full.primaryConstructor +import kotlin.reflect.jvm.kotlinFunction -// TODO (Later): Use ClassGraph to automatically find all relevant classes on build. +//// TODO (Later, maybe.): Use ClassGraph to automatically find all relevant classes on build. // https://github.com/classgraph/classgraph/wiki/Build-Time-Scanning +// Honestly, it's fine. Having scripts provide the qualpaths themselves keeps everything dynamic, and the LazyMap caching keeps it (as) performant (as the rest of the API is, probably), so the only real "benefit" of indexing everything beforehand would be enabling autocompletion. +// TODO: Rename this, to "Jvm", maybe. + /** * For use in ScriptingScope. Allows interpreted scripts to make new instances of Kotlin/JVM classes. */ object ScriptingApiFactories { - //This, and possible ApiHelpers itself, need better nested namespaces. - val Game = object { - } - val Math = object { - } - val Rulesets = object { - } - val Kotlin = object { - } - val Gui = object { - } - -// fun kotlinClassFromQualname(qualName: String) = memoizedQualnameToKclass(qualName) // For debug. Probably comment out in builds. // TODO: Check is disabled in unit tests. Actually nah. May as well leave this around. - - val kotlinClassByQualname= LazyMap({ qualName: String -> Class.forName(qualName).kotlin }, exposeState = true) // Mostly for debug. Generally scripts shouldn't be using this. - - val constructorByQualname = LazyMap({ qualName: String -> makeFunctionDispatcher(Class.forName(qualName).kotlin.constructors) -// val cls = Class.forName(qualName).kotlin -// val cons = cls.primaryConstructor -// if (false && cons != null) -// makeFunctionDispatcher(listOf(cons::call)) -// // Seems that empty constructors take vararg arrays? E.G. MapEditorScreen, MainMenuScreen -// // Actually, it seems like .primaryConstructor is a lie. -// else makeFunctionDispatcher(cls.constructors) + + //val javaClassByQualname + + val kotlinClassByQualname = LazyMap({ qualName: String -> Class.forName(qualName).kotlin }, exposeState = true) + //// Mostly for debug. Scripts may, but probably usually shouldn't, use this. + + // Actually, this works for singletons too: + // apiHelpers.Factories.kotlinClassByQualname['com.unciv.scripting.reflection.Reflection'].objectInstance + + // Works for files: apiHelpers.Factories.kotlinClassByQualname['com.unciv.scripting.ScriptingStateKt']. Kinda. (Top-level functions?) + // apiHelpers.instancesAsInstances[apiHelpers.Factories.kotlinClassByQualname['com.unciv.ui.utils.ExtensionFunctionsKt'].jClass.getMethods()][0].getName() + + // [m.getName() for m in apiHelpers.instancesAsInstances[apiHelpers.Factories.kotlinClassByQualname['com.unciv.ui.utils.ExtensionFunctionsKt'].jClass.getMethods()] ] + // m=next(m for m in apiHelpers.instancesAsInstances[apiHelpers.Factories.kotlinClassByQualname['com.unciv.ui.utils.ExtensionFunctionsKt'].jClass.getMethods()] if m.getName() == 'enable') + + //val companionByQualName TODO? + // Fails: apiHelpers.Factories.kotlinClassByQualname["com.unciv.scripting.SpyScriptingBackend.Metadata"] + +// val toplevelFunctionByQualname = LazyMap ({ qualName: String -> +// val simpleName = qualName.substringAfterLast('.') +// makeFunctionDispatcher(Class.forName("${qualName.substringBeforeLast('.')}Kt").getDeclaredMethods().asSequence().filter { it.name == simpleName }.map { it.kotlinFunction }.toList() as List>) +// // I think I read somewhere at some point that the "${Filename}Kt" name for files is documented, but I couldn't find it again. +// }, exposeState = true) +// // TODO: exposeState should probably be unset in all these? +// // apiHelpers.Factories.toplevelFunctionByQualname["com.unciv.ui.utils.ExtensionFunctions.onClick"] +// // apiHelpers.Factories.toplevelFunctionByQualname["com.unciv.ui.utils.ExtensionFunctions.toLabel"]("Test") +// // This *is* rather convenient, but maybe it should be unboundMethodsByQualname or something like that instead? "Top-level function" and "Java method" seem quite far intuitively in Kotlin semantics, but the code is only two string characters apart (and they're apparently the same thing on the JVM). + + val functionByQualClassAndMethodName = LazyMap ({ jclassQualname: String -> + val cls = Class.forName(jclassQualname) + LazyMap({ methodName: String -> makeFunctionDispatcher(cls.getDeclaredMethods().asSequence().filter { it.name == methodName }.map { it.kotlinFunction }.toList() as List>) }, exposeState = true) + // Could initialize the second LazyMap here by accessing for all names— Only benefit would be for autocomplete, at higher first-call time and memory use, though. }, exposeState = true) + // TODO: exposeState should probably be unset in all these? + // apiHelpers.Factories.functionByQualClassAndMethodName["com.unciv.ui.utils.ExtensionFunctionsKt"]["toLabel"]("Test") + + val staticPropertyByQualClassAndName = LazyMap ({ jclassQualname: String -> + val kcls = Class.forName(jclassQualname).kotlin + LazyMap({ name: String -> Reflection.readClassProperty(kcls, name) as Any? }, exposeState = true) + }, exposeState = true) + // apiHelpers.Factories.kotlinClassByQualname["com.badlogic.gdx.graphics.Color"].members[50].get() + // apiHelpers.Factories.staticPropertyByQualClassAndName["com.badlogic.gdx.graphics.Color"]['WHITE'] + + val constructorByQualname = LazyMap({ qualName: String -> makeFunctionDispatcher(Class.forName(qualName).kotlin.constructors) }, exposeState = true) + // TODO (Later, Maybe): This would actually be quite easy to whitelist by package paths. -// fun instanceFromQualname(qualName: String, args: List): Any? { -// // TODO: Deprecate. -// val cls = Class.forName(qualName).kotlin -// val cons = cls.primaryConstructor -// return if (cons != null) -// cons.call(*args.toTypedArray()) -// else FunctionDispatcher( -// functions = cls.constructors, -// matchNumbersLeniently = true, -// matchClassesQualnames = false, -// resolveAmbiguousSpecificity = true -// ).call(args.toTypedArray()) -// } -// // TODO: Use generalized InstanceMethodDispatcher to find right callable in KClass.constructors if no primary constructor. - - // apiHelpers.Factories.instanceFromQualname('java.lang.String', []) - // apiHelpers.Factories.instanceFromQualname('com.unciv.logic.map.MapUnit', []) - // apiHelpers.Factories.instanceFromQualname('com.unciv.logic.city.CityStats', [civInfo.cities[0]]) - // apiHelpers.Factories.instanceFromQualname('com.badlogic.gdx.math.Vector2', [1, 2]) - - // Refer: https://stackoverflow.com/questions/40672880/creating-a-new-instance-of-a-kclass - - // See https://stackoverflow.com/questions/59936471/kotlin-reflect-package-and-get-all-classes. Build structured map of all classes? - // https://stackoverflow.com/questions/3845823/getting-list-of-fully-qualified-names-from-a-simple-name - // https://stackoverflow.com/questions/52573605/kotlin-can-i-get-all-objects-that-implements-specific-interface - - // The JAR for Reflections is only a couple hundred KB. It also doesn't work on Android. - - // apiHelpers.registeredInstances['x'] = apiHelpers.Factories.test2('com.badlogic.gdx.math.Vector2') - // apiHelpers.registeredInstances['x'].constructors[1].call(apiHelpers.Factories.arrayOf([1, 2])) - // apiHelpers.registeredInstances['y'] = apiHelpers.Factories.test('com.badlogic.gdx.math.Vector2') fun arrayOf(elements: Collection): Array<*> = elements.toTypedArray() fun arrayOfAny(elements: Collection): Array = elements.toTypedArray() fun arrayOfString(elements: Collection): Array = elements.toTypedArray() diff --git a/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt b/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt index dc5bbce560a46..61ed516dc004d 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt @@ -1,5 +1,35 @@ package com.unciv.scripting.api +import java.util.Collections.newSetFromMap +import java.util.WeakHashMap +import kotlin.NoSuchElementException +import kotlin.concurrent.thread + +object AllScriptingApiInstanceRegistries { + private val registries = newSetFromMap(WeakHashMap()) + // Apparently this will be a WeakSet? Oh, all sets just wrap Maps? + fun init() { + Runtime.getRuntime().addShutdownHook( + // TODO: Move uses of this into UncivGame.dispose()? + // TODO: Allow arbitrary callbacks to be added for UncivGame.dispose(). + thread(start = false, name = "Check ScriptingApiInstanceRegistry()s are empty.") { + val allkeys = getAllKeys() + if (allkeys.isNotEmpty()) { + println("WARNING: ${allkeys.size} ScriptingApiInstanceRegistry()s still have keys in them:") + println("\t" + allkeys.map { "${it.value.size} keys in ${it.key}\n\t\t"+it.value.joinToString("\n\t\t") }.joinToString("\n\t")) + } + } + ) + } + fun add(registry: ScriptingApiInstanceRegistry) { + registries.add(registry) + } + fun getAllKeys(): Map> { + return registries.filter { it.isNotEmpty() }.associateWith { it.keys } + } +} + +// TODO: Check on game close that all InstanceRegistries are empty. /** * Namespace in ScriptingScope().apiHelpers, for scripts to do their own memory management by keeping references to objects alive. @@ -11,6 +41,9 @@ package com.unciv.scripting.api */ class ScriptingApiInstanceRegistry: MutableMap { private val backingMap = mutableMapOf() +// fun init() { +// AllScriptingApiInstanceRegistries.add(this) +// } override val entries get() = backingMap.entries override val keys @@ -35,7 +68,11 @@ class ScriptingApiInstanceRegistry: MutableMap { } return backingMap.put(key, value) } - override fun putAll(from: Map) = backingMap.putAll(from) + override fun putAll(from: Map) { + for ((key, value) in from) { + put(key, value) + } + } override fun remove(key: String): Any? { if (key !in this) { throw NoSuchElementException("\"${key}\" not in ${this}.") diff --git a/core/src/com/unciv/scripting/api/ScriptingApiMappers.kt b/core/src/com/unciv/scripting/api/ScriptingApiMappers.kt index 58fe8976398d6..788dada796e23 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiMappers.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiMappers.kt @@ -8,21 +8,21 @@ object ScriptingApiMappers { //// Some ways to access or assign the same property(s) on a lot of instances at once, using only one IPC call. Maybe use parseKotlinPath? Probably preserve order in return instead of mapping from each instance, since script must already have list of tokens anyway (ideally also from a single IPC call). Or preserve mapping, since order could be messy, and to fit with the assignment function? fun mapPathCodes(instances: List, pathcodes: Collection): List> { - val pathElementLists = pathcodes.associateWith{ Reflection.parseKotlinPath(it) } + val pathElementLists = pathcodes.associateWith { Reflection.parseKotlinPath(it) } // TODO: Meth ref? return instances.map { val instance = it - pathElementLists.mapValues{ + pathElementLists.mapValues { Reflection.resolveInstancePath(instance, it.value) } } } - fun getPathCodesFrom(instance: Any, pathcodes: Collection) = pathcodes.associateWith{ Reflection.resolveInstancePath(instance, Reflection.parseKotlinPath(it)) } + fun getPathCodesFrom(instance: Any, pathcodes: Collection) = pathcodes.associateWith { Reflection.resolveInstancePath(instance, Reflection.parseKotlinPath(it)) } fun getPathCodes(instancesAndPathcodes: Map>): Map> { val pathElementLists = LazyMap>(Reflection::parseKotlinPath) - return instancesAndPathcodes.mapValues{ (instance, pathcodes) -> - pathcodes.associateWith{ + return instancesAndPathcodes.mapValues { (instance, pathcodes) -> + pathcodes.associateWith { Reflection.resolveInstancePath(instance, pathElementLists[it]) } } diff --git a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt index f30d1bb4ad151..73046b1e4c56f 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt @@ -197,7 +197,7 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = fun autocomplete(packet: ScriptingPacket): AutocompleteResults = if (packet.data is JsonArray) - AutocompleteResults((packet.data as List).map{ (it as JsonPrimitive).content }) + AutocompleteResults((packet.data as List).map { (it as JsonPrimitive).content }) else AutocompleteResults(listOf(), true, (packet.data as JsonPrimitive).content) @@ -282,7 +282,7 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = scope, TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) ) - data = TokenizingJson.getJsonElement(leaf!!::class.members.map{it.name}) // TODO: Honestly, probably restrict to non-privates and other members that are actually accessible. Test from Python. + data = TokenizingJson.getJsonElement(leaf!!::class.members.map {it.name}) // TODO: Honestly, probably restrict to non-privates and other members that are actually accessible. Test from Python. } catch (e: Exception) { data = JsonPrimitive(e.stringifyException()) flags.add(KnownFlag.Exception.value) @@ -322,7 +322,7 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = try { data = TokenizingJson.getJsonElement((leaf as Array<*>).size) // AFAICT avoiding these casts/checks would require reflection. - } catch (e: Exception) { + } catch (e: Exception) { // TODO: Switch these catches to ClassCastException. try { data = TokenizingJson.getJsonElement((leaf as Map<*, *>).size) } catch (e: Exception) { @@ -391,7 +391,7 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = data = TokenizingJson.getJsonElement( mapOf>>( *((leaf as FunctionDispatcher).functions.map { - it.toString() to it.parameters.map{ listOf(it.name?.toString(), it.type.toString()) } + it.toString() to it.parameters.map { listOf(it.name?.toString(), it.type.toString()) } }).toTypedArray() // The innermost listOf should semantically be a Pair as per the spec in Module.md, but a List seems safer to serialize. ) diff --git a/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt b/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt index 3c0add4fab964..22a42c56e3ddc 100644 --- a/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt +++ b/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt @@ -19,6 +19,7 @@ class SubprocessBlackbox(val processCmd: Array): Blackbox { * STDOUT of the wrapped process, or null. */ var inStream: BufferedReader? = null + /** * STDIN of the wrapped process, or null. */ @@ -30,7 +31,16 @@ class SubprocessBlackbox(val processCmd: Array): Blackbox { var processLaunchFail: String? = null override val isAlive: Boolean - get() = process != null && process!!.isAlive() // TODO: API Level 26. But also, it's not like using subprocesses is actually planned for Android. + get() = process != null && process!!.isAlive() +// get() { +// return try { +// @Suppress("NewApi") +// process != null && process!!.isAlive() +// // Usually process will be null on Android anyway. +// } catch(e: NoSuchMethodError) { true } // Compiled access. +// catch(e: NoSuchMethodException) { true } // Reflective access. +// // TODO: API Level 26. But also, it's not like using subprocesses is actually planned for scripting on Android. +// } override val readyForWrite: Boolean get() = isAlive @@ -43,7 +53,7 @@ class SubprocessBlackbox(val processCmd: Array): Blackbox { } override fun toString(): String { - return "${this::class.simpleName}(process=${process}).apply{ inStream=${inStream}; outStream=${outStream}; processLaunchFail=${processLaunchFail} }" + return "${this::class.simpleName}(processCmd=${processCmd}).apply{ process=${process}; inStream=${inStream}; outStream=${outStream}; processLaunchFail=${processLaunchFail} }" } /** @@ -61,7 +71,7 @@ class SubprocessBlackbox(val processCmd: Array): Blackbox { try { process = Runtime.getRuntime().exec(processCmd) } catch (e: Exception) { - process = null + process = null // Comment this out to test the API level thing. processLaunchFail = e.toString() return } diff --git a/core/src/com/unciv/scripting/reflection/FunctionDispatcher.kt b/core/src/com/unciv/scripting/reflection/FunctionDispatcher.kt index dd7668f0e5028..90be3f8ed1523 100644 --- a/core/src/com/unciv/scripting/reflection/FunctionDispatcher.kt +++ b/core/src/com/unciv/scripting/reflection/FunctionDispatcher.kt @@ -42,6 +42,8 @@ open class FunctionDispatcher( // Not supporting varargs for now. Doing so would require rebuilding the arguments array to move all vararg arguments into a new array for KCallable.call(). + // Right. It's called "Overload Resolution" when done statically, and Kotlin has specs under that title. + /** * @return Whether a given argument value can be cast to the type of a given KParameter. */ @@ -99,6 +101,8 @@ open class FunctionDispatcher( // Every relevant parameter type in it must be either the same as the corresponding parameter type in every other KCallable or a subtype of the corresponding parameter type in every other KCallable. // At least one parameter type in it must be a strict subtype of the corresponding parameter type for every other KCallable. + // This is essentially equivalent to the behaviour specified in Chapter 11.7 "Choosing the most specific candidate from the overload candidate set" of the Kotlin language specification. + // private fun getMostSpecificCallable(matches: List>, paramKtypes: Map, ArrayList>): KCallable? { // Private because subclasses may choose to modify the arguments passed to call(). @@ -149,6 +153,7 @@ open class FunctionDispatcher( // gameInfo.civilizations.add(1, civInfo) // gameInfo.civilizations.add(civInfo) // Both need to work. + // Supporting named parameters would greatly complicate both signature matching and specificity resolution, and is not currently planned. val callableparamktypes = hashMapOf, ArrayList>() // Map of all traversed KCallables to lists of their parameters' KTypes. // Only parameters, and not arguments, are saved, though both are traversed. @@ -156,17 +161,18 @@ open class FunctionDispatcher( val matches = getMatchingCallables(arguments, paramKtypeAppends=callableparamktypes) var match: KCallable? = null if (matches.size < 1) { - throw IllegalArgumentException("No matching signatures found for calling ${nounifyFunctions()} with given arguments: (${arguments.map{if (it == null) "null" else it::class?.simpleName ?: "null"}.joinToString(", ")})") + throw IllegalArgumentException("No matching signatures found for calling ${nounifyFunctions()} with given arguments: (${arguments.map {if (it == null) "null" else it::class?.simpleName ?: "null"}.joinToString(", ")})") //FIXME: A lot of non-null assertions and null checks (not here, but generally in the codebase) can probably be replaced with safe calls. } if (matches.size > 1) { if (resolveAmbiguousSpecificity) { //Kotlin seems to choose the most specific signatures based on inheritance hierarchy. //E.G. UncivGame.setScreen(), which uses a more specific parameter type than its GDX base class and thus creates a different, semi-ambiguous (sub-)signature, but still gets resolved. + //Yeah, Kotlin semantics "choose the most specific function": https://kotlinlang.org/spec/overload-resolution.html#the-forms-of-call-expression match = getMostSpecificCallable(matches, paramKtypes = callableparamktypes) } if (match == null) { - throw IllegalArgumentException("Multiple matching signatures found for calling ${nounifyFunctions()} with given arguments:\n\t(${arguments.map{if (it == null) "null" else it::class?.simpleName ?: "null"}.joinToString(", ")})\n\t${matches.map{it.toString()}.joinToString("\n\t")}") + throw IllegalArgumentException("Multiple matching signatures found for calling ${nounifyFunctions()} with given arguments:\n\t(${arguments.map {if (it == null) "null" else it::class?.simpleName ?: "null"}.joinToString(", ")})\n\t${matches.map {it.toString()}.joinToString("\n\t")}") } } else { match = matches[0]!! diff --git a/core/src/com/unciv/scripting/reflection/Reflection.kt b/core/src/com/unciv/scripting/reflection/Reflection.kt index 88b20fe1f0d30..0f059d2aa0bfb 100644 --- a/core/src/com/unciv/scripting/reflection/Reflection.kt +++ b/core/src/com/unciv/scripting/reflection/Reflection.kt @@ -2,23 +2,22 @@ package com.unciv.scripting.reflection import com.unciv.scripting.utils.TokenizingJson import kotlin.collections.ArrayList -import kotlin.reflect.KCallable -import kotlin.reflect.KMutableProperty1 -import kotlin.reflect.KProperty1 import kotlinx.serialization.Serializable +import kotlin.reflect.* object Reflection { @Suppress("UNCHECKED_CAST") - fun readInstanceProperty(instance: Any, propertyName: String): R? { + fun readClassProperty(cls: KClass<*>, propertyName: String) + = (cls.members.first { it.name == propertyName } as KProperty0<*>).get() as R? + + @Suppress("UNCHECKED_CAST") + fun readInstanceProperty(instance: Any, propertyName: String) // From https://stackoverflow.com/a/35539628/12260302 - val property = instance::class.members - .first { it.name == propertyName } as KProperty1 + = (instance::class.members.first { it.name == propertyName } as KProperty1).get(instance) as R? // If scripting member access performance becomes an issue, memoizing this could be a potential first step. // TODO: Throw more helpful error on failure. - return property.get(instance) as R? - } // Return an [InstanceMethodDispatcher]() with consistent settings for the scripting API. fun makeInstanceMethodDispatcher(instance: Any, methodName: String) = InstanceMethodDispatcher( @@ -46,7 +45,8 @@ object Reflection { matchClassesQualnames: Boolean = false, resolveAmbiguousSpecificity: Boolean = false ) : FunctionDispatcher( - functions = instance::class.members.filter{ it.name == methodName }.map{ it as KCallable }, + functions = instance::class.members.filter { it.name == methodName }, + // TODO: .functions? Choose one that includes superclasses but excludes extensions. matchNumbersLeniently = matchNumbersLeniently, matchClassesQualnames = matchClassesQualnames, resolveAmbiguousSpecificity = resolveAmbiguousSpecificity @@ -74,7 +74,8 @@ object Reflection { fun readInstanceItem(instance: Any, keyOrIndex: Any): Any? { // TODO: Make this work with operator overloading. Though Map is already an interface that anything can implement, so maybe not. if (keyOrIndex is Int) { - return (instance as List)[keyOrIndex] + return try { (instance as List)[keyOrIndex] } + catch (e: ClassCastException) { (instance as Array)[keyOrIndex] } } else { return (instance as Map)[keyOrIndex] } @@ -225,10 +226,10 @@ object Reflection { //} fun splitToplevelExprs(code: String, delimiters: String = ","): List { - return code.split(',').map{ it.trim(' ') } + return code.split(',').map { it.trim(' ') } var segs = ArrayList() val bracketdepths = mutableMapOf( - *brackettypes.keys.map{ it to 0 }.toTypedArray() + *brackettypes.keys.map { it to 0 }.toTypedArray() ) //TODO: Actually try to parse for parenthesization, strings, etc. } @@ -242,6 +243,7 @@ object Reflection { PathElementType.Property -> { try { obj = readInstanceProperty(obj!!, element.name) + // TODO: Consider a LBYL instead of AFP here. } catch (e: ClassCastException) { obj = makeInstanceMethodDispatcher( obj!!, @@ -260,12 +262,13 @@ object Reflection { } PathElementType.Call -> { // TODO: Handle invoke operator. Easy enough, just recurse to access the .invoke. - // Maybe TODO: Handle lambdas. E.G. WorldScreen.nextTurnAction. Honestly, it may be better to just expose wrapping non-lambdas. + // Test in Python: apiHelpers.Factories.constructorByQualname.invoke('com.unciv.UncivGame'). Also do an object for multi-arg testing, I guess? + // Maybe TODO: Handle lambdas. E.G. WorldScreen.nextTurnAction. But honestly, it may be better to just expose wrapping non-lambdas. obj = (obj as FunctionDispatcher).call( // Undocumented implicit behaviour: Using the last object means that this should work with explicitly created FunctionDispatcher()s. ( if (element.doEval) - splitToplevelExprs(element.name).map{ evalKotlinString(instance!!, it) } + splitToplevelExprs(element.name).map { evalKotlinString(instance!!, it) } else element.params ).toTypedArray() @@ -280,7 +283,7 @@ object Reflection { } - fun evalKotlinString(scope: Any, string: String): Any? { + fun evalKotlinString(scope: Any?, string: String): Any? { val trimmed = string.trim(' ') if (trimmed == "null") { return null @@ -302,7 +305,7 @@ object Reflection { if (asfloat != null) { return asfloat } - return resolveInstancePath(scope, parseKotlinPath(trimmed)) + return resolveInstancePath(scope!!, parseKotlinPath(trimmed)) } diff --git a/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt b/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt index 55058002db32f..e84c960d17e0e 100644 --- a/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt +++ b/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt @@ -48,7 +48,7 @@ fun makeMemberSpecDef(member: KCallable<*>): ApiSpecDef { println("Error accessing name of property ${m}.") } }*/ - //val tmp: Collection = kclass.members.filter{ it.name != null }.map{ it.name!! } + //val tmp: Collection = kclass.members.filter { it.name != null }.map { it.name!! } //submembers.addAll(tmp) //Using a straight .map return ApiSpecDef( @@ -56,7 +56,7 @@ fun makeMemberSpecDef(member: KCallable<*>): ApiSpecDef { isIterable = "iterator" in submembers, isMapping = "get" in submembers, isCallable = member is KFunction, - callableArgs = member.parameters.map{ it.name } + callableArgs = member.parameters.map { it.name } ) } @@ -97,7 +97,7 @@ class ApiSpecGenerator(val scriptingScope: ScriptingScope) { } fun generateFlatApi(): List { - return scriptingScope::class.members.map{ it.name } + return scriptingScope::class.members.map { it.name } } fun generateClassApi(): Map> { @@ -105,8 +105,8 @@ class ApiSpecGenerator(val scriptingScope: ScriptingScope) { val classes = getAllUncivClasses() var c = 0 // Test count. Something like 5,400, IIRC. For now, it's easier to just dynamically generate the API using Python's magic methods and the reflective tools in ScriptingProtocol. JS has proxies too, but other languages may not be so dynamic. // TBF I think some of those might have been GDX/Kotlin/JVM classes, which I should filter oout by .qualifiedName. val output = mutableMapOf>( - *classes.map{ - it.qualifiedName!! to it.members.map{ c += 1; makeMemberSpecDef(it) } //Reflective function reference instead of wrapping lambda? + *classes.map { + it.qualifiedName!! to it.members.map { c += 1; makeMemberSpecDef(it) } //Reflective function reference instead of wrapping lambda? }.toTypedArray() ) println("\nGathered ${c} property specifications across ${classes.size} classes.\n") diff --git a/core/src/com/unciv/scripting/utils/InstanceTokenizer.kt b/core/src/com/unciv/scripting/utils/InstanceTokenizer.kt index fe3d66695912e..4caa371c240e5 100644 --- a/core/src/com/unciv/scripting/utils/InstanceTokenizer.kt +++ b/core/src/com/unciv/scripting/utils/InstanceTokenizer.kt @@ -37,6 +37,9 @@ object InstanceTokenizer { */ private const val tokenMaxLength = 100 +// private fun newTokenFromInstance(value: Any?): String { +// } // Move existing code here, publicize tokenFromInstance, check against existing WeakMap of tokens, and maybe forbid nullable input… Hm. Need to use identity instead of equality comparison. Oh wow. They have "WeakIdentityHashMap", the maniacs. + /** * Generate a distinctive token string to represent a Kotlin/JVM object. * @@ -73,6 +76,7 @@ object InstanceTokenizer { fun clean() { //FIXME (if I become a problem): Because a new unique token is currently generated even if the instance is already tokenized as something else, this will eventually get slower over time if a script makes lots of requests that result in new instance tokens for objects that last a long time (E.G. uncivGame). And since any stored instances should ideally be WeakReferences to prevent garbage collection from being broken for *all* instances, fixing that may not be as simple as checking for existing tokens to reuse them. //TODO: Probably keep another map of instance hashes to weakrefs and their existing token? The hashes will have to have counts instead of just containment, since otherwise a hash collision would cause the earlier token to become inaccessible. + // Can I use WeakReferences as keys? Do they implement hash and equality? Huh, WeakHashMap is a thing here too. Cool. Auto cleaning. And could check against for cleaning the other map. Hm. Need to use identity instead of equality comparison. Oh wow. They have "WeakIdentityHashMap", the maniacs. val badtokens = mutableListOf() for ((t, o) in instances) { if (o.get() == null) { @@ -88,7 +92,7 @@ object InstanceTokenizer { * @param obj Instance to tokenize. * @return Token string that can later be detokenized back into the original instance. */ - fun getToken(obj: Any?): String { + fun getToken(obj: Any?): String { // TOOD: Switch to Any, since null will just be cleaned anyway? clean() val token = tokenFromInstance(obj) instances[token] = WeakReference(obj) @@ -101,13 +105,13 @@ object InstanceTokenizer { * Accepts non-token values, and passes them through unchanged. So can be used to E.G. blindly transform a Collection/JSON Array that only maybe contains some token strings by being called on every element. * * @param token Previously generated token, or any instance or value. - * @throws NullPointerException (I think) if given a token string but not a valid one (E.G. if its object was garbage-collected, or if it's fake). + * @throws NullPointerException If given a token string but not a valid one (E.G. if its object was garbage-collected, or if it's fake). * @return Real instance from detokenizing input if given a token string, input value or instance unchanged if not given a token string. */ fun getReal(token: Any?): Any? { clean() if (isToken(token)) { - return instances[token]!!.get() + return instances[token]!!.get()// TODO: Add another non-null assertion here? Unknown tokens and expired tokens are only a cleaning cycle apart, which seems race condition-y. } else { return token } diff --git a/core/src/com/unciv/scripting/utils/SourceManager.kt b/core/src/com/unciv/scripting/utils/SourceManager.kt index 4f459d9db98d1..5241ab7ce6003 100644 --- a/core/src/com/unciv/scripting/utils/SourceManager.kt +++ b/core/src/com/unciv/scripting/utils/SourceManager.kt @@ -53,6 +53,7 @@ object SourceManager { thread(start = false, name = "Delete ${outdir.toString()}.") { outdir.deleteDirectory() } + // Will JVM outlive app on Android? Hopefully temporary directory will get cleaned long-term, but may need more robust behaviour short-term. ) return outdir } diff --git a/core/src/com/unciv/scripting/utils/StringifyException.kt b/core/src/com/unciv/scripting/utils/StringifyException.kt index 00c08dd76d033..0a7bd5cfb0b81 100644 --- a/core/src/com/unciv/scripting/utils/StringifyException.kt +++ b/core/src/com/unciv/scripting/utils/StringifyException.kt @@ -16,7 +16,7 @@ fun Exception.stringifyException(): String { "\n", *this.stackTrace, "\n", - *causes.asReversed().map{ it.toString() }.toTypedArray() + *causes.asReversed().map { it.toString() }.toTypedArray() ).joinToString("\n") } // TODO: Move this to ExtensionFunctions.kt. diff --git a/core/src/com/unciv/scripting/utils/TokenizingJson.kt b/core/src/com/unciv/scripting/utils/TokenizingJson.kt index 6a6b4033016b4..bb7e2ba05f6f7 100644 --- a/core/src/com/unciv/scripting/utils/TokenizingJson.kt +++ b/core/src/com/unciv/scripting/utils/TokenizingJson.kt @@ -121,12 +121,14 @@ object TokenizingJson { // TODO: Currently, this means that string keys are encoded with quotes as part of the string. // Serialized keys are additionally different from Kotlin/JVM keys exposed reflectively. // Treating keys as their own JSON strings may be the only way to let all types of values be represented unambiguously in keys, though. + // Maybe just treat strings normally but stringify everything else? That would be consistent with Python behaviour and a superset of normal string-encoding behaviour. + // Collisions would be tricky— Probably just either throw an exception or make real strings always take precedence. It's probably not worth it to specify that JSON keys have to be encoded. // TODO: More testing/documentation is needed. } ) } if (value is Iterable<*>) { // Apparently ::class.java.isArray can be used to check for primitive arrays, but it breaks - return JsonArray(value.map{ getJsonElement(it) }) + return JsonArray(value.map { getJsonElement(it) }) } if (value is Sequence<*>) { return getJsonElement((value as Sequence).toList()) @@ -158,10 +160,10 @@ object TokenizingJson { return null } if (value is JsonArray) { - return (value as List).map{ getJsonReal(it) }// as List + return (value as List).map { getJsonReal(it) }// as List } if (value is JsonObject) { - return (value as Map).entries.associate{ + return (value as Map).entries.associate { InstanceTokenizer.getReal(it.key) to getJsonReal(it.value) }// as Map } diff --git a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt index 2158a188f17b0..5b0c0aa7e45bd 100644 --- a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt @@ -16,6 +16,11 @@ import com.unciv.ui.utils.AutoScrollPane as ScrollPane import kotlin.math.max import kotlin.math.min +//TODO: "WARNING\n\nThe Unciv scripting API is a HIGHLY EXPERIMENTAL feature intended for advanced users!\nIt may be possible to damage your device and files by running malicious or poorly designed code!" +//"Show this warning next time." + +//"I understand and wish to continue." // Probably grey this out for five seconds. +//"Get me out of here!" class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Unit): BaseScreen() { @@ -158,18 +163,18 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un if (i == scriptingState.activeBackend) { button.color = Color.GREEN } - button.onClick({ + button.onClick { scriptingState.switchToBackend(index) updateRunning() - }) + } var termbutton = ImageGetter.getImage("OtherIcons/Stop") - termbutton.onClick({ + termbutton.onClick { val exc: Exception? = scriptingState.termBackend(index) updateRunning() if (exc != null) { echo("Failed to stop ${backend.metadata.displayName} backend: ${exc.toString()}") } - }) + } runningList.add(termbutton.surroundWithCircle(40f)).row() i += 1 } diff --git a/core/src/com/unciv/ui/consolescreen/IConsoleScreenAccessible.kt b/core/src/com/unciv/ui/consolescreen/IConsoleScreenAccessible.kt index a56b567080811..7d88258088dd4 100644 --- a/core/src/com/unciv/ui/consolescreen/IConsoleScreenAccessible.kt +++ b/core/src/com/unciv/ui/consolescreen/IConsoleScreenAccessible.kt @@ -9,6 +9,9 @@ import com.unciv.ui.utils.BaseScreen import com.unciv.ui.worldscreen.WorldScreen import com.unciv.ui.mapeditor.MapEditorScreen +// TODO: Disable in Multiplayer WorldScreen. +// https://github.com/yairm210/Unciv/pull/5125/files + //Interface that extends BaseScreen with methods for exposing the global ConsoleScreen. interface IConsoleScreenAccessible { @@ -39,7 +42,7 @@ interface IConsoleScreenAccessible { //@param gameInfo Active GameInfo. //@param civInfo Active CivilizationInfo. //@param worldScreen Active WorldScreen. - fun BaseScreen.updateScriptingState( + fun BaseScreen.updateScriptingState( // TODO: Rename to setScriptingState gameInfo: GameInfo? = null, civInfo: CivilizationInfo? = null, worldScreen: WorldScreen? = null, @@ -52,4 +55,6 @@ interface IConsoleScreenAccessible { it.mapEditorScreen = mapEditorScreen } // .apply errors on compile with "val cannot be reassigned". } + +// fun BaseScreen.updateScriptingState(){} // TODO: Same, but don't clear. } diff --git a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt index 9d1ea3cf2a826..b9d0f6659c65e 100644 --- a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt +++ b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt @@ -196,7 +196,7 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas setConsoleScreenCloseAction({ game.setWorldScreen() }) updateScriptingState( gameInfo = gameInfo, - civInfo = selectedCiv, + civInfo = selectedCiv, // TODO: Spectator. worldScreen = this ) } diff --git a/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt b/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt index 1b0cb860380c0..dd06bb51008ae 100644 --- a/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt +++ b/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt @@ -250,9 +250,12 @@ class OptionsPopup(val previousScreen: BaseScreen) : Popup(previousScreen) { settings.enableScriptingConsole) { settings.enableScriptingConsole = it } - //TODO: Disable on Android. + ////TODO: Disable on Android. //TODO (Later): Persist command history? - //TODO (Later): Startup macros per backend type. + //TODO: Move to separate tab. + //TODO: Startup macros per backend type. + //TODO: ConsoleScreen warning toggle. + //https://thenounproject.com/icon/code-787514/ if (previousScreen.game.limitOrientationsHelper != null) { addYesNoRow("Enable portrait orientation", settings.allowAndroidPortrait) { From 190f3d30b1853a849d4def488631ec21d07d23c0 Mon Sep 17 00:00:00 2001 From: will-ca Date: Tue, 7 Dec 2021 16:16:24 +0000 Subject: [PATCH 60/93] Reorganize API helpers. Plan to completely change binding semantics. --- .../enginefiles/python/PythonScripting.md | 12 +- .../enginefiles/python/unciv_lib/wrapping.py | 21 ++- .../unciv_scripting_examples/EndTimes.py | 2 +- .../MapEditingMacros.py | 6 +- .../unciv_scripting_examples/Merfolk.py | 2 +- .../python/unciv_scripting_examples/Tests.py | 8 +- .../{LifesOut.py => TicTacToe.py} | 4 +- core/Module.md | 10 +- core/src/com/unciv/UncivGame.kt | 2 +- .../com/unciv/scripting/ScriptingBackend.kt | 8 +- .../src/com/unciv/scripting/ScriptingScope.kt | 155 ------------------ .../src/com/unciv/scripting/ScriptingState.kt | 5 +- .../scripting/api/ScriptingApiAppHelpers.kt | 35 ++++ .../unciv/scripting/api/ScriptingApiEnums.kt | 23 --- .../scripting/api/ScriptingApiFactories.kt | 72 -------- .../scripting/api/ScriptingApiHelpers.kt | 38 +++++ .../api/ScriptingApiInstanceRegistry.kt | 1 + .../scripting/api/ScriptingApiJvmHelpers.kt | 63 +++++++ .../scripting/api/ScriptingApiMappers.kt | 7 +- .../scripting/api/ScriptingApiSysHelpers.kt | 13 ++ .../unciv/scripting/api/ScriptingApiUnciv.kt | 13 ++ .../com/unciv/scripting/api/ScriptingScope.kt | 76 +++++++++ .../protocol/ScriptingReplManager.kt | 4 +- .../scripting/protocol/SubprocessBlackbox.kt | 19 +-- .../unciv/scripting/reflection/Reflection.kt | 3 +- .../unciv/scripting/utils/ApiSpecGenerator.kt | 2 +- .../scripting/{api => utils}/FakeMaps.kt | 23 ++- .../scripting/utils/InstanceTokenizer.kt | 4 + 28 files changed, 320 insertions(+), 311 deletions(-) rename android/assets/scripting/enginefiles/python/unciv_scripting_examples/{LifesOut.py => TicTacToe.py} (68%) delete mode 100644 core/src/com/unciv/scripting/ScriptingScope.kt create mode 100644 core/src/com/unciv/scripting/api/ScriptingApiAppHelpers.kt delete mode 100644 core/src/com/unciv/scripting/api/ScriptingApiEnums.kt delete mode 100644 core/src/com/unciv/scripting/api/ScriptingApiFactories.kt create mode 100644 core/src/com/unciv/scripting/api/ScriptingApiHelpers.kt create mode 100644 core/src/com/unciv/scripting/api/ScriptingApiJvmHelpers.kt create mode 100644 core/src/com/unciv/scripting/api/ScriptingApiSysHelpers.kt create mode 100644 core/src/com/unciv/scripting/api/ScriptingApiUnciv.kt create mode 100644 core/src/com/unciv/scripting/api/ScriptingScope.kt rename core/src/com/unciv/scripting/{api => utils}/FakeMaps.kt (69%) diff --git a/android/assets/scripting/enginefiles/python/PythonScripting.md b/android/assets/scripting/enginefiles/python/PythonScripting.md index 25acd1b3d86a5..314e03688a894 100644 --- a/android/assets/scripting/enginefiles/python/PythonScripting.md +++ b/android/assets/scripting/enginefiles/python/PythonScripting.md @@ -205,25 +205,25 @@ Note that because of this, it is also perfectly safe to use token strings as arg ```python3 # Each ">>>" represents a new script execution initiated from Kotlin— E.G., A new command entered into the console screen, or a new handler execution from the modding API— And not just a new line of code. Code on multiple lines can still be run in the same REPL loop, as long as the script's control isn't handed back to Kotlin/the JVM in between. ->>> worldScreen.mapHolder.setCenterPosition(apiHelpers.Factories.Vector2(1,2), True, True) +>>> worldScreen.mapHolder.setCenterPosition(apiHelpers.Jvm.Vector2(1,2), True, True) # Works, because the instance creation and the call with a tokenized argument happen in the same REPL execution. ->>> apiHelpers.registeredInstances["x"] = apiHelpers.Factories.Vector2(1,2) +>>> apiHelpers.registeredInstances["x"] = apiHelpers.Jvm.Vector2(1,2) >>> worldScreen.mapHolder.setCenterPosition(apiHelpers.registeredInstances["x"], True, True) #TODO: This doesn't actually use any subpath. # Works, because the instance creation and token-based assignment in Kotlin are done in the same REPL execution. ->>> x = apiHelpers.Factories.Vector2(1,2); civInfo.endTurn(); apiHelpers.registeredInstances["x"] = x +>>> x = apiHelpers.Jvm.Vector2(1,2); civInfo.endTurn(); apiHelpers.registeredInstances["x"] = x >>> worldScreen.mapHolder.setCenterPosition(apiHelpers.registeredInstances["x"], True, True) # Also works. ``` ```python3 ->>> x = apiHelpers.Factories.Vector2(1,2) +>>> x = apiHelpers.Jvm.Vector2(1,2) >>> apiHelpers.registeredInstances["x"] = x >>> worldScreen.mapHolder.setCenterPosition(apiHelpers.registeredInstances["x"], True, True) # May not work, because the created instance has no reference in Kotlin between the first two script executions and can be garbage-collected. ->>> x = apiHelpers.Factories.Vector2(1,2) +>>> x = apiHelpers.Jvm.Vector2(1,2) >>> worldScreen.mapHolder.setCenterPosition(x, True, True) # Also may not work. ``` @@ -271,7 +271,7 @@ class MyForeignContextManager: del memalloc[key] self.memallocKeys.clear() -with MyForeignContextManager(apiHelpers.Factories.MapUnit(), ) as mapUnit, : +with MyForeignContextManager(apiHelpers.Jvm.MapUnit(), ) as mapUnit, : mapUnit del apiHelpers.registeredInstances["python-module:myName/myCoolScript"] diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py index 623f0f1b35787..17cfcdd3f41d8 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py @@ -186,10 +186,18 @@ def alreadyhas(name): # Would have to instantiate in the JSON decoder, though. # I'm not sure it's necessary, since tokens will still have to be encoded as strings in JSON, which means you'd still need apiconstants['kotlinInstanceTokenPrefix'] and isForeignToken in api.py. + +BIND_BY_REFERENCE = False +"""Early versions of this API bound Python objects to Kotlin/JVM instances by keeping track of paths and lazily evaluating them as needed. E.G. ".a.b[5].c" would create an internal tuple like `("a", "b", [5], "c")`, without actually accessing any Kotlin/JVM values at first. Benefits: Fewer IPC actions, lazy resolution of values only as they're used. Drawbacks: Deeper (slow) reflective loops per IPC action, scripting semantics not perfectly synced with JVM state, ugly tricks needed to deal with values that can't be safely accessed as paths from the same scope root, like the properties and methods of instances returned by function calls. + +The current API instead keeps track of every """ + @ResolveForOperators class ForeignObject: """Wrapper for a foreign object. Implements the specifications on IPC packet action types and data structures in Module.md.""" def __init__(self, path, foreignrequester=dummyForeignRequester): + # object.__setattr__(self, '_isbaked', False) + # object.__setattr__(self, '_realvalue', ...) # TODO! object.__setattr__(self, '_path', (makePathElement(name=path),) if isinstance(path, str) else tuple(path)) object.__setattr__(self, '_foreignrequester', foreignrequester) def __repr__(self): @@ -198,10 +206,17 @@ def _ipcjson_(self): return self._getvalue_() def _getpath_(self): return tuple(self._path) + # def _bakereal_(self): # TODO! Switch to API-by-reference instead of API-by-path? + # object.__setattr__(self, '_unbaked', self.__class__()) # For in-place operations. + # object.__setattr__(self, '_realvalue', self._getvalue_()) + # object.__setattr__(self, '_isbaked', True) + # object.__setattr__(self, '_path', ()) + # This will break in-place operations. def __getattr__(self, name): # Due to lazy IPC calling, hasattr will never work with this. Instead, check for in dir(). # TODO: Shouldn't I special-casing get_help or _docstring_ here? Wait, no, I think I thought it would be accessed on the class. return self.__class__((*self._path, makePathElement(name=name)), self._foreignrequester) + # attr._bakereal_() def __getattribute__(self, name): if name in ('values', 'keys', 'items'): # Don't expose real .keys, .values, or .items unless wrapping a foreign mapping. This prevents foreign attributes like TileMap.values from being blocked. @@ -223,9 +238,13 @@ def __setitem__(self, key, value): return self[key]._setvalue_(value) @ForeignRequestMethod def _getvalue_(self): + # if self._isbaked and not self._path: + # pass return ({ 'action': 'read', 'data': { + # 'use_root': self._isbaked, # TODO! + # 'root': self._realvalue, 'path': self._getpath_() } }, @@ -323,7 +342,7 @@ def __call__(self, *args): } }, 'read_response', - foreignValueParser) + foreignValueParser) # TODO: Behaviour, semantics, and structure can be unified with __getattr__ with API-by-reference. @ForeignRequestMethod def __delitem__(self, key): return ({ diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EndTimes.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EndTimes.py index 25f17f7e30e91..1b17bccd031bb 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EndTimes.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EndTimes.py @@ -50,7 +50,7 @@ def spreadFalloutType(improvementtype, tilepermitter=lambda t: True): pass #worldScreen.mapHolder.selectedTile.position in civInfo.exploredTiles -#civInfo.addNotification("A volcano has risen from the Earth!", worldScreen.mapHolder.selectedTile.position, apiHelpers.Factories.arrayOfString(["TileSets/FantasyHex/Tiles/Krakatoa"])) +#civInfo.addNotification("A volcano has risen from the Earth!", worldScreen.mapHolder.selectedTile.position, apiHelpers.Jvm.arrayOfString(["TileSets/FantasyHex/Tiles/Krakatoa"])) #civInfo.exploredTiles.add(worldScreen.mapHolder.selectedTile.position) def eruptVolcanoes(): diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py index b053199ed05a7..4f88c21214994 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py @@ -20,7 +20,7 @@ # If you modify this file, please add any new functions to Tests.py. -t = re.sub("//.*", "", re.sub('/\*.*\*/', "", unciv.apiHelpers.assetFileString("jsons/Civ V - Gods & Kings/Terrains.json"), flags=re.DOTALL)) +t = re.sub("//.*", "", re.sub('/\*.*\*/', "", unciv.apiHelpers.App.assetFileString("jsons/Civ V - Gods & Kings/Terrains.json"), flags=re.DOTALL)) terrainsjson = json.loads(t) # In an actual implementation, you would want to read from the ruleset instead of the JSON. But this is eaiser for me. del t @@ -275,9 +275,9 @@ def terrainImagePath(feature): def compositedTerrainImage(terrain): import PIL.Image base, features = terrainFromString(terrain) - image = PIL.Image.open(io.BytesIO(base64.b64decode(unciv.apiHelpers.assetImageB64(terrainImagePath(base))))) + image = PIL.Image.open(io.BytesIO(base64.b64decode(unciv.apiHelpers.App.assetImageB64(terrainImagePath(base))))) for feature in features: - with PIL.Image.open(io.BytesIO(base64.b64decode(unciv.apiHelpers.assetImageB64(terrainImagePath(feature))))) as layer: + with PIL.Image.open(io.BytesIO(base64.b64decode(unciv.apiHelpers.App.assetImageB64(terrainImagePath(feature))))) as layer: image.alpha_composite(layer, (0, image.size[1]-layer.size[1])) return image diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Merfolk.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Merfolk.py index 2c98095707e9a..52ccbd8310846 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Merfolk.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Merfolk.py @@ -27,7 +27,7 @@ #civInfo.cities[0].cityStats.cityInfo.resistanceCounter #CityFlags.Resistance -#civInfo.addNotification("Test", civInfo.cities[0].location, apiHelpers.Factories.arrayOfString(["StatIcons/Gold"])) +#civInfo.addNotification("Test", civInfo.cities[0].location, apiHelpers.Jvm.arrayOfString(["StatIcons/Gold"])) def moveCity(): tileInfo.owningCity = city #Seems to be used for rendering only. diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py index 61b47aba1bb1b..85e046bd0792e 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py @@ -34,10 +34,10 @@ def getTestGame(): - return unciv.GameSaver.gameInfoFromString(Elizabeth300) + return unciv.Unciv.GameSaver.gameInfoFromString(Elizabeth300) def goToMainMenu(): - unciv.uncivGame.setScreen(unciv.apiHelpers.Factories.constructorByQualname['com.unciv.MainMenuScreen']())#unciv.apiHelpers.Factories.Gui.MainMenuScreen()) + unciv.uncivGame.setScreen(unciv.apiHelpers.Jvm.constructorByQualname['com.unciv.MainMenuScreen']())#unciv.apiHelpers.Jvm.Gui.MainMenuScreen()) @Utils.singleton() @@ -54,7 +54,7 @@ class InMapEditor: def __enter__(self): with Utils.TokensAsWrappers(getTestGame()) as (gameinfo,): unciv.uncivGame.setScreen( - unciv.apiHelpers.Factories.constructorByQualname['com.unciv.ui.mapeditor.MapEditorScreen'](gameinfo.tileMap) + unciv.apiHelpers.Jvm.constructorByQualname['com.unciv.ui.mapeditor.MapEditorScreen'](gameinfo.tileMap) # SetScreen doesn't seem to be needed here. But that seems like a glitch in the core Unciv code. ) def __exit__(self, *exc): @@ -100,7 +100,7 @@ def _print(*args, **kwargs): print(*args, **kwargs) if debugprint: # When run as part of build, the Kotlin test-running code should be capturing the Python STDOUT anyway. - unciv.apiHelpers.printLine(str(args[0]) if len(args) == 1 and not kwargs else " ".join(str(a) for a in args)) + unciv.apiHelpers.Sys.printLine(str(args[0]) if len(args) == 1 and not kwargs else " ".join(str(a) for a in args)) for test in self._tests: try: test() diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/LifesOut.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/TicTacToe.py similarity index 68% rename from android/assets/scripting/enginefiles/python/unciv_scripting_examples/LifesOut.py rename to android/assets/scripting/enginefiles/python/unciv_scripting_examples/TicTacToe.py index aee27f38685f0..2bd0e1cea207e 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/LifesOut.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/TicTacToe.py @@ -15,9 +15,7 @@ """ -# Actually, a Tic-Tac-Toe game with open trade deals for stake would be a better and easier demo. - -#get apiHelpers.instancesAsInstances[apiHelpers.Factories.constructorByQualname["com.unciv.ui.utils.Popup"](uncivGame.consoleScreen)].open(False) +#get apiHelpers.instancesAsInstances[apiHelpers.Jvm.constructorByQualname["com.unciv.ui.utils.Popup"](uncivGame.consoleScreen)].open(False) def showPopup(): pass diff --git a/core/Module.md b/core/Module.md index 7b0c26e02cbff..1f82870553448 100644 --- a/core/Module.md +++ b/core/Module.md @@ -228,17 +228,11 @@ Some action types, data formats, and expected response types and data formats fo *Implemented by `class ScriptingProtocol(){}` and `class ForeignObject()`.* ``` - 'read': {'path': List<{'type':String, 'name':String, 'params':List}>} -> + 'read': {'use_root' Boolean, 'root': Any?, 'path': List<{'type':String, 'name':String, 'params':List}>} -> 'read_reponse': Any? or String //Attribute/property access, by list of `PathElement` properties. //Response must be String if sent with Exception flag. - ``` - - ``` - //'call': {'path': List<{'type':String, 'name':String, 'params':List}>, 'args': Collection, 'kwargs': Map} -> - //'call_response': {'value': Any?, 'exception': String?} - //Method/function call. - //Deprecated and removed. Instead, use `"action":"read"` with a "path" that has `"type":"Call"` element(s) as per `PathElement`. + //TODO: Implement and use use_root and root. ``` ``` diff --git a/core/src/com/unciv/UncivGame.kt b/core/src/com/unciv/UncivGame.kt index 54424897a6c78..2b25ce48900ba 100644 --- a/core/src/com/unciv/UncivGame.kt +++ b/core/src/com/unciv/UncivGame.kt @@ -13,8 +13,8 @@ import com.unciv.models.metadata.GameSettings import com.unciv.models.ruleset.RulesetCache import com.unciv.models.tilesets.TileSetCache import com.unciv.models.translations.Translations -import com.unciv.scripting.ScriptingScope import com.unciv.scripting.ScriptingState +import com.unciv.scripting.api.ScriptingScope import com.unciv.ui.consolescreen.ConsoleScreen import com.unciv.ui.LanguagePickerScreen import com.unciv.ui.audio.MusicController diff --git a/core/src/com/unciv/scripting/ScriptingBackend.kt b/core/src/com/unciv/scripting/ScriptingBackend.kt index 8eb5a74d65000..bcb92ba52bc3f 100644 --- a/core/src/com/unciv/scripting/ScriptingBackend.kt +++ b/core/src/com/unciv/scripting/ScriptingBackend.kt @@ -2,7 +2,7 @@ package com.unciv.scripting //import com.badlogic.gdx.Gdx import com.badlogic.gdx.files.FileHandle - +import com.unciv.scripting.api.ScriptingScope import com.unciv.scripting.reflection.Reflection import com.unciv.scripting.protocol.Blackbox import com.unciv.scripting.protocol.ScriptingReplManager @@ -348,9 +348,9 @@ class ReflectiveScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacke "set \"Cattle\" worldScreen.mapHolder.selectedTile.resource", "set \"Krakatoa\" worldScreen.mapHolder.selectedTile.naturalWonder", "get civInfo.addGold(civInfo.tech.techsResearched.size)", - "get uncivGame.setScreen(apiHelpers.Factories.constructorByQualname[\"com.unciv.ui.mapeditor.MapEditorScreen\"](gameInfo.tileMap))", - "get apiHelpers.Factories.constructorByQualname[\"com.unciv.ui.utils.ToastPopup\"](\"This is a popup!\", uncivGame.consoleScreen, 2000)" - //apiHelpers.Factories.constructorByQualname["com.unciv.ui.worldscreen.AlertPopup"](worldScreen, apiHelpers.Factories.constructorByQualname["com.unciv.logic.civilization.PopupAlert"](apiHelpers.Enums.enumMapsByQualname["com.unciv.logic.civilization.AlertType"]["FirstContact"], "Carthage")) + "get uncivGame.setScreen(apiHelpers.Jvm.constructorByQualname[\"com.unciv.ui.mapeditor.MapEditorScreen\"](gameInfo.tileMap))", + "get apiHelpers.Jvm.constructorByQualname[\"com.unciv.ui.utils.ToastPopup\"](\"This is a popup!\", uncivGame.consoleScreen, 2000)" + //apiHelpers.Jvm.constructorByQualname["com.unciv.ui.worldscreen.AlertPopup"](worldScreen, apiHelpers.Jvm.constructorByQualname["com.unciv.logic.civilization.PopupAlert"](apiHelpers.Enums.enumMapsByQualname["com.unciv.logic.civilization.AlertType"]["FirstContact"], "Carthage")) ) override fun motd(): String { diff --git a/core/src/com/unciv/scripting/ScriptingScope.kt b/core/src/com/unciv/scripting/ScriptingScope.kt deleted file mode 100644 index 98ace38306aa8..0000000000000 --- a/core/src/com/unciv/scripting/ScriptingScope.kt +++ /dev/null @@ -1,155 +0,0 @@ -package com.unciv.scripting - -import com.badlogic.gdx.Gdx -//import com.badlogic.gdx.graphics.glutils.FileTextureData -import com.badlogic.gdx.graphics.PixmapIO -import com.badlogic.gdx.utils.Base64Coder -import com.unciv.UncivGame -import com.unciv.logic.GameInfo -//import com.unciv.logic.GameSaver -import com.unciv.logic.civilization.CivilizationInfo -import com.unciv.scripting.api.FakeMap -import com.unciv.scripting.api.ScriptingApiEnums -import com.unciv.scripting.api.ScriptingApiFactories -import com.unciv.scripting.api.ScriptingApiInstanceRegistry -import com.unciv.scripting.api.ScriptingApiMappers -import com.unciv.scripting.reflection.Reflection -import com.unciv.ui.mapeditor.MapEditorScreen -import com.unciv.ui.utils.ImageGetter -import com.unciv.ui.utils.toPixmap -import com.unciv.ui.worldscreen.WorldScreen -import java.io.ByteArrayOutputStream - - -// TODO: Move this, and all nested objects and classes, to api/? - -// TODO: Search core code for Transient lazy init caches (E.G. natural wonders saved in TileMap apparently, and TileMap.resources), and add functions to refresh them. - - -/** - * Holds references to all internal game data that scripting backends have access to. - * - * Also where to put any future PlayerAPI, CheatAPI, ModAPI, etc. - * - * For LuaScriptingBackend, UpyScriptingBackend, QjsScriptingBackend, etc, the hierarchy of data under this class definition should probably directly mirror the wrappers in the namespace exposed to running scripts. - * - * WorldScreen gives access to UnitTable.selectedUnit, MapHolder.selectedTile, etc. Useful for contextual operations. - * - * The members of this class and its nested classes should be designed for use by running scripts, not for implementing the protocol or API of scripting backends. - * E.G.: If you need access to a file to build the scripting environment, then add it to ScriptingEngineConstants.json instead of using apiHelpers.assetFileB64. If you need access to some new type of property, then geneneralize it as much as possible and add an IPC request type for it in ScriptingProtocol.kt or add support for it in Reflection.kt. - * In Python terms, that means that magic methods all directly send and parse IPC packets, while running scripts transparently use those magic methods to access the functions here. - * API calls are for running scripts, and may be less stable. Building the scripting environment itself should be done directly using the IPC protocol and other lower-level constructs. - * - * To reduce the chance of E.G. name collisions in .apiHelpers.registeredInstances, or one misbehaving mod breaking everything by unassigning .gameInfo, different ScriptingState()s should each have their own ScriptingScope(). - */ -class ScriptingScope( - // This entire API should still be considered unstable. It may be drastically changed at any time. - - //If this is going to be exposed to downloaded mods, then every declaration here, as well as *every* declaration that is safe for scripts to have access to, should probably be whitelisted with annotations and checked or errored at the point of reflection. - var civInfo: CivilizationInfo? = null, - var gameInfo: GameInfo? = null, - var uncivGame: UncivGame? = null, - var worldScreen: WorldScreen? = null, - var mapEditorScreen: MapEditorScreen? = null, - //var scriptingBackend: ScriptingBackend? = null // TODO: Currently executing backend. - //val _availableNames = listOf("civInfo", "gameInfo", "uncivGame", "worldScreen", "apiHelpers") // Nope. Annotate instead. - ) { - - val GameSaver = com.unciv.logic.GameSaver //TODO: Organize. - - val apiHelpers = ApiHelpers(this) - - //var modApiHelpers: ModApiHelpers? - - class ApiHelpers(val scriptingScope: ScriptingScope) { - // This, and the classes of its members, should try to implement only the minimum number of helper functions that are needed for each type of functionality otherwise not possible in scripts. E.G. Don't add special "loadGame" functions or whatever here, but do expose the existing methods of UncivGame. E.G. Don't add factories to speed up making alert popups, because all the required constructors can already be called through constructorByQualname anyway. Let the rest of the codebase and the scripts themselves do the work— Maintenance of the API itself will be easier if all it does is expose existing Kotlin code to dynamic Python/JS/Lua code. - // TODO: Move this into its own file. - // This could probably eventually include ways for scripts to create and inject their own UI elements too. Create, populate, show even popups for mods, inject buttons that execute script strings for macros. - // TODO: The vast majority of these don't need scriptingScope access, and thus can be put on singletons. - val isInGame: Boolean - get() = (scriptingScope.civInfo != null && scriptingScope.gameInfo != null && scriptingScope.uncivGame != null) - @Suppress("PropertyName") - val Factories = ScriptingApiFactories - @Suppress("PropertyName") - val Enums = ScriptingApiEnums - @Suppress("PropertyName") - val Mappers = ScriptingApiMappers - val registeredInstances = ScriptingApiInstanceRegistry() - val instancesAsInstances = FakeMap() // TODO: Rename this. - // Scripting language bindings work by keeping track of the paths to values, and making Kotlin/the JVM resolve them only when needed. - // This creates a dilemma: Resolving a path into a Kotlin value too early means that no further paths (E.G. attribute, keys, calls) can be built on top of it. But resolving it late means that expected side effects may not happen (E.G. function calls probably shouldn't be deferred). And values that *must* be resolved, like the results of function calls, cannot have their own members and method accessed until they themselves are assigned to a path, because they're just kinda floating around as far as the scripting-exposed semantics are concerned. - // So this fake Map works around that, by providing a way for any random object to appear to have a path. - - //Debug/dev identity function for both Kotlin and scripts. Check if value survives serialization, force something to be added to ScriptingProtocol.instanceSaver, etc. - fun echo(obj: Any?) = obj // TODO: Rename back to echo. - fun printLine(msg: Any?) = println(msg.toString()) // Different name from Kotlin's is deliberate, to abstract for scripts. - //fun readLine() - //Return a line from the main game process's STDIN. - fun toString(obj: Any?) = obj.toString() - fun copyToClipboard(value: Any?) { - //Better than scripts potentially doing it themselves. In Python, for example, a way to do this would involve setting up an invisible TKinter window. - Gdx.app.clipboard.contents = value.toString() - } - //fun typeOf(obj: Any?) = obj::class.simpleName - //fun typeOfQualified(obj: Any?) = obj::class.qualifiedName - // @param path Path of an internal file as exposed in Gdx.files.internal. - // @return The contents of the internal file read as a text string. - fun assetFileString(path: String) = Gdx.files.internal(path).readString() - // @param path Path of an internal file as exposed in Gdx.files.internal. - // @return The contents of the internal file encoded as a Base64 string. - fun assetFileB64(path: String) = String(Base64Coder.encode(Gdx.files.internal(path).readBytes())) - // @param path Path of an internal image as exposed in ImageGetter as a TextureRegionDrawable from an atlas. - // @return The image encoded as a PNG file encoded as a Base64 string. - fun assetImageB64(path: String): String { - // When/if letting scripts make UI elements becomes a thing, these should probably be organized together with the factories for that. - // To test in Python: - // import PIL.Image, io, base64; PIL.Image.open(io.BytesIO(base64.b64decode(apiHelpers.assetImage("StatIcons/Resistance")))).show() - val fakepng = ByteArrayOutputStream() - //Close this stream? Well, the docs say doing so "has no effect", and it should clearly get GC'd anyway. - val pixmap = ImageGetter.getDrawable(path).getRegion().toPixmap() - val exporter = PixmapIO.PNG() // Could be kept and "reused to encode multiple PNGs with minimal allocation", according to the docs. I don't see it as a sufficient bottleneck yet to necesarily justify the complexity and risk, though. - exporter.setFlipY(false) - exporter.write(fakepng, pixmap) - pixmap.dispose() // In theory needed to avoid memory leak. Doesn't seem to actually have any impact, compared to the .dispose() inside .toPixmap(). Maybe the exporter's dispose also calls this? - exporter.dispose() // This one should be called automatically by GC anyway. - return String(Base64Coder.encode(fakepng.toByteArray())) - } - - //setTimeout? - //fun lambdifyScript(code: String ): () -> Unit = fun(){ scriptingScope.uncivGame!!.scriptingState.exec(code); return } // FIXME: Requires awareness of which scriptingState and which backend to use. - //Directly invoking the resulting lambda from a running script will almost certainly break the REPL loop/IPC protocol. - } - -} - -// Does having one state manage multiple backends that all share the same scope really make sense? Mod handler dispatch, callbacks, etc might all be easier if the multi-backend functionality of ScriptingState were implemented only for ConsoleScreen. -// ScriptingState also helps separate , keep the shared ScriptingScope between all of them (such that it only needs to be updated once on game context changes), and update - -/* -//class ModApiHelpers { - var handlerContext: NamedTuple? - // Why not just use a map? String keys will be clearer in scripts than integers anyway. - // Collection that gets replaced with any contextual parameters when running script handlers. E.G. Unit moved, city founded, tech researched, construction finished. -//fun showScriptedChoicePopup(title: String, body: String, options: List, callback: String?) // Actually, no. Popup() seems to work on its own. - // TODO: Mods blacklist, for security threats. -} - - -//class NamedTuple(val map: Map) { - //Default order-preserving implementation of Map is important. - //SortedMap? - val keys = map.keys.toList() - //Default Set implementation also preserver order, so hopefully fine. - fun contains(vararg args: Any?) { - throw UnsupportedOperationException() - //Check against values? Keys? - } - fun get() { - if (is Int) { - } - } -} -*/ - - - diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index 7f1193306f99f..fc76bd84e82d0 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -1,9 +1,6 @@ package com.unciv.scripting -//import com.unciv.logic.GameInfo -//import com.unciv.logic.civilization.CivilizationInfo -//import com.unciv.UncivGame -//import com.unciv.ui.worldscreen.WorldScreen +import com.unciv.scripting.api.ScriptingScope import kotlin.collections.ArrayList import kotlin.math.max import kotlin.math.min diff --git a/core/src/com/unciv/scripting/api/ScriptingApiAppHelpers.kt b/core/src/com/unciv/scripting/api/ScriptingApiAppHelpers.kt new file mode 100644 index 0000000000000..d4bf83ef9f743 --- /dev/null +++ b/core/src/com/unciv/scripting/api/ScriptingApiAppHelpers.kt @@ -0,0 +1,35 @@ +package com.unciv.scripting.api + +import com.badlogic.gdx.Gdx +import com.badlogic.gdx.graphics.PixmapIO +import com.badlogic.gdx.utils.Base64Coder +import com.unciv.ui.utils.ImageGetter +import com.unciv.ui.utils.toPixmap +import java.io.ByteArrayOutputStream + +object ScriptingApiAppHelpers { + //Debug/dev identity function for both Kotlin and scripts. Check if value survives serialization, force something to be added to ScriptingProtocol.instanceSaver, etc. +// fun echo(obj: Any?) = obj // Redundant with instancesAsInstances. + // @param path Path of an internal file as exposed in Gdx.files.internal. + // @return The contents of the internal file read as a text string. + fun assetFileString(path: String) = Gdx.files.internal(path).readString() + // @param path Path of an internal file as exposed in Gdx.files.internal. + // @return The contents of the internal file encoded as a Base64 string. + fun assetFileB64(path: String) = String(Base64Coder.encode(Gdx.files.internal(path).readBytes())) + // @param path Path of an internal image as exposed in ImageGetter as a TextureRegionDrawable from an atlas. + // @return The image encoded as a PNG file encoded as a Base64 string. + fun assetImageB64(path: String): String { + // When/if letting scripts make UI elements becomes a thing, these should probably be organized together with the factories for that. + // To test in Python: + // import PIL.Image, io, base64; PIL.Image.open(io.BytesIO(base64.b64decode(apiHelpers.assetImage("StatIcons/Resistance")))).show() + val fakepng = ByteArrayOutputStream() + //Close this stream? Well, the docs say doing so "has no effect", and it should clearly get GC'd anyway. + val pixmap = ImageGetter.getDrawable(path).getRegion().toPixmap() + val exporter = PixmapIO.PNG() // Could be kept and "reused to encode multiple PNGs with minimal allocation", according to the docs. I don't see it as a sufficient bottleneck yet to necesarily justify the complexity and risk, though. + exporter.setFlipY(false) + exporter.write(fakepng, pixmap) + pixmap.dispose() // In theory needed to avoid memory leak. Doesn't seem to actually have any impact, compared to the .dispose() inside .toPixmap(). Maybe the exporter's dispose also calls this? + exporter.dispose() // This one should be called automatically by GC anyway. + return String(Base64Coder.encode(fakepng.toByteArray())) + } +} diff --git a/core/src/com/unciv/scripting/api/ScriptingApiEnums.kt b/core/src/com/unciv/scripting/api/ScriptingApiEnums.kt deleted file mode 100644 index 72acc1688f449..0000000000000 --- a/core/src/com/unciv/scripting/api/ScriptingApiEnums.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.unciv.scripting.api - -// Convert an Enum type parameter into a Map of its constants by their names. -inline fun > enumToMap() = enumValues().associateBy { it.name } - -fun enumQualnameToMap(qualName: String) = Class.forName(qualName).enumConstants.associateBy { (it as Enum<*>).name } -// Always return a built-in Map class instance here, so its gets serialized as JSON object instead of tokenized, and scripts can refer directly to its items. -// I cast to Enum<*> fully expecting it would crash because it felt metaclass-y. But apparently it's just a base class, so it works? - -// TODO (Later): Use ClassGraph to automatically find all relevant classes on build. -// https://github.com/classgraph/classgraph/wiki/Build-Time-Scanning - - -/** - * For use in ScriptingScope. Allows interpreted scripts to access Unciv Enum constants. - * - * Currently exposes enum values as maps. - */ -object ScriptingApiEnums { - val enumMapsByQualname = LazyMap(::enumQualnameToMap) - - // apiHelpers.Enums.enumMapsByQualname["com.unciv.logic.automation.ThreatLevel"]['VeryLow'] -} diff --git a/core/src/com/unciv/scripting/api/ScriptingApiFactories.kt b/core/src/com/unciv/scripting/api/ScriptingApiFactories.kt deleted file mode 100644 index f708b37b8a9ab..0000000000000 --- a/core/src/com/unciv/scripting/api/ScriptingApiFactories.kt +++ /dev/null @@ -1,72 +0,0 @@ -package com.unciv.scripting.api - -import com.unciv.scripting.reflection.FunctionDispatcher -import com.unciv.scripting.reflection.Reflection -import com.unciv.scripting.reflection.makeFunctionDispatcher -import kotlin.reflect.KCallable -import kotlin.reflect.full.primaryConstructor -import kotlin.reflect.jvm.kotlinFunction - - -//// TODO (Later, maybe.): Use ClassGraph to automatically find all relevant classes on build. -// https://github.com/classgraph/classgraph/wiki/Build-Time-Scanning -// Honestly, it's fine. Having scripts provide the qualpaths themselves keeps everything dynamic, and the LazyMap caching keeps it (as) performant (as the rest of the API is, probably), so the only real "benefit" of indexing everything beforehand would be enabling autocompletion. - - -// TODO: Rename this, to "Jvm", maybe. - -/** - * For use in ScriptingScope. Allows interpreted scripts to make new instances of Kotlin/JVM classes. - */ -object ScriptingApiFactories { - - //val javaClassByQualname - - val kotlinClassByQualname = LazyMap({ qualName: String -> Class.forName(qualName).kotlin }, exposeState = true) - //// Mostly for debug. Scripts may, but probably usually shouldn't, use this. - - // Actually, this works for singletons too: - // apiHelpers.Factories.kotlinClassByQualname['com.unciv.scripting.reflection.Reflection'].objectInstance - - // Works for files: apiHelpers.Factories.kotlinClassByQualname['com.unciv.scripting.ScriptingStateKt']. Kinda. (Top-level functions?) - // apiHelpers.instancesAsInstances[apiHelpers.Factories.kotlinClassByQualname['com.unciv.ui.utils.ExtensionFunctionsKt'].jClass.getMethods()][0].getName() - - // [m.getName() for m in apiHelpers.instancesAsInstances[apiHelpers.Factories.kotlinClassByQualname['com.unciv.ui.utils.ExtensionFunctionsKt'].jClass.getMethods()] ] - // m=next(m for m in apiHelpers.instancesAsInstances[apiHelpers.Factories.kotlinClassByQualname['com.unciv.ui.utils.ExtensionFunctionsKt'].jClass.getMethods()] if m.getName() == 'enable') - - //val companionByQualName TODO? - // Fails: apiHelpers.Factories.kotlinClassByQualname["com.unciv.scripting.SpyScriptingBackend.Metadata"] - -// val toplevelFunctionByQualname = LazyMap ({ qualName: String -> -// val simpleName = qualName.substringAfterLast('.') -// makeFunctionDispatcher(Class.forName("${qualName.substringBeforeLast('.')}Kt").getDeclaredMethods().asSequence().filter { it.name == simpleName }.map { it.kotlinFunction }.toList() as List>) -// // I think I read somewhere at some point that the "${Filename}Kt" name for files is documented, but I couldn't find it again. -// }, exposeState = true) -// // TODO: exposeState should probably be unset in all these? -// // apiHelpers.Factories.toplevelFunctionByQualname["com.unciv.ui.utils.ExtensionFunctions.onClick"] -// // apiHelpers.Factories.toplevelFunctionByQualname["com.unciv.ui.utils.ExtensionFunctions.toLabel"]("Test") -// // This *is* rather convenient, but maybe it should be unboundMethodsByQualname or something like that instead? "Top-level function" and "Java method" seem quite far intuitively in Kotlin semantics, but the code is only two string characters apart (and they're apparently the same thing on the JVM). - - val functionByQualClassAndMethodName = LazyMap ({ jclassQualname: String -> - val cls = Class.forName(jclassQualname) - LazyMap({ methodName: String -> makeFunctionDispatcher(cls.getDeclaredMethods().asSequence().filter { it.name == methodName }.map { it.kotlinFunction }.toList() as List>) }, exposeState = true) - // Could initialize the second LazyMap here by accessing for all names— Only benefit would be for autocomplete, at higher first-call time and memory use, though. - }, exposeState = true) - // TODO: exposeState should probably be unset in all these? - // apiHelpers.Factories.functionByQualClassAndMethodName["com.unciv.ui.utils.ExtensionFunctionsKt"]["toLabel"]("Test") - - val staticPropertyByQualClassAndName = LazyMap ({ jclassQualname: String -> - val kcls = Class.forName(jclassQualname).kotlin - LazyMap({ name: String -> Reflection.readClassProperty(kcls, name) as Any? }, exposeState = true) - }, exposeState = true) - // apiHelpers.Factories.kotlinClassByQualname["com.badlogic.gdx.graphics.Color"].members[50].get() - // apiHelpers.Factories.staticPropertyByQualClassAndName["com.badlogic.gdx.graphics.Color"]['WHITE'] - - val constructorByQualname = LazyMap({ qualName: String -> makeFunctionDispatcher(Class.forName(qualName).kotlin.constructors) }, exposeState = true) - // TODO (Later, Maybe): This would actually be quite easy to whitelist by package paths. - - fun arrayOf(elements: Collection): Array<*> = elements.toTypedArray() - fun arrayOfAny(elements: Collection): Array = elements.toTypedArray() - fun arrayOfString(elements: Collection): Array = elements.toTypedArray() -} - diff --git a/core/src/com/unciv/scripting/api/ScriptingApiHelpers.kt b/core/src/com/unciv/scripting/api/ScriptingApiHelpers.kt new file mode 100644 index 0000000000000..43454f4e7b733 --- /dev/null +++ b/core/src/com/unciv/scripting/api/ScriptingApiHelpers.kt @@ -0,0 +1,38 @@ +package com.unciv.scripting.api + +import com.badlogic.gdx.Gdx +import com.badlogic.gdx.graphics.PixmapIO +import com.badlogic.gdx.utils.Base64Coder +import com.unciv.scripting.utils.FakeMap +import com.unciv.ui.utils.ImageGetter +import com.unciv.ui.utils.toPixmap +import java.io.ByteArrayOutputStream + +// TODO: Search core code for Transient lazy init caches (E.G. natural wonders saved in TileMap apparently, and TileMap.resources), and add functions to refresh them? + +class ScriptingApiHelpers(val scriptingScope: ScriptingScope) { + // This, and the classes of its members, should try to implement only the minimum number of helper functions that are needed for each type of functionality otherwise not possible in scripts. E.G. Don't add special "loadGame" functions or whatever here, but do expose the existing methods of UncivGame. E.G. Don't add factories to speed up making alert popups, because all the required constructors can already be called through constructorByQualname anyway. Let the rest of the codebase and the scripts themselves do the work— Maintenance of the API itself will be easier if all it does is expose existing Kotlin code to dynamic Python/JS/Lua code. + // TODO: The vast majority of these don't need scriptingScope access, and thus can be put on singletons. + val isInGame: Boolean + get() = (scriptingScope.civInfo != null && scriptingScope.gameInfo != null && scriptingScope.uncivGame != null) + + val App = ScriptingApiAppHelpers + + val Sys = ScriptingApiSysHelpers + + val Jvm = ScriptingApiJvmHelpers + + val Mappers = ScriptingApiMappers + + val registeredInstances = ScriptingApiInstanceRegistry() + val instancesAsInstances = FakeMap{obj: Any? -> obj} // TODO: Rename this. + // Scripting language bindings work by keeping track of the paths to values, and making Kotlin/the JVM resolve them only when needed. + // This creates a dilemma: Resolving a path into a Kotlin value too early means that no further paths (E.G. attribute, keys, calls) can be built on top of it. But resolving it late means that expected side effects may not happen (E.G. function calls probably shouldn't be deferred). And values that *must* be resolved, like the results of function calls, cannot have their own members and method accessed until they themselves are assigned to a path, because they're just kinda floating around as far as the scripting-exposed semantics are concerned. + // So this fake Map works around that, by providing a way for any random object to appear to have a path. + + + //setTimeout? + //fun lambdifyScript(code: String ): () -> Unit = fun(){ scriptingScope.uncivGame!!.scriptingState.exec(code); return } // FIXME: Requires awareness of which scriptingState and which backend to use. + // TODO: Move to modApiHelpers. + //Directly invoking the resulting lambda from a running script will almost certainly break the REPL loop/IPC protocol. +} diff --git a/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt b/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt index 61ed516dc004d..282b6399cb206 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt @@ -63,6 +63,7 @@ class ScriptingApiInstanceRegistry: MutableMap { override fun isEmpty() = backingMap.isEmpty() override fun clear() = backingMap.clear() override fun put(key: String, value: Any?): Any? { + println("\nAssigning ${key} directly in ScriptingApiInstanceRegistry(). It is recommended that every script/mod do this only once per application lifespan, creating its own mapping for further assignments named according to the following format:\n\t-<'mod'|'module'|'package'>:/\n\tE.G.: \"python-module:myName/myCoolScript\"\n") if (key in this) { throw IllegalArgumentException("\"${key}\" already in ${this}.") } diff --git a/core/src/com/unciv/scripting/api/ScriptingApiJvmHelpers.kt b/core/src/com/unciv/scripting/api/ScriptingApiJvmHelpers.kt new file mode 100644 index 0000000000000..572dec9b7be9a --- /dev/null +++ b/core/src/com/unciv/scripting/api/ScriptingApiJvmHelpers.kt @@ -0,0 +1,63 @@ +package com.unciv.scripting.api + +import com.unciv.scripting.reflection.Reflection +import com.unciv.scripting.reflection.makeFunctionDispatcher +import com.unciv.scripting.utils.FakeMap +import com.unciv.scripting.utils.LazyMap +import kotlin.reflect.KCallable +import kotlin.reflect.full.companionObjectInstance +import kotlin.reflect.jvm.kotlinFunction + +// Could also use ClassGraph to automatically find all relevant classes on build. +// https://github.com/classgraph/classgraph/wiki/Build-Time-Scanning +// Honestly, it's fine though. Having scripts provide the qualpaths themselves keeps everything dynamic, and the LazyMap caching keeps it (as) performant (as the rest of the API is, probably). The only real "benefit" of indexing everything beforehand would be enabling autocompletion. + +// Convert an Enum type parameter into a Map of its constants by their names. +// inline fun > enumToMap() = enumValues().associateBy { it.name } + +fun enumQualnameToMap(qualName: String) = Class.forName(qualName).enumConstants.associateBy { (it as Enum<*>).name } +// Always return a built-in Map class instance here, so its gets serialized as JSON object instead of tokenized, and scripts can refer directly to its items. +// I cast to Enum<*> fully expecting it would crash because it felt metaclass-y. But apparently it's just a base class, so it works? + + +/** + * For use in ScriptingScope. Allows interpreted scripts access Kotlin/JVM class functionality that isn't attached to any application instances. + */ +object ScriptingApiJvmHelpers { + + private const val exposeStates = true // Probably keep this false? + + val enumMapsByQualname = LazyMap(::enumQualnameToMap) + + val kotlinClassByQualname = LazyMap({ qualName: String -> Class.forName(qualName).kotlin }, exposeState = exposeStates) + + val kotlinSingletonByQualname = LazyMap({ qualName: String -> kotlinClassByQualname[qualName]?.objectInstance }, exposeState = exposeStates) + + val kotlinCompanionByQualClass = LazyMap({ qualName: String -> kotlinClassByQualname[qualName]?.companionObjectInstance }, exposeState = exposeStates) + + val functionByQualClassAndMethodName = LazyMap({ jclassQualname: String -> + val cls = Class.forName(jclassQualname) + LazyMap({ methodName: String -> makeFunctionDispatcher(cls.getDeclaredMethods().asSequence().filter { it.name == methodName }.map { it.kotlinFunction }.toList() as List>) }, exposeState = exposeStates) + // Could initialize the second LazyMap here by accessing for all names— Only benefit would be for autocomplete, at higher first-call time and memory use, though. + }, exposeState = exposeStates) + // apiHelpers.Jvm.functionByQualClassAndMethodName["com.unciv.ui.utils.ExtensionFunctionsKt"]["toLabel"]("Test") + + val staticPropertyByQualClassAndName = LazyMap({ jclassQualname: String -> + val kcls = Class.forName(jclassQualname).kotlin + LazyMap({ name: String -> Reflection.readClassProperty(kcls, name) as Any? }, exposeState = exposeStates) + }, exposeState = exposeStates) + // apiHelpers.Jvm.kotlinClassByQualname["com.badlogic.gdx.graphics.Color"].members[50].get() + // apiHelpers.Jvm.staticPropertyByQualClassAndName["com.badlogic.gdx.graphics.Color"]['WHITE'] + + val constructorByQualname = LazyMap({ qualName: String -> makeFunctionDispatcher(Class.forName(qualName).kotlin.constructors) }, exposeState = exposeStates) + // TODO (Later, Maybe): This would actually be quite easy to whitelist by package paths. + + val kotlinClassByInstance = FakeMap{ obj: Any? -> obj!!::class } + + fun toString(obj: Any?) = obj.toString() + +// fun arrayOf(elements: Collection): Array<*> = elements.toTypedArray() +// fun arrayOfAny(elements: Collection): Array = elements.toTypedArray() +// fun arrayOfString(elements: Collection): Array = elements.toTypedArray() +} + diff --git a/core/src/com/unciv/scripting/api/ScriptingApiMappers.kt b/core/src/com/unciv/scripting/api/ScriptingApiMappers.kt index 788dada796e23..ef218246972e6 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiMappers.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiMappers.kt @@ -1,6 +1,7 @@ package com.unciv.scripting.api import com.unciv.scripting.reflection.Reflection +import com.unciv.scripting.utils.LazyMap // TODO: Rename this. @@ -23,7 +24,7 @@ object ScriptingApiMappers { val pathElementLists = LazyMap>(Reflection::parseKotlinPath) return instancesAndPathcodes.mapValues { (instance, pathcodes) -> pathcodes.associateWith { - Reflection.resolveInstancePath(instance, pathElementLists[it]) + Reflection.resolveInstancePath(instance, pathElementLists[it]!!) } } } @@ -39,12 +40,12 @@ object ScriptingApiMappers { val pathElementLists = LazyMap>(Reflection::parseKotlinPath) for ((instance, assignments) in instancesPathcodesAndValues) { for ((pathcode, value) in assignments) { - Reflection.setInstancePath(instance, pathElementLists[pathcode], value) + Reflection.setInstancePath(instance, pathElementLists[pathcode]!!, value) } } } } // st=time.time(); [real(t.baseTerrain) for t in gameInfo.tileMap.values]; print(time.time()-st) // st=time.time(); apiHelpers.Mappers.mapPathCodes(gameInfo.tileMap.values, ['baseTerrain']); print(time.time()-st) -// FIXME: Gets slower each time run. +// FIXME: Gets slower each time run (presumably due to InstanceTokenizer.clean()'s leak). // Actually around the same speed. Or wait: Is that the InstanceTokenizer slowdown? diff --git a/core/src/com/unciv/scripting/api/ScriptingApiSysHelpers.kt b/core/src/com/unciv/scripting/api/ScriptingApiSysHelpers.kt new file mode 100644 index 0000000000000..04a64016185f0 --- /dev/null +++ b/core/src/com/unciv/scripting/api/ScriptingApiSysHelpers.kt @@ -0,0 +1,13 @@ +package com.unciv.scripting.api + +import com.badlogic.gdx.Gdx + +object ScriptingApiSysHelpers { + fun printLine(msg: Any?) = println(msg.toString()) // Different name from Kotlin's is deliberate, to abstract for scripts. + //fun readLine() // TODO + //Return a line from the main game process's STDIN. + fun copyToClipboard(value: Any?) { + //Better than scripts potentially doing it themselves. In Python, for example, a way to do this would involve setting up an invisible TKinter window. + Gdx.app.clipboard.contents = value.toString() + } +} diff --git a/core/src/com/unciv/scripting/api/ScriptingApiUnciv.kt b/core/src/com/unciv/scripting/api/ScriptingApiUnciv.kt new file mode 100644 index 0000000000000..7399c1dd9dc00 --- /dev/null +++ b/core/src/com/unciv/scripting/api/ScriptingApiUnciv.kt @@ -0,0 +1,13 @@ +package com.unciv.scripting.api + +import com.unciv.ui.utils.UncivDateFormat + +object ScriptingApiUnciv { + // These are also all accessible by qualified name through ScriptingApiJvmHelpers. + // But the functionality they provide is basic enough that it probably merits explicitly exposing. + val GameSaver = com.unciv.logic.GameSaver + val GameStarter = com.unciv.logic.GameStarter + val HexMath = com.unciv.logic.HexMath + val MapSaver = com.unciv.logic.MapSaver + val UncivDateFormat = com.unciv.ui.utils.UncivDateFormat +} diff --git a/core/src/com/unciv/scripting/api/ScriptingScope.kt b/core/src/com/unciv/scripting/api/ScriptingScope.kt new file mode 100644 index 0000000000000..e61caa5a03a45 --- /dev/null +++ b/core/src/com/unciv/scripting/api/ScriptingScope.kt @@ -0,0 +1,76 @@ +package com.unciv.scripting.api + +import com.unciv.UncivGame +import com.unciv.logic.GameInfo +import com.unciv.logic.civilization.CivilizationInfo +import com.unciv.ui.mapeditor.MapEditorScreen +import com.unciv.ui.worldscreen.WorldScreen + + +/** + * Holds references to all internal game data that scripting backends have access to. + * + * Also where to put any future PlayerAPI, CheatAPI, ModAPI, etc. + * + * For LuaScriptingBackend, UpyScriptingBackend, QjsScriptingBackend, etc, the hierarchy of data under this class definition should probably directly mirror the wrappers in the namespace exposed to running scripts. + * + * WorldScreen gives access to UnitTable.selectedUnit, MapHolder.selectedTile, etc. Useful for contextual operations. + * + * The members of this class and its nested classes should be designed for use by running scripts, not for implementing the protocol or API of scripting backends. + * E.G.: If you need access to a file to build the scripting environment, then add it to ScriptingEngineConstants.json instead of using apiHelpers.assetFileB64. If you need access to some new type of property, then geneneralize it as much as possible and add an IPC request type for it in ScriptingProtocol.kt or add support for it in Reflection.kt. + * In Python terms, that means that magic methods all directly send and parse IPC packets, while running scripts transparently use those magic methods to access the functions here. + * API calls are for running scripts, and may be less stable. Building the scripting environment itself should be done directly using the IPC protocol and other lower-level constructs. + * + * To reduce the chance of E.G. name collisions in .apiHelpers.registeredInstances, or one misbehaving mod breaking everything by unassigning .gameInfo, different ScriptingState()s should each have their own ScriptingScope(). + */ +class ScriptingScope( + // This entire API should still be considered unstable. It may be drastically changed at any time. + + //If this is going to be exposed to downloaded mods, then every declaration here, as well as *every* declaration that is safe for scripts to have access to, should probably be whitelisted with annotations and checked or errored at the point of reflection. + var civInfo: CivilizationInfo? = null, + var gameInfo: GameInfo? = null, + var uncivGame: UncivGame? = null, + var worldScreen: WorldScreen? = null, + var mapEditorScreen: MapEditorScreen? = null, + //var scriptingBackend: ScriptingBackend? = null // TODO: Currently executing backend. + ) { + + val Unciv = ScriptingApiUnciv + + val apiHelpers = ScriptingApiHelpers(this) + + //var modApiHelpers: ModApiHelpers? + +} + +// Does having one state manage multiple backends that all share the same scope really make sense? Mod handler dispatch, callbacks, etc might all be easier if the multi-backend functionality of ScriptingState were implemented only for ConsoleScreen. +// ScriptingState also helps separate , keep the shared ScriptingScope between all of them (such that it only needs to be updated once on game context changes), and update + +/* +//class ModApiHelpers { + var handlerContext: NamedTuple? + // Why not just use a map? String keys will be clearer in scripts than integers anyway. + // Collection that gets replaced with any contextual parameters when running script handlers. E.G. Unit moved, city founded, tech researched, construction finished. +//fun showScriptedChoicePopup(title: String, body: String, options: List, callback: String?) // Actually, no. Popup() seems to work on its own. + // TODO: Mods blacklist, for security threats. +} + + +//class NamedTuple(val map: Map) { + //Default order-preserving implementation of Map is important. + //SortedMap? + val keys = map.keys.toList() + //Default Set implementation also preserver order, so hopefully fine. + fun contains(vararg args: Any?) { + throw UnsupportedOperationException() + //Check against values? Keys? + } + fun get() { + if (is Int) { + } + } +} +*/ + + + diff --git a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt index b9320c8c00a8c..14975e5ba4726 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt @@ -3,7 +3,7 @@ package com.unciv.scripting.protocol import com.unciv.UncivGame // For debug packet print only. import com.unciv.scripting.AutocompleteResults import com.unciv.scripting.ScriptingBackend -import com.unciv.scripting.ScriptingScope +import com.unciv.scripting.api.ScriptingScope //import com.unciv.scripting.protocol.ScriptingPacket //import com.unciv.scripting.protocol.ScriptingProtocol @@ -56,7 +56,7 @@ class ScriptingProtocolReplManager(scriptingScope: ScriptingScope, blackbox: Bla * * This makes sure a single script execution doesn't get its tokenized Kotlin/JVM objects garbage collected, and has a chance to save them elsewhere (E.G. ScriptingScope.apiHelpers.registeredInstances) if it needs them later. * Should preserve each instance, not just each value, so should be List and not Set. - * To test in Python console backend: x = apiHelpers.Factories.Vector2(1,2); civInfo.endTurn(); print(apiHelpers.toString(x)) + * To test in Python console backend: x = apiHelpers.Jvm.Vector2(1,2); civInfo.endTurn(); print(apiHelpers.toString(x)) */ val instanceSaver = mutableListOf() diff --git a/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt b/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt index 22a42c56e3ddc..885b2a3cfdfe7 100644 --- a/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt +++ b/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt @@ -31,16 +31,15 @@ class SubprocessBlackbox(val processCmd: Array): Blackbox { var processLaunchFail: String? = null override val isAlive: Boolean - get() = process != null && process!!.isAlive() -// get() { -// return try { -// @Suppress("NewApi") -// process != null && process!!.isAlive() -// // Usually process will be null on Android anyway. -// } catch(e: NoSuchMethodError) { true } // Compiled access. -// catch(e: NoSuchMethodException) { true } // Reflective access. -// // TODO: API Level 26. But also, it's not like using subprocesses is actually planned for scripting on Android. -// } + get() { + return try { + @Suppress("NewApi") + process != null && process!!.isAlive() + // Usually process will be null on Android anyway. + } catch(e: NoSuchMethodError) { true } // NoSuchMethodError is for compiled access. NoSuchMethodException is for reflective access. There's no reflection happening in the try{} block. + // I'm not planning to use subprocesses on Android, so it's okay if the catch{} block returns an incorrect answer. + // But if subprocesses are to be used on Android, then more work should be done to return an accurate answer in all cases. + } override val readyForWrite: Boolean get() = isAlive diff --git a/core/src/com/unciv/scripting/reflection/Reflection.kt b/core/src/com/unciv/scripting/reflection/Reflection.kt index 0f059d2aa0bfb..5a616a8e908da 100644 --- a/core/src/com/unciv/scripting/reflection/Reflection.kt +++ b/core/src/com/unciv/scripting/reflection/Reflection.kt @@ -262,7 +262,8 @@ object Reflection { } PathElementType.Call -> { // TODO: Handle invoke operator. Easy enough, just recurse to access the .invoke. - // Test in Python: apiHelpers.Factories.constructorByQualname.invoke('com.unciv.UncivGame'). Also do an object for multi-arg testing, I guess? + // object test{operator fun invoke(a:Any?,b:Any?){println("$a $b")}}; test(1,2) + // Test in Python: apiHelpers.Jvm.constructorByQualname('com.unciv.UncivGame'). Also do an object for multi-arg testing, I guess? // Maybe TODO: Handle lambdas. E.G. WorldScreen.nextTurnAction. But honestly, it may be better to just expose wrapping non-lambdas. obj = (obj as FunctionDispatcher).call( // Undocumented implicit behaviour: Using the last object means that this should work with explicitly created FunctionDispatcher()s. diff --git a/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt b/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt index e84c960d17e0e..7cf021dd7cf9e 100644 --- a/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt +++ b/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt @@ -1,6 +1,6 @@ package com.unciv.scripting.utils -import com.unciv.scripting.ScriptingScope +import com.unciv.scripting.api.ScriptingScope import kotlin.reflect.KCallable import kotlin.reflect.KClass import kotlin.reflect.KFunction diff --git a/core/src/com/unciv/scripting/api/FakeMaps.kt b/core/src/com/unciv/scripting/utils/FakeMaps.kt similarity index 69% rename from core/src/com/unciv/scripting/api/FakeMaps.kt rename to core/src/com/unciv/scripting/utils/FakeMaps.kt index 30b0feecf3fbc..2d9f261c88dd9 100644 --- a/core/src/com/unciv/scripting/api/FakeMaps.kt +++ b/core/src/com/unciv/scripting/utils/FakeMaps.kt @@ -1,5 +1,9 @@ -package com.unciv.scripting.api +package com.unciv.scripting.utils +// Functions may have side effects. +// Containers, however, should change neither their own nor other data's public state on their own. +// Therefore, for certain models of scripting language bindings, it is easier to make guarantees about and expose flexible semantics for key indexing than for function calls. +// The classes here help expose functions as Map-like instances, letting simple, side-effect-free functions with direct mappings from input to output be presented in scripting language bindings without having to worry about side effects from E.G. repeated or deferred calling. abstract class StatelessMap: Map { protected fun noStateError(): Nothing = throw(UnsupportedOperationException("Cannot access backing state of ${this::class.simpleName} by default.")) @@ -12,6 +16,10 @@ abstract class StatelessMap: Map { override fun isEmpty(): Boolean = noStateError() } +interface InvokableMap: Map { + operator fun invoke(key: K): V? = get(key) +} + // Lazy Map of indeterminate size. // Memoizes a single-argument function. @@ -20,20 +28,19 @@ abstract class StatelessMap: Map { // @property func The function that returns the value for a given key. // @property exposeState Whether to expose the content-specific members of the backing map. -class LazyMap(val func: (K) -> V, val exposeState: Boolean = false): StatelessMap() { +class LazyMap(val func: (K) -> V, val exposeState: Boolean = false): StatelessMap(), InvokableMap { // Benefit of a Map over a function is that because mapping access can be safely assumed by scripting language bindings to have no side effects, it's semantically easier for scripting language bindings to let the returned value be immediately called, autocompleted, indexed/attribute-read, etc. private val backingMap = hashMapOf() - override fun get(key: K): V { - val result: V + override fun get(key: K): V? { + val result: V? if (key !in backingMap) { result = func(key) backingMap[key] = result } else { - result = backingMap[key]!! + result = backingMap[key] } return result } - fun invoke(key: K): V = get(key) override val entries get() = if (exposeState) backingMap.entries else noStateError() override val keys get() = if (exposeState) backingMap.keys else noStateError() override val values get() = if (exposeState) backingMap.values else noStateError() @@ -43,6 +50,6 @@ class LazyMap(val func: (K) -> V, val exposeState: Boolean = false): State override fun isEmpty() = if (exposeState) backingMap.isEmpty() else noStateError() } -class FakeMap: StatelessMap() { - override fun get(key: Any?) = key +class FakeMap(private val getter: (K) -> V): StatelessMap(), InvokableMap { + override fun get(key: K) = getter(key) } diff --git a/core/src/com/unciv/scripting/utils/InstanceTokenizer.kt b/core/src/com/unciv/scripting/utils/InstanceTokenizer.kt index 4caa371c240e5..3ec7bb3b91f4a 100644 --- a/core/src/com/unciv/scripting/utils/InstanceTokenizer.kt +++ b/core/src/com/unciv/scripting/utils/InstanceTokenizer.kt @@ -15,6 +15,10 @@ import java.util.UUID */ object InstanceTokenizer { + // TODO: This could potentially be used as the basis for a fully reference-based script execution model, as opposed to the path-based one right now. Basically, make ForeignObject always keep a root token/value, and make ScriptingProtocol's actions resolve paths from the supplied base object instead of from scriptingScope. It may greatly increase the number of IPC calls (one per attribute access), but it would also unify all semantics better (and allow, E.G., reading directly from function returns). + + // Could even potentially get rid of `.registeredInstances` completely by automatically registering/reference counting in the JVM and freeing in scripting language destructors. But JS apparently doesn't give any way to control garbage collection, so the risk of memory leaks wouldn't be worth it. + /** * Weakmap of currently known token strings to WeakReferences of the Kotlin/JVM instances they represent. */ From 9c08e39110944b693be5563562f8d03eada72eaf Mon Sep 17 00:00:00 2001 From: will-ca Date: Wed, 8 Dec 2021 04:30:51 +0000 Subject: [PATCH 61/93] Switch to binding by reference instead of binding by path. Old model was deferred and lazy evaluation, new target is 1:1 behaviour between scripting and JVM. --- .../enginefiles/python/PythonScripting.md | 4 + .../enginefiles/python/unciv_lib/api.py | 9 +- .../python/unciv_lib/autocompletion.py | 6 +- .../enginefiles/python/unciv_lib/wrapping.py | 156 +++++++++------ .../MapEditingMacros.py | 6 +- .../ProceduralTechtree.py | 9 +- .../python/unciv_scripting_examples/Tests.py | 27 +-- core/Module.md | 30 +-- core/src/com/unciv/UncivGame.kt | 9 +- .../com/unciv/scripting/ScriptingBackend.kt | 22 +-- .../src/com/unciv/scripting/ScriptingState.kt | 8 +- .../scripting/api/ScriptingApiAppHelpers.kt | 3 +- .../scripting/api/ScriptingApiHelpers.kt | 5 +- .../api/ScriptingApiInstanceRegistry.kt | 5 +- .../scripting/api/ScriptingApiMappers.kt | 54 ++--- .../scripting/protocol/ScriptingProtocol.kt | 187 ++++++++++-------- .../protocol/ScriptingReplManager.kt | 13 +- .../scripting/protocol/SubprocessBlackbox.kt | 2 +- .../reflection/FunctionDispatcher.kt | 2 +- .../unciv/scripting/reflection/Reflection.kt | 93 +++++---- .../InstanceTokenizer.kt | 15 +- .../TokenizingJson.kt | 64 +++--- .../utils/ScriptingDebugParameters.kt | 6 + .../unciv/scripting/utils/ScriptingLock.kt | 6 + .../scripting/utils/StringifyException.kt | 22 --- 25 files changed, 424 insertions(+), 339 deletions(-) rename core/src/com/unciv/scripting/{utils => serialization}/InstanceTokenizer.kt (85%) rename core/src/com/unciv/scripting/{utils => serialization}/TokenizingJson.kt (77%) create mode 100644 core/src/com/unciv/scripting/utils/ScriptingDebugParameters.kt create mode 100644 core/src/com/unciv/scripting/utils/ScriptingLock.kt delete mode 100644 core/src/com/unciv/scripting/utils/StringifyException.kt diff --git a/android/assets/scripting/enginefiles/python/PythonScripting.md b/android/assets/scripting/enginefiles/python/PythonScripting.md index 314e03688a894..1c34da62d2ca2 100644 --- a/android/assets/scripting/enginefiles/python/PythonScripting.md +++ b/android/assets/scripting/enginefiles/python/PythonScripting.md @@ -2,6 +2,10 @@ The Python API described by this document is built on the [IPC protocols and execution model described in `/core/Module.md`](../../../../../core/Module.md#package-comuncivscriptingprotocol). +TODO: Most of this is completely obsolete now, with the switch from bind-by-path to bind-by-reference. + +All values used in Python should pass through real(). + --- ## Overview diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/api.py b/android/assets/scripting/enginefiles/python/unciv_lib/api.py index 172482cd202ac..e56ddddb9bae2 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/api.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/api.py @@ -99,10 +99,11 @@ def __init__(self, *args, apiscope=None, autocompleter=None, **kwargs): self.apiscope = {} if apiscope is None else apiscope def populateApiScope(self): """Use dir() on a foreign object wrapper with an empty path to populate the execution scope with all available names.""" - names = dir(wrapping.ForeignObject((), foreignrequester=self.GetForeignActionResponse)) - for n in names: + uncivscope = wrapping.ForeignObject(path=(), foreignrequester=self.GetForeignActionResponse) + #self.apiscope['unciv'] = uncivscope + for n in dir(uncivscope): if n not in self.apiscope: - self.apiscope[n] = wrapping.ForeignObject(n, foreignrequester=self.GetForeignActionResponse) + self.apiscope[n] = wrapping.ForeignObject(path=n, foreignrequester=self.GetForeignActionResponse) self.scope.update({**self.apiscope, **self.scope}) # TODO: Replace this update with Kotlin-side init? def passMic(self): @@ -144,7 +145,7 @@ def EvalForeignExec(self, packet): print(f">>> {str(line)}") try: # TODO: See if you can use signals to catch infinite loops/excess run duration. - # Actually, no. Interactivity should be retained from the Kotlin side by running/calling ScriptingState in a different thread. + # Actually, no. Interactivity should be retained from the Kotlin side by running/calling ScriptingState in a different thread. try: code = compile(line, 'STDIN', 'eval') except SyntaxError: diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py b/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py index efe107089bee4..f371bfcce3270 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py @@ -106,7 +106,9 @@ def GetAutocomplete(self, command, cursorpos=None): if working_base: base_obj = self.Evaled(working_base) attrs = dir(base_obj) - get_a = lambda a: getattr(base_obj, a, None) + def get_a(a): + try: return getattr(base_obj, a, None) + except: return None else: attrs = self.scope get_a = lambda a: self.scope[a] @@ -116,7 +118,7 @@ def GetAutocomplete(self, command, cursorpos=None): + working_dot + ( f"{a}[" - if self.get_keys(get_a(a)) else + if self.get_keys(get_a(a)) else # TODO: Use the new is_mapping check. f"{a}(" if self.check_callable(get_a(a)) else a diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py index 17cfcdd3f41d8..50dff0a47ed18 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py @@ -183,48 +183,73 @@ def alreadyhas(name): # class ForeignToken(str): # __slots__ = () # TODO: Could do this for more informative error messages, hidden magic methods that don't make sense. - # Would have to instantiate in the JSON decoder, though. - # I'm not sure it's necessary, since tokens will still have to be encoded as strings in JSON, which means you'd still need apiconstants['kotlinInstanceTokenPrefix'] and isForeignToken in api.py. + # Would have to instantiate in the JSON decoder, though. + # I'm not sure it's necessary, since tokens will still have to be encoded as strings in JSON, which means you'd still need apiconstants['kotlinInstanceTokenPrefix'] and isForeignToken in api.py. -BIND_BY_REFERENCE = False +class AttributeProxy: + def __init__(self, obj): + object.__setattr__(self, 'obj', obj) + def __getattribute__(self, name): + return object.__getattribute__(object.__getattribute__(self, 'obj'), name) + def __setattr__(self, name, value): + return object.__setattr__(object.__getattribute__(self, 'obj'), name, value) + # FIXME: Does this seem like a performance issue? + + +BIND_BY_REFERENCE = True """Early versions of this API bound Python objects to Kotlin/JVM instances by keeping track of paths and lazily evaluating them as needed. E.G. ".a.b[5].c" would create an internal tuple like `("a", "b", [5], "c")`, without actually accessing any Kotlin/JVM values at first. Benefits: Fewer IPC actions, lazy resolution of values only as they're used. Drawbacks: Deeper (slow) reflective loops per IPC action, scripting semantics not perfectly synced with JVM state, ugly tricks needed to deal with values that can't be safely accessed as paths from the same scope root, like the properties and methods of instances returned by function calls. The current API instead keeps track of every """ +# TODO: The more complicated tests are all significantly slower with bind-by-reference than with bind-by-path. Hopefully it will be fixed by plugging the leak in InstanceTokenizer. + +# TODO: Maybe test to see if a path-based approach for keys and attributes might still be faster? + @ResolveForOperators class ForeignObject: """Wrapper for a foreign object. Implements the specifications on IPC packet action types and data structures in Module.md.""" - def __init__(self, path, foreignrequester=dummyForeignRequester): - # object.__setattr__(self, '_isbaked', False) - # object.__setattr__(self, '_realvalue', ...) # TODO! - object.__setattr__(self, '_path', (makePathElement(name=path),) if isinstance(path, str) else tuple(path)) - object.__setattr__(self, '_foreignrequester', foreignrequester) + def __init__(self, *, path, use_root=False, root=None, foreignrequester=dummyForeignRequester): + object.__setattr__(self, '_attrs', AttributeProxy(self)) + self._attrs._isbaked = False + self._attrs._unbaked = None # For in-place operations, a version should be kept that + self._attrs._use_root = use_root + self._attrs._root = root + self._attrs._path = (makePathElement(name=path),) if isinstance(path, str) else tuple(path) + self._attrs._foreignrequester = foreignrequester def __repr__(self): - return f"{self.__class__.__name__}({stringPathList(self._getpath_())}):{self._getvalue_()}" + return f"{self.__class__.__name__}({self._root}, {stringPathList(self._getpath_())}):{self._getvalue_()}" + def _clone_(self, **kwargs): + return self.__class__(**{'path': self._path, 'use_root': self._use_root, 'root': self._root, 'foreignrequester': self._foreignrequester, **kwargs}) def _ipcjson_(self): return self._getvalue_() def _getpath_(self): return tuple(self._path) - # def _bakereal_(self): # TODO! Switch to API-by-reference instead of API-by-path? - # object.__setattr__(self, '_unbaked', self.__class__()) # For in-place operations. - # object.__setattr__(self, '_realvalue', self._getvalue_()) - # object.__setattr__(self, '_isbaked', True) - # object.__setattr__(self, '_path', ()) - # This will break in-place operations. - def __getattr__(self, name): + def _bakereal_(self): + assert not self._isbaked + self._attrs._unbaked = self._clone_() # For in-place operations. + self._attrs._root = self._getvalue_() # TODO: Would the fallback for in-places go through __setattr__, and result in one fewer IPC call? + self._attrs._use_root = True + self._attrs._path = () + self._attrs._isbaked = True + def __getattr__(self, name, *, do_bake=True): # Due to lazy IPC calling, hasattr will never work with this. Instead, check for in dir(). # TODO: Shouldn't I special-casing get_help or _docstring_ here? Wait, no, I think I thought it would be accessed on the class. - return self.__class__((*self._path, makePathElement(name=name)), self._foreignrequester) - # attr._bakereal_() - def __getattribute__(self, name): + attr = self._clone_(path=(*self._path, makePathElement(name=name))) + if BIND_BY_REFERENCE and do_bake: + attr._bakereal_() + return attr + def __getattribute__(self, name, **kwargs): if name in ('values', 'keys', 'items'): # Don't expose real .keys, .values, or .items unless wrapping a foreign mapping. This prevents foreign attributes like TileMap.values from being blocked. if not self._ismapping_(): - return self.__getattr__(name) + raise AttributeError(name) return object.__getattribute__(self, name) - def __getitem__(self, key): - return self.__class__((*self._path, makePathElement(ttype='Key', params=(key,))), self._foreignrequester) + def __getitem__(self, key, *, do_bake=True): + item = self._clone_(path=(*self._path, makePathElement(ttype='Key', params=(key,)))) + if BIND_BY_REFERENCE and do_bake: + item._bakereal_() + return item #TODO: Should negative indexing from end be supported? #IIRC I decided "No". Not entirely sure why. Probably a mix of needing a __len__ IPC call for that, plus incongruency with Kotlin (and other languages') behaviour, and complexity here. def __iter__(self): @@ -233,28 +258,47 @@ def __iter__(self): except: return (self[i] for i in range(0, len(self))) #TODO: Obviously this won't work for sets. Practical example why that's a problem: CityInfo stores HashSet()s of tiles. Workaround: Call real() on the whole set, and use the resulting values or foreign tokens. Unindexability/potential unorderedness of sets means that iteration would have to be handled from the Kotlin side, which means, at minimum implementing the BeginIteration and EndIteration flags, plus an entire new type of PassMic loop. Even then, without indexes, you'd only get the raw value or foreign token anyway def __setattr__(self, name, value): - return getattr(self, name)._setvalue_(value) + return self.__getattr__(name, do_bake=False)._setvalue_(value) def __setitem__(self, key, value): - return self[key]._setvalue_(value) - @ForeignRequestMethod + return self.__getitem__(key, do_bake=False)._setvalue_(value) def _getvalue_(self): - # if self._isbaked and not self._path: - # pass + if self._isbaked: + return self._root + else: + return self._getvalueraw_() + def _setvalue_(self, value): + if self._isbaked: + return self._unbaked._setvalue_(value) + else: + return self._setvalueraw_(value) + def __call__(self, *args): + result = self._clone_(path=(*self._getpath_(), makePathElement(ttype='Call', params=args))) + if BIND_BY_REFERENCE: + result._bakereal_() + return result + else: + return result._getvalue_() + @ForeignRequestMethod + def _getvalueraw_(self): + # Should never be called except for by _getvalue_. return ({ 'action': 'read', 'data': { - # 'use_root': self._isbaked, # TODO! - # 'root': self._realvalue, + 'use_root': self._use_root, + 'root': self._root, 'path': self._getpath_() } }, 'read_response', foreignValueParser) @ForeignRequestMethod - def _setvalue_(self, value): + def _setvalueraw_(self, value): + # Should never be called except for by _setvalue_. return ({ 'action': 'assign', 'data': { + 'use_root': self._use_root, + 'root': self._root, 'path': self._getpath_(), 'value': value } @@ -266,6 +310,8 @@ def _ismapping_(self): return ({ 'action': 'ismapping', 'data': { + 'use_root': self._use_root, + 'root': self._root, 'path': self._getpath_() } }, @@ -276,6 +322,8 @@ def _callable_(self, *, raise_exceptions=True): return ({ 'action': 'callable', 'data': { + 'use_root': self._use_root, + 'root': self._root, 'path': self._getpath_() } }, @@ -286,6 +334,8 @@ def _args_(self, *, raise_exceptions=True): return ({ 'action': 'args', 'data': { + 'use_root': self._use_root, + 'root': self._root, 'path': self._getpath_() } }, @@ -296,6 +346,8 @@ def _docstring_(self, *, raise_exceptions=True): return ({ 'action': 'docstring', 'data': { + 'use_root': self._use_root, + 'root': self._root, 'path': self._getpath_() } }, @@ -306,49 +358,21 @@ def __dir__(self): return ({ 'action': 'dir', 'data': { + 'use_root': self._use_root, + 'root': self._root, 'path': self._getpath_() } }, 'dir_response', foreignValueParser) - # @ForeignRequestMethod - # def __hash__(self): - # return ({ - # 'action': 'hash', - # 'data': { - # 'path': self._getpath_() - # } - # }, - # 'hash_response', - # foreignValueParser) - # Implemented and works, but disabled for now. See ScriptingProtocol.kt and Module.md. - @ForeignRequestMethod - def __call__(self, *args): - # From an IPC and protocol perspective, there isn't anything wrong with supporting directly accessing a value after a call. - # The problems are 1. Not knowing when/if to reify, 2. Reified and unrefied things behaving differently, 3. Implicit calls and static-emulating behaviour becoming unpredictable. - # E.G.: `a = civInfo.addGold(5); del a` and `civInfo.addGold(5)` would be different from `apiHelpers.printLine(civInfo.addGold(5))`. - # That was a deliberate concern and design decision at first, at think. - # But after writing some more example scripts, docs, and fleshing out the API, I'm wondering if it might be better to let scripts construct paths however they want while requiring them to explicitly reify foreign objects in most cases, instead of having to assign to apiHelpers.registeredInstances so often. - # That might work better in a typed language, I think. But it could also be a bit strange in Python, and implicit conversion could be more confusing with it. Right now foreign wrappers are basically interchangeable in most cases with real values, forbidding calls lets them be safely used with lazy resolution, and scripts don't really have to think about the path list. - # E.G.: `v = civInfo.someFunction().x; v+5; print(v)` would call `civInfo.someFunction()` every time that `v` is used, because `.x` is still a wrapper that includes a call buried in its path. - # Hm. And while avoiding assignments to registeredInstances might improve performance, having to make sure there aren't any 'type':'Call's buried deep in each wrapper's path before every implicit use would eat some of those gains right back up, in addition to being an opaque error-prone mess from the perspective of normal Python code. - # It would also be much easier to write Python code that accidentally has abysmal performance (in addition to unexpected side effects) due to implicitly re-calling an expensive Kotlin function every time a variable assigned from an attribute is used, when by all normal Python semantics an attribute should not behave like that. - # Point is: Function calls do not have static, access-safe values like properties or keys. So wrapping them up in a dynamic ForeignObject that pretends to be static, the way I have properties and keys, would make language semantics wildly deviate from language behaviour. - # Yeah, I forced calls to terminate wrappers for a reason. - return ({ - 'action': 'read', - 'data': { - 'path': (*self._getpath_(), makePathElement(ttype='Call', params=args)), - } - }, - 'read_response', - foreignValueParser) # TODO: Behaviour, semantics, and structure can be unified with __getattr__ with API-by-reference. @ForeignRequestMethod def __delitem__(self, key): return ({ 'action': 'delete', 'data': { - 'path': self[key]._getpath_() + 'use_root': self._use_root, + 'root': self._root, + 'path': self.__getitem__(key, do_bake=False)._getpath_() } }, 'delete_response', @@ -358,6 +382,8 @@ def __len__(self): return ({ 'action': 'length', 'data': { + 'use_root': self._use_root, + 'root': self._root, 'path': self._getpath_(), } }, @@ -368,6 +394,8 @@ def __contains__(self, item): return ({ 'action': 'contains', 'data': { + 'use_root': self._use_root, + 'root': self._root, 'path': self._getpath_(), 'value': item } @@ -379,6 +407,8 @@ def keys(self): return ({ 'action': 'keys', 'data': { + 'use_root': self._use_root, + 'root': self._root, 'path': self._getpath_(), } }, diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py index 4f88c21214994..da09bea32f4bf 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py @@ -20,7 +20,7 @@ # If you modify this file, please add any new functions to Tests.py. -t = re.sub("//.*", "", re.sub('/\*.*\*/', "", unciv.apiHelpers.App.assetFileString("jsons/Civ V - Gods & Kings/Terrains.json"), flags=re.DOTALL)) +t = re.sub("//.*", "", re.sub('/\*.*\*/', "", real(unciv.apiHelpers.App.assetFileString("jsons/Civ V - Gods & Kings/Terrains.json")), flags=re.DOTALL)) terrainsjson = json.loads(t) # In an actual implementation, you would want to read from the ruleset instead of the JSON. But this is eaiser for me. del t @@ -275,9 +275,9 @@ def terrainImagePath(feature): def compositedTerrainImage(terrain): import PIL.Image base, features = terrainFromString(terrain) - image = PIL.Image.open(io.BytesIO(base64.b64decode(unciv.apiHelpers.App.assetImageB64(terrainImagePath(base))))) + image = PIL.Image.open(io.BytesIO(base64.b64decode(real(unciv.apiHelpers.App.assetImageB64(terrainImagePath(base)))))) for feature in features: - with PIL.Image.open(io.BytesIO(base64.b64decode(unciv.apiHelpers.App.assetImageB64(terrainImagePath(feature))))) as layer: + with PIL.Image.open(io.BytesIO(base64.b64decode(real(unciv.apiHelpers.App.assetImageB64(terrainImagePath(feature)))))) as layer: image.alpha_composite(layer, (0, image.size[1]-layer.size[1])) return image diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/ProceduralTechtree.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/ProceduralTechtree.py index f57c6cef50430..aaddd1ca8bb14 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/ProceduralTechtree.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/ProceduralTechtree.py @@ -11,6 +11,9 @@ Call scrambleTechTree() to keep all current technologies but randomize the order in which they are unlocked. """ +# from unciv_scripting_examples.ProceduralTechtree import * +# from unciv_scripting_examples.ProceduralTechtree import *; scrambleTechTree() +# tuple(real(t) for t in apiHelpers.instancesAsInstances[gameInfo.ruleSet.technologies['Civil Service'].prerequisites.toArray()]) # This means that some kind of handler for the modding API, once it's implemented, would have to be called before the JVM has a chance to crash, so the script can read its own serialized data out of GameInfo and inject items into the ruleset… You can already provably have a GameInfo with invalid values that doesn't crash until WorldScreen tries to render it, so running onGameLoad immediately after deserializing the save might be good enough. # Or I guess there could be a Kotlin-side mechanism for serializing injected rules. But on the Kotlin side, I think it would be cleaner to just let the script handle everything. Such a mechanism still may not cover the entire range of wild behaviours that can be done by scripts, and the entire point of having a dynamic scripting API is to avoid having to statically hard-code niche or esoteric uses. @@ -108,11 +111,10 @@ def clearTechTree(*, safe=True): def scrambleTechTree(): """Randomly shuffle the order of all items on the tech tree.""" - # from unciv_scripting_examples.ProceduralTechtree import *; scrambleTechTree() technames = [*techtree().keys()] random.shuffle(technames) techpositions = {n:n for n in technames} - originalpreqs = {n:real(techtree()[n].prerequisites) for n in technames} + originalpreqs = {n:tuple(real(tname) for tname in apiHelpers.instancesAsInstances[techtree()[n].prerequisites.toArray()]) for n in technames} # .prerequisites is a HashSet that becomes inaccessible with always-tokenizing serialization. for tname in technames: oname = random.sample(technames, 1)[0] tech, other = (techtree()[n] for n in (tname, oname)) @@ -128,5 +130,8 @@ def scrambleTechTree(): for tname in technames: tech = techtree()[tname] tech.prerequisites.clear() + for ot in originalpreqs[techpositions[tname]]: + try: techreplacements[ot] + except: print(tname, ot) tech.prerequisites.addAll([techreplacements[ot] for ot in originalpreqs[techpositions[tname]]]) # toprereqs, oprereqs = (real(t.prerequisites) for t in (tech, other)) diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py index 85e046bd0792e..24c218baa91af 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py @@ -28,8 +28,7 @@ with open(Utils.exampleAssetPath("Elizabeth300"), 'r') as save: - # TODO: Compress this. - # Unciv uses Base64 and GZIP. + # TODO: Compress this. Unciv uses Base64 and GZIP. Elizabeth300 = save.read() @@ -76,11 +75,14 @@ def __init__(self, func, name=None, runwith=None, args=(), kwargs={}): self.args = args self.kwargs = kwargs def __call__(self): + def run(): + args = self.args() if callable(self.args) else self.args + self.func(*args, **self.kwargs) if self.runwith is None: - self.func(*self.args, **self.kwargs) + run() else: with self.runwith: - self.func(*self.args, **self.kwargs) + run() def keys(self): return [t.name for t in self._tests] def __getitem__(self, key): @@ -140,8 +142,9 @@ def LoadGameTest(): assert unciv_pyhelpers.isForeignToken(v) -@TestRunner.Test(runwith=InGame, name="NoPrivatesTest-InGame", args=(unciv, 2)) -@TestRunner.Test(runwith=InMapEditor, name="NoPrivatesTest-InMapEditor", args=(unciv, 2)) +# @TestRunner.Test(runwith=InGame, name="NoPrivatesTest-InGame", args=(unciv, 2)) +# @TestRunner.Test(runwith=InMapEditor, name="NoPrivatesTest-InMapEditor", args=(unciv, 2)) +# Enable this if it's ever decided to guarantee that the 'dir' IPC action type won't return inaccessible names. def NoPrivatesTest(start, maxdepth, *, _depth=0, _failures=None, _namestack=None): # Would have to differentiate between unitialized properties and the like, and privates. if _failures is None: @@ -167,22 +170,22 @@ def NoPrivatesTest(start, maxdepth, *, _depth=0, _failures=None, _namestack=None # Tests for PlayerMacros.py. -TestRunner.Test(runwith=InGame, args=(unciv.civInfo.cities, 0.5))( +TestRunner.Test(runwith=InGame, args=lambda: (unciv.civInfo.cities, 0.5))( PlayerMacros.gatherBiggestCities ) -TestRunner.Test(runwith=InGame, args=(unciv.civInfo.cities,))( +TestRunner.Test(runwith=InGame, args=lambda: (unciv.civInfo.cities,))( PlayerMacros.clearCitiesProduction ) -TestRunner.Test(runwith=InGame, args=(unciv.civInfo.cities, ("Scout", "Warrior", "Worker")))( +TestRunner.Test(runwith=InGame, args=lambda: (unciv.civInfo.cities, ("Scout", "Warrior", "Worker")))( PlayerMacros.addCitiesProduction ) -TestRunner.Test(runwith=InGame, args=(unciv.civInfo.cities,))( +TestRunner.Test(runwith=InGame, args=lambda: (unciv.civInfo.cities,))( PlayerMacros.clearCitiesSpecialists ) -TestRunner.Test(runwith=InGame, args=(unciv.civInfo.cities,))( +TestRunner.Test(runwith=InGame, args=lambda: (unciv.civInfo.cities,))( PlayerMacros.focusCitiesFood ) -TestRunner.Test(runwith=InGame, args=(unciv.civInfo.cities, ("Monument", "Shrine", "Worker")))( +TestRunner.Test(runwith=InGame, args=lambda: (unciv.civInfo.cities, ("Monument", "Shrine", "Worker")))( PlayerMacros.buildCitiesQueue ) TestRunner.Test(runwith=InGame)( diff --git a/core/Module.md b/core/Module.md index 1f82870553448..a5db0a230cc46 100644 --- a/core/Module.md +++ b/core/Module.md @@ -230,82 +230,82 @@ Some action types, data formats, and expected response types and data formats fo ``` 'read': {'use_root' Boolean, 'root': Any?, 'path': List<{'type':String, 'name':String, 'params':List}>} -> 'read_reponse': Any? or String - //Attribute/property access, by list of `PathElement` properties. + //Attribute/property access, by list of `PathElement` properties, relative to the root object if given or a default scope otherwise. + //The use_root and root fields are optional. //Response must be String if sent with Exception flag. - //TODO: Implement and use use_root and root. ``` ``` - 'assign': {'path': List<{'type':String, 'name':String, 'params':List}>, 'value': Any} -> + 'assign': {'use_root' Boolean, 'root': Any?, 'path': List<{'type':String, 'name':String, 'params':List}>, 'value': Any} -> 'assign_response': String? //Error message or null. ``` ``` - 'delete': {'path': List<{'type':String, 'name':String, 'params':List}>} -> + 'delete': {'use_root' Boolean, 'root': Any?, 'path': List<{'type':String, 'name':String, 'params':List}>} -> 'delete_response': String? //Error message or null. //Only meaningful and implemented for MutableMap() keys and MutableList() indices. ``` ``` - 'dir': {'path': List<{'type':String, 'name':String, 'params':List}>} -> + 'dir': {'use_root' Boolean, 'root': Any?, 'path': List<{'type':String, 'name':String, 'params':List}>} -> 'dir_response': Collection or String //Names of all members/properties/attributes/methods. //Response must be String if sent with Exception flag. ``` ``` - //'hash': {'path': List<{'type':String, 'name':String, 'params':List}>} -> + //'hash': {'use_root' Boolean, 'root': Any?, 'path': List<{'type':String, 'name':String, 'params':List}>} -> //'hash_response': Any? or String //Response must be String if sent with Exception flag. - //Implemented, but disabled. I'm not actually sure what the use of this would be. Hashes are usually just a shortcut for (in)equality, so I think a Kotlin-side equality or identity operator might be needed for this to be useful. + //Implemented, but removed. I'm not actually sure what the use of this would be. Hashes are usually just a shortcut for (in)equality, so I think a Kotlin-side equality or identity operator might be needed for this to be useful. ``` ``` - 'keys': {'path': List<{'type':String, 'name':String, 'params':List}>} -> + 'keys': {'use_root' Boolean, 'root': Any?, 'path': List<{'type':String, 'name':String, 'params':List}>} -> 'keys_response': Collection or String //Response must be String if sent with Exception flag. //Keys of Map-interfaced instances. Used by Python bindings for iteration and autocomplete. ``` ``` - 'length': {'path': List<{'type':String, 'name':String, 'params':List}>} -> + 'length': {'use_root' Boolean, 'root': Any?, 'path': List<{'type':String, 'name':String, 'params':List}>} -> 'length_response': Int or String //Response must be String if sent with Exception flag. //Used by Python bindings for length and also for iteration. ``` ``` - 'contains': {'path': List<{'type':String, 'name':String, 'params':List}>, 'value': Any?} -> + 'contains': {'use_root' Boolean, 'root': Any?, 'path': List<{'type':String, 'name':String, 'params':List}>, 'value': Any?} -> 'contains_response': Boolean or String //Doing this through an IPC call instead of in the script interpreter should let tokenized instances be checked for properly. //Response must be String if sent with Exception flag. ``` ``` - //'isiterable': {'path': List<{'type':String, 'name':String, 'params':List}>} -> + //'isiterable': {'use_root' Boolean, 'root': Any?, 'path': List<{'type':String, 'name':String, 'params':List}>} -> //'isiterable_response': Boolean or String //Response must be String if sent with Exception flag. //Not implemented. Implement if needed. ``` ``` - 'ismapping': {'path': List<{'type':String, 'name':String, 'params':List}>} -> + 'ismapping': {'use_root' Boolean, 'root': Any?, 'path': List<{'type':String, 'name':String, 'params':List}>} -> 'isiterable_response': Boolean or String //Response must be String if sent with Exception flag. //Used by Python bindings to hide Python-emulating .values, .keys, and .entries to allow access to Kotlin objects when not a mapping. ``` ``` - 'callable': {'path': List<{'type':String, 'name':String, 'params':List}>} -> + 'callable': {'use_root' Boolean, 'root': Any?, 'path': List<{'type':String, 'name':String, 'params':List}>} -> 'callable_response': Boolean or String //Response must be String if sent with Exception flag. //Used by Python autocompleter to add opening bracket to methods and function suggestions. Quite useful for exploring API at a glance. ``` ``` - 'args': 'path': List<{'type':String, 'name':String, 'params':List}>} -> + 'args': {'use_root' Boolean, 'root': Any?, 'path': List<{'type':String, 'name':String, 'params':List}>} -> 'args_response': Map>> or String //Map of dispatchable signatures as strings to lists of pairs of names and types of arguments accepted by a function. //Response must be String if sent with Exception flag. @@ -314,7 +314,7 @@ Some action types, data formats, and expected response types and data formats fo ``` ``` - 'docstring': {'path': List<{'type':String, 'name':String, 'params':List}>} -> + 'docstring': {'use_root' Boolean, 'root': Any?, 'path': List<{'type':String, 'name':String, 'params':List}>} -> 'docstring_response': String or String //Response must be String if sent with Exception flag. //Used by Python wrappers and autocompleter to get help text showing arguments and types for callables. Useful for exploring API without having to browse code. diff --git a/core/src/com/unciv/UncivGame.kt b/core/src/com/unciv/UncivGame.kt index 2b25ce48900ba..fc21461a47e52 100644 --- a/core/src/com/unciv/UncivGame.kt +++ b/core/src/com/unciv/UncivGame.kt @@ -15,6 +15,7 @@ import com.unciv.models.tilesets.TileSetCache import com.unciv.models.translations.Translations import com.unciv.scripting.ScriptingState import com.unciv.scripting.api.ScriptingScope +import com.unciv.scripting.utils.ScriptingDebugParameters import com.unciv.ui.consolescreen.ConsoleScreen import com.unciv.ui.LanguagePickerScreen import com.unciv.ui.audio.MusicController @@ -50,7 +51,8 @@ class UncivGame(parameters: UncivGameParameters) : Game() { var viewEntireMapForDebug = false /** For when you need to test something in an advanced game and don't have time to faff around */ var superchargedForDebug = false - var printScriptingPacketsForDebug = false + + val scriptingParametersForDebug = ScriptingDebugParameters /** Simulate until this turn on the first "Next turn" button press. * Does not update World View changes until finished. @@ -155,19 +157,16 @@ class UncivGame(parameters: UncivGameParameters) : Game() { } } - fun createScripting() { - + private fun createScripting() { scriptingState = ScriptingState( ScriptingScope( uncivGame = this ) ) - consoleScreen = ConsoleScreen( scriptingState, { } ) - } fun setScreen(screen: BaseScreen) { diff --git a/core/src/com/unciv/scripting/ScriptingBackend.kt b/core/src/com/unciv/scripting/ScriptingBackend.kt index bcb92ba52bc3f..2661a99812c8f 100644 --- a/core/src/com/unciv/scripting/ScriptingBackend.kt +++ b/core/src/com/unciv/scripting/ScriptingBackend.kt @@ -54,7 +54,7 @@ abstract class EnvironmentedScriptBackend_metadata: ScriptingBackend_metadata() /** * Interface for a single object that parses, interprets, and executes scripts. */ -interface ScriptingBackend { +interface ScriptingImplementation { /** * @return Message to print on launch. Should be called exactly once per instance, and prior to calling any of the other methods defined here. @@ -94,10 +94,9 @@ interface ScriptingBackend { } -// TODO: Rename ScriptingBackend to ScriptingImplementation. // TODO: Add .userTerminable flag and per-instance display string to ScriptingBackendBase. Let mod command histories be seen on ConsoleScreen? -open class ScriptingBackendBase(val scriptingScope: ScriptingScope): ScriptingBackend { +open class ScriptingBackendBase(val scriptingScope: ScriptingScope): ScriptingImplementation { //A little bit confusing. //Hm. Couldn't the metadata getter go on the interface? //That would mean this exists only as a code example for defining the companion object. @@ -441,7 +440,7 @@ abstract class EnvironmentedScriptingBackend(scriptingScope: ScriptingScope): Sc // Since the companion object type is different, we have to define a new getter for the subclass instance companion getter to get its new members. get() = this::class.companionObjectInstance as EnvironmentedScriptBackend_metadata - val folderHandle: FileHandle by lazy { SourceManager.setupInterpreterEnvironment(metadata.engine) } + val folderHandle: FileHandle by lazy { SourceManager.setupInterpreterEnvironment(engine) } // This requires the overridden values for engine, so setting it in the constructor causes a null error... May be fixed since moving engine to the companions. // Also, BlackboxScriptingBackend inherits from this, but not all subclasses of BlackboxScriptingBackend might need it. So as long as it's not accessed, it won't be initialized. @@ -470,8 +469,7 @@ abstract class BlackboxScriptingBackend(scriptingScope: ScriptingScope): Environ try { return replManager.motd() } catch (e: Exception) { - return "No MOTD for ${metadata.engine} backend: ${e}\n" - //TODO: Can't you access companion properties directly from instances? + return "No MOTD for ${engine} backend: ${e}\n" } } @@ -512,7 +510,7 @@ abstract class SubprocessScriptingBackend(scriptingScope: ScriptingScope): Black override fun motd(): String { return """ - Welcome to the Unciv '${metadata.displayName}' API. This backend relies on running the system ${processCmd.firstOrNull()} command as a subprocess. + Welcome to the Unciv '${displayName}' API. This backend relies on running the system ${processCmd.firstOrNull()} command as a subprocess. If you do not have an interactive REPL below, then please make sure the following command is valid on your system: @@ -632,8 +630,8 @@ enum class ScriptingBackendType(val metadata: ScriptingBackend_metadata) { // TODO: Have .new function? } -// TODO: Lowercase name. Actually, no. Just get rid of this. -fun SpawnNamedScriptingBackend(backendtype: ScriptingBackendType, scriptingScope: ScriptingScope): ScriptingBackendBase { - // Seems unnecessary? - return backendtype.metadata.new(scriptingScope) -} +//// TODO: Lowercase name. Actually, no. Just get rid of this. +//fun SpawnNamedScriptingBackend(backendtype: ScriptingBackendType, scriptingScope: ScriptingScope): ScriptingBackendBase { +// // Seems unnecessary? +// return backendtype.metadata.new(scriptingScope) +//} diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index fc76bd84e82d0..cccbe23feede8 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -82,7 +82,7 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr fun spawnBackendAndReturnMotd(backendtype: ScriptingBackendType, switchTo: Boolean = true) { // TODO - // The script manager/handler dispatcher for mods is going to want to keep track of which backends belong to which mods. + // The script manager/handler dispatcher for mods is going to want to keep track of which backends belong to which mods. } fun switchToBackend(index: Int) { @@ -179,14 +179,14 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr return out } -// fun acquireUiLock() { +// fun acquireScriptLock() { // scriptingScope.worldScreen?.isPlayersTurn = false - //TODO + //TODO: Move to ScriptingLock. //Not perfect. I think scriptingScope also exposes mutating the GUI itself, and many things that aren't protected by this? Then again, a script that *wants* to cause a crash/ANR will always be able to do so by just assigning an invalid value or deleting a required node somewhere. Could make mod handlers outside of worldScreen blocking, with written stipulations on (dis)recommended size, and then //https://github.com/yairm210/Unciv/pull/5592/commits/a1f51e08ab782ab46bda220e0c4aaae2e8ba21a4 // } -// fun releaseUiLock() { +// fun releaseScriptLock() { // scriptingScope.worldScreen?.isPlayersTurn = true //Hm. Should return to original value, not necessarily true. That means keeping a property, which means I'd rather put this in its own class. // } diff --git a/core/src/com/unciv/scripting/api/ScriptingApiAppHelpers.kt b/core/src/com/unciv/scripting/api/ScriptingApiAppHelpers.kt index d4bf83ef9f743..6e09e3b396a96 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiAppHelpers.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiAppHelpers.kt @@ -19,13 +19,12 @@ object ScriptingApiAppHelpers { // @param path Path of an internal image as exposed in ImageGetter as a TextureRegionDrawable from an atlas. // @return The image encoded as a PNG file encoded as a Base64 string. fun assetImageB64(path: String): String { - // When/if letting scripts make UI elements becomes a thing, these should probably be organized together with the factories for that. // To test in Python: // import PIL.Image, io, base64; PIL.Image.open(io.BytesIO(base64.b64decode(apiHelpers.assetImage("StatIcons/Resistance")))).show() val fakepng = ByteArrayOutputStream() //Close this stream? Well, the docs say doing so "has no effect", and it should clearly get GC'd anyway. val pixmap = ImageGetter.getDrawable(path).getRegion().toPixmap() - val exporter = PixmapIO.PNG() // Could be kept and "reused to encode multiple PNGs with minimal allocation", according to the docs. I don't see it as a sufficient bottleneck yet to necesarily justify the complexity and risk, though. + val exporter = PixmapIO.PNG() // Could be kept and "reused to encode multiple PNGs with minimal allocation", according to the docs. Probably not a sufficient bottleneck to justify the complexity and risk, though. exporter.setFlipY(false) exporter.write(fakepng, pixmap) pixmap.dispose() // In theory needed to avoid memory leak. Doesn't seem to actually have any impact, compared to the .dispose() inside .toPixmap(). Maybe the exporter's dispose also calls this? diff --git a/core/src/com/unciv/scripting/api/ScriptingApiHelpers.kt b/core/src/com/unciv/scripting/api/ScriptingApiHelpers.kt index 43454f4e7b733..b84715936c929 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiHelpers.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiHelpers.kt @@ -12,7 +12,6 @@ import java.io.ByteArrayOutputStream class ScriptingApiHelpers(val scriptingScope: ScriptingScope) { // This, and the classes of its members, should try to implement only the minimum number of helper functions that are needed for each type of functionality otherwise not possible in scripts. E.G. Don't add special "loadGame" functions or whatever here, but do expose the existing methods of UncivGame. E.G. Don't add factories to speed up making alert popups, because all the required constructors can already be called through constructorByQualname anyway. Let the rest of the codebase and the scripts themselves do the work— Maintenance of the API itself will be easier if all it does is expose existing Kotlin code to dynamic Python/JS/Lua code. - // TODO: The vast majority of these don't need scriptingScope access, and thus can be put on singletons. val isInGame: Boolean get() = (scriptingScope.civInfo != null && scriptingScope.gameInfo != null && scriptingScope.uncivGame != null) @@ -25,8 +24,8 @@ class ScriptingApiHelpers(val scriptingScope: ScriptingScope) { val Mappers = ScriptingApiMappers val registeredInstances = ScriptingApiInstanceRegistry() - val instancesAsInstances = FakeMap{obj: Any? -> obj} // TODO: Rename this. - // Scripting language bindings work by keeping track of the paths to values, and making Kotlin/the JVM resolve them only when needed. + val instancesAsInstances = FakeMap{obj: Any? -> obj} // TODO: Rename this, and singleton it. + /// Scripting language bindings work by keeping track of the paths to values, and making Kotlin/the JVM resolve them only when needed. // This creates a dilemma: Resolving a path into a Kotlin value too early means that no further paths (E.G. attribute, keys, calls) can be built on top of it. But resolving it late means that expected side effects may not happen (E.G. function calls probably shouldn't be deferred). And values that *must* be resolved, like the results of function calls, cannot have their own members and method accessed until they themselves are assigned to a path, because they're just kinda floating around as far as the scripting-exposed semantics are concerned. // So this fake Map works around that, by providing a way for any random object to appear to have a path. diff --git a/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt b/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt index 282b6399cb206..fa74cba33ac68 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt @@ -12,6 +12,7 @@ object AllScriptingApiInstanceRegistries { Runtime.getRuntime().addShutdownHook( // TODO: Move uses of this into UncivGame.dispose()? // TODO: Allow arbitrary callbacks to be added for UncivGame.dispose(). + // TODO: Also, doesn't actually work. thread(start = false, name = "Check ScriptingApiInstanceRegistry()s are empty.") { val allkeys = getAllKeys() if (allkeys.isNotEmpty()) { @@ -29,8 +30,6 @@ object AllScriptingApiInstanceRegistries { } } -// TODO: Check on game close that all InstanceRegistries are empty. - /** * Namespace in ScriptingScope().apiHelpers, for scripts to do their own memory management by keeping references to objects alive. * @@ -63,7 +62,7 @@ class ScriptingApiInstanceRegistry: MutableMap { override fun isEmpty() = backingMap.isEmpty() override fun clear() = backingMap.clear() override fun put(key: String, value: Any?): Any? { - println("\nAssigning ${key} directly in ScriptingApiInstanceRegistry(). It is recommended that every script/mod do this only once per application lifespan, creating its own mapping for further assignments named according to the following format:\n\t-<'mod'|'module'|'package'>:/\n\tE.G.: \"python-module:myName/myCoolScript\"\n") + println("\nAssigning ${key} directly in ScriptingApiInstanceRegistry(). It is recommended that every script/mod do this only once per application lifespan, creating its own mapping under the registry for further assignments named according to the following format:\n\t-<'mod'|'module'|'package'>:/\n\tE.G.: \"python-module:myName/myCoolScript\"\n") if (key in this) { throw IllegalArgumentException("\"${key}\" already in ${this}.") } diff --git a/core/src/com/unciv/scripting/api/ScriptingApiMappers.kt b/core/src/com/unciv/scripting/api/ScriptingApiMappers.kt index ef218246972e6..9085c5a9bc311 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiMappers.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiMappers.kt @@ -7,43 +7,43 @@ import com.unciv.scripting.utils.LazyMap object ScriptingApiMappers { - //// Some ways to access or assign the same property(s) on a lot of instances at once, using only one IPC call. Maybe use parseKotlinPath? Probably preserve order in return instead of mapping from each instance, since script must already have list of tokens anyway (ideally also from a single IPC call). Or preserve mapping, since order could be messy, and to fit with the assignment function? - fun mapPathCodes(instances: List, pathcodes: Collection): List> { - val pathElementLists = pathcodes.associateWith { Reflection.parseKotlinPath(it) } // TODO: Meth ref? - return instances.map { - val instance = it - pathElementLists.mapValues { - Reflection.resolveInstancePath(instance, it.value) - } + //// Some ways to access or assign the same property(s) on a lot of instances at once, using only one IPC call. Maybe use parseKotlinPath? Probably preserve order in return instead of mapping from each instance, since script must already have list of tokens anyway (ideally also from a single IPC call). Or preserve mapping, since order could be messy, and to fit with the assignment function? + fun mapPathCodes(instances: List, pathcodes: Collection): List> { + val pathElementLists = pathcodes.associateWith { Reflection.parseKotlinPath(it) } // TODO: Meth ref? + return instances.map { + val instance = it + pathElementLists.mapValues { + Reflection.resolveInstancePath(instance, it.value) } } + } - fun getPathCodesFrom(instance: Any, pathcodes: Collection) = pathcodes.associateWith { Reflection.resolveInstancePath(instance, Reflection.parseKotlinPath(it)) } + fun getPathCodesFrom(instance: Any, pathcodes: Collection) = pathcodes.associateWith { Reflection.resolveInstancePath(instance, Reflection.parseKotlinPath(it)) } - fun getPathCodes(instancesAndPathcodes: Map>): Map> { - val pathElementLists = LazyMap>(Reflection::parseKotlinPath) - return instancesAndPathcodes.mapValues { (instance, pathcodes) -> - pathcodes.associateWith { - Reflection.resolveInstancePath(instance, pathElementLists[it]!!) - } + fun getPathCodes(instancesAndPathcodes: Map>): Map> { + val pathElementLists = LazyMap>(Reflection::parseKotlinPath) + return instancesAndPathcodes.mapValues { (instance, pathcodes) -> + pathcodes.associateWith { + Reflection.resolveInstancePath(instance, pathElementLists[it]!!) } } + } - fun applyPathCodesTo(instance: Any, pathcodesAndValues: Map): Any { - for ((pathcode, value) in pathcodesAndValues) { - Reflection.setInstancePath(instance, Reflection.parseKotlinPath(pathcode), value) - } - return instance + fun applyPathCodesTo(instance: Any, pathcodesAndValues: Map): Any { + for ((pathcode, value) in pathcodesAndValues) { + Reflection.setInstancePath(instance, Reflection.parseKotlinPath(pathcode), value) } - - fun applyPathCodes(instancesPathcodesAndValues: Map>) { - val pathElementLists = LazyMap>(Reflection::parseKotlinPath) - for ((instance, assignments) in instancesPathcodesAndValues) { - for ((pathcode, value) in assignments) { - Reflection.setInstancePath(instance, pathElementLists[pathcode]!!, value) - } + return instance + } + + fun applyPathCodes(instancesPathcodesAndValues: Map>) { + val pathElementLists = LazyMap>(Reflection::parseKotlinPath) + for ((instance, assignments) in instancesPathcodesAndValues) { + for ((pathcode, value) in assignments) { + Reflection.setInstancePath(instance, pathElementLists[pathcode]!!, value) } } + } } // st=time.time(); [real(t.baseTerrain) for t in gameInfo.tileMap.values]; print(time.time()-st) // st=time.time(); apiHelpers.Mappers.mapPathCodes(gameInfo.tileMap.values, ['baseTerrain']); print(time.time()-st) diff --git a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt index 73046b1e4c56f..1c292a051c99c 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt @@ -3,6 +3,7 @@ package com.unciv.scripting.protocol import com.unciv.scripting.AutocompleteResults import com.unciv.scripting.reflection.FunctionDispatcher import com.unciv.scripting.reflection.Reflection +import com.unciv.scripting.utils.ScriptingDebugParameters import com.unciv.scripting.utils.stringifyException import com.unciv.scripting.utils.TokenizingJson import kotlin.random.Random @@ -95,7 +96,9 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = * @property value Serialized string value of this flag. */ enum class KnownFlag(val value: String) { - PassMic("PassMic"),//TODO: Would getting rid of the string value work? I think I may have decided that having KotlinX Serialization implicitly coerce enums to/from strings would be worse than explicitly accessing the string even if raw enums do work. + PassMic("PassMic"), + //TODO: Would getting rid of the string value work? I think I may have decided that having KotlinX Serialization implicitly coerce enums to/from strings would be worse than explicitly accessing the string even if raw enums do work. + // Okay, yeah, these should already have `.name`, right? Exception("Exception") } @@ -223,6 +226,32 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = return obj } + // Helper class for parsing the data field of a common request packet structure from script interpreters. + + // THIS IS A CONVENTION THAT MANY REQUEST TYPES HAVE CONVERGED ON, NOT A SPECIFICATION THAT IS INTEGRAL TO THE PROTOCOL ITSELF. + // See Module.md. + + // @param packet Packet from which to read the data field. + private class ScriptingPacketPathedData(val packet: ScriptingPacket) { + val packetData = packet.data as JsonObject + + val use_root = (getRealOrNull(packetData["use_root"]) ?: false) as Boolean + val root = getRealOrNull(packetData["root"]) + val path = TokenizingJson.json.decodeFromJsonElement>(packetData["path"]!!) + val value = getRealOrNull(packetData["value"]) + + init { + if (ScriptingDebugParameters.printScriptingAccessForDebug) printDebug() + } + + private fun getRealOrNull(jsonElement: JsonElement?): Any? { + if (jsonElement == null) return null + return TokenizingJson.getJsonReal(jsonElement) + } + + private fun printDebug() = println("${packet.action}: ${if (use_root) "Root: ${root} " else ""}Path: ${Reflection.stringifyKotlinPath(path)}${if (value == null) "" else " Value: ${value}"}") + } + /** * Return a valid response packet for a request packet from a script interpreter. * @@ -234,161 +263,160 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = */ fun makeActionResponse(packet: ScriptingPacket): ScriptingPacket { val action = responseTypes[packet.action]!! - var data: JsonElement? = null - val flags = mutableListOf() + var responseData: JsonElement? = null + val responseFlags = mutableListOf() when (packet.action) { // There's a lot of repetition here, because I don't want to enforce any specification on what form the request and response data fields for actions must take. // I prefer to try to keep the code for each response type independent enough to be readable on its own. // This is kinda the reference (and only) implementation of the protocol spec. So the serialization and such can be and is handled with functions, but the actual structure and logic of each response should be hardcoded manually IMO. "read" -> { try { - data = TokenizingJson.getJsonElement(trySaveInstance( - Reflection.resolveInstancePath( - scope, - TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) - ) - )) + val packetData = ScriptingPacketPathedData(packet) + responseData = TokenizingJson.getJsonElement( + trySaveInstance( + Reflection.resolveInstancePath( + if (packetData.use_root) packetData.root else scope, + packetData.path + ) + ), + requireTokenization = TokenizingJson::isNotPrimitive + ) } catch (e: Exception) { - data = JsonPrimitive(e.stringifyException()) - flags.add(KnownFlag.Exception.value) + responseData = JsonPrimitive(e.stringifyException()) + responseFlags.add(KnownFlag.Exception.value) } } "assign" -> { try { + val packetData = ScriptingPacketPathedData(packet) Reflection.setInstancePath( - scope, - TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!), - TokenizingJson.getJsonReal((packet.data as JsonObject)["value"]!!) + if (packetData.use_root) packetData.root else scope, + packetData.path, + packetData.value ) } catch (e: Exception) { - data = JsonPrimitive(e.stringifyException()) - flags.add(KnownFlag.Exception.value) + responseData = JsonPrimitive(e.stringifyException()) + responseFlags.add(KnownFlag.Exception.value) } } "delete" -> { try { + val packetData = ScriptingPacketPathedData(packet) Reflection.removeInstancePath( - scope, - TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) + if (packetData.use_root) packetData.root else scope, + packetData.path ) } catch (e: Exception) { - data = JsonPrimitive(e.stringifyException()) - flags.add(KnownFlag.Exception.value) + responseData = JsonPrimitive(e.stringifyException()) + responseFlags.add(KnownFlag.Exception.value) } } "dir" -> { try { + val packetData = ScriptingPacketPathedData(packet) val leaf = Reflection.resolveInstancePath( - scope, - TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) + if (packetData.use_root) packetData.root else scope, + packetData.path ) - data = TokenizingJson.getJsonElement(leaf!!::class.members.map {it.name}) // TODO: Honestly, probably restrict to non-privates and other members that are actually accessible. Test from Python. + responseData = TokenizingJson.getJsonElement(leaf!!::class.members.map {it.name}) } catch (e: Exception) { - data = JsonPrimitive(e.stringifyException()) - flags.add(KnownFlag.Exception.value) + responseData = JsonPrimitive(e.stringifyException()) + responseFlags.add(KnownFlag.Exception.value) } } -// "hash" -> { -// try { -// val leaf = Reflection.resolveInstancePath( -// scope, -// TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) -// ) -// data = TokenizingJson.getJsonElement(leaf.hashCode()) -// } catch (e: Exception) { -// data = JsonPrimitive(e.stringifyException()) -// flags.add(KnownFlag.Exception.value) -// } -// } - // Implemented and works, but disabled for now. See Module.md. "keys" -> { try { + val packetData = ScriptingPacketPathedData(packet) val leaf = Reflection.resolveInstancePath( - scope, - TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) + if (packetData.use_root) packetData.root else scope, + packetData.path ) - data = TokenizingJson.getJsonElement((leaf as Map).keys) + responseData = TokenizingJson.getJsonElement((leaf as Map).keys) } catch (e: Exception) { - data = JsonPrimitive(e.stringifyException()) - flags.add(KnownFlag.Exception.value) + responseData = JsonPrimitive(e.stringifyException()) + responseFlags.add(KnownFlag.Exception.value) } } "length" -> { try { + val packetData = ScriptingPacketPathedData(packet) val leaf = Reflection.resolveInstancePath( - scope, - TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) + if (packetData.use_root) packetData.root else scope, + packetData.path ) try { - data = TokenizingJson.getJsonElement((leaf as Array<*>).size) + responseData = TokenizingJson.getJsonElement((leaf as Array<*>).size) // AFAICT avoiding these casts/checks would require reflection. } catch (e: Exception) { // TODO: Switch these catches to ClassCastException. try { - data = TokenizingJson.getJsonElement((leaf as Map<*, *>).size) + responseData = TokenizingJson.getJsonElement((leaf as Map<*, *>).size) } catch (e: Exception) { - data = TokenizingJson.getJsonElement((leaf as Collection<*>).size) + responseData = TokenizingJson.getJsonElement((leaf as Collection<*>).size) } } } catch (e: Exception) { - data = JsonPrimitive(e.stringifyException()) - flags.add(KnownFlag.Exception.value) + responseData = JsonPrimitive(e.stringifyException()) + responseFlags.add(KnownFlag.Exception.value) } } "contains" -> { try { + val packetData = ScriptingPacketPathedData(packet) val leaf = Reflection.resolveInstancePath( - scope, - TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) + if (packetData.use_root) packetData.root else scope, + packetData.path ) - val _check = TokenizingJson.getJsonReal((packet.data as JsonObject)["value"]!!) try { - data = TokenizingJson.getJsonElement(_check in (leaf as Map)) + responseData = TokenizingJson.getJsonElement(packetData.value in (leaf as Map)) } catch (e: Exception) { - data = TokenizingJson.getJsonElement(_check in (leaf as Collection)) + responseData = TokenizingJson.getJsonElement(packetData.value in (leaf as Collection)) } } catch (e: Exception) { - data = JsonPrimitive(e.stringifyException()) - flags.add(KnownFlag.Exception.value) + responseData = JsonPrimitive(e.stringifyException()) + responseFlags.add(KnownFlag.Exception.value) } } "ismapping" -> { try { + val packetData = ScriptingPacketPathedData(packet) val leaf = Reflection.resolveInstancePath( - scope, - TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) + if (packetData.use_root) packetData.root else scope, + packetData.path ) try { leaf as Map // Ensure same behaviour as "keys" action. IK It's probably/hopefully the same as using the is operator, but I'm not sure. // TODO: Make this and other key operations work with operator overloading. Though Map is already an interface that anything can implement, so maybe not. - data = TokenizingJson.getJsonElement(true) + responseData = TokenizingJson.getJsonElement(true) } catch (e: Exception) { - data = TokenizingJson.getJsonElement(false) + responseData = TokenizingJson.getJsonElement(false) } } catch (e: Exception) { - data = JsonPrimitive(e.stringifyException()) - flags.add(KnownFlag.Exception.value) + responseData = JsonPrimitive(e.stringifyException()) + responseFlags.add(KnownFlag.Exception.value) } } "callable" -> { try { + val packetData = ScriptingPacketPathedData(packet) val leaf = Reflection.resolveInstancePath( - scope, - TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) + if (packetData.use_root) packetData.root else scope, + packetData.path ) - data = TokenizingJson.getJsonElement(leaf is FunctionDispatcher) + responseData = TokenizingJson.getJsonElement(leaf is FunctionDispatcher) } catch (e: Exception) { - data = JsonPrimitive(e.stringifyException()) - flags.add(KnownFlag.Exception.value) + responseData = JsonPrimitive(e.stringifyException()) + responseFlags.add(KnownFlag.Exception.value) } } "args" -> { try { + val packetData = ScriptingPacketPathedData(packet) val leaf = Reflection.resolveInstancePath( - scope, - TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) + if (packetData.use_root) packetData.root else scope, + packetData.path ) - data = TokenizingJson.getJsonElement( + responseData = TokenizingJson.getJsonElement( mapOf>>( *((leaf as FunctionDispatcher).functions.map { it.toString() to it.parameters.map { listOf(it.name?.toString(), it.type.toString()) } @@ -397,27 +425,28 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = ) ) } catch (e: Exception) { - data = JsonPrimitive(e.stringifyException()) - flags.add(KnownFlag.Exception.value) + responseData = JsonPrimitive(e.stringifyException()) + responseFlags.add(KnownFlag.Exception.value) } } "docstring" -> { try { + val packetData = ScriptingPacketPathedData(packet) val leaf = Reflection.resolveInstancePath( - scope, - TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) + if (packetData.use_root) packetData.root else scope, + packetData.path ) - data = TokenizingJson.getJsonElement(leaf.toString()) + responseData = TokenizingJson.getJsonElement(leaf.toString()) } catch (e: Exception) { - data = JsonPrimitive(e.stringifyException()) - flags.add(KnownFlag.Exception.value) + responseData = JsonPrimitive(e.stringifyException()) + responseFlags.add(KnownFlag.Exception.value) } } else -> { throw IllegalArgumentException("Unknown action received in scripting request packet: ${packet.action}") } } - return ScriptingPacket(action, packet.identifier, data, flags) + return ScriptingPacket(action, packet.identifier, responseData, responseFlags) } } diff --git a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt index 14975e5ba4726..3902e424fe1d0 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt @@ -1,9 +1,10 @@ package com.unciv.scripting.protocol -import com.unciv.UncivGame // For debug packet print only. import com.unciv.scripting.AutocompleteResults import com.unciv.scripting.ScriptingBackend import com.unciv.scripting.api.ScriptingScope +import com.unciv.scripting.utils.ScriptingDebugParameters + //import com.unciv.scripting.protocol.ScriptingPacket //import com.unciv.scripting.protocol.ScriptingProtocol @@ -65,11 +66,11 @@ class ScriptingProtocolReplManager(scriptingScope: ScriptingScope, blackbox: Bla //TODO: Doc fun getRequestResponse(packetToSend: ScriptingPacket, enforceValidity: Boolean = true, execLoop: () -> Unit = fun(){}): ScriptingPacket { // Please update the specifications in Module.md if you change the basic structure of this REPL loop. - if (UncivGame.Current.printScriptingPacketsForDebug) println("\nSending: ${packetToSend}") // TODO: Add debug flag. + if (ScriptingDebugParameters.printScriptingPacketsForDebug) println("\nSending: ${packetToSend}") // TODO: Move this to ScriptingProtocol? blackbox.write(packetToSend.toJson() + "\n") execLoop() val response = ScriptingPacket.fromJson(blackbox.read(block=true)) - if (UncivGame.Current.printScriptingPacketsForDebug) println("\nReceived: ${response}") + if (ScriptingDebugParameters.printScriptingPacketsForDebug) println("\nReceived: ${response}") if (enforceValidity) { ScriptingProtocol.enforceIsResponse(packetToSend, response) } @@ -84,10 +85,10 @@ class ScriptingProtocolReplManager(scriptingScope: ScriptingScope, blackbox: Bla fun foreignExecLoop() { while (true) { val request = ScriptingPacket.fromJson(blackbox.read(block=true)) - if (UncivGame.Current.printScriptingPacketsForDebug) println("\nReceived: ${request}") + if (ScriptingDebugParameters.printScriptingPacketsForDebug) println("\nReceived: ${request}") if (request.action != null) { val response = scriptingProtocol.makeActionResponse(request) - if (UncivGame.Current.printScriptingPacketsForDebug) println("\nSending: ${response}") + if (ScriptingDebugParameters.printScriptingPacketsForDebug) println("\nSending: ${response}") blackbox.write(response.toJson() + "\n") } if (request.hasFlag(ScriptingProtocol.KnownFlag.PassMic)) { @@ -100,7 +101,7 @@ class ScriptingProtocolReplManager(scriptingScope: ScriptingScope, blackbox: Bla return ScriptingProtocol.parseActionResponses.motd( getRequestResponse( ScriptingProtocol.makeActionRequests.motd(), - execLoop = { foreignExecLoop() } //TODO: Replace with reflective method access? + execLoop = { foreignExecLoop() } //TODO: Replace with reflective method reference? ) ) } diff --git a/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt b/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt index 885b2a3cfdfe7..350e485120666 100644 --- a/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt +++ b/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt @@ -98,7 +98,7 @@ class SubprocessBlackbox(val processCmd: Array): Blackbox { return null } - override fun read(block: Boolean): String { + override fun read(block: Boolean): String { // TODO: Max wait time (and periodic checking that process hasn't crashed) possible? if (block || readyForRead > 0) { return inStream!!.readLine() } else { diff --git a/core/src/com/unciv/scripting/reflection/FunctionDispatcher.kt b/core/src/com/unciv/scripting/reflection/FunctionDispatcher.kt index 90be3f8ed1523..2d169da612168 100644 --- a/core/src/com/unciv/scripting/reflection/FunctionDispatcher.kt +++ b/core/src/com/unciv/scripting/reflection/FunctionDispatcher.kt @@ -48,7 +48,7 @@ open class FunctionDispatcher( * @return Whether a given argument value can be cast to the type of a given KParameter. */ private fun checkParameterMatches(kparam: KParameter, arg: Any?, paramKtypeAppend: ArrayList): Boolean { - // TODO: If performance becomes an issue, try inlining these. Then again, the JVM presumably optimizes it at runtime already (and there's far more calls than this containing function). + // If performance becomes an issue, try inlining these. Then again, the JVM presumably optimizes it at runtime already (and there's far more calls than this containing function). paramKtypeAppend.add(kparam.type) if (arg == null) { // Multiple dispatch of null between Any and Any? seems ambiguous in Kotlin even without reflection. diff --git a/core/src/com/unciv/scripting/reflection/Reflection.kt b/core/src/com/unciv/scripting/reflection/Reflection.kt index 5a616a8e908da..051e4ba1cc0a2 100644 --- a/core/src/com/unciv/scripting/reflection/Reflection.kt +++ b/core/src/com/unciv/scripting/reflection/Reflection.kt @@ -208,8 +208,17 @@ object Reflection { } -// fun stringifyKotlinPath() { -// } + fun stringifyKotlinPath(path: List): String { + val components = ArrayList() + for (element in path) { // TODO: Encoded strings. + components.add( when (element.type) { + PathElementType.Property -> ".${element.name}" + PathElementType.Key -> "[${if (element.doEval) element.name else element.params[0]!!}]" + PathElementType.Call -> "(${if (element.doEval) element.name else element.params.joinToString(", ")}])" + }) + } + return components.joinToString() + } // private val closingbrackets = null @@ -235,49 +244,53 @@ object Reflection { } - fun resolveInstancePath(instance: Any, path: List): Any? { + fun resolveInstancePath(instance: Any?, path: List): Any? { //TODO: Allow passing an ((Any?)->Unit)? (or maybe Boolean) function as a parameter that gets called at every stage of resolution, to let exceptions be thrown if accessing something not whitelisted. var obj: Any? = instance for (element in path) { - when (element.type) { - PathElementType.Property -> { - try { - obj = readInstanceProperty(obj!!, element.name) - // TODO: Consider a LBYL instead of AFP here. - } catch (e: ClassCastException) { - obj = makeInstanceMethodDispatcher( - obj!!, - element.name - ) + try { + when (element.type) { // TODO: Use as expression? + PathElementType.Property -> { + try { + obj = readInstanceProperty(obj!!, element.name) + // TODO: Consider a LBYL instead of AFP here. + } catch (e: ClassCastException) { + obj = makeInstanceMethodDispatcher( + obj!!, + element.name + ) + } } - } - PathElementType.Key -> { - obj = readInstanceItem( - obj!!, - if (element.doEval) - evalKotlinString(instance!!, element.name)!! - else - element.params[0]!! - ) - } - PathElementType.Call -> { - // TODO: Handle invoke operator. Easy enough, just recurse to access the .invoke. - // object test{operator fun invoke(a:Any?,b:Any?){println("$a $b")}}; test(1,2) - // Test in Python: apiHelpers.Jvm.constructorByQualname('com.unciv.UncivGame'). Also do an object for multi-arg testing, I guess? - // Maybe TODO: Handle lambdas. E.G. WorldScreen.nextTurnAction. But honestly, it may be better to just expose wrapping non-lambdas. - obj = (obj as FunctionDispatcher).call( - // Undocumented implicit behaviour: Using the last object means that this should work with explicitly created FunctionDispatcher()s. - ( + PathElementType.Key -> { + obj = readInstanceItem( + obj!!, if (element.doEval) - splitToplevelExprs(element.name).map { evalKotlinString(instance!!, it) } + evalKotlinString(instance!!, element.name)!! else - element.params - ).toTypedArray() - ) + element.params[0]!! + ) + } + PathElementType.Call -> { + // TODO: Handle invoke operator. Easy enough, just recurse to access the .invoke. + // object test{operator fun invoke(a:Any?,b:Any?){println("$a $b")}}; test(1,2) + // Test in Python: apiHelpers.Jvm.constructorByQualname('com.unciv.UncivGame'). Also do an object for multi-arg testing, I guess? + // Maybe TODO: Handle lambdas. E.G. WorldScreen.nextTurnAction. But honestly, it may be better to just expose wrapping non-lambdas. + obj = (obj as FunctionDispatcher).call( + // Undocumented implicit behaviour: Using the last object means that this should work with explicitly created FunctionDispatcher()s. + ( + if (element.doEval) + splitToplevelExprs(element.name).map { evalKotlinString(instance!!, it) } + else + element.params + ).toTypedArray() + ) + } + // else -> { + // throw UnsupportedOperationException("Unknown path element type: ${element.type}") + // } } -// else -> { -// throw UnsupportedOperationException("Unknown path element type: ${element.type}") -// } + } catch (e: Exception) { + throw IllegalAccessException("Cannot access $element on $obj: $e") } } return obj @@ -310,7 +323,7 @@ object Reflection { } - fun setInstancePath(instance: Any, path: List, value: Any?) { + fun setInstancePath(instance: Any?, path: List, value: Any?) { val leafobj = resolveInstancePath(instance, path.slice(0..path.size-2)) val leafelement = path[path.size - 1] when (leafelement.type) { @@ -336,7 +349,7 @@ object Reflection { } } - fun removeInstancePath(instance: Any, path: List) { + fun removeInstancePath(instance: Any?, path: List) { val leafobj = resolveInstancePath(instance, path.slice(0..path.size-2)) val leafelement = path[path.size - 1] when (leafelement.type) { diff --git a/core/src/com/unciv/scripting/utils/InstanceTokenizer.kt b/core/src/com/unciv/scripting/serialization/InstanceTokenizer.kt similarity index 85% rename from core/src/com/unciv/scripting/utils/InstanceTokenizer.kt rename to core/src/com/unciv/scripting/serialization/InstanceTokenizer.kt index 3ec7bb3b91f4a..8c9798a22dec8 100644 --- a/core/src/com/unciv/scripting/utils/InstanceTokenizer.kt +++ b/core/src/com/unciv/scripting/serialization/InstanceTokenizer.kt @@ -1,4 +1,4 @@ -package com.unciv.scripting.utils +package com.unciv.scripting.serialization import com.unciv.scripting.ScriptingConstants //import kotlin.math.min @@ -15,8 +15,6 @@ import java.util.UUID */ object InstanceTokenizer { - // TODO: This could potentially be used as the basis for a fully reference-based script execution model, as opposed to the path-based one right now. Basically, make ForeignObject always keep a root token/value, and make ScriptingProtocol's actions resolve paths from the supplied base object instead of from scriptingScope. It may greatly increase the number of IPC calls (one per attribute access), but it would also unify all semantics better (and allow, E.G., reading directly from function returns). - // Could even potentially get rid of `.registeredInstances` completely by automatically registering/reference counting in the JVM and freeing in scripting language destructors. But JS apparently doesn't give any way to control garbage collection, so the risk of memory leaks wouldn't be worth it. /** @@ -55,15 +53,15 @@ object InstanceTokenizer { */ private fun tokenFromInstance(value: Any?): String { var stringified: String - try { // Because this can be overridden, it can fail. E.G. MapUnit.toString() depends on a lateinit. - stringified = value.toString() + stringified = try { // Because this can be overridden, it can fail. E.G. MapUnit.toString() depends on a lateinit. + value.toString() } catch (e: Exception) { - stringified = "?" // TODO: Use exception text? + "?" // TODO: Use exception text? } if (stringified.length > tokenMaxLength) { - stringified = stringified.slice(0..tokenMaxLength-4) + "..." + stringified = stringified.slice(0..tokenMaxLength -4) + "..." } - return "${tokenPrefix}${System.identityHashCode(value)}:${if (value == null) "null" else value::class.qualifiedName}/${stringified}:${UUID.randomUUID().toString()}" + return "$tokenPrefix${System.identityHashCode(value)}:${if (value == null) "null" else value::class.qualifiedName}/${stringified}:${UUID.randomUUID().toString()}" } /** @@ -116,6 +114,7 @@ object InstanceTokenizer { clean() if (isToken(token)) { return instances[token]!!.get()// TODO: Add another non-null assertion here? Unknown tokens and expired tokens are only a cleaning cycle apart, which seems race condition-y. + // TODO: Helpful exception message for invalid tokens. } else { return token } diff --git a/core/src/com/unciv/scripting/utils/TokenizingJson.kt b/core/src/com/unciv/scripting/serialization/TokenizingJson.kt similarity index 77% rename from core/src/com/unciv/scripting/utils/TokenizingJson.kt rename to core/src/com/unciv/scripting/serialization/TokenizingJson.kt index bb7e2ba05f6f7..6e93b4a3aeac0 100644 --- a/core/src/com/unciv/scripting/utils/TokenizingJson.kt +++ b/core/src/com/unciv/scripting/serialization/TokenizingJson.kt @@ -57,7 +57,7 @@ object TokenizingJson { encoder.encodeNull() } else { val classname = value!!::class.simpleName!! - if (classname in dataTypeSerializers && !isTokenizationMandatory(value)) { + if (classname in dataTypeSerializers) {// && !isTokenizationMandatory(value)) { encoder.encodeSerializableValue(dataTypeSerializers[value!!::class.simpleName!!]!!, value!!) } else { encoder.encodeString(InstanceTokenizer.getToken(value!!)) @@ -85,36 +85,50 @@ object TokenizingJson { // serializersModule = serializersModule } - /** - * Forbid some objects from being serialized as normal JSON values. - * - * com.unciv.models.ruleset.Building, for example, and resumably all other types that inherit from Stats, implements Iterable<*> and thus gets serialized as JSON Arrays by default even though it's probably better to tokenize them. - * Python example: civInfo.cities[0].cityConstructions.getBuildableBuildings() - * (Should return list of Building tokens, not list of lists of tokens for seemingly unrelated stats.) - * - * @param value Value or instance to check. - * @return Whether the given value is required to be tokenized. - */ - private fun isTokenizationMandatory(value: Any?): Boolean { - if (value == null) { - return false +// /** +// * Forbid some objects from being serialized as normal JSON values. +// * +// * com.unciv.models.ruleset.Building, for example, and resumably all other types that inherit from Stats, implements Iterable<*> and thus gets serialized as JSON Arrays by default even though it's probably better to tokenize them. +// * Python example: civInfo.cities[0].cityConstructions.getBuildableBuildings() +// * (Should return list of Building tokens, not list of lists of tokens for seemingly unrelated stats.) +// * +// * @param value Value or instance to check. +// * @return Whether the given value is required to be tokenized. +// */ +// private fun isTokenizationMandatory(value: Any?): Boolean { +// // TODO: Remove this? +// if (value == null) { +// return false +// } +// val qualname = value::class.qualifiedName +// return qualname != null && qualname.startsWith("com.unciv") +// // Originally, containers would be serialized when requested so they could be efficiently traversed in scripting languages with their own semantics. +// // With the switch away from bind-by-path to bind-by-reference, that behaviour is now a greater deviation from the convention of everything on the scripting side being a wrapper for Kotlin/JVM instances by default. +// } + + // @return Whether the value corresponds to a primitive JSON value type. + fun isNotPrimitive(value: Any?): Boolean { + return when (value) { + null -> false + is String -> false + is Boolean -> false + is Number -> false + else -> true } - val qualname = value::class.qualifiedName - return qualname != null && qualname.startsWith("com.unciv") } - /** * Get a KotlinX JsonElement for any Kotlin/JVM value or instance. * * @param value Any Kotlin/JVM value or instance to turn into a JsonElement. + * @param requireTokenization If given, a function that returns whether the value must be tokenized even if it can be serialized. * @return Input value unchanged if value is already a JsonElement, otherwise JsonElement best representing value— Generally best effort to create JsonObject, JsonArray, JsonPrimitive, or JsonNull directly from value, and token string JsonPrimitive if best effort fails or tokenization is mandatory for the given value. */ - fun getJsonElement(value: Any?): JsonElement { + fun getJsonElement(value: Any?, requireTokenization: ((Any?) -> Boolean)? = null): JsonElement { if (value is JsonElement) { return value } - if (!isTokenizationMandatory(value)) { + if (requireTokenization == null || !requireTokenization(value)) { if (value is Map<*, *>) { return JsonObject( (value as Map).entries.associate { json.encodeToString(getJsonElement(it.key)) to getJsonElement(it.value) @@ -128,7 +142,7 @@ object TokenizingJson { } if (value is Iterable<*>) { // Apparently ::class.java.isArray can be used to check for primitive arrays, but it breaks - return JsonArray(value.map { getJsonElement(it) }) + return JsonArray(value.map(::getJsonElement)) } if (value is Sequence<*>) { return getJsonElement((value as Sequence).toList()) @@ -160,7 +174,7 @@ object TokenizingJson { return null } if (value is JsonArray) { - return (value as List).map { getJsonReal(it) }// as List + return (value as List).map(::getJsonReal)// as List } if (value is JsonObject) { return (value as Map).entries.associate { @@ -169,16 +183,16 @@ object TokenizingJson { } if (value is JsonPrimitive) { val v = value as JsonPrimitive - if (v.isString) { - return InstanceTokenizer.getReal(v.content) + return if (v.isString) { + InstanceTokenizer.getReal(v.content) } else { - return v.content.toIntOrNull() + v.content.toIntOrNull() ?: v.content.toLongOrNull() ?: v.content.toFloatOrNull() // NOTE: This may prevent .toDoubleOrNull() from ever being used. I think the implicit number type conflation in FunctionDispatcher means that Floats can still be used where Doubles are expected, though. ?: v.content.toDoubleOrNull() ?: v.content.toBooleanStrictOrNull() ?: v.content - }//TODO: This may be better ternary. + } } throw IllegalArgumentException("Unrecognized type of JsonElement: ${value::class}/${value}") } diff --git a/core/src/com/unciv/scripting/utils/ScriptingDebugParameters.kt b/core/src/com/unciv/scripting/utils/ScriptingDebugParameters.kt new file mode 100644 index 0000000000000..a7112a549aadd --- /dev/null +++ b/core/src/com/unciv/scripting/utils/ScriptingDebugParameters.kt @@ -0,0 +1,6 @@ +package com.unciv.scripting.utils + +object ScriptingDebugParameters { + var printScriptingPacketsForDebug = false + var printScriptingAccessForDebug = false +} diff --git a/core/src/com/unciv/scripting/utils/ScriptingLock.kt b/core/src/com/unciv/scripting/utils/ScriptingLock.kt new file mode 100644 index 0000000000000..10097eb6e43bd --- /dev/null +++ b/core/src/com/unciv/scripting/utils/ScriptingLock.kt @@ -0,0 +1,6 @@ +package com.unciv.scripting.utils + +object ScriptingLock { + private var isRunning = false + // TODO: Prevent UI collision, and also prevent recursive script executions. +} diff --git a/core/src/com/unciv/scripting/utils/StringifyException.kt b/core/src/com/unciv/scripting/utils/StringifyException.kt deleted file mode 100644 index 0a7bd5cfb0b81..0000000000000 --- a/core/src/com/unciv/scripting/utils/StringifyException.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.unciv.scripting.utils - - -/** - * @return String of exception preceded by entire stack trace. - */ -fun Exception.stringifyException(): String { - val causes = arrayListOf() - var cause: Throwable? = this - while (cause != null) { - causes.add(cause) - cause = cause.cause - //I swear this is okay to do. - } - return listOf( - "\n", - *this.stackTrace, - "\n", - *causes.asReversed().map { it.toString() }.toTypedArray() - ).joinToString("\n") -} -// TODO: Move this to ExtensionFunctions.kt. From fe5a3cd0e4090f631562c1e2f816a02692b534f1 Mon Sep 17 00:00:00 2001 From: will-ca Date: Wed, 8 Dec 2021 05:25:28 +0000 Subject: [PATCH 62/93] Close 20-something TODOs. --- .../enginefiles/python/unciv_lib/api.py | 7 +- .../python/unciv_lib/autocompletion.py | 11 +-- .../enginefiles/python/unciv_lib/ipc.py | 5 +- .../enginefiles/python/unciv_lib/wrapping.py | 22 +++--- .../com/unciv/scripting/ScriptingBackend.kt | 14 ++-- .../src/com/unciv/scripting/ScriptingState.kt | 73 +++++++------------ .../scripting/api/ScriptingApiHelpers.kt | 5 -- .../scripting/api/ScriptingApiMappers.kt | 2 +- .../scripting/api/ScriptingApiSysHelpers.kt | 2 +- .../com/unciv/scripting/api/ScriptingScope.kt | 12 +-- .../scripting/protocol/ScriptingProtocol.kt | 52 +++++++------ .../protocol/ScriptingReplManager.kt | 22 +++--- .../reflection/FunctionDispatcher.kt | 4 +- .../unciv/scripting/reflection/Reflection.kt | 51 +++++++------ .../scripting/serialization/TokenizingJson.kt | 4 +- .../scripting/utils/ScriptingModApiHelpers.kt | 14 ++++ .../unciv/ui/consolescreen/ConsoleScreen.kt | 8 +- .../com/unciv/ui/utils/ExtensionFunctions.kt | 29 ++++++++ 18 files changed, 171 insertions(+), 166 deletions(-) create mode 100644 core/src/com/unciv/scripting/utils/ScriptingModApiHelpers.kt diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/api.py b/android/assets/scripting/enginefiles/python/unciv_lib/api.py index e56ddddb9bae2..233b6a134596c 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/api.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/api.py @@ -2,7 +2,6 @@ from . import ipc, utils -#TODO: Expose ForeignError class. enginedir = os.path.dirname(__file__) @@ -90,6 +89,8 @@ def pathcodeFromWrapper(wrapper): return wrapping.stringPathList(wrapper._getpath_()) # TODO +expose()(ipc.ForeignError) + class UncivReplTransceiver(ipc.ForeignActionReceiver, ipc.ForeignActionSender): """Class that implements the Unciv IPC and scripting protocol by receiving and responding to its packets. See Module.md.""" @@ -108,7 +109,6 @@ def populateApiScope(self): # TODO: Replace this update with Kotlin-side init? def passMic(self): """Send a 'PassMic' packet.""" - #TODO: This should use ForeignPacket(), no? self.SendForeignAction({'action':None, 'identifier': None, 'data':None, 'flags':('PassMic',)}) @ipc.receiverMethod('motd', 'motd_response') def EvalForeignMotd(self, packet): @@ -144,8 +144,6 @@ def EvalForeignExec(self, packet): with ipc.FakeStdout() as fakeout: print(f">>> {str(line)}") try: - # TODO: See if you can use signals to catch infinite loops/excess run duration. - # Actually, no. Interactivity should be retained from the Kotlin side by running/calling ScriptingState in a different thread. try: code = compile(line, 'STDIN', 'eval') except SyntaxError: @@ -158,7 +156,6 @@ def EvalForeignExec(self, packet): finally: self.passMic() return fakeout.getvalue() - @ipc.receiverMethod('terminate', 'terminate_response') def EvalForeignTerminate(self, packet): return None diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py b/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py index f371bfcce3270..c5ca445a10e9f 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py @@ -14,7 +14,7 @@ def __init__( ): self.scope = globals() if scope is None else scope self.get_keys, self.get_help, self.check_callable = get_keys, get_help, check_callable - def GetCommandComponents(self, command): + def getCommandComponents(self, command): """Try to return the the last atomic evaluable expression in a statement, everything before it, and the token at the end of everything before it.""" #Call recursively if you need to resolve multiple values. Test string: # abc.cde().fgh[0].ijk(lmn[1].opq["dea @@ -42,7 +42,7 @@ def GetCommandComponents(self, command): continue if char in '([:,;+-*/|&<>=%{~^@': # Should probably split at multi-character tokens like 'in', 'for', 'if', etc. too. - # TODO: Maybe just put read spaces as a token type for now? + # TODO: Maybe just put read spaces as a token type for now?.. No, do it properly by checking against word tokens. prefixsplit += 1 lasttoken = char break @@ -55,6 +55,7 @@ def GetCommandComponents(self, command): return prefix, workingcode, lasttoken def GetAutocomplete(self, command): """Return either a sequence of full autocomplete matches or a help string for a given command.""" + return () # class AstAutocompleteManager(AutocompleteManager): @@ -75,12 +76,12 @@ def GetAutocomplete(self, command, cursorpos=None): try: if cursorpos is None: cursorpos = len(command) - (prefix, workingcode, lasttoken), suffix = self.GetCommandComponents(command[:cursorpos]), command[cursorpos:] + (prefix, workingcode, lasttoken), suffix = self.getCommandComponents(command[:cursorpos]), command[cursorpos:] if ')' in workingcode: # Avoid function calls. return () if lasttoken in {*'[('}:# Compare to set because None can't be used in string containment check. - prefix_prefix, prefix_workingcode, prefix_lasttoken = self.GetCommandComponents(prefix[:-1]) + prefix_prefix, prefix_workingcode, prefix_lasttoken = self.getCommandComponents(prefix[:-1]) assert prefix[-1] == lasttoken if ')' not in prefix_workingcode: # Avoid function calls. @@ -136,7 +137,7 @@ class RlAutocompleteManager(AutocompleteManager): def GetAutocomplete(self, command, cursorpos=None): #Adds brackets to everything, due to presence of dynamic `.__call__` on `ForeignObject`.. Technically, I might be able to control `callable()` by implementing a metaclass with a custom `.__getattribute__` with custom descriptors on `ForeignObject`. Perhaps such a sin is still beyond even my bumbling arrogance, though. completer = rlcompleter.Completer(self.scope) - (prefix, workingcode, lasttoken), suffix = self.GetCommandComponents(command[:cursorpos]), command[cursorpos:] + (prefix, workingcode, lasttoken), suffix = self.getCommandComponents(command[:cursorpos]), command[cursorpos:] if workingcode: matches = [] for i in itertools.count(): diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/ipc.py b/android/assets/scripting/enginefiles/python/unciv_lib/ipc.py index 57475908225ac..01e382e811851 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/ipc.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/ipc.py @@ -10,7 +10,7 @@ def default(self, obj): return json.JSONEncoder.default(self, obj) -def MakeUniqueId(): +def makeUniqueId(): """Return a string that should never repeat or collide. Used for IPC packet identity fields.""" return f"{time.time_ns()}-{random.getrandbits(30)}" @@ -19,6 +19,7 @@ class ForeignError(RuntimeError): class ForeignPacket: """Class for IPC packet conforming to specification in Module.md and ScriptingProtocol.kt.""" + # TODO: Speed? Well, I'll cProfile the whole thing eventaully I guess. def __init__(self, action, identifier, data, flags=()): self.action = action self.identifier = identifier @@ -67,7 +68,7 @@ class ForeignActionSender(ForeignActionManager): def SendForeignAction(self, actionparams): self.sender(ForeignPacket(**actionparams).serialized()) def GetForeignActionResponse(self, actionparams, responsetype): - identifier = MakeUniqueId() + identifier = makeUniqueId() self.SendForeignAction({**actionparams, 'identifier': identifier}) return ForeignPacket.deserialized(self.receiver()).enforce_type(responsetype, identifier) diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py index 50dff0a47ed18..28f49d23510b8 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py @@ -26,7 +26,7 @@ def meth(*a, **kw): return meth -def ResolvingFunction(op, *, allowforeigntokens=False): +def resolvingFunction(op, *, allowforeigntokens=False): """Return a function that passes its arguments through `api.real()`.""" def _resolvingfunction(*arguments, **keywords): args = [api.real(a) for a in arguments] @@ -42,7 +42,7 @@ def _resolvingfunction(*arguments, **keywords): _resolvingfunction.__doc__ = f"{op.__doc__ or name + ' operator.'}\n\nCalls `api.real()` on all arguments." return _resolvingfunction -def ReversedMethod(func): +def reversedMethod(func): """Return a `.__rop__` version of an `.__op__` magic method function.""" def _reversedop(a, b, *args, **kwargs): return func(b, a, *args, **kwargs) @@ -50,7 +50,7 @@ def _reversedop(a, b, *args, **kwargs): _reversedop.__doc__ = f"{func.__doc__ or name + ' operator.'}\n\nReversed version." return _reversedop -def InplaceMethod(func): # TODO: Lower-case these names? +def inplaceMethod(func): """Return a wrapped a function that calls ._setvalue_() on its first self argument with its original result.""" def _inplacemethod(self, *args, **kwargs): self._setvalue_(func(self, *args, **kwargs)) @@ -156,7 +156,7 @@ def stringPathList(pathlist): '__ior__' ) -def ResolveForOperators(cls): +def resolveForOperators(cls): """Decorator. Adds missing magic methods to a class, which resolve their arguments with `api.real(a)`.""" def alreadyhas(name): return (hasattr(cls, name) and getattr(cls, name) is not getattr(object, name, None)) @@ -167,16 +167,16 @@ def alreadyhas(name): name, opname = meth if not alreadyhas(name): # Set the magic method only if neither it nor any of its base classes have already defined a custom implementation. - setattr(cls, name, ResolvingFunction(getattr(operator, opname), allowforeigntokens=False)) + setattr(cls, name, resolvingFunction(getattr(operator, opname), allowforeigntokens=False)) for rmeth in _rmagicmeths: normalname = rmeth.replace('__r', '__', 1) if not alreadyhas(rmeth) and hasattr(cls, normalname): - setattr(cls, rmeth, ReversedMethod(getattr(cls, normalname))) + setattr(cls, rmeth, reversedMethod(getattr(cls, normalname))) for imeth in _imagicmethods: normalname = imeth.replace('__i', '__', 1) if not alreadyhas(imeth) and hasattr(cls, normalname): normalfunc = getattr(cls, normalname) - setattr(cls, imeth, InplaceMethod(normalfunc)) + setattr(cls, imeth, inplaceMethod(normalfunc)) return cls @@ -206,7 +206,7 @@ def __setattr__(self, name, value): # TODO: Maybe test to see if a path-based approach for keys and attributes might still be faster? -@ResolveForOperators +@resolveForOperators class ForeignObject: """Wrapper for a foreign object. Implements the specifications on IPC packet action types and data structures in Module.md.""" def __init__(self, *, path, use_root=False, root=None, foreignrequester=dummyForeignRequester): @@ -228,7 +228,7 @@ def _getpath_(self): def _bakereal_(self): assert not self._isbaked self._attrs._unbaked = self._clone_() # For in-place operations. - self._attrs._root = self._getvalue_() # TODO: Would the fallback for in-places go through __setattr__, and result in one fewer IPC call? + self._attrs._root = self._getvalue_() # TODO: Would the fallback for inplaces go through __setattr__, and result in one fewer IPC call? self._attrs._use_root = True self._attrs._path = () self._attrs._isbaked = True @@ -250,8 +250,8 @@ def __getitem__(self, key, *, do_bake=True): if BIND_BY_REFERENCE and do_bake: item._bakereal_() return item - #TODO: Should negative indexing from end be supported? - #IIRC I decided "No". Not entirely sure why. Probably a mix of needing a __len__ IPC call for that, plus incongruency with Kotlin (and other languages') behaviour, and complexity here. + # Indexing from end with negative numbers is not supported. + # Mostly a complexity choice. Matching Kotlin semantics is better than doing an extra IPC call. def __iter__(self): try: return iter(self.keys()) diff --git a/core/src/com/unciv/scripting/ScriptingBackend.kt b/core/src/com/unciv/scripting/ScriptingBackend.kt index 2661a99812c8f..0b1165896cdba 100644 --- a/core/src/com/unciv/scripting/ScriptingBackend.kt +++ b/core/src/com/unciv/scripting/ScriptingBackend.kt @@ -115,6 +115,8 @@ open class ScriptingBackendBase(val scriptingScope: ScriptingScope): ScriptingIm /** * Let the companion object of the correct subclass be accessed in subclass instances. + * + * Any access to the companion from instance methods should be done using this property, or else the companion object accessed will be the one from where instances was declared. */ open val metadata get() = this::class.companionObjectInstance as ScriptingBackend_metadata @@ -440,7 +442,7 @@ abstract class EnvironmentedScriptingBackend(scriptingScope: ScriptingScope): Sc // Since the companion object type is different, we have to define a new getter for the subclass instance companion getter to get its new members. get() = this::class.companionObjectInstance as EnvironmentedScriptBackend_metadata - val folderHandle: FileHandle by lazy { SourceManager.setupInterpreterEnvironment(engine) } + val folderHandle: FileHandle by lazy { SourceManager.setupInterpreterEnvironment(metadata.engine) } // This requires the overridden values for engine, so setting it in the constructor causes a null error... May be fixed since moving engine to the companions. // Also, BlackboxScriptingBackend inherits from this, but not all subclasses of BlackboxScriptingBackend might need it. So as long as it's not accessed, it won't be initialized. @@ -469,7 +471,7 @@ abstract class BlackboxScriptingBackend(scriptingScope: ScriptingScope): Environ try { return replManager.motd() } catch (e: Exception) { - return "No MOTD for ${engine} backend: ${e}\n" + return "No MOTD for ${metadata.engine} backend: ${e}\n" } } @@ -510,7 +512,7 @@ abstract class SubprocessScriptingBackend(scriptingScope: ScriptingScope): Black override fun motd(): String { return """ - Welcome to the Unciv '${displayName}' API. This backend relies on running the system ${processCmd.firstOrNull()} command as a subprocess. + Welcome to the Unciv '${metadata.displayName}' API. This backend relies on running the system ${processCmd.firstOrNull()} command as a subprocess. If you do not have an interactive REPL below, then please make sure the following command is valid on your system: @@ -629,9 +631,3 @@ enum class ScriptingBackendType(val metadata: ScriptingBackend_metadata) { //For running ApiSpecGenerator. Comment in releases. Uncomment if needed. // TODO: Have .new function? } - -//// TODO: Lowercase name. Actually, no. Just get rid of this. -//fun SpawnNamedScriptingBackend(backendtype: ScriptingBackendType, scriptingScope: ScriptingScope): ScriptingBackendBase { -// // Seems unnecessary? -// return backendtype.metadata.new(scriptingScope) -//} diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index cccbe23feede8..78fd55ff14a4a 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -1,16 +1,11 @@ package com.unciv.scripting import com.unciv.scripting.api.ScriptingScope +import com.unciv.ui.utils.clipIndexToBounds import kotlin.collections.ArrayList import kotlin.math.max import kotlin.math.min -// TODO: Move to ExtensionFunctions.kt. - -fun ArrayList.clipIndexToBounds(index: Int, extendsize: Int = 0): Int { - return max(0, min(this.size-1+extendsize, index)) -} - // TODO: Check for places to use Sequences. // Hm. It seems that Sequence performance isn't even a simple question of number of loops, and is also affected by boxed types and who know what else. // Premature optimization and such. Clearly long chains of loops can be rewritten as sequences. @@ -46,9 +41,8 @@ fun ArrayList.enforceValidIndex(index: Int) { * * @property scriptingScope ScriptingScope instance at the root of all scripting API. */ -//TODO: Probably deprecate/remove initialBackendType. //TODO: Actually, probably should be only one instance in game, since various context changes set various ScriptingScope properties through it, and being able to view mod command history will also be useful. -class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: ScriptingBackendType? = null) { +class ScriptingState(val scriptingScope: ScriptingScope) { val scriptingBackends = ArrayList() @@ -63,26 +57,17 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr var activeCommandHistory: Int = 0 // Actually inverted, because history items are added to end of list and not start. 0 means nothing, 1 means most recent command at end of list. - init { - if (initialBackendType != null) { - echo(spawnBackend(initialBackendType)) - } - } - fun getOutputHistory() = outputHistory.toList() - fun spawnBackend(backendtype: ScriptingBackendType): String { - val backend: ScriptingBackendBase = SpawnNamedScriptingBackend(backendtype, scriptingScope) + data class BackendSpawnResult(val backend: ScriptingBackendBase, val motd: String) + + fun spawnBackend(backendtype: ScriptingBackendType): BackendSpawnResult { + val backend: ScriptingBackendBase = backendtype.metadata.new(scriptingScope) scriptingBackends.add(backend) activeBackend = scriptingBackends.size - 1 val motd = backend.motd() echo(motd) - return motd - } - - fun spawnBackendAndReturnMotd(backendtype: ScriptingBackendType, switchTo: Boolean = true) { - // TODO - // The script manager/handler dispatcher for mods is going to want to keep track of which backends belong to which mods. + return BackendSpawnResult(backend, motd) } fun switchToBackend(index: Int) { @@ -91,22 +76,24 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr } fun switchToBackend(backend: ScriptingBackendBase) { - for ((i, b) in scriptingBackends.withIndex()) { - // TODO: Apparently there's a bunch of extensions like .withIndex(), .indices, and .lastIndex that I can use to replace a lot of stuff currently done with .size. - // TODO: Why didn't I use indexOf? - if (b == backend) { - return switchToBackend(index = i) - } - } + // TODO: Apparently there's a bunch of extensions like .withIndex(), .indices, and .lastIndex that I can use to replace a lot of stuff currently done with .size. + val index = scriptingBackends.indexOf(backend) + if (index >= 0) + return switchToBackend(index = index) +// for ((i, b) in scriptingBackends.withIndex()) { +// if (b == backend) { +// return switchToBackend(index = i) +// } +// } throw IllegalArgumentException("Could not find scripting backend base: ${backend}") } - fun switchToBackend(displayName: String) { - //TODO - } - - fun switchToBackend(backendType: ScriptingBackendType) { - } +// fun switchToBackend(displayName: String) { +// // Are these really necessary? +// } +// +// fun switchToBackend(backendType: ScriptingBackendType) { +// } fun termBackend(index: Int): Exception? { scriptingBackends.enforceValidIndex(index) @@ -122,8 +109,7 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr } fun hasBackend(): Boolean { - // TODO: Any check like this can be replaced with .isEmpty(). - return scriptingBackends.size > 0 + return scriptingBackends.isNotEmpty() } fun getActiveBackend(): ScriptingBackendBase { @@ -148,7 +134,7 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr } fun navigateHistory(increment: Int): String { - activeCommandHistory = commandHistory.clipIndexToBounds(activeCommandHistory + increment, extendsize = 1) + activeCommandHistory = commandHistory.clipIndexToBounds(activeCommandHistory + increment, extendEnd = 1) if (activeCommandHistory <= 0) { return "" } else { @@ -159,7 +145,8 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr fun exec(command: String): String { // TODO: Allow "passing" args that get assigned to something under ScriptingScope here. //scriptingScope.scriptingBackend = if (command.length > 0) { - commandHistory.add(command) // TODO: Also don't add duplicates. + if (command != commandHistory.lastOrNull()) + commandHistory.add(command) while (commandHistory.size > maxCommandHistory) { commandHistory.removeAt(0) // No need to restrict activeCommandHistory to valid indices here because it gets set to zero anyway. @@ -167,13 +154,7 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr } } activeCommandHistory = 0 - var out: String - if (hasBackend()) { - // TODO: Ternary. - out = getActiveBackend().exec(command) - } else { - out = "" - } + var out = if (hasBackend()) getActiveBackend().exec(command) else "" echo(out) //scriptingScope.scriptingBackend = null // TODO return out diff --git a/core/src/com/unciv/scripting/api/ScriptingApiHelpers.kt b/core/src/com/unciv/scripting/api/ScriptingApiHelpers.kt index b84715936c929..3d4d7a0b4ee83 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiHelpers.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiHelpers.kt @@ -29,9 +29,4 @@ class ScriptingApiHelpers(val scriptingScope: ScriptingScope) { // This creates a dilemma: Resolving a path into a Kotlin value too early means that no further paths (E.G. attribute, keys, calls) can be built on top of it. But resolving it late means that expected side effects may not happen (E.G. function calls probably shouldn't be deferred). And values that *must* be resolved, like the results of function calls, cannot have their own members and method accessed until they themselves are assigned to a path, because they're just kinda floating around as far as the scripting-exposed semantics are concerned. // So this fake Map works around that, by providing a way for any random object to appear to have a path. - - //setTimeout? - //fun lambdifyScript(code: String ): () -> Unit = fun(){ scriptingScope.uncivGame!!.scriptingState.exec(code); return } // FIXME: Requires awareness of which scriptingState and which backend to use. - // TODO: Move to modApiHelpers. - //Directly invoking the resulting lambda from a running script will almost certainly break the REPL loop/IPC protocol. } diff --git a/core/src/com/unciv/scripting/api/ScriptingApiMappers.kt b/core/src/com/unciv/scripting/api/ScriptingApiMappers.kt index 9085c5a9bc311..23f6e146d9e6d 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiMappers.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiMappers.kt @@ -9,7 +9,7 @@ object ScriptingApiMappers { //// Some ways to access or assign the same property(s) on a lot of instances at once, using only one IPC call. Maybe use parseKotlinPath? Probably preserve order in return instead of mapping from each instance, since script must already have list of tokens anyway (ideally also from a single IPC call). Or preserve mapping, since order could be messy, and to fit with the assignment function? fun mapPathCodes(instances: List, pathcodes: Collection): List> { - val pathElementLists = pathcodes.associateWith { Reflection.parseKotlinPath(it) } // TODO: Meth ref? + val pathElementLists = pathcodes.associateWith(Reflection::parseKotlinPath) return instances.map { val instance = it pathElementLists.mapValues { diff --git a/core/src/com/unciv/scripting/api/ScriptingApiSysHelpers.kt b/core/src/com/unciv/scripting/api/ScriptingApiSysHelpers.kt index 04a64016185f0..7f9a04f43b9da 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiSysHelpers.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiSysHelpers.kt @@ -4,7 +4,7 @@ import com.badlogic.gdx.Gdx object ScriptingApiSysHelpers { fun printLine(msg: Any?) = println(msg.toString()) // Different name from Kotlin's is deliberate, to abstract for scripts. - //fun readLine() // TODO + fun readLine() = kotlin.io.readLine() // Kotlin 1.6+ exposes readln(), unified name with println(). //Return a line from the main game process's STDIN. fun copyToClipboard(value: Any?) { //Better than scripts potentially doing it themselves. In Python, for example, a way to do this would involve setting up an invisible TKinter window. diff --git a/core/src/com/unciv/scripting/api/ScriptingScope.kt b/core/src/com/unciv/scripting/api/ScriptingScope.kt index e61caa5a03a45..ef3056ed1c109 100644 --- a/core/src/com/unciv/scripting/api/ScriptingScope.kt +++ b/core/src/com/unciv/scripting/api/ScriptingScope.kt @@ -3,6 +3,7 @@ package com.unciv.scripting.api import com.unciv.UncivGame import com.unciv.logic.GameInfo import com.unciv.logic.civilization.CivilizationInfo +import com.unciv.scripting.utils.ScriptingModApiHelpers import com.unciv.ui.mapeditor.MapEditorScreen import com.unciv.ui.worldscreen.WorldScreen @@ -39,7 +40,7 @@ class ScriptingScope( val apiHelpers = ScriptingApiHelpers(this) - //var modApiHelpers: ModApiHelpers? + val modApiHelpers = ScriptingModApiHelpers(this) } @@ -47,15 +48,6 @@ class ScriptingScope( // ScriptingState also helps separate , keep the shared ScriptingScope between all of them (such that it only needs to be updated once on game context changes), and update /* -//class ModApiHelpers { - var handlerContext: NamedTuple? - // Why not just use a map? String keys will be clearer in scripts than integers anyway. - // Collection that gets replaced with any contextual parameters when running script handlers. E.G. Unit moved, city founded, tech researched, construction finished. -//fun showScriptedChoicePopup(title: String, body: String, options: List, callback: String?) // Actually, no. Popup() seems to work on its own. - // TODO: Mods blacklist, for security threats. -} - - //class NamedTuple(val map: Map) { //Default order-preserving implementation of Map is important. //SortedMap? diff --git a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt index 1c292a051c99c..23cd185db4436 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt @@ -4,8 +4,8 @@ import com.unciv.scripting.AutocompleteResults import com.unciv.scripting.reflection.FunctionDispatcher import com.unciv.scripting.reflection.Reflection import com.unciv.scripting.utils.ScriptingDebugParameters -import com.unciv.scripting.utils.stringifyException -import com.unciv.scripting.utils.TokenizingJson +import com.unciv.scripting.serialization.TokenizingJson +import com.unciv.ui.utils.stringifyException import kotlin.random.Random import kotlinx.serialization.Serializable import kotlinx.serialization.encodeToString @@ -71,7 +71,7 @@ data class ScriptingPacket( * @param flag Flag type to check for. * @return Whether the given flag is present in this packet's flags field. */ - fun hasFlag(flag: ScriptingProtocol.KnownFlag) = flag.value in flags + fun hasFlag(flag: ScriptingProtocol.KnownFlag) = flag.name in flags } @@ -93,13 +93,11 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = * * The flag field requires strings, so currently the .value property is usually used when constructing and working with scripting packets. * - * @property value Serialized string value of this flag. + * @property name Serialized string value of this flag. */ - enum class KnownFlag(val value: String) { - PassMic("PassMic"), - //TODO: Would getting rid of the string value work? I think I may have decided that having KotlinX Serialization implicitly coerce enums to/from strings would be worse than explicitly accessing the string even if raw enums do work. - // Okay, yeah, these should already have `.name`, right? - Exception("Exception") + enum class KnownFlag { + PassMic, + Exception } companion object { @@ -169,19 +167,19 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = "motd", makeUniqueId(), JsonNull, - listOf(KnownFlag.PassMic.value) + listOf(KnownFlag.PassMic.name) ) fun autocomplete(command: String, cursorPos: Int?) = ScriptingPacket( "autocomplete", makeUniqueId(), JsonObject(mapOf("command" to JsonPrimitive(command), "cursorpos" to JsonPrimitive(cursorPos))), - listOf(KnownFlag.PassMic.value) + listOf(KnownFlag.PassMic.name) ) fun exec(command: String) = ScriptingPacket( "exec", makeUniqueId(), JsonPrimitive(command), - listOf(KnownFlag.PassMic.value) + listOf(KnownFlag.PassMic.name) ) fun terminate() = ScriptingPacket( "terminate", @@ -283,7 +281,7 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = ) } catch (e: Exception) { responseData = JsonPrimitive(e.stringifyException()) - responseFlags.add(KnownFlag.Exception.value) + responseFlags.add(KnownFlag.Exception.name) } } "assign" -> { @@ -296,7 +294,7 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = ) } catch (e: Exception) { responseData = JsonPrimitive(e.stringifyException()) - responseFlags.add(KnownFlag.Exception.value) + responseFlags.add(KnownFlag.Exception.name) } } "delete" -> { @@ -308,7 +306,7 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = ) } catch (e: Exception) { responseData = JsonPrimitive(e.stringifyException()) - responseFlags.add(KnownFlag.Exception.value) + responseFlags.add(KnownFlag.Exception.name) } } "dir" -> { @@ -321,7 +319,7 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = responseData = TokenizingJson.getJsonElement(leaf!!::class.members.map {it.name}) } catch (e: Exception) { responseData = JsonPrimitive(e.stringifyException()) - responseFlags.add(KnownFlag.Exception.value) + responseFlags.add(KnownFlag.Exception.name) } } "keys" -> { @@ -334,7 +332,7 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = responseData = TokenizingJson.getJsonElement((leaf as Map).keys) } catch (e: Exception) { responseData = JsonPrimitive(e.stringifyException()) - responseFlags.add(KnownFlag.Exception.value) + responseFlags.add(KnownFlag.Exception.name) } } "length" -> { @@ -347,16 +345,16 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = try { responseData = TokenizingJson.getJsonElement((leaf as Array<*>).size) // AFAICT avoiding these casts/checks would require reflection. - } catch (e: Exception) { // TODO: Switch these catches to ClassCastException. + } catch (e: ClassCastException) { try { responseData = TokenizingJson.getJsonElement((leaf as Map<*, *>).size) - } catch (e: Exception) { + } catch (e: ClassCastException) { responseData = TokenizingJson.getJsonElement((leaf as Collection<*>).size) } } } catch (e: Exception) { responseData = JsonPrimitive(e.stringifyException()) - responseFlags.add(KnownFlag.Exception.value) + responseFlags.add(KnownFlag.Exception.name) } } "contains" -> { @@ -368,12 +366,12 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = ) try { responseData = TokenizingJson.getJsonElement(packetData.value in (leaf as Map)) - } catch (e: Exception) { + } catch (e: ClassCastException) { responseData = TokenizingJson.getJsonElement(packetData.value in (leaf as Collection)) } } catch (e: Exception) { responseData = JsonPrimitive(e.stringifyException()) - responseFlags.add(KnownFlag.Exception.value) + responseFlags.add(KnownFlag.Exception.name) } } "ismapping" -> { @@ -388,12 +386,12 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = // Ensure same behaviour as "keys" action. IK It's probably/hopefully the same as using the is operator, but I'm not sure. // TODO: Make this and other key operations work with operator overloading. Though Map is already an interface that anything can implement, so maybe not. responseData = TokenizingJson.getJsonElement(true) - } catch (e: Exception) { + } catch (e: ClassCastException) { responseData = TokenizingJson.getJsonElement(false) } } catch (e: Exception) { responseData = JsonPrimitive(e.stringifyException()) - responseFlags.add(KnownFlag.Exception.value) + responseFlags.add(KnownFlag.Exception.name) } } "callable" -> { @@ -406,7 +404,7 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = responseData = TokenizingJson.getJsonElement(leaf is FunctionDispatcher) } catch (e: Exception) { responseData = JsonPrimitive(e.stringifyException()) - responseFlags.add(KnownFlag.Exception.value) + responseFlags.add(KnownFlag.Exception.name) } } "args" -> { @@ -426,7 +424,7 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = ) } catch (e: Exception) { responseData = JsonPrimitive(e.stringifyException()) - responseFlags.add(KnownFlag.Exception.value) + responseFlags.add(KnownFlag.Exception.name) } } "docstring" -> { @@ -439,7 +437,7 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = responseData = TokenizingJson.getJsonElement(leaf.toString()) } catch (e: Exception) { responseData = JsonPrimitive(e.stringifyException()) - responseFlags.add(KnownFlag.Exception.value) + responseFlags.add(KnownFlag.Exception.name) } } else -> { diff --git a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt index 3902e424fe1d0..bfeff2d7ac070 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt @@ -1,15 +1,14 @@ package com.unciv.scripting.protocol import com.unciv.scripting.AutocompleteResults -import com.unciv.scripting.ScriptingBackend -import com.unciv.scripting.api.ScriptingScope +import com.unciv.scripting.ScriptingImplementation import com.unciv.scripting.utils.ScriptingDebugParameters //import com.unciv.scripting.protocol.ScriptingPacket //import com.unciv.scripting.protocol.ScriptingProtocol -abstract class ScriptingReplManager(val scriptingScope: ScriptingScope, val blackbox: Blackbox): ScriptingBackend { +abstract class ScriptingReplManager(val scope: Any, val blackbox: Blackbox): ScriptingImplementation { // //Thus, separate partly as a semantic distinction. ScriptingBackend is designed mostly to interact with ScriptingState and (indirectly, through ScriptingState) ConsoleScreen by presenting a clean interface to shallower classes in the call stack. This class is designed to do the opposite, and keep all the code for wrapping the interfaces of the deeper and more complicated ScriptingProtocol and Blackbox classes in one place. @@ -19,7 +18,7 @@ abstract class ScriptingReplManager(val scriptingScope: ScriptingScope, val blac /** * REPL manager that sends and receives only raw code and prints raw strings with a black box. Allows interacting with an external script interpreter, but not suitable for exposing Kotlin-side API in external scripts. */ -class ScriptingRawReplManager(scriptingScope: ScriptingScope, blackbox: Blackbox): ScriptingReplManager(scriptingScope, blackbox) { +class ScriptingRawReplManager(scope: Any, blackbox: Blackbox): ScriptingReplManager(scope, blackbox) { override fun motd(): String { return "${exec("motd()\n")}\n" @@ -47,9 +46,7 @@ class ScriptingRawReplManager(scriptingScope: ScriptingScope, blackbox: Blackbox /** * REPL manager that uses the IPC protocol defined in ScriptingProtocol.kt to communicate with a black box. Suitable for presenting arbitrary access to Kotlin/JVM state to scripting APIs. See Module.md for a detailed description of the REPL loop. */ -class ScriptingProtocolReplManager(scriptingScope: ScriptingScope, blackbox: Blackbox): ScriptingReplManager(scriptingScope, blackbox) { - -// TODO: scriptingScope can be an Any. Hm. "scriptingScope" is more readable within Unciv, but Any communicates a cleaner design and cleaner design constraints. Hm. That means "scriptingScope" communicates the wrong thing with its readability. It's called "scope" in ScriptingProtocol, which should be plenty clear enough. +class ScriptingProtocolReplManager(scope: Any, blackbox: Blackbox): ScriptingReplManager(scope, blackbox) { /** * ScriptingProtocol puts references to pre-tokenized returned objects in here. @@ -61,12 +58,13 @@ class ScriptingProtocolReplManager(scriptingScope: ScriptingScope, blackbox: Bla */ val instanceSaver = mutableListOf() - val scriptingProtocol = ScriptingProtocol(scriptingScope, instanceSaver = instanceSaver) + val scriptingProtocol = ScriptingProtocol(scope, instanceSaver = instanceSaver) //TODO: Doc fun getRequestResponse(packetToSend: ScriptingPacket, enforceValidity: Boolean = true, execLoop: () -> Unit = fun(){}): ScriptingPacket { // Please update the specifications in Module.md if you change the basic structure of this REPL loop. - if (ScriptingDebugParameters.printScriptingPacketsForDebug) println("\nSending: ${packetToSend}") // TODO: Move this to ScriptingProtocol? + if (ScriptingDebugParameters.printScriptingPacketsForDebug) println("\nSending: ${packetToSend}") + // TODO: Move this to ScriptingProtocol? blackbox.write(packetToSend.toJson() + "\n") execLoop() val response = ScriptingPacket.fromJson(blackbox.read(block=true)) @@ -101,7 +99,7 @@ class ScriptingProtocolReplManager(scriptingScope: ScriptingScope, blackbox: Bla return ScriptingProtocol.parseActionResponses.motd( getRequestResponse( ScriptingProtocol.makeActionRequests.motd(), - execLoop = { foreignExecLoop() } //TODO: Replace with reflective method reference? + execLoop = ::foreignExecLoop ) ) } @@ -110,7 +108,7 @@ class ScriptingProtocolReplManager(scriptingScope: ScriptingScope, blackbox: Bla return ScriptingProtocol.parseActionResponses.autocomplete( getRequestResponse( ScriptingProtocol.makeActionRequests.autocomplete(command, cursorPos), - execLoop = { foreignExecLoop() } + execLoop = ::foreignExecLoop ) ) } @@ -122,7 +120,7 @@ class ScriptingProtocolReplManager(scriptingScope: ScriptingScope, blackbox: Bla return ScriptingProtocol.parseActionResponses.exec( getRequestResponse( ScriptingProtocol.makeActionRequests.exec(command), - execLoop = { foreignExecLoop() } + execLoop = ::foreignExecLoop ) ) } diff --git a/core/src/com/unciv/scripting/reflection/FunctionDispatcher.kt b/core/src/com/unciv/scripting/reflection/FunctionDispatcher.kt index 2d169da612168..eec27e7541f80 100644 --- a/core/src/com/unciv/scripting/reflection/FunctionDispatcher.kt +++ b/core/src/com/unciv/scripting/reflection/FunctionDispatcher.kt @@ -147,7 +147,7 @@ open class FunctionDispatcher( * @return The result from dispatching the given arguments to the function definition with a compatible signature. * @throws IllegalArgumentException If no compatible signature was found, or if more than one compatible signature was found. */ - open fun call(arguments: Array): Any? { + open fun call(arguments: Array): Any? { // TODO: Let return be typed. // KCallable's .call() takes varargs instead of an array object. But spreads are expensive, so I'm not doing that. // To test from Python: // gameInfo.civilizations.add(1, civInfo) @@ -160,7 +160,7 @@ open class FunctionDispatcher( // KCallables that don't match the call arguments should only have as many parameter KTypes saved as it took to find out they don't match. val matches = getMatchingCallables(arguments, paramKtypeAppends=callableparamktypes) var match: KCallable? = null - if (matches.size < 1) { + if (matches.isEmpty()) { throw IllegalArgumentException("No matching signatures found for calling ${nounifyFunctions()} with given arguments: (${arguments.map {if (it == null) "null" else it::class?.simpleName ?: "null"}.joinToString(", ")})") //FIXME: A lot of non-null assertions and null checks (not here, but generally in the codebase) can probably be replaced with safe calls. } diff --git a/core/src/com/unciv/scripting/reflection/Reflection.kt b/core/src/com/unciv/scripting/reflection/Reflection.kt index 051e4ba1cc0a2..853501a60ee94 100644 --- a/core/src/com/unciv/scripting/reflection/Reflection.kt +++ b/core/src/com/unciv/scripting/reflection/Reflection.kt @@ -1,6 +1,6 @@ package com.unciv.scripting.reflection -import com.unciv.scripting.utils.TokenizingJson +import com.unciv.scripting.serialization.TokenizingJson import kotlin.collections.ArrayList import kotlinx.serialization.Serializable import kotlin.reflect.* @@ -71,7 +71,7 @@ object Reflection { } - fun readInstanceItem(instance: Any, keyOrIndex: Any): Any? { + fun readInstanceItem(instance: Any, keyOrIndex: Any): Any? { // TODO: Unify type param/casting behaviour across this, readInstanceProperty, and FunctionDispatcher.call. // TODO: Make this work with operator overloading. Though Map is already an interface that anything can implement, so maybe not. if (keyOrIndex is Int) { return try { (instance as List)[keyOrIndex] } @@ -249,20 +249,20 @@ object Reflection { var obj: Any? = instance for (element in path) { try { - when (element.type) { // TODO: Use as expression? + obj = when (element.type) { PathElementType.Property -> { try { - obj = readInstanceProperty(obj!!, element.name) + readInstanceProperty(obj!!, element.name) // Not explicitly typing the function call makes it always fail an implicit cast or something. // TODO: Consider a LBYL instead of AFP here. } catch (e: ClassCastException) { - obj = makeInstanceMethodDispatcher( + makeInstanceMethodDispatcher( obj!!, element.name ) } } PathElementType.Key -> { - obj = readInstanceItem( + readInstanceItem( obj!!, if (element.doEval) evalKotlinString(instance!!, element.name)!! @@ -275,19 +275,30 @@ object Reflection { // object test{operator fun invoke(a:Any?,b:Any?){println("$a $b")}}; test(1,2) // Test in Python: apiHelpers.Jvm.constructorByQualname('com.unciv.UncivGame'). Also do an object for multi-arg testing, I guess? // Maybe TODO: Handle lambdas. E.G. WorldScreen.nextTurnAction. But honestly, it may be better to just expose wrapping non-lambdas. - obj = (obj as FunctionDispatcher).call( + if (obj is FunctionDispatcher) { // Undocumented implicit behaviour: Using the last object means that this should work with explicitly created FunctionDispatcher()s. - ( - if (element.doEval) - splitToplevelExprs(element.name).map { evalKotlinString(instance!!, it) } - else - element.params - ).toTypedArray() - ) + (obj).call( + ( + if (element.doEval) + splitToplevelExprs(element.name).map { evalKotlinString(instance!!, it) } + else + element.params + ).toTypedArray() + ) + } else { + throw UnsupportedOperationException() + resolveInstancePath( // TODO: Test this. + obj, + listOf( + PathElement( + type = PathElementType.Property, + name = "invoke" + ), + element + ) + ) + } } - // else -> { - // throw UnsupportedOperationException("Unknown path element type: ${element.type}") - // } } } catch (e: Exception) { throw IllegalAccessException("Cannot access $element on $obj: $e") @@ -343,9 +354,6 @@ object Reflection { PathElementType.Call -> { throw UnsupportedOperationException("Cannot assign to function call.") } -// else -> { -// throw UnsupportedOperationException("Unknown path element type: ${leafelement.type}") -// } } } @@ -368,9 +376,6 @@ object Reflection { PathElementType.Call -> { throw UnsupportedOperationException("Cannot remove function call.") } -// else -> { -// throw UnsupportedOperationException("Unknown path element type: ${leafelement.type}") -// } } } } diff --git a/core/src/com/unciv/scripting/serialization/TokenizingJson.kt b/core/src/com/unciv/scripting/serialization/TokenizingJson.kt index 6e93b4a3aeac0..540fb40fffc65 100644 --- a/core/src/com/unciv/scripting/serialization/TokenizingJson.kt +++ b/core/src/com/unciv/scripting/serialization/TokenizingJson.kt @@ -1,4 +1,4 @@ -package com.unciv.scripting.utils +package com.unciv.scripting.serialization import kotlinx.serialization.* //import kotlinx.serialization.builtins.serializer @@ -14,8 +14,6 @@ import kotlinx.serialization.json.JsonPrimitive //import kotlinx.serialization.json.decodeFromJsonElement //import kotlinx.serialization.modules.SerializersModule -// TODO: Move to serialization package with InstanceTokenizer.kt - /** * Json serialization that accepts Any?, and converts non-primitive values to string keys using InstanceTokenizer. */ diff --git a/core/src/com/unciv/scripting/utils/ScriptingModApiHelpers.kt b/core/src/com/unciv/scripting/utils/ScriptingModApiHelpers.kt new file mode 100644 index 0000000000000..8a54b02374008 --- /dev/null +++ b/core/src/com/unciv/scripting/utils/ScriptingModApiHelpers.kt @@ -0,0 +1,14 @@ +package com.unciv.scripting.utils + +import com.unciv.scripting.api.ScriptingScope + +class ScriptingModApiHelpers(val scriptingScope: ScriptingScope) { + // var handlerContext: NamedTuple? + // Why not just use a map? String keys will be clearer in scripts than integers anyway. + // Collection that gets replaced with any contextual parameters when running script handlers. E.G. Unit moved, city founded, tech researched, construction finished. +//fun showScriptedChoicePopup(title: String, body: String, options: List, callback: String?) // Actually, no. Popup() seems to work on its own. + // TODO: Mods blacklist, for security threats. + //fun lambdifyScript(code: String ): () -> Unit = fun(){ scriptingScope.uncivGame!!.scriptingState.exec(code); return } // FIXME: Requires awareness of which scriptingState and which backend to use. + //Directly invoking the resulting lambda from a running script will almost certainly break the REPL loop/IPC protocol. + //setTimeout? +} diff --git a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt index 5b0c0aa7e45bd..e4f366058a66c 100644 --- a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt @@ -64,10 +64,10 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un backendsAdders.add("Launch new backend:".toLabel()).padRight(30f).padLeft(20f) for (backendtype in ScriptingBackendType.values()) { var backendadder = backendtype.metadata.displayName.toTextButton() - backendadder.onClick({ - echo(scriptingState.spawnBackend(backendtype)) + backendadder.onClick { + echo(scriptingState.spawnBackend(backendtype).motd) updateRunning() - }) + } backendsAdders.add(backendadder) } backendsScroll = ScrollPane(backendsAdders) @@ -75,7 +75,7 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un backendsAdders.left() val cell_backendsScroll = topBar.add(backendsScroll) - layoutUpdators.add( { cell_backendsScroll.minWidth(stage.width - closeButton.getPrefWidth()) } ) + layoutUpdators.add { cell_backendsScroll.minWidth(stage.width - closeButton.getPrefWidth()) } topBar.add(closeButton) printHistory.left() diff --git a/core/src/com/unciv/ui/utils/ExtensionFunctions.kt b/core/src/com/unciv/ui/utils/ExtensionFunctions.kt index 53ca7763c7eea..532bca0ec438f 100644 --- a/core/src/com/unciv/ui/utils/ExtensionFunctions.kt +++ b/core/src/com/unciv/ui/utils/ExtensionFunctions.kt @@ -15,6 +15,8 @@ import com.unciv.models.translations.tr import java.text.SimpleDateFormat import java.util.* import kotlin.concurrent.thread +import kotlin.math.max +import kotlin.math.min import kotlin.random.Random /** @@ -157,6 +159,7 @@ fun ArrayList.withItem(item:T): ArrayList { return newArrayList } + /** Gets a clone of a [HashSet] with an additional item * * Solves concurrent modification problems - everyone who had a reference to the previous hashSet can keep using it because it hasn't changed @@ -187,6 +190,13 @@ fun HashSet.withoutItem(item:T): HashSet { return newHashSet } +// @param index Integer index to clip to the List's bounds. +// @param extendSize Allow indices that exceed the maximum index in this array by this amount. +// @return Input index clipped to the optionally extended range of the List's indices. +fun List.clipIndexToBounds(index: Int, extendEnd: Int = 0): Int { + return max(0, min(this.size-1+extendEnd, index)) +} + /** Translate a percentage number - e.g. 25 - to the multiplication value - e.g. 1.25f */ fun String.toPercent() = toFloat().toPercent() @@ -290,6 +300,25 @@ fun List.randomWeighted(weights: List, random: Random = Random): T return this.last() } +/** + * @return String of exception preceded by entire stack trace. + */ +fun Exception.stringifyException(): String { + val causes = arrayListOf() + var cause: Throwable? = this + while (cause != null) { + causes.add(cause) + cause = cause.cause + //I swear this is okay to do. + } + return listOf( + "\n", + *this.stackTrace, + "\n", + *causes.asReversed().map { it.toString() }.toTypedArray() + ).joinToString("\n") +} + /** * Turn a TextureRegion into a Pixmap. * From 018c3db20905a02b320ee57728ae6d3b78946538 Mon Sep 17 00:00:00 2001 From: will-ca Date: Thu, 9 Dec 2021 05:02:38 +0000 Subject: [PATCH 63/93] Implement WeakIdentityMap --- .../scripting/enginefiles/python/main.py | 6 +- .../enginefiles/python/unciv_lib/shadow.py | 2 +- .../enginefiles/python/unciv_lib/wrapping.py | 17 ++- .../MapEditingMacros.py | 10 ++ .../assets/scripting/enginefiles/qjs/main.js | 5 +- .../com/unciv/scripting/ScriptingBackend.kt | 4 +- .../src/com/unciv/scripting/ScriptingState.kt | 18 +-- .../scripting/api/ScriptingApiAppHelpers.kt | 1 - .../scripting/api/ScriptingApiJvmHelpers.kt | 4 +- .../scripting/api/ScriptingApiSysHelpers.kt | 1 + .../{utils => api}/ScriptingModApiHelpers.kt | 4 +- .../com/unciv/scripting/api/ScriptingScope.kt | 1 - .../scripting/protocol/ScriptingProtocol.kt | 3 + .../serialization/InstanceTokenizer.kt | 18 ++- .../scripting/utils/WeakIdentityHashmap.kt | 137 ++++++++++++++++++ .../unciv/ui/consolescreen/ConsoleScreen.kt | 4 +- .../com/unciv/ui/utils/ExtensionFunctions.kt | 14 ++ 17 files changed, 207 insertions(+), 42 deletions(-) rename core/src/com/unciv/scripting/{utils => api}/ScriptingModApiHelpers.kt (91%) create mode 100644 core/src/com/unciv/scripting/utils/WeakIdentityHashmap.kt diff --git a/android/assets/scripting/enginefiles/python/main.py b/android/assets/scripting/enginefiles/python/main.py index 0ffc68c71ccdb..c142cdafaafaf 100644 --- a/android/assets/scripting/enginefiles/python/main.py +++ b/android/assets/scripting/enginefiles/python/main.py @@ -4,7 +4,11 @@ # Huh: https://stackoverflow.com/questions/15093663/packaging-linux-binary-in-android-apk -#"""Due to the massive standard library and tbird-party libraries available to Python, due to the similarly heavy footprint of the CPython interpreter, the recommended use cases of this scripting backend are user automation, prototyping, and experimentation or research. For mods, use the JS backend instead.""" + +#"""Due to the massive standard library and third-party libraries available to Python, due to the similarly heavy footprint of the CPython interpreter, the recommended use cases of this scripting backend are user automation, custom tools, prototyping, and experimentation or research. For mods, use the JS backend instead. + +#It is not considered feasible to support scripting by Python on mobile platforms.""" + try: import os diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/shadow.py b/android/assets/scripting/enginefiles/python/unciv_lib/shadow.py index 2e8decb9fa885..4f0d1dd31ee4c 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/shadow.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/shadow.py @@ -1,3 +1,3 @@ -# Implement a class/object that just returns itself for all magic methods. +# TODO: Implement a class/object that just returns itself for all magic methods. diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py index 28f49d23510b8..ee814559baded 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py @@ -3,6 +3,7 @@ from . import ipc, api +# TODO: Definitely CProfile this. class ForeignRequestMethod: """Decorator and descriptor protocol implementation for methods of ForeignObject subclasses that return values from foreign requests.""" @@ -89,6 +90,7 @@ def stringPathList(pathlist): '__lt__', '__le__', '__eq__', # Kinda undefined behaviour for comparison with Kotlin object tokens. Well, tokens are just strings that will always equal themselves, but multiple tokens can refer to the same Kotlin object. `ForeignObject()`s resolve to new tokens, that are currently uniquely generated in InstanceTokenizer.kt, on every `._getvalue_()`, so I think even the same `ForeignObject()` will never equal itself. # Actually, now raises exception when used with tokens, I think. + # Once guaranteed token reuse is implemented in Kotlin, this should work in all cases. '__ne__', '__ge__', '__gt__', @@ -218,12 +220,16 @@ def __init__(self, *, path, use_root=False, root=None, foreignrequester=dummyFor self._attrs._path = (makePathElement(name=path),) if isinstance(path, str) else tuple(path) self._attrs._foreignrequester = foreignrequester def __repr__(self): - return f"{self.__class__.__name__}({self._root}, {stringPathList(self._getpath_())}):{self._getvalue_()}" + return f"{self.__class__.__name__}({repr(self._root)}{', '+stringPathList(self._getpath_()) if self._getpath_() else ''}){':'+repr(self._getvalue_()) if self._getpath_() else ''}" + # TODO: This has become less informative under bind-by-reference. Look at tokenization format too. + def __str__(self): + # TODO: Maybe also just show _getvalue_() for __str__? + return f"<{repr(self._getvalue_())}>" def _clone_(self, **kwargs): return self.__class__(**{'path': self._path, 'use_root': self._use_root, 'root': self._root, 'foreignrequester': self._foreignrequester, **kwargs}) def _ipcjson_(self): return self._getvalue_() - def _getpath_(self): + def _getpath_(self): # FIXME: Slow and unncessary? return tuple(self._path) def _bakereal_(self): assert not self._isbaked @@ -251,7 +257,7 @@ def __getitem__(self, key, *, do_bake=True): item._bakereal_() return item # Indexing from end with negative numbers is not supported. - # Mostly a complexity choice. Matching Kotlin semantics is better than doing an extra IPC call. + # Mostly a complexity choice. Matching Kotlin semantics is better than translating with an extra IPC call for length. def __iter__(self): try: return iter(self.keys()) @@ -273,11 +279,12 @@ def _setvalue_(self, value): return self._setvalueraw_(value) def __call__(self, *args): result = self._clone_(path=(*self._getpath_(), makePathElement(ttype='Call', params=args))) + # Care must be taken that the resulting ForeignObject never has ._getvalueraw_ called more than once, including by __repr__ or __str__, as resolving it means actually running the call in Kotlin/the JVM. This means it must not be able to produce children with the same 'Call' element in their paths either. if BIND_BY_REFERENCE: - result._bakereal_() + result._bakereal_() # Resolve the foreign value right away and clear path with bind-by-reference, so future accesses don't cause IPC actions. return result else: - return result._getvalue_() + return result._getvalue_() # Also resolve the foreign value right away with bind-by-path, and discard the ForeignObject. @ForeignRequestMethod def _getvalueraw_(self): # Should never be called except for by _getvalue_. diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py index da09bea32f4bf..ba5fea13903ec 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py @@ -360,3 +360,13 @@ def makeImageFromTerrainColours(allowedterrains, allow_compute_colours, visualsp requireComputedColours pass# Make a PIL image, but don't mess with the user's filesystem. #I guess inversing the output from this using loadImageColours could be a unit test. + + +def showToolPopup(): + pass + +def showToolboxPopup(): + pass + +def injectButton(actor): + pass diff --git a/android/assets/scripting/enginefiles/qjs/main.js b/android/assets/scripting/enginefiles/qjs/main.js index fe19633498511..8b85e7ca2ceb8 100644 --- a/android/assets/scripting/enginefiles/qjs/main.js +++ b/android/assets/scripting/enginefiles/qjs/main.js @@ -1,10 +1,13 @@ -`The recommended use case of this backend is for modding. For user automation, debug, prototyping, or experimentation, the Python backend may provide more features.` +`The recommended use case of the JS backend is for modding. For user automation, debug, custom tools, prototyping, or experimentation, the Python backend may provide more features. + +However, JS is the only scripting backend planned to be supported on mobile platforms.` function motd() { return "\nThis backend is HIGHLY EXPERIMENTAL. It does not implement any API bindings yet, and it may not be stable. Use it at your own risk!\n\n" } + // So... cashapp/zipline is clearly the best JS library to use for this (which in turn means that embedded QuickJS, and not Webview V8 or anything will indeed be the engine). // Maybe LiquidCore? But I assume that's way heavier. diff --git a/core/src/com/unciv/scripting/ScriptingBackend.kt b/core/src/com/unciv/scripting/ScriptingBackend.kt index 0b1165896cdba..64583d6a1fcce 100644 --- a/core/src/com/unciv/scripting/ScriptingBackend.kt +++ b/core/src/com/unciv/scripting/ScriptingBackend.kt @@ -40,7 +40,7 @@ abstract class ScriptingBackend_metadata { /** * @return A new instance of the parent class of which this object is a companion. */ - abstract fun new(scriptingScope: ScriptingScope): ScriptingBackendBase + abstract fun new(scriptingScope: ScriptingScope): ScriptingBackendBase // TODO: Um, class references are totally a thing, and probably distinct from KClass, right? abstract val displayName: String val syntaxHighlighting: SyntaxHighlighter? = null } @@ -371,7 +371,7 @@ class ReflectiveScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacke if (workingpath.any { it.type == Reflection.PathElementType.Call }) { return AutocompleteResults(listOf(), true, "No autocomplete available for function calls.") } - val leafname = if (workingpath.size > 0) workingpath[workingpath.size - 1].name else "" + val leafname = if (workingpath.isNotEmpty()) workingpath[workingpath.size - 1].name else "" val prefix = command.dropLast(leafname.length) val branchobj = Reflection.resolveInstancePath(scriptingScope, workingpath.slice(0..workingpath.size-2)) return AutocompleteResults( diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index 78fd55ff14a4a..18951bb88c963 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -2,9 +2,8 @@ package com.unciv.scripting import com.unciv.scripting.api.ScriptingScope import com.unciv.ui.utils.clipIndexToBounds +import com.unciv.ui.utils.enforceValidIndex import kotlin.collections.ArrayList -import kotlin.math.max -import kotlin.math.min // TODO: Check for places to use Sequences. // Hm. It seems that Sequence performance isn't even a simple question of number of loops, and is also affected by boxed types and who know what else. @@ -18,21 +17,6 @@ import kotlin.math.min // See https://github.com/yairm210/Unciv/pull/5592/commits/a1f51e08ab782ab46bda220e0c4aaae2e8ba21a4 for example of running locking operation in separate thread. -/** - * Make sure an index is valid for this array. - * - * Doing all checks with the same function and error message is probably easier to debug than letting an array access fail at the point of access. - * - * @param index Index to check. - * @throws IndexOutOfBoundsException if given invalid index. - */ -fun ArrayList.enforceValidIndex(index: Int) { - if (index < 0 || this.size <= index) { - throw IndexOutOfBoundsException("Index {index} is out of range of ArrayList().") - } -} - - /** * Self-contained instance of scripting API use. * diff --git a/core/src/com/unciv/scripting/api/ScriptingApiAppHelpers.kt b/core/src/com/unciv/scripting/api/ScriptingApiAppHelpers.kt index 6e09e3b396a96..f8f257ab32c68 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiAppHelpers.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiAppHelpers.kt @@ -9,7 +9,6 @@ import java.io.ByteArrayOutputStream object ScriptingApiAppHelpers { //Debug/dev identity function for both Kotlin and scripts. Check if value survives serialization, force something to be added to ScriptingProtocol.instanceSaver, etc. -// fun echo(obj: Any?) = obj // Redundant with instancesAsInstances. // @param path Path of an internal file as exposed in Gdx.files.internal. // @return The contents of the internal file read as a text string. fun assetFileString(path: String) = Gdx.files.internal(path).readString() diff --git a/core/src/com/unciv/scripting/api/ScriptingApiJvmHelpers.kt b/core/src/com/unciv/scripting/api/ScriptingApiJvmHelpers.kt index 572dec9b7be9a..72cf0052c46da 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiJvmHelpers.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiJvmHelpers.kt @@ -35,7 +35,7 @@ object ScriptingApiJvmHelpers { val kotlinCompanionByQualClass = LazyMap({ qualName: String -> kotlinClassByQualname[qualName]?.companionObjectInstance }, exposeState = exposeStates) - val functionByQualClassAndMethodName = LazyMap({ jclassQualname: String -> + val functionByQualClassAndMethodName = LazyMap({ jclassQualname: String -> // TODO: Rename, remove "Method". val cls = Class.forName(jclassQualname) LazyMap({ methodName: String -> makeFunctionDispatcher(cls.getDeclaredMethods().asSequence().filter { it.name == methodName }.map { it.kotlinFunction }.toList() as List>) }, exposeState = exposeStates) // Could initialize the second LazyMap here by accessing for all names— Only benefit would be for autocomplete, at higher first-call time and memory use, though. @@ -59,5 +59,7 @@ object ScriptingApiJvmHelpers { // fun arrayOf(elements: Collection): Array<*> = elements.toTypedArray() // fun arrayOfAny(elements: Collection): Array = elements.toTypedArray() // fun arrayOfString(elements: Collection): Array = elements.toTypedArray() + + //TODO: Heavily overloaded toList or some such converters for Arrays, Sets, Sequences, Iterators, etc. } diff --git a/core/src/com/unciv/scripting/api/ScriptingApiSysHelpers.kt b/core/src/com/unciv/scripting/api/ScriptingApiSysHelpers.kt index 7f9a04f43b9da..585cdcc4854a6 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiSysHelpers.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiSysHelpers.kt @@ -10,4 +10,5 @@ object ScriptingApiSysHelpers { //Better than scripts potentially doing it themselves. In Python, for example, a way to do this would involve setting up an invisible TKinter window. Gdx.app.clipboard.contents = value.toString() } + // Native file chooser could be cool too (E.G. modded map editing tools), but doesn't look to be simple across all platforms. } diff --git a/core/src/com/unciv/scripting/utils/ScriptingModApiHelpers.kt b/core/src/com/unciv/scripting/api/ScriptingModApiHelpers.kt similarity index 91% rename from core/src/com/unciv/scripting/utils/ScriptingModApiHelpers.kt rename to core/src/com/unciv/scripting/api/ScriptingModApiHelpers.kt index 8a54b02374008..c60c8593d37c7 100644 --- a/core/src/com/unciv/scripting/utils/ScriptingModApiHelpers.kt +++ b/core/src/com/unciv/scripting/api/ScriptingModApiHelpers.kt @@ -1,6 +1,4 @@ -package com.unciv.scripting.utils - -import com.unciv.scripting.api.ScriptingScope +package com.unciv.scripting.api class ScriptingModApiHelpers(val scriptingScope: ScriptingScope) { // var handlerContext: NamedTuple? diff --git a/core/src/com/unciv/scripting/api/ScriptingScope.kt b/core/src/com/unciv/scripting/api/ScriptingScope.kt index ef3056ed1c109..c05277440a60d 100644 --- a/core/src/com/unciv/scripting/api/ScriptingScope.kt +++ b/core/src/com/unciv/scripting/api/ScriptingScope.kt @@ -3,7 +3,6 @@ package com.unciv.scripting.api import com.unciv.UncivGame import com.unciv.logic.GameInfo import com.unciv.logic.civilization.CivilizationInfo -import com.unciv.scripting.utils.ScriptingModApiHelpers import com.unciv.ui.mapeditor.MapEditorScreen import com.unciv.ui.worldscreen.WorldScreen diff --git a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt index 23cd185db4436..abfd9ca20c583 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt @@ -29,6 +29,9 @@ import java.util.UUID // Use in scripting backend, E.G. wrapping.py +// TODO: Profile script execution. + + /** * Implementation of IPC packet specified in Module.md. * diff --git a/core/src/com/unciv/scripting/serialization/InstanceTokenizer.kt b/core/src/com/unciv/scripting/serialization/InstanceTokenizer.kt index 8c9798a22dec8..abed66e724203 100644 --- a/core/src/com/unciv/scripting/serialization/InstanceTokenizer.kt +++ b/core/src/com/unciv/scripting/serialization/InstanceTokenizer.kt @@ -2,7 +2,7 @@ package com.unciv.scripting.serialization import com.unciv.scripting.ScriptingConstants //import kotlin.math.min -import java.lang.ref.WeakReference +import java.lang.ref.WeakReference // Could use SoftReferences— Would seem convenient, but probably lead to mysterious bugs in scripts. import java.util.UUID @@ -20,7 +20,9 @@ object InstanceTokenizer { /** * Weakmap of currently known token strings to WeakReferences of the Kotlin/JVM instances they represent. */ - private val instances = mutableMapOf>() + private val instancesByTokens = mutableMapOf>() + +// private val tokensByInstances = WeakHashMap // private val instanceHashes = mutableMapOf>>()>>() // TODO: See note under clean(). @@ -34,6 +36,7 @@ object InstanceTokenizer { private val tokenPrefix //I considered other structures like integer IDs, and objects with a particular structure and key. But semantically and syntactically, immutable and often-singleton/interned strings are really the best JSON representations of completely opaque Kotlin/JVM objects. get() = ScriptingConstants.apiConstants.kotlinInstanceTokenPrefix + /** * Length to clip generated token strings to. Here in case token string generation uses the instance's .toString(), which it currently does. */ @@ -56,7 +59,7 @@ object InstanceTokenizer { stringified = try { // Because this can be overridden, it can fail. E.G. MapUnit.toString() depends on a lateinit. value.toString() } catch (e: Exception) { - "?" // TODO: Use exception text? + "?" // Use exception text? } if (stringified.length > tokenMaxLength) { stringified = stringified.slice(0..tokenMaxLength -4) + "..." @@ -80,13 +83,14 @@ object InstanceTokenizer { //TODO: Probably keep another map of instance hashes to weakrefs and their existing token? The hashes will have to have counts instead of just containment, since otherwise a hash collision would cause the earlier token to become inaccessible. // Can I use WeakReferences as keys? Do they implement hash and equality? Huh, WeakHashMap is a thing here too. Cool. Auto cleaning. And could check against for cleaning the other map. Hm. Need to use identity instead of equality comparison. Oh wow. They have "WeakIdentityHashMap", the maniacs. val badtokens = mutableListOf() - for ((t, o) in instances) { + // TOOD: Print messages on major size milestone changes. + for ((t, o) in instancesByTokens) { if (o.get() == null) { badtokens.add(t) } } for (t in badtokens) { - instances.remove(t) + instancesByTokens.remove(t) } } @@ -97,7 +101,7 @@ object InstanceTokenizer { fun getToken(obj: Any?): String { // TOOD: Switch to Any, since null will just be cleaned anyway? clean() val token = tokenFromInstance(obj) - instances[token] = WeakReference(obj) + instancesByTokens[token] = WeakReference(obj) return token } @@ -113,7 +117,7 @@ object InstanceTokenizer { fun getReal(token: Any?): Any? { clean() if (isToken(token)) { - return instances[token]!!.get()// TODO: Add another non-null assertion here? Unknown tokens and expired tokens are only a cleaning cycle apart, which seems race condition-y. + return instancesByTokens[token]!!.get()// TODO: Add another non-null assertion here? Unknown tokens and expired tokens are only a cleaning cycle apart, which seems race condition-y. // TODO: Helpful exception message for invalid tokens. } else { return token diff --git a/core/src/com/unciv/scripting/utils/WeakIdentityHashmap.kt b/core/src/com/unciv/scripting/utils/WeakIdentityHashmap.kt new file mode 100644 index 0000000000000..410150223030a --- /dev/null +++ b/core/src/com/unciv/scripting/utils/WeakIdentityHashmap.kt @@ -0,0 +1,137 @@ +package com.unciv.scripting.utils + +import java.lang.ref.WeakReference + + +// TODO: These will need tests. + + +// WeakReference with equality comparison by referential equality between its and other WeakIdentityMapKey()s' referents. + +// Has value equality with itself. +// Has value equality with any other WeakIdentityMapKey()s that points to the same living referent. +// If referent has been garbage collected, points to null, and does not have value equality to any other WeakIdentityMapKey()s. +// Does not have value equality with anything else. + +// Should have valid hashCode behaviour given this. + +//// Has value equality with its own keyPlaceholder property. +class WeakIdentityMapKey(referent: T): WeakReference(referent) { + // Two states: + // 1. Behaviour with living referent, such as when added to Map or used for containment check or index access. Should equal any other WeakIdentityMapKey with the same referent. + // 3. Behaviour with with dead referent, such as when removed from map. Referent has become null, so can't use that. Should equal itself to still be removable, and should not equal anything else. + // +// // Class used to remove a WeakIdentityMapKey() from a MutableMap() when its referent . +// // Has value equality to the supplied WeakIdentityReference<*>. +// // Has value equality to itself. +// // Does not have value equality to anything else. +// +// // Has valid hashCode behaviour given this. +// class BrokenWeakIdentityReference(val weakIdentityReference: WeakIdentityReference<*>) { +// override fun hashCode() = weakIdentityReference.hashCode() +// // Fulfills reflexive, symmetric, consistent, and null inequality, contracts. +// // Breaks transitivity contract, as +// override fun equals(other: Any?): Boolean { +// return this === other || (other is WeakIdentityReference<*> && weakIdentityReference === other) +// } +// } +// val keyPlaceholder = BrokenWeakIdentityReference(this) + val hashCode = System.identityHashCode(referent) // Keep hash immutable, and keep this in the same Map bucket. + override fun hashCode() = hashCode + override fun equals(other: Any?): Boolean { + // Fulfills reflexive, symmetric, consistent, null inequality, and transitivity contracts. + return if (other === this) { + true // Makes sure removal can always be done by itself. + } else if (other is WeakIdentityMapKey<*>) { + val resolved = get() + if (resolved == null) + false + else + resolved === other.get() // Allows containment to be checked and access to be done by new WeakIdentityMapKey()s with the same referent. + } else { + false + } + // Originally, I wanted to have it equal the referent. But there's no way to make that symmetric/commutative, is there? + //// And it would make BrokenWeakIdentityReference.equals() less transitive. +//// is BrokenWeakIdentityReference -> other.equals(this) // Allows broken references to be removed as keys— Or actually, given the reflexive equality contract, why don't I just use itself? + //return get() === if (other is WeakIdentityReference<*>) other.get() else other + } +} + +// Map-like class that uses special WeakReferences as keys. + +// For now, clean() must be called manually. +class WeakIdentityMap(): MutableMap { + private val backingMap = mutableMapOf, V>() + override val entries get() = throw NotImplementedError() // backingMap.entries // TODO: Make IdentityWeakReference transparent. + override val keys get() = throw NotImplementedError() //backingMap.keys.map { it.get() } + override val size get() = backingMap.size + override val values get() = backingMap.values // Does exposing state make any sense? + override fun clear() = backingMap.clear() + override fun containsKey(key: K) = backingMap.containsKey(WeakIdentityMapKey(key)) + override fun containsValue(value: V) = backingMap.containsValue(value) + override fun get(key: K) = backingMap.get(WeakIdentityMapKey(key)) + override fun isEmpty() = backingMap.isEmpty() + override fun put(key: K, value: V) = backingMap.put(WeakIdentityMapKey(key), value) + override fun putAll(from: Map) = backingMap.putAll(from.entries.associate { WeakIdentityMapKey(it.key) to it.value }) + override fun remove(key: K) = backingMap.remove(WeakIdentityMapKey(key)) + // Free up all invalid + fun clean() { + val badkeys = mutableListOf>() + for (k in backingMap.keys) { + val referent = k.get() + if (referent == null) + badkeys.add(k) + } + for (k in badkeys) { + backingMap.remove(k) + } + } + override fun toString() = "{${backingMap.entries.joinToString(", ") { "${it.key.get()}=${it.value}" }}}" +} + +fun weakIdentityMapOf(vararg pairs: Pair): WeakIdentityMap { + val map = WeakIdentityMap() + map.putAll(pairs) + return map +} + +//mapOf(mutableListOf(1,2) to 5)[mutableListOf(1,2)] +//mapOf(WeakReference(mutableListOf(1,2)) to 5)[WeakReference(mutableListOf(1,2))] +//var l=mutableListOf(1,2); mapOf(WeakReference(l) to 5)[WeakReference(l)] + +//var a=mutableListOf(1,2); var b=mutableListOf(1); var m=mutableMapOf(a to 1, b to 2) +//m[listOf(1,2)] +//m[listOf(1)] +//b.add(2) + +//var a=mutableListOf(1,2); var b=mutableListOf(1); var m=weakIdentityMapOf(a to 1, b to 2) +//m[a] // 1 +//m[b] // 2 +//m[listOf(1,2)] // null +//m[listOf(1)] // null +//m[a.toList()] // null +//m[b.toList()] // null +//b.add(2) +//m[a] // 1 +//m[b] // 2 +//m.remove(a) +//m[a] // null +//m[b] // 2 +//m.remove(b) +//m[a] // null +//m[b] // null + +//object o {var a=mutableListOf(1,2); var b=mutableListOf(1); var c=mutableListOf(1,2)}; var m=weakIdentityMapOf(o.a to 1, o.b to 2, o.c to 3) +//listOf(m[o.a], m[o.b], m[o.c]) // 1,2,3 +//var c = WeakIdentityMapKey(o.c) +//o.c = mutableListOf() +//for (i in 1..2000) {(1..i).map{it to object{var x = i; val y=it}}.associate{it}} // GC cannot be forced on JVM, apparently, but this should be fairly strong hint. +//val x = WeakReference(object{val x=5}); while (x.get() != null){System.gc()} // Stronger hint yet. +////GC will never run no matter what in Kotlin REPL, seemingly. +//println(c.get()) // null +//println(m) +//println(m.size) // 3 +//m.clean() +//println(m) +//println(m.size) // 2 diff --git a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt index e4f366058a66c..75ecdc998fde8 100644 --- a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt @@ -22,7 +22,7 @@ import kotlin.math.min //"I understand and wish to continue." // Probably grey this out for five seconds. //"Get me out of here!" -class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Unit): BaseScreen() { +class ConsoleScreen(val scriptingState: ScriptingState, var closeAction: () -> Unit): BaseScreen() { private val layoutTable: Table = Table() @@ -244,7 +244,7 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un } } - private fun navigateHistory(increment:Int) { + private fun navigateHistory(increment: Int) { setText(scriptingState.navigateHistory(increment), SetTextCursorMode.End) } diff --git a/core/src/com/unciv/ui/utils/ExtensionFunctions.kt b/core/src/com/unciv/ui/utils/ExtensionFunctions.kt index 532bca0ec438f..cb1653796765d 100644 --- a/core/src/com/unciv/ui/utils/ExtensionFunctions.kt +++ b/core/src/com/unciv/ui/utils/ExtensionFunctions.kt @@ -197,6 +197,20 @@ fun List.clipIndexToBounds(index: Int, extendEnd: Int = 0): Int { return max(0, min(this.size-1+extendEnd, index)) } +/** + * Make sure an index is valid for this List. + * + * Doing all checks with the same function and error message is probably easier to debug than letting an array access fail at the point of access. + * + * @param index Index to check. + * @throws IndexOutOfBoundsException If given invalid index. + */ +fun List.enforceValidIndex(index: Int) { + if (index < 0 || this.size <= index) { + throw IndexOutOfBoundsException("Index {index} is out of range of ${this::class.simpleName}().") + } +} + /** Translate a percentage number - e.g. 25 - to the multiplication value - e.g. 1.25f */ fun String.toPercent() = toFloat().toPercent() From 69399feaff4cbf70af1f85f929ab70a5dda36a28 Mon Sep 17 00:00:00 2001 From: will-ca Date: Thu, 9 Dec 2021 05:04:47 +0000 Subject: [PATCH 64/93] Implement token reuse via weak references to try to fix long-standing memory leak and performance slowdown. --- .../scripting/protocol/ScriptingProtocol.kt | 2 +- .../protocol/ScriptingReplManager.kt | 8 ++-- .../serialization/InstanceTokenizer.kt | 37 +++++++++++++++++-- .../utils/ScriptingDebugParameters.kt | 5 ++- .../scripting/utils/WeakIdentityHashmap.kt | 33 +++++------------ 5 files changed, 51 insertions(+), 34 deletions(-) diff --git a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt index abfd9ca20c583..8deb715222cc4 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt @@ -242,7 +242,7 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = val value = getRealOrNull(packetData["value"]) init { - if (ScriptingDebugParameters.printScriptingAccessForDebug) printDebug() + if (ScriptingDebugParameters.printAccessForDebug) printDebug() } private fun getRealOrNull(jsonElement: JsonElement?): Any? { diff --git a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt index bfeff2d7ac070..15e57ee91f397 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt @@ -63,12 +63,12 @@ class ScriptingProtocolReplManager(scope: Any, blackbox: Blackbox): ScriptingRep //TODO: Doc fun getRequestResponse(packetToSend: ScriptingPacket, enforceValidity: Boolean = true, execLoop: () -> Unit = fun(){}): ScriptingPacket { // Please update the specifications in Module.md if you change the basic structure of this REPL loop. - if (ScriptingDebugParameters.printScriptingPacketsForDebug) println("\nSending: ${packetToSend}") + if (ScriptingDebugParameters.printPacketsForDebug) println("\nSending: ${packetToSend}") // TODO: Move this to ScriptingProtocol? blackbox.write(packetToSend.toJson() + "\n") execLoop() val response = ScriptingPacket.fromJson(blackbox.read(block=true)) - if (ScriptingDebugParameters.printScriptingPacketsForDebug) println("\nReceived: ${response}") + if (ScriptingDebugParameters.printPacketsForDebug) println("\nReceived: ${response}") if (enforceValidity) { ScriptingProtocol.enforceIsResponse(packetToSend, response) } @@ -83,10 +83,10 @@ class ScriptingProtocolReplManager(scope: Any, blackbox: Blackbox): ScriptingRep fun foreignExecLoop() { while (true) { val request = ScriptingPacket.fromJson(blackbox.read(block=true)) - if (ScriptingDebugParameters.printScriptingPacketsForDebug) println("\nReceived: ${request}") + if (ScriptingDebugParameters.printPacketsForDebug) println("\nReceived: ${request}") if (request.action != null) { val response = scriptingProtocol.makeActionResponse(request) - if (ScriptingDebugParameters.printScriptingPacketsForDebug) println("\nSending: ${response}") + if (ScriptingDebugParameters.printPacketsForDebug) println("\nSending: ${response}") blackbox.write(response.toJson() + "\n") } if (request.hasFlag(ScriptingProtocol.KnownFlag.PassMic)) { diff --git a/core/src/com/unciv/scripting/serialization/InstanceTokenizer.kt b/core/src/com/unciv/scripting/serialization/InstanceTokenizer.kt index abed66e724203..35225599732e1 100644 --- a/core/src/com/unciv/scripting/serialization/InstanceTokenizer.kt +++ b/core/src/com/unciv/scripting/serialization/InstanceTokenizer.kt @@ -1,9 +1,14 @@ package com.unciv.scripting.serialization import com.unciv.scripting.ScriptingConstants +import com.unciv.scripting.utils.ScriptingDebugParameters +import com.unciv.scripting.utils.WeakIdentityMap //import kotlin.math.min import java.lang.ref.WeakReference // Could use SoftReferences— Would seem convenient, but probably lead to mysterious bugs in scripts. import java.util.UUID +import kotlin.math.floor +import kotlin.math.log +import kotlin.math.pow /** @@ -18,11 +23,19 @@ object InstanceTokenizer { // Could even potentially get rid of `.registeredInstances` completely by automatically registering/reference counting in the JVM and freeing in scripting language destructors. But JS apparently doesn't give any way to control garbage collection, so the risk of memory leaks wouldn't be worth it. /** - * Weakmap of currently known token strings to WeakReferences of the Kotlin/JVM instances they represent. + * Map of currently known token strings to WeakReferences of the Kotlin/JVM instances they represent. */ private val instancesByTokens = mutableMapOf>() -// private val tokensByInstances = WeakHashMap + // Map of WeakReferences of Kotlin/JVM instances to token strings that represent them. + private val tokensByInstances = WeakIdentityMap() + // Without this, running the Python tests twice in a row led to a token count exceeding 32768 (and over 16348 after the first run) as of this comment, and very significant slowdown. + // With it… It's actually only slightly less, and the performance still hurts a lot. + + + // Logarithm and base for the number of known tokens after the last cleaning. Used for printing debug info. + private var lastTokenCountLog: Int = 0 + private const val tokenCountLogBase = 2f // private val instanceHashes = mutableMapOf>>()>>() // TODO: See note under clean(). @@ -55,6 +68,10 @@ object InstanceTokenizer { * @return Token string. */ private fun tokenFromInstance(value: Any?): String { + var token: String? = tokensByInstances[value] // Atomicity. Separating containment check would give the GC a chance to clear the key. + if (token != null) { + return token + } var stringified: String stringified = try { // Because this can be overridden, it can fail. E.G. MapUnit.toString() depends on a lateinit. value.toString() @@ -64,7 +81,9 @@ object InstanceTokenizer { if (stringified.length > tokenMaxLength) { stringified = stringified.slice(0..tokenMaxLength -4) + "..." } - return "$tokenPrefix${System.identityHashCode(value)}:${if (value == null) "null" else value::class.qualifiedName}/${stringified}:${UUID.randomUUID().toString()}" + token = "$tokenPrefix${System.identityHashCode(value)}:${if (value == null) "null" else value::class.qualifiedName}/${stringified}:${UUID.randomUUID().toString()}" + tokensByInstances[value] = token + return token } /** @@ -82,16 +101,26 @@ object InstanceTokenizer { //FIXME (if I become a problem): Because a new unique token is currently generated even if the instance is already tokenized as something else, this will eventually get slower over time if a script makes lots of requests that result in new instance tokens for objects that last a long time (E.G. uncivGame). And since any stored instances should ideally be WeakReferences to prevent garbage collection from being broken for *all* instances, fixing that may not be as simple as checking for existing tokens to reuse them. //TODO: Probably keep another map of instance hashes to weakrefs and their existing token? The hashes will have to have counts instead of just containment, since otherwise a hash collision would cause the earlier token to become inaccessible. // Can I use WeakReferences as keys? Do they implement hash and equality? Huh, WeakHashMap is a thing here too. Cool. Auto cleaning. And could check against for cleaning the other map. Hm. Need to use identity instead of equality comparison. Oh wow. They have "WeakIdentityHashMap", the maniacs. + tokensByInstances.clean() val badtokens = mutableListOf() - // TOOD: Print messages on major size milestone changes. + // TODO: Print messages on major size milestone changes. for ((t, o) in instancesByTokens) { if (o.get() == null) { badtokens.add(t) } } + // TODO: Maybe O(n) cleaning strategy is just not great. for (t in badtokens) { instancesByTokens.remove(t) } + if (ScriptingDebugParameters.printTokenizerMilestones) { + val count = instancesByTokens.size + val current = floor(log(count.toFloat(), tokenCountLogBase)).toInt() + if (current != lastTokenCountLog) { + println("${this::class.simpleName} now contains ${count} tokens.") + } + lastTokenCountLog = current + } } /** diff --git a/core/src/com/unciv/scripting/utils/ScriptingDebugParameters.kt b/core/src/com/unciv/scripting/utils/ScriptingDebugParameters.kt index a7112a549aadd..74966a68f9233 100644 --- a/core/src/com/unciv/scripting/utils/ScriptingDebugParameters.kt +++ b/core/src/com/unciv/scripting/utils/ScriptingDebugParameters.kt @@ -1,6 +1,7 @@ package com.unciv.scripting.utils object ScriptingDebugParameters { - var printScriptingPacketsForDebug = false - var printScriptingAccessForDebug = false + var printPacketsForDebug = false + var printAccessForDebug = false + var printTokenizerMilestones = true } diff --git a/core/src/com/unciv/scripting/utils/WeakIdentityHashmap.kt b/core/src/com/unciv/scripting/utils/WeakIdentityHashmap.kt index 410150223030a..67ee911071dad 100644 --- a/core/src/com/unciv/scripting/utils/WeakIdentityHashmap.kt +++ b/core/src/com/unciv/scripting/utils/WeakIdentityHashmap.kt @@ -20,28 +20,12 @@ class WeakIdentityMapKey(referent: T): WeakReference(referent) { // Two states: // 1. Behaviour with living referent, such as when added to Map or used for containment check or index access. Should equal any other WeakIdentityMapKey with the same referent. // 3. Behaviour with with dead referent, such as when removed from map. Referent has become null, so can't use that. Should equal itself to still be removable, and should not equal anything else. - // -// // Class used to remove a WeakIdentityMapKey() from a MutableMap() when its referent . -// // Has value equality to the supplied WeakIdentityReference<*>. -// // Has value equality to itself. -// // Does not have value equality to anything else. -// -// // Has valid hashCode behaviour given this. -// class BrokenWeakIdentityReference(val weakIdentityReference: WeakIdentityReference<*>) { -// override fun hashCode() = weakIdentityReference.hashCode() -// // Fulfills reflexive, symmetric, consistent, and null inequality, contracts. -// // Breaks transitivity contract, as -// override fun equals(other: Any?): Boolean { -// return this === other || (other is WeakIdentityReference<*> && weakIdentityReference === other) -// } -// } -// val keyPlaceholder = BrokenWeakIdentityReference(this) val hashCode = System.identityHashCode(referent) // Keep hash immutable, and keep this in the same Map bucket. override fun hashCode() = hashCode override fun equals(other: Any?): Boolean { // Fulfills reflexive, symmetric, consistent, null inequality, and transitivity contracts. return if (other === this) { - true // Makes sure removal can always be done by itself. + true // Makes sure Map key removal can always be done by using itself. } else if (other is WeakIdentityMapKey<*>) { val resolved = get() if (resolved == null) @@ -52,9 +36,6 @@ class WeakIdentityMapKey(referent: T): WeakReference(referent) { false } // Originally, I wanted to have it equal the referent. But there's no way to make that symmetric/commutative, is there? - //// And it would make BrokenWeakIdentityReference.equals() less transitive. -//// is BrokenWeakIdentityReference -> other.equals(this) // Allows broken references to be removed as keys— Or actually, given the reflexive equality contract, why don't I just use itself? - //return get() === if (other is WeakIdentityReference<*>) other.get() else other } } @@ -63,10 +44,10 @@ class WeakIdentityMapKey(referent: T): WeakReference(referent) { // For now, clean() must be called manually. class WeakIdentityMap(): MutableMap { private val backingMap = mutableMapOf, V>() - override val entries get() = throw NotImplementedError() // backingMap.entries // TODO: Make IdentityWeakReference transparent. + override val entries get() = throw NotImplementedError() // backingMap.entries override val keys get() = throw NotImplementedError() //backingMap.keys.map { it.get() } override val size get() = backingMap.size - override val values get() = backingMap.values // Does exposing state make any sense? + override val values get() = backingMap.values // Does exposing state make any sense? Meh. It's easier to do than not, TBH. override fun clear() = backingMap.clear() override fun containsKey(key: K) = backingMap.containsKey(WeakIdentityMapKey(key)) override fun containsValue(value: V) = backingMap.containsValue(value) @@ -75,7 +56,9 @@ class WeakIdentityMap(): MutableMap { override fun put(key: K, value: V) = backingMap.put(WeakIdentityMapKey(key), value) override fun putAll(from: Map) = backingMap.putAll(from.entries.associate { WeakIdentityMapKey(it.key) to it.value }) override fun remove(key: K) = backingMap.remove(WeakIdentityMapKey(key)) - // Free up all invalid + // Free up all invalid keys that have been garbage collected. + + // Runs in O(n) time relative to size. fun clean() { val badkeys = mutableListOf>() for (k in backingMap.keys) { @@ -124,8 +107,10 @@ fun weakIdentityMapOf(vararg pairs: Pair): WeakIdentityMap { //object o {var a=mutableListOf(1,2); var b=mutableListOf(1); var c=mutableListOf(1,2)}; var m=weakIdentityMapOf(o.a to 1, o.b to 2, o.c to 3) //listOf(m[o.a], m[o.b], m[o.c]) // 1,2,3 +//listOf(o.a in m, o.b in m, o.c in m) // true, true, true //var c = WeakIdentityMapKey(o.c) //o.c = mutableListOf() +//listOf(o.a.toList() in m, o.b in m, o.c in m) // false, true, false //for (i in 1..2000) {(1..i).map{it to object{var x = i; val y=it}}.associate{it}} // GC cannot be forced on JVM, apparently, but this should be fairly strong hint. //val x = WeakReference(object{val x=5}); while (x.get() != null){System.gc()} // Stronger hint yet. ////GC will never run no matter what in Kotlin REPL, seemingly. @@ -135,3 +120,5 @@ fun weakIdentityMapOf(vararg pairs: Pair): WeakIdentityMap { //m.clean() //println(m) //println(m.size) // 2 + +// TODO: Add test for behaviour with null values. (Should basically never be accessible, and then immediately be cleared, I think.) From f19c7fbe732dc202cc834d1a480d3dc7b566085e Mon Sep 17 00:00:00 2001 From: will-ca Date: Thu, 9 Dec 2021 18:05:05 +0000 Subject: [PATCH 65/93] Stop trying to clean all invalid tokens on every single (de)tokenization. Massive performance improvement. Add higher order API helper helpers. Add reflective access to `const`s. Basic scripted UI example. --- .../scripting/ScriptingEngineConstants.json | 2 + .../enginefiles/python/unciv_lib/wrapping.py | 22 +++-- .../unciv_scripting_examples/EventPopup.py | 40 ++++++++ .../python/unciv_scripting_examples/Tests.py | 9 ++ .../unciv_scripting_examples/TicTacToe.py | 7 +- .../unciv_scripting_examples/__init__.py | 4 +- .../scripting/api/ScriptingApiJvmHelpers.kt | 15 ++- .../scripting/api/ScriptingModApiHelpers.kt | 52 +++++++++- .../com/unciv/scripting/api/ScriptingScope.kt | 2 + .../unciv/scripting/reflection/Reflection.kt | 31 ++++-- .../serialization/InstanceTokenizer.kt | 96 +++++++++++-------- .../utils/ScriptingDebugParameters.kt | 5 +- .../scripting/utils/WeakIdentityHashmap.kt | 26 ++--- .../consolescreen/IConsoleScreenAccessible.kt | 2 +- 14 files changed, 230 insertions(+), 83 deletions(-) create mode 100644 android/assets/scripting/enginefiles/python/unciv_scripting_examples/EventPopup.py diff --git a/android/assets/scripting/ScriptingEngineConstants.json b/android/assets/scripting/ScriptingEngineConstants.json index 3147568a9343e..7868573dbf285 100644 --- a/android/assets/scripting/ScriptingEngineConstants.json +++ b/android/assets/scripting/ScriptingEngineConstants.json @@ -15,12 +15,14 @@ unciv_scripting_examples/ unciv_scripting_examples/__init__.py unciv_scripting_examples/EndTimes.py + unciv_scripting_examples/EventPopup.py unciv_scripting_examples/ExternalPipe.py unciv_scripting_examples/MapEditingMacros.py unciv_scripting_examples/Merfolk.py unciv_scripting_examples/PlayerMacros.py unciv_scripting_examples/ProceduralTechtree.py unciv_scripting_examples/Tests.py + unciv_scripting_examples/TicTacToe.py unciv_scripting_examples/Utils.py unciv_scripting_examples/example_assets/EarthTerrainFantasyHex.jpg unciv_scripting_examples/example_assets/EarthTerrainRaw.png diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py index ee814559baded..051dc1b55d82e 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py @@ -199,14 +199,21 @@ def __setattr__(self, name, value): # FIXME: Does this seem like a performance issue? -BIND_BY_REFERENCE = True -"""Early versions of this API bound Python objects to Kotlin/JVM instances by keeping track of paths and lazily evaluating them as needed. E.G. ".a.b[5].c" would create an internal tuple like `("a", "b", [5], "c")`, without actually accessing any Kotlin/JVM values at first. Benefits: Fewer IPC actions, lazy resolution of values only as they're used. Drawbacks: Deeper (slow) reflective loops per IPC action, scripting semantics not perfectly synced with JVM state, ugly tricks needed to deal with values that can't be safely accessed as paths from the same scope root, like the properties and methods of instances returned by function calls. +BIND_BY_REFERENCE = True # Should be True. +"""Early versions of this API bound Python objects to Kotlin/JVM instances by keeping track of paths and lazily evaluating them as needed. E.G. ".a.b[5].c" would create an internal tuple like `("a", "b", [5], "c")`, without actually accessing any Kotlin/JVM values at first. The Kotlin/JVM value at that path would only be resolved when it was needed. Benefits: Fewer IPC actions, lazy resolution of values only as they're used. Drawbacks: Deeper (slow) reflective loops per IPC action, scripting semantics not perfectly synced with JVM state, ugly tricks needed to deal with values that can't be safely accessed as paths from the same scope root, like the properties and methods of instances returned by function calls. -The current API instead keeps track of every """ +The current API instead evaluates the real Kotlin/JVM values of most attributes/items/returns as soon as they're accessed in Python, and keeps track of those internally, using paths only relative to those root values. Of the scripting semantics made more convenient by the tighter binding, the most significant is probably that properties and methods can be directly used on the results returned from function calls. -# TODO: The more complicated tests are all significantly slower with bind-by-reference than with bind-by-path. Hopefully it will be fixed by plugging the leak in InstanceTokenizer. +Set this flag to False to go back to the old, bind-by-path behaviour. Every Python backend can actually choose for themselves. But in general, bind-by-reference is increasingly the canonical model.""" -# TODO: Maybe test to see if a path-based approach for keys and attributes might still be faster? +# timeit.repeat results for running Python tests 3 times as of this comment: +# Bind-by-path: [30.61577351100277, 29.83002521295566, 32.639805707964115] +# Bind-by-reference: [33.53914805594832, 35.568275933968835, 36.279464731924236] +# This is with code that was written to try to squeeze out performance from the bind-by-path model, though. +# Subjectively, some of the tests feel like they may be faster with bind-by-reference too. +# Also of note: Kotlin/JVM-side token count reached well above 100k with bind-by-reference (though a realistic script would provide opportunities for cleanup to keep the count at any one time far below that), but didn't seem to reach 2k wtih bind-by-path. + +# TODO: Try to reduce IPC calls where it's not needed. @resolveForOperators class ForeignObject: @@ -223,7 +230,6 @@ def __repr__(self): return f"{self.__class__.__name__}({repr(self._root)}{', '+stringPathList(self._getpath_()) if self._getpath_() else ''}){':'+repr(self._getvalue_()) if self._getpath_() else ''}" # TODO: This has become less informative under bind-by-reference. Look at tokenization format too. def __str__(self): - # TODO: Maybe also just show _getvalue_() for __str__? return f"<{repr(self._getvalue_())}>" def _clone_(self, **kwargs): return self.__class__(**{'path': self._path, 'use_root': self._use_root, 'root': self._root, 'foreignrequester': self._foreignrequester, **kwargs}) @@ -262,7 +268,7 @@ def __iter__(self): try: return iter(self.keys()) except: - return (self[i] for i in range(0, len(self))) #TODO: Obviously this won't work for sets. Practical example why that's a problem: CityInfo stores HashSet()s of tiles. Workaround: Call real() on the whole set, and use the resulting values or foreign tokens. Unindexability/potential unorderedness of sets means that iteration would have to be handled from the Kotlin side, which means, at minimum implementing the BeginIteration and EndIteration flags, plus an entire new type of PassMic loop. Even then, without indexes, you'd only get the raw value or foreign token anyway + return (self[i] for i in range(0, len(self))) def __setattr__(self, name, value): return self.__getattr__(name, do_bake=False)._setvalue_(value) def __setitem__(self, key, value): @@ -279,7 +285,7 @@ def _setvalue_(self, value): return self._setvalueraw_(value) def __call__(self, *args): result = self._clone_(path=(*self._getpath_(), makePathElement(ttype='Call', params=args))) - # Care must be taken that the resulting ForeignObject never has ._getvalueraw_ called more than once, including by __repr__ or __str__, as resolving it means actually running the call in Kotlin/the JVM. This means it must not be able to produce children with the same 'Call' element in their paths either. + # Care must be taken that the resulting ForeignObject never has ._getvalueraw_ called more than once, including by __repr__ or __str__, as resolving it means actually running the call in Kotlin/the JVM. This also means it must not be able to produce children with the same 'Call' element in their paths either. if BIND_BY_REFERENCE: result._bakereal_() # Resolve the foreign value right away and clear path with bind-by-reference, so future accesses don't cause IPC actions. return result diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EventPopup.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EventPopup.py new file mode 100644 index 0000000000000..d5d3407435a6c --- /dev/null +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EventPopup.py @@ -0,0 +1,40 @@ +""" +Examples for scripting simple, Paradox-style event popups. + +""" + +#get apiHelpers.instancesAsInstances[apiHelpers.Jvm.constructorByQualname["com.unciv.ui.utils.Popup"](uncivGame.getScreen())].open(False) + +#Constructors=apiHelpers.Jvm.constructorByQualname; ExtensionFunctions=apiHelpers.Jvm.functionByQualClassAndName["com.unciv.ui.utils.ExtensionFunctionsKt"]; Singletons=apiHelpers.Jvm.kotlinSingletonByQualname; p=Constructors["com.unciv.ui.utils.Popup"](uncivGame.getScreen()); p.add(ExtensionFunctions["toLabel"]("Test Text.")).row(); p.add(ExtensionFunctions["toTextButton"]("Test Button.")).row(); closebutton=ExtensionFunctions["toTextButton"](Singletons["com.unciv.Constants"].close); ExtensionFunctions["onClick"](closebutton, modApiHelpers.lambdifyIgnoreExceptions(modApiHelpers.lambdifyPathcode(p, ".close()"))); p.add(closebutton); p.open(False) + +#from unciv_scripting_examples.EventPopup import * + +#from . import Utils + + +from unciv import * + +Constructors = apiHelpers.Jvm.constructorByQualname +ExtensionFunctions = apiHelpers.Jvm.functionByQualClassAndName["com.unciv.ui.utils.ExtensionFunctionsKt"] +Singletons = apiHelpers.Jvm.kotlinSingletonByQualname + +def showPopup(): + p = Constructors["com.unciv.ui.utils.Popup"](uncivGame.getScreen()) + p.add(ExtensionFunctions["toLabel"]("Test Text.")).row() + p.add(ExtensionFunctions["toTextButton"]("Test Button.")).row() + closebutton = ExtensionFunctions["toTextButton"](Singletons["com.unciv.Constants"].close) + ExtensionFunctions["onClick"]( + closebutton, + modApiHelpers.lambdifyIgnoreExceptions( + modApiHelpers.lambdifyReadPathcode(p, ".close()") + ) + ) + p.add(closebutton) + p.open(False) + + +def showPopup2(): + pass # Make button show toast and do something else. + +def showEventPopup(title=None, image=None, text=None, options=None): + pass diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py index 24c218baa91af..f69bd88aa1951 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py @@ -17,6 +17,15 @@ # from unciv_scripting_examples.Tests import *; TestRunner.run_tests() # from unciv_scripting_examples.Tests import *; InMapEditor.__enter__() +# from unciv_scripting_examples.Tests import *; import time; Start=time.time(); tryRun(TestRunner.run_tests); print(time.time()-Start) +# from unciv_scripting_examples.Tests import *; import timeit; x=timeit.repeat(stmt='tryRun(TestRunner.run_tests)', setup='tryRun(TestRunner.run_tests)', repeat=3, number=3, globals=globals()); print(x); unciv.apiHelpers.Sys.copyToClipboard(repr(x)); unciv.apiHelpers.Sys.printLine(repr(x)) + + +def tryRun(func): + # For debug and manual runs. Not used otherwise. + try: return func() + except Exception as e: return e + try: assert False diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/TicTacToe.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/TicTacToe.py index 2bd0e1cea207e..ff6ee5993ea2e 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/TicTacToe.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/TicTacToe.py @@ -1,7 +1,7 @@ """ Demo and experimental mod for script-created and script-controlled GUI elements. -Adds combination Lights Out and Conways Game of Life minigame. +Adds Tic-Tac-Toe minigame. Winner gets whatever they're asking for from any currently open trade deal. Call showPopup() to start playing. @@ -15,10 +15,7 @@ """ -#get apiHelpers.instancesAsInstances[apiHelpers.Jvm.constructorByQualname["com.unciv.ui.utils.Popup"](uncivGame.consoleScreen)].open(False) - -def showPopup(): - pass +# TradeEvaluation().isTradeAcceptable def injectWorldScreen(): diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/__init__.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/__init__.py index dfee43e97e3eb..2d36ff514f061 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/__init__.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/__init__.py @@ -3,6 +3,6 @@ Nothing should depend on anything in this module. """ -__all__ = ["EndTimes", "ExternalPipe", "MapEditingMacros", "Merfolk", "PlayerMacros", "ProceduralTechtree", "Tests", "Utils"] +__all__ = ["EndTimes", "EventPopup", "ExternalPipe", "MapEditingMacros", "Merfolk", "PlayerMacros", "ProceduralTechtree", "Tests", "TicTacToe", "Utils"] -from . import EndTimes, ExternalPipe, MapEditingMacros, Merfolk, PlayerMacros, ProceduralTechtree, Tests, Utils +from . import EndTimes, EventPopup, ExternalPipe, MapEditingMacros, Merfolk, PlayerMacros, ProceduralTechtree, Tests, TicTacToe, Utils diff --git a/core/src/com/unciv/scripting/api/ScriptingApiJvmHelpers.kt b/core/src/com/unciv/scripting/api/ScriptingApiJvmHelpers.kt index 72cf0052c46da..1288b57703811 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiJvmHelpers.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiJvmHelpers.kt @@ -19,14 +19,13 @@ fun enumQualnameToMap(qualName: String) = Class.forName(qualName).enumConstants. // Always return a built-in Map class instance here, so its gets serialized as JSON object instead of tokenized, and scripts can refer directly to its items. // I cast to Enum<*> fully expecting it would crash because it felt metaclass-y. But apparently it's just a base class, so it works? +private const val exposeStates = true // TODO: Probably keep this false? /** * For use in ScriptingScope. Allows interpreted scripts access Kotlin/JVM class functionality that isn't attached to any application instances. */ object ScriptingApiJvmHelpers { - private const val exposeStates = true // Probably keep this false? - val enumMapsByQualname = LazyMap(::enumQualnameToMap) val kotlinClassByQualname = LazyMap({ qualName: String -> Class.forName(qualName).kotlin }, exposeState = exposeStates) @@ -35,12 +34,18 @@ object ScriptingApiJvmHelpers { val kotlinCompanionByQualClass = LazyMap({ qualName: String -> kotlinClassByQualname[qualName]?.companionObjectInstance }, exposeState = exposeStates) - val functionByQualClassAndMethodName = LazyMap({ jclassQualname: String -> // TODO: Rename, remove "Method". + val functionByQualClassAndName = LazyMap({ jclassQualname: String -> val cls = Class.forName(jclassQualname) - LazyMap({ methodName: String -> makeFunctionDispatcher(cls.getDeclaredMethods().asSequence().filter { it.name == methodName }.map { it.kotlinFunction }.toList() as List>) }, exposeState = exposeStates) + LazyMap({ methodName: String -> makeFunctionDispatcher(cls.getDeclaredMethods().asSequence().filter { it.name == methodName }.map { it.kotlinFunction }.filterNotNull().toList() as List>) }, exposeState = exposeStates) // Could initialize the second LazyMap here by accessing for all names— Only benefit would be for autocomplete, at higher first-call time and memory use, though. }, exposeState = exposeStates) - // apiHelpers.Jvm.functionByQualClassAndMethodName["com.unciv.ui.utils.ExtensionFunctionsKt"]["toLabel"]("Test") + // apiHelpers.Jvm.functionByQualClassAndName["com.unciv.ui.utils.ExtensionFunctionsKt"]["toLabel"]("Test") + + // TODO: Right... Extension properties? + // Class.forName("kotlin.reflect.full.KClasses").getMethods().map{it.name} + // apiHelpers.Jvm.functionByQualClassAndName["kotlin.reflect.full.KClasses"]["getFunctions"](apiHelpers.Jvm.kotlinClassByQualname["com.badlogic.gdx.scenes.scene2d.ui.Cell"]) + // Right. .kotlinFunction is null for extension property getters: + // Class.forName("kotlin.reflect.full.KClasses").getDeclaredMethods().first{it.name == "getFunctions"}.kotlinFunction val staticPropertyByQualClassAndName = LazyMap({ jclassQualname: String -> val kcls = Class.forName(jclassQualname).kotlin diff --git a/core/src/com/unciv/scripting/api/ScriptingModApiHelpers.kt b/core/src/com/unciv/scripting/api/ScriptingModApiHelpers.kt index c60c8593d37c7..cf57ccda6696e 100644 --- a/core/src/com/unciv/scripting/api/ScriptingModApiHelpers.kt +++ b/core/src/com/unciv/scripting/api/ScriptingModApiHelpers.kt @@ -1,12 +1,60 @@ package com.unciv.scripting.api +import com.unciv.scripting.reflection.Reflection + +// Wrapper for a function that takes no arguments. +// @param func The function to wrap. +class LambdaWrapper0(func: () -> R): () -> R { + // Kotlin reflection has difficulties with the anonymous classes used for Lambdas, and this seems easier than trying to figure out why and cleaner than trying to work through it. + val lambda: () -> R = func.unwrapped() + override fun invoke() = lambda() +} + +// Extension function to strip a zero-argument function of a wrapping LambdaWrapper0 if present. +fun (() -> R).unwrapped() = if (this is LambdaWrapper0) this.lambda else this + + class ScriptingModApiHelpers(val scriptingScope: ScriptingScope) { // var handlerContext: NamedTuple? // Why not just use a map? String keys will be clearer in scripts than integers anyway. // Collection that gets replaced with any contextual parameters when running script handlers. E.G. Unit moved, city founded, tech researched, construction finished. -//fun showScriptedChoicePopup(title: String, body: String, options: List, callback: String?) // Actually, no. Popup() seems to work on its own. // TODO: Mods blacklist, for security threats. - //fun lambdifyScript(code: String ): () -> Unit = fun(){ scriptingScope.uncivGame!!.scriptingState.exec(code); return } // FIXME: Requires awareness of which scriptingState and which backend to use. + //fun lambdifyExecScript(code: String ): () -> Unit = fun(){ scriptingScope.uncivGame!!.scriptingState.exec(code) } // FIXME: Requires awareness of which scriptingState and which backend to use. //Directly invoking the resulting lambda from a running script will almost certainly break the REPL loop/IPC protocol. //setTimeout? + fun lambdifyReadPathcode(instance: Any?, pathcode: String): () -> Any? { + val path = Reflection.parseKotlinPath(pathcode) + return LambdaWrapper0 { Reflection.resolveInstancePath(instance, path) } + } + fun lambdifyAssignPathcode(instance: Any?, pathcode: String, value: Any?): () -> Unit { + val path = Reflection.parseKotlinPath(pathcode) + return LambdaWrapper0 { Reflection.setInstancePath(instance, path, value) } + } + fun lambdifyAssignPathcode(instance: Any?, pathcode: String, value: () -> Any?): () -> Unit { + val path = Reflection.parseKotlinPath(pathcode) + val getter = value.unwrapped() + return LambdaWrapper0 { Reflection.setInstancePath(instance, path, getter()) } + } +// fun lambdifyCallWithArgs() // Wouldn't it be easier to just use lambdifyExecScript? This is rapidly growing into its own, very verbose, functional programming language otherwise. +// fun lambdifyCallWithDynamicArgs() // Okay, these aren't possible unless you type as Function, due to static arg counts, I think. + fun lambdifySuppressReturn(func: () -> R): () -> Unit { + val lambda = func.unwrapped() + return LambdaWrapper0 { lambda() } + } + fun lambdifyIgnoreExceptions(func: () -> R): () -> R? { + val lambda = func.unwrapped() + return LambdaWrapper0 { + try { + lambda() + } catch (e: Exception) { + println("Error in function from ${this::class.simpleName}.lambdifySilentFailure():\n${e.toString().prependIndent("\t")}") + null + } + } + } + fun lambdifyCombine(funcs: List<() -> Any?>): () -> Unit { + val lambdas = funcs.map { it.unwrapped() } + return LambdaWrapper0 { for (f in lambdas) { f() } } + } + //fun lambdifyReduce // Not sure about this, since so far the model for script-created lambdas makes sense only for side effects, and not for return values. } diff --git a/core/src/com/unciv/scripting/api/ScriptingScope.kt b/core/src/com/unciv/scripting/api/ScriptingScope.kt index c05277440a60d..28bcd1cc45259 100644 --- a/core/src/com/unciv/scripting/api/ScriptingScope.kt +++ b/core/src/com/unciv/scripting/api/ScriptingScope.kt @@ -41,6 +41,8 @@ class ScriptingScope( val modApiHelpers = ScriptingModApiHelpers(this) + // TODO: Some way to clear the instancesaver? + } // Does having one state manage multiple backends that all share the same scope really make sense? Mod handler dispatch, callbacks, etc might all be easier if the multi-backend functionality of ScriptingState were implemented only for ConsoleScreen. diff --git a/core/src/com/unciv/scripting/reflection/Reflection.kt b/core/src/com/unciv/scripting/reflection/Reflection.kt index 853501a60ee94..542c9c1592946 100644 --- a/core/src/com/unciv/scripting/reflection/Reflection.kt +++ b/core/src/com/unciv/scripting/reflection/Reflection.kt @@ -5,6 +5,8 @@ import kotlin.collections.ArrayList import kotlinx.serialization.Serializable import kotlin.reflect.* +// I've noticed that the first time running a script is significantly slower than any subsequent times. Takes 50% longer to run the Python test suite the first time than the second time, and simple functions go from incurring a noticeable delay to being visually instant. +// I don't think anything either can or needs to be done about that, but I assume it's the JVM JIT'ing. object Reflection { @@ -13,11 +15,20 @@ object Reflection { = (cls.members.first { it.name == propertyName } as KProperty0<*>).get() as R? @Suppress("UNCHECKED_CAST") - fun readInstanceProperty(instance: Any, propertyName: String) + fun readInstanceProperty(instance: Any, propertyName: String): R? { // From https://stackoverflow.com/a/35539628/12260302 - = (instance::class.members.first { it.name == propertyName } as KProperty1).get(instance) as R? - // If scripting member access performance becomes an issue, memoizing this could be a potential first step. + val kprop = (instance::class.members.first { it.name == propertyName } as KProperty1) // TODO: Throw more helpful error on failure. + return (if (kprop.isConst) + kprop.getter.call() + else + kprop.get(instance)) as R? + // KProperty1().get(instance) Fails for consts: apiHelpers.Jvm.kotlinSingletonByQualname["com.unciv.Constants"].close + // m=next(m for m in apiHelpers.Jvm.kotlinClassByQualname["com.unciv.Constants"].members if m.name == 'close') + // object o {val a=1; const val b=2} + // (o::class.members.first{it.name == "a"} as KProperty1).get(o) + // (o::class.members.first{it.name == "b"} as KProperty1).getter.call() + } // Return an [InstanceMethodDispatcher]() with consistent settings for the scripting API. fun makeInstanceMethodDispatcher(instance: Any, methodName: String) = InstanceMethodDispatcher( @@ -45,8 +56,16 @@ object Reflection { matchClassesQualnames: Boolean = false, resolveAmbiguousSpecificity: Boolean = false ) : FunctionDispatcher( - functions = instance::class.members.filter { it.name == methodName }, + functions = instance::class.members.filter { it is KFunction<*> && it.name == methodName }, // TODO: .functions? Choose one that includes superclasses but excludes extensions. + // FIXME: Right. Cell.row is an example of a name used as both a property and a function. + // p=apiHelpers.Jvm.constructorByQualname["com.unciv.ui.utils.Popup"](uncivGame.consoleScreen); disp=p.add(apiHelpers.Jvm.functionByQualClassAndName["com.unciv.ui.utils.ExtensionFunctionsKt"]["toLabel"]("Test Text.")).row + // [apiHelpers.Jvm.kotlinClassByInstance[f] for f in disp.functions] + // KFunctionImpl vs KMutableProperty1Impl, apparently. + // Adding `is Function` to the filter should do it, I think? + // apiHelpers.Jvm.kotlinClassByQualname["com.badlogic.gdx.scenes.scene2d.ui.Cell"].members + // apiHelpers.Jvm.kotlinClassByQualname["com.badlogic.gdx.scenes.scene2d.ui.Cell"] + // apiHelpers.Jvm.functionByQualClassAndName["kotlin.reflect.full.KClasses"]["getFunctions"](apiHelpers.Jvm.kotlinClassByQualname["com.badlogic.gdx.scenes.scene2d.ui.Cell"]) matchNumbersLeniently = matchNumbersLeniently, matchClassesQualnames = matchClassesQualnames, resolveAmbiguousSpecificity = resolveAmbiguousSpecificity @@ -235,7 +254,7 @@ object Reflection { //} fun splitToplevelExprs(code: String, delimiters: String = ","): List { - return code.split(',').map { it.trim(' ') } + return if (code.isBlank()) listOf() else code.split(',').map { it.trim(' ') } var segs = ArrayList() val bracketdepths = mutableMapOf( *brackettypes.keys.map { it to 0 }.toTypedArray() @@ -301,7 +320,7 @@ object Reflection { } } } catch (e: Exception) { - throw IllegalAccessException("Cannot access $element on $obj: $e") + throw IllegalAccessException("Cannot access $element on $obj:\n${e.toString().prependIndent("\t")}") } } return obj diff --git a/core/src/com/unciv/scripting/serialization/InstanceTokenizer.kt b/core/src/com/unciv/scripting/serialization/InstanceTokenizer.kt index 35225599732e1..ada1a0c2d8bff 100644 --- a/core/src/com/unciv/scripting/serialization/InstanceTokenizer.kt +++ b/core/src/com/unciv/scripting/serialization/InstanceTokenizer.kt @@ -3,12 +3,11 @@ package com.unciv.scripting.serialization import com.unciv.scripting.ScriptingConstants import com.unciv.scripting.utils.ScriptingDebugParameters import com.unciv.scripting.utils.WeakIdentityMap -//import kotlin.math.min import java.lang.ref.WeakReference // Could use SoftReferences— Would seem convenient, but probably lead to mysterious bugs in scripts. import java.util.UUID import kotlin.math.floor import kotlin.math.log -import kotlin.math.pow +import kotlin.random.Random /** @@ -24,21 +23,33 @@ object InstanceTokenizer { /** * Map of currently known token strings to WeakReferences of the Kotlin/JVM instances they represent. + * Used for basic functionality of tracking tokens and transforming them back into arbitrary instances. */ private val instancesByTokens = mutableMapOf>() // Map of WeakReferences of Kotlin/JVM instances to token strings that represent them. + // Used to reuse existing tokens for previously tokenized objects, improving performance and avoiding a memory leak. private val tokensByInstances = WeakIdentityMap() - // Without this, running the Python tests twice in a row led to a token count exceeding 32768 (and over 16348 after the first run) as of this comment, and very significant slowdown. - // With it… It's actually only slightly less, and the performance still hurts a lot. + // Without this, repeatedly running the Python tests led to a token count over 16835 after the first run, exceeding 25252 after the second run, and over 37887 after the third run, as of this comment. + // With it: Over 11223 after the first run, back down to 4399 in the middle of the second run and then up again to over 11223, down to 4396 in the middle of the third and back up to over 11223 afterwards, over 85000 following ten runs in a non-stop Python loop, but back down to 7809 after running as separate commands again and hitting the next cleanup at 127834. + // (Oh. Yeah. Duh. Because I made ScriptingProtocol save everything in the same script execution from being garbage collected, cleanup doesn't happen much in an ongoing Python loop. Oh well; That took a long time to run, and no script should be doing anywhere near that much in one go anyway (and even if it is processing that much data in one go, it should use its own data structures and only write out to Unciv at the very end).) - - // Logarithm and base for the number of known tokens after the last cleaning. Used for printing debug info. + // Logarithm of number of known tokens after the last cleaning, with tokenCountLogBase as base. Cleaning is triggered when changing this. private var lastTokenCountLog: Int = 0 - private const val tokenCountLogBase = 2f + // Logarithm base for lastTokenCountLog. Acts as factor threshold for cleaning invalid WeakReferences. + private const val tokenCountLogBase = 1.3f + + // Above this value, have a forceCleanChance to perform cleaning even if a no token count thresholds have been crossed. + // Needed because in theory, token count, as measured from a Collection size, can apparently max out with a large enough collection. + private const val forceCleanThreshold = Int.MAX_VALUE / 2 + // Chance of performing a cleaning when token count is above forceCleanThreshold. + private const val forceCleanChance = 0.001 -// private val instanceHashes = mutableMapOf>>()>>() - // TODO: See note under clean(). + // Compile-time flag on whether to keep track of previously tokenized objects and always reuse the same tokens for them. + // Disabling this could cause long-lived objects to create a lot of tokens that have no way of ever being cleaned. + // Keep it set to true to avoid scripts causing a memory leak and degrading token cleaning performance, basically. + private const val tokenReuse = true + // So why leave the flag in? It marks where the reuse behaviour happens, and helps keep its relationship to core functionality clear. /** * Prefix that all generated token strings should start with. @@ -55,9 +66,6 @@ object InstanceTokenizer { */ private const val tokenMaxLength = 100 -// private fun newTokenFromInstance(value: Any?): String { -// } // Move existing code here, publicize tokenFromInstance, check against existing WeakMap of tokens, and maybe forbid nullable input… Hm. Need to use identity instead of equality comparison. Oh wow. They have "WeakIdentityHashMap", the maniacs. - /** * Generate a distinctive token string to represent a Kotlin/JVM object. * @@ -69,7 +77,7 @@ object InstanceTokenizer { */ private fun tokenFromInstance(value: Any?): String { var token: String? = tokensByInstances[value] // Atomicity. Separating containment check would give the GC a chance to clear the key. - if (token != null) { + if (tokenReuse && token != null) { return token } var stringified: String @@ -82,7 +90,9 @@ object InstanceTokenizer { stringified = stringified.slice(0..tokenMaxLength -4) + "..." } token = "$tokenPrefix${System.identityHashCode(value)}:${if (value == null) "null" else value::class.qualifiedName}/${stringified}:${UUID.randomUUID().toString()}" - tokensByInstances[value] = token + if (tokenReuse) { + tokensByInstances[value] = token + } return token } @@ -96,30 +106,37 @@ object InstanceTokenizer { /** * Remove all tokens and WeakReferences whose instances have already been garbage-collected. + * + * Runs in O(n) time relative to token count. + * + * Should not have to be called manually. */ fun clean() { - //FIXME (if I become a problem): Because a new unique token is currently generated even if the instance is already tokenized as something else, this will eventually get slower over time if a script makes lots of requests that result in new instance tokens for objects that last a long time (E.G. uncivGame). And since any stored instances should ideally be WeakReferences to prevent garbage collection from being broken for *all* instances, fixing that may not be as simple as checking for existing tokens to reuse them. - //TODO: Probably keep another map of instance hashes to weakrefs and their existing token? The hashes will have to have counts instead of just containment, since otherwise a hash collision would cause the earlier token to become inaccessible. - // Can I use WeakReferences as keys? Do they implement hash and equality? Huh, WeakHashMap is a thing here too. Cool. Auto cleaning. And could check against for cleaning the other map. Hm. Need to use identity instead of equality comparison. Oh wow. They have "WeakIdentityHashMap", the maniacs. - tokensByInstances.clean() - val badtokens = mutableListOf() - // TODO: Print messages on major size milestone changes. - for ((t, o) in instancesByTokens) { - if (o.get() == null) { - badtokens.add(t) - } - } - // TODO: Maybe O(n) cleaning strategy is just not great. + val badtokens = if (tokenReuse) + tokensByInstances.clean(true)!! + else + instancesByTokens.entries.asSequence().filter { it.value.get() == null }.map { it.key }.toList() // Technically the .toLists()'s not needed, but this is a legacy thing anyway— TODO: Wait, can't you type as Iterable? for (t in badtokens) { instancesByTokens.remove(t) } - if (ScriptingDebugParameters.printTokenizerMilestones) { - val count = instancesByTokens.size - val current = floor(log(count.toFloat(), tokenCountLogBase)).toInt() - if (current != lastTokenCountLog) { - println("${this::class.simpleName} now contains ${count} tokens.") + } + + // Try to clean all invalid tokens. + + // Only does anything if detects a sufficient change in token count from the last cleanup, as defined by lastTokenCountLog and tokenCountLogBase. + + // Should not have to be called manually. + fun tryClean() { + val count = instancesByTokens.size + val countLog = floor(log(count.toFloat(), tokenCountLogBase)).toInt() + if (countLog != lastTokenCountLog || (count >= forceCleanThreshold && Random.nextDouble() <= forceCleanChance)) { + // In theory, could cause repeated and inefficient bouncing near a trigger threshold— Unlikeliness aside, that also won't happen because ScriptingProtocol prevents new tokens from being freed per script execution. + // forceCleanThreshold should make sure cleaning still happens even with count clipped to MAX_INT. + if (ScriptingDebugParameters.printTokenizerMilestones) { + println("${this::class.simpleName} now tracks ${count} tokens and ${tokensByInstances.size} instances. Cleaning.") } - lastTokenCountLog = current + clean() + lastTokenCountLog = countLog } } @@ -128,7 +145,7 @@ object InstanceTokenizer { * @return Token string that can later be detokenized back into the original instance. */ fun getToken(obj: Any?): String { // TOOD: Switch to Any, since null will just be cleaned anyway? - clean() + tryClean() val token = tokenFromInstance(obj) instancesByTokens[token] = WeakReference(obj) return token @@ -144,13 +161,12 @@ object InstanceTokenizer { * @return Real instance from detokenizing input if given a token string, input value or instance unchanged if not given a token string. */ fun getReal(token: Any?): Any? { - clean() - if (isToken(token)) { - return instancesByTokens[token]!!.get()// TODO: Add another non-null assertion here? Unknown tokens and expired tokens are only a cleaning cycle apart, which seems race condition-y. - // TODO: Helpful exception message for invalid tokens. - } else { - return token - } + tryClean() + return if (isToken(token)) + instancesByTokens[token]!!.get()// TODO: Add another non-null assertion here? Unknown tokens and expired tokens are only a cleaning cycle apart, which seems race condition-y. + // TODO: Helpful exception message for invalid tokens? + else + token } } diff --git a/core/src/com/unciv/scripting/utils/ScriptingDebugParameters.kt b/core/src/com/unciv/scripting/utils/ScriptingDebugParameters.kt index 74966a68f9233..e12cb1b419816 100644 --- a/core/src/com/unciv/scripting/utils/ScriptingDebugParameters.kt +++ b/core/src/com/unciv/scripting/utils/ScriptingDebugParameters.kt @@ -1,7 +1,10 @@ package com.unciv.scripting.utils object ScriptingDebugParameters { + // Whether to print out all/most IPC packets for debug. var printPacketsForDebug = false + // Whether to print out all/most IPC actions for debug (more readable than printing all packets). var printAccessForDebug = false - var printTokenizerMilestones = true + // Whether to print out major token count changes and cleaning events in InstanceTokenizer for debug. + var printTokenizerMilestones = false } diff --git a/core/src/com/unciv/scripting/utils/WeakIdentityHashmap.kt b/core/src/com/unciv/scripting/utils/WeakIdentityHashmap.kt index 67ee911071dad..a6179d34cb418 100644 --- a/core/src/com/unciv/scripting/utils/WeakIdentityHashmap.kt +++ b/core/src/com/unciv/scripting/utils/WeakIdentityHashmap.kt @@ -10,16 +10,14 @@ import java.lang.ref.WeakReference // Has value equality with itself. // Has value equality with any other WeakIdentityMapKey()s that points to the same living referent. -// If referent has been garbage collected, points to null, and does not have value equality to any other WeakIdentityMapKey()s. +// If referent has been garbage collected, then points to null, and does not have value equality to any other WeakIdentityMapKey()s. // Does not have value equality with anything else. // Should have valid hashCode behaviour given this. - -//// Has value equality with its own keyPlaceholder property. class WeakIdentityMapKey(referent: T): WeakReference(referent) { // Two states: // 1. Behaviour with living referent, such as when added to Map or used for containment check or index access. Should equal any other WeakIdentityMapKey with the same referent. - // 3. Behaviour with with dead referent, such as when removed from map. Referent has become null, so can't use that. Should equal itself to still be removable, and should not equal anything else. + // 2. Behaviour with with dead referent, such as when removed from map. Referent has become null, so can't use that. Should still have the same hashCode to not break hash bucket, should equal itself to still be removable, and should not equal anything else. val hashCode = System.identityHashCode(referent) // Keep hash immutable, and keep this in the same Map bucket. override fun hashCode() = hashCode override fun equals(other: Any?): Boolean { @@ -39,9 +37,9 @@ class WeakIdentityMapKey(referent: T): WeakReference(referent) { } } -// Map-like class that uses special WeakReferences as keys. +// Map-like class that uses special WeakReferences to wrap keys and correlates keys based on referential identity instead of value equality. -// For now, clean() must be called manually. +// For now, clean() must be called manually to free all keys that have been garbage collected. class WeakIdentityMap(): MutableMap { private val backingMap = mutableMapOf, V>() override val entries get() = throw NotImplementedError() // backingMap.entries @@ -59,16 +57,18 @@ class WeakIdentityMap(): MutableMap { // Free up all invalid keys that have been garbage collected. // Runs in O(n) time relative to size. - fun clean() { - val badkeys = mutableListOf>() - for (k in backingMap.keys) { - val referent = k.get() - if (referent == null) - badkeys.add(k) - } + + // @param returnValues Whether or not to return a list of values from all the removed keys. + fun clean(returnValues: Boolean = false): List? { + val badkeys = backingMap.keys.filter { it.get() == null } + val out = if (returnValues) + badkeys.map { backingMap[it] } + else + null for (k in badkeys) { backingMap.remove(k) } + return out } override fun toString() = "{${backingMap.entries.joinToString(", ") { "${it.key.get()}=${it.value}" }}}" } diff --git a/core/src/com/unciv/ui/consolescreen/IConsoleScreenAccessible.kt b/core/src/com/unciv/ui/consolescreen/IConsoleScreenAccessible.kt index 7d88258088dd4..b61a9d9363ef3 100644 --- a/core/src/com/unciv/ui/consolescreen/IConsoleScreenAccessible.kt +++ b/core/src/com/unciv/ui/consolescreen/IConsoleScreenAccessible.kt @@ -15,7 +15,7 @@ import com.unciv.ui.mapeditor.MapEditorScreen //Interface that extends BaseScreen with methods for exposing the global ConsoleScreen. interface IConsoleScreenAccessible { - val BaseScreen.consoleScreen: ConsoleScreen + val BaseScreen.consoleScreen: ConsoleScreen // TODO: Oh, apparently don't need to explicitly refer to this? Change in other extension functions too, I guess. get() = this.game.consoleScreen val BaseScreen.scriptingState: ScriptingState From 1ad5bd59af3557159036336fb2d2f464b92ce233 Mon Sep 17 00:00:00 2001 From: will-ca Date: Thu, 9 Dec 2021 19:27:52 +0000 Subject: [PATCH 66/93] Rebase! From 0e0eb5447a5f49f309664dd5d04739b0fc830a52 Mon Sep 17 00:00:00 2001 From: will-ca Date: Thu, 9 Dec 2021 19:44:57 +0000 Subject: [PATCH 67/93] Remove backup files accidentally included in rebase. (Rewrite history, empty commit.) From 8c426e9dccc638108ad71a85988779b40da576d3 Mon Sep 17 00:00:00 2001 From: will-ca Date: Fri, 10 Dec 2021 06:50:44 +0000 Subject: [PATCH 68/93] Event popup example. Prepare for another major restructuring. --- .../python/unciv_lib/autocompletion.py | 2 +- .../enginefiles/python/unciv_lib/wrapping.py | 24 ++- .../unciv_scripting_examples/EventPopup.py | 155 ++++++++++++++++-- .../python/unciv_scripting_examples/Reskin.py | 3 + .../python/unciv_scripting_examples/Tests.py | 18 +- .../com/unciv/scripting/ScriptingBackend.kt | 18 +- .../src/com/unciv/scripting/ScriptingState.kt | 23 ++- .../api/ScriptingApiExecutionContext.kt | 10 ++ .../scripting/api/ScriptingApiJvmHelpers.kt | 25 ++- .../unciv/scripting/api/ScriptingApiUnciv.kt | 3 +- .../scripting/api/ScriptingModApiHelpers.kt | 20 +-- .../com/unciv/scripting/api/ScriptingScope.kt | 23 +-- .../scripting/protocol/ScriptingProtocol.kt | 2 +- .../unciv/scripting/reflection/Reflection.kt | 9 +- .../scripting/serialization/TokenizingJson.kt | 2 +- .../utils/ScriptingDebugParameters.kt | 2 + .../unciv/scripting/utils/ScriptingLock.kt | 54 +++++- .../consolescreen/IConsoleScreenAccessible.kt | 2 +- .../com/unciv/ui/utils/ExtensionFunctions.kt | 6 +- 19 files changed, 306 insertions(+), 95 deletions(-) create mode 100644 android/assets/scripting/enginefiles/python/unciv_scripting_examples/Reskin.py create mode 100644 core/src/com/unciv/scripting/api/ScriptingApiExecutionContext.kt diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py b/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py index c5ca445a10e9f..775010f30f8a3 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/autocompletion.py @@ -119,7 +119,7 @@ def get_a(a): + working_dot + ( f"{a}[" - if self.get_keys(get_a(a)) else # TODO: Use the new is_mapping check. + if self.get_keys(get_a(a)) else # TODO: Use the new ismapping check. f"{a}(" if self.check_callable(get_a(a)) else a diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py index 051dc1b55d82e..15e223872261f 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py @@ -89,8 +89,8 @@ def stringPathList(pathlist): _magicmeths = ( '__lt__', '__le__', - '__eq__', # Kinda undefined behaviour for comparison with Kotlin object tokens. Well, tokens are just strings that will always equal themselves, but multiple tokens can refer to the same Kotlin object. `ForeignObject()`s resolve to new tokens, that are currently uniquely generated in InstanceTokenizer.kt, on every `._getvalue_()`, so I think even the same `ForeignObject()` will never equal itself. # Actually, now raises exception when used with tokens, I think. - # Once guaranteed token reuse is implemented in Kotlin, this should work in all cases. + '__eq__', # Kinda undefined behaviour for comparison with Kotlin object tokens. Well, tokens are just strings that will always equal themselves, but multiple tokens can refer to the same Kotlin object. `ForeignObject()`s resolve to new tokens, that are currently uniquely generated in InstanceTokenizer.kt, on every `._getvalue_()`, so I think even the same `ForeignObject()` will never equal itself. # Actually, now raises exception when used with tokens, I think. Do you support hash too? + # TODO: Once guaranteed token reuse is implemented in Kotlin, this should work in all cases. And probably just use Python hashes. '__ne__', '__ge__', '__gt__', @@ -113,8 +113,8 @@ def stringPathList(pathlist): '__or__', '__pos__', '__pow__', - '__rshift__', - '__sub__', + '__rshift__', # I think one of these might be used for int(), which seems to mysteriously work? + '__sub__', # Further indication that a bitshift is used for int(): A wrapped foreign float can't be converted. '__truediv__', '__xor__', '__concat__', @@ -158,6 +158,11 @@ def stringPathList(pathlist): '__ior__' ) +_tokensafemethods = { # TODO + '__eq__', + '__ne__' +} # TODO: Hash. + def resolveForOperators(cls): """Decorator. Adds missing magic methods to a class, which resolve their arguments with `api.real(a)`.""" def alreadyhas(name): @@ -187,6 +192,7 @@ def alreadyhas(name): # TODO: Could do this for more informative error messages, hidden magic methods that don't make sense. # Would have to instantiate in the JSON decoder, though. # I'm not sure it's necessary, since tokens will still have to be encoded as strings in JSON, which means you'd still need apiconstants['kotlinInstanceTokenPrefix'] and isForeignToken in api.py. + # Hm. Enable Python semantics with isinstance(), though. class AttributeProxy: @@ -247,9 +253,13 @@ def _bakereal_(self): def __getattr__(self, name, *, do_bake=True): # Due to lazy IPC calling, hasattr will never work with this. Instead, check for in dir(). # TODO: Shouldn't I special-casing get_help or _docstring_ here? Wait, no, I think I thought it would be accessed on the class. + # TODO: I suppose I could catch ForeignError to raise AttributeError here. attr = self._clone_(path=(*self._path, makePathElement(name=name))) if BIND_BY_REFERENCE and do_bake: - attr._bakereal_() + try: + attr._bakereal_() + except ipc.ForeignError as e: + raise AttributeError(e) return attr def __getattribute__(self, name, **kwargs): if name in ('values', 'keys', 'items'): @@ -260,7 +270,7 @@ def __getattribute__(self, name, **kwargs): def __getitem__(self, key, *, do_bake=True): item = self._clone_(path=(*self._path, makePathElement(ttype='Key', params=(key,)))) if BIND_BY_REFERENCE and do_bake: - item._bakereal_() + item._bakereal_() # Since __contains__ exists, there's no need to hide ForeignError()s from this behind a KeyError like how an AttributeError is needed for hasattr(). return item # Indexing from end with negative numbers is not supported. # Mostly a complexity choice. Matching Kotlin semantics is better than translating with an extra IPC call for length. @@ -294,6 +304,7 @@ def __call__(self, *args): @ForeignRequestMethod def _getvalueraw_(self): # Should never be called except for by _getvalue_. + assert not self._isbaked return ({ 'action': 'read', 'data': { @@ -307,6 +318,7 @@ def _getvalueraw_(self): @ForeignRequestMethod def _setvalueraw_(self, value): # Should never be called except for by _setvalue_. + assert not self._isbaked return ({ 'action': 'assign', 'data': { diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EventPopup.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EventPopup.py index d5d3407435a6c..aebc5ea85eda2 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EventPopup.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EventPopup.py @@ -1,32 +1,30 @@ """ -Examples for scripting simple, Paradox-style event popups. +Examples for scripting simple, Paradox-style/Beyond Earth-style event popups. """ -#get apiHelpers.instancesAsInstances[apiHelpers.Jvm.constructorByQualname["com.unciv.ui.utils.Popup"](uncivGame.getScreen())].open(False) +#from unciv_scripting_examples.EventPopup import *; r=showEventPopup(**EVENT_POPUP_DEMOARGS()) -#Constructors=apiHelpers.Jvm.constructorByQualname; ExtensionFunctions=apiHelpers.Jvm.functionByQualClassAndName["com.unciv.ui.utils.ExtensionFunctionsKt"]; Singletons=apiHelpers.Jvm.kotlinSingletonByQualname; p=Constructors["com.unciv.ui.utils.Popup"](uncivGame.getScreen()); p.add(ExtensionFunctions["toLabel"]("Test Text.")).row(); p.add(ExtensionFunctions["toTextButton"]("Test Button.")).row(); closebutton=ExtensionFunctions["toTextButton"](Singletons["com.unciv.Constants"].close); ExtensionFunctions["onClick"](closebutton, modApiHelpers.lambdifyIgnoreExceptions(modApiHelpers.lambdifyPathcode(p, ".close()"))); p.add(closebutton); p.open(False) - -#from unciv_scripting_examples.EventPopup import * +#apiHelpers.Jvm.constructorByQualname["com.unciv.ui.utils.ToastPopup"]("Test", uncivGame.getScreen(), 8000) #from . import Utils - from unciv import * Constructors = apiHelpers.Jvm.constructorByQualname -ExtensionFunctions = apiHelpers.Jvm.functionByQualClassAndName["com.unciv.ui.utils.ExtensionFunctionsKt"] +ExtensionFunctions = apiHelpers.Jvm.functionByQualClassAndName['com.unciv.ui.utils.ExtensionFunctionsKt'] Singletons = apiHelpers.Jvm.kotlinSingletonByQualname +Constants = Singletons['com.unciv.Constants'] # With bind-by-reference, doing fewer key and attribute accesses by doing them earlier should actually be faster. def showPopup(): - p = Constructors["com.unciv.ui.utils.Popup"](uncivGame.getScreen()) - p.add(ExtensionFunctions["toLabel"]("Test Text.")).row() - p.add(ExtensionFunctions["toTextButton"]("Test Button.")).row() - closebutton = ExtensionFunctions["toTextButton"](Singletons["com.unciv.Constants"].close) - ExtensionFunctions["onClick"]( + p = Constructors['com.unciv.ui.utils.Popup'](uncivGame.getScreen()) + p.add(ExtensionFunctions['toLabel']("Test Text.")).row() + p.add(ExtensionFunctions['toTextButton']("Test Button.")).row() + closebutton = ExtensionFunctions['toTextButton'](Constants.close) + ExtensionFunctions['onClick']( closebutton, modApiHelpers.lambdifyIgnoreExceptions( - modApiHelpers.lambdifyReadPathcode(p, ".close()") + modApiHelpers.lambdifyReadPathcode(p, '.close()') ) ) p.add(closebutton) @@ -34,7 +32,134 @@ def showPopup(): def showPopup2(): + p = Constructors['com.unciv.ui.utils.Popup'](uncivGame.getScreen()) + p.add(ExtensionFunctions['toLabel']("Test Text.")).row() + p.add(ExtensionFunctions['toTextButton']("Test Button.")).row() + closebutton = ExtensionFunctions['toTextButton'](Constants.close) + ExtensionFunctions['onClick']( + closebutton, + modApiHelpers.lambdifyIgnoreExceptions( + modApiHelpers.lambdifyReadPathcode(p, '.close()') + ) + ) + p.add(closebutton) + p.open(False) pass # Make button show toast and do something else. -def showEventPopup(title=None, image=None, text=None, options=None): - pass + +def execCodeInModule(code): + exec(code, globals(), None) + +def makeLocalLambda(code): + assert isinstance(code, str) + f'import unciv_scripting_examples.EventPopup; unciv_scripting_examples.EventPopup.execCodeInModule({repr(code)})' + # Could cache a compile(code) if Python performance is a huge issue. + + +Companions = apiHelpers.Jvm.kotlinCompanionByQualClass +Enums = apiHelpers.Jvm.enumMapsByQualname +GdxColours = apiHelpers.Jvm.staticPropertyByQualClassAndName['com.badlogic.gdx.graphics.Color'] +#StatColours = TODO +Fonts = Singletons['com.unciv.ui.utils.Fonts'] + + +import math +from unciv_pyhelpers import * + + +def showEventPopup(title=None, image=None, text="No text event text provided!", options={}): + assert apiHelpers.isInGame + # uncivGame.getScreen().stage.width + defaultcolour = GdxColours['WHITE'] + popup = Constructors['com.unciv.ui.utils.Popup'](uncivGame.getScreen()) + closeaction = modApiHelpers.lambdifyReadPathcode(popup, '.close()') + if title is not None: + popup.addGoodSizedLabel(title, 24).row() + popup.addSeparator().row() + popup.addGoodSizedLabel(text, 18).row() + for labels, clickaction in options.items(): + button = Constructors['com.badlogic.gdx.scenes.scene2d.ui.Button'](Companions['com.unciv.ui.utils.BaseScreen'].skin) + if isinstance(labels, str): + labels = (labels,) + elif isinstance(labels[0], str): + labels = (labels,) + for label in labels: + buttontext, buttoncolour = (label, None) if isinstance(label, str) else label + buttonlabel = ExtensionFunctions['toLabel'](buttontext, real(buttoncolour) or defaultcolour, 18) + button.add(buttonlabel).row() + ExtensionFunctions['onClick']( + button, + modApiHelpers.lambdifyCombine([ + modApiHelpers.lambdifyIgnoreExceptions( + action + ) + for action in + ( + *((clickaction,) if real(clickaction) else ()), + closeaction + ) + ]) + ) + popup.add(button).row() + popup.open(False) + return {**locals()} + + +def EVENT_POPUP_DEMOARGS(): + stats = civInfo.statsForNextTurn + goldboost, cultureboost, scienceboost = int(50+stats.gold*10), int(50+stats.culture*10), int(50+stats.science*10) + omniboost = 70 + (goldboost+cultureboost+scienceboost) // 2 + omniresistance = 20 + resistanceFlag = Enums["com.unciv.logic.city.CityFlags"]["Resistance"] + cities = tuple(civInfo.cities) + omniproductionboosts = tuple(int(real(min(production*10, max(production, cityconstructions.getRemainingWork(cityconstructions.getCurrentConstruction().name, True)+1)))) for city in cities for production, cityconstructions in [(city.cityStats.currentCityStats.production, city.cityConstructions)]) + return { + 'title': "Something has happened in your empire!", + 'image': "Generic And Dramatic Artwork!", # TODO # Note: Recommended method for mods is to ship file as internal image. + 'text': """A societally and politically significant event has occurred in your empire! + +A political factor has been invisibly building up over the last ten turns or so of gameplay, and it has finally reached a tipping point where we think it will be narratively compelling! Because of the old way things were, things happened. Because things happened, things have changed, and now things have to change some more. From now on, the new way your empire is will be different from the old way it was before! + +Things can change in different ways. If we do one thing, things can change. If we do another thing, things can also change. + +This is your chance to roleplay a political decision: +""", + 'options': { # TODO: Serialize Chars as string? + (f"I'll take a Gold stat bonus. (+{goldboost} {real(Fonts.gold.toString())})", GdxColours['GOLD']): + modApiHelpers.lambdifyReadPathcode(civInfo, f'.addGold({goldboost})'), # Can actually just read addGold for this. + (f"I'll take a Culture stat bonus. (+{cultureboost} {real(Fonts.culture.toString())})", GdxColours['VIOLET']): + modApiHelpers.lambdifyReadPathcode(civInfo, f'.policies.addCulture({cultureboost})'), + (f"I'll take a Science stat bonus. (+{scienceboost} {real(Fonts.science.toString())})", GdxColours['CYAN']): + modApiHelpers.lambdifyReadPathcode(civInfo, f'.tech.addScience({scienceboost})'), + ( + (f"Let Chaos reign! (+{omniboost} {real(Fonts.gold.toString())}, {real(Fonts.culture.toString())}, {real(Fonts.science.toString())})", None), + (f"(+{sum(omniproductionboosts)} {real(Fonts.production.toString())} spread across all your cities.)", None), + (f"All cities enter resistance for +{omniresistance} turns.", GdxColours['SCARLET']) + ): + modApiHelpers.lambdifyCombine([ + modApiHelpers.lambdifyReadPathcode(civInfo, f'.addGold({omniboost})'), + modApiHelpers.lambdifyReadPathcode(civInfo, f'.policies.addCulture({omniboost})'), + modApiHelpers.lambdifyReadPathcode(civInfo, f'.tech.addScience({omniboost})'), + *( + modApiHelpers.lambdifyReadPathcode(None, f'civInfo.cities[{i}].cityConstructions.addProductionPoints({p})') # Will be a wrong result if somthing else changes the resistance turns between the popup being spawned and being shown. + for i, p in enumerate(omniproductionboosts) + ), + *( + modApiHelpers.lambdifyReadPathcode(None, f'''civInfo.cities[{ + i + }].setFlag(apiHelpers.Jvm.enumMapsByQualname["com.unciv.logic.city.CityFlags"]["Resistance"], { + int( + (omniresistance + city.getFlag(resistanceFlag)) + if city.hasFlag(resistanceFlag) else + omniresistance + ) + })''') # Will be a wrong result if somthing else changes the resistance turns between the popup being spawned and being shown. + for i, city in enumerate(civInfo.cities) + ), + modApiHelpers.lambdifyReadPathcode(None, 'civInfo.addNotification("Your empire is FURIOUS!!!\nWhat did you even do???", civInfo.cities[0].location, apiHelpers.Jvm.arrayOfTyped1("StatIcons/Resistance"))') + ]), + "Nah. I'm good.": + None + } + } + diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Reskin.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Reskin.py new file mode 100644 index 0000000000000..16ec0f37771e9 --- /dev/null +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Reskin.py @@ -0,0 +1,3 @@ +""" +Experimental example for applying a custom look and feel to the entire Unciv app by mutating the global Skin instance. +""" diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py index f69bd88aa1951..412bc119c1c1b 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py @@ -12,7 +12,7 @@ import unciv, unciv_pyhelpers#, unciv_lib -from . import EndTimes, ExternalPipe, MapEditingMacros, Merfolk, PlayerMacros, ProceduralTechtree, Utils +from . import EndTimes, EventPopup, ExternalPipe, MapEditingMacros, Merfolk, PlayerMacros, ProceduralTechtree, Utils # from unciv_scripting_examples.Tests import *; TestRunner.run_tests() @@ -85,8 +85,7 @@ def __init__(self, func, name=None, runwith=None, args=(), kwargs={}): self.kwargs = kwargs def __call__(self): def run(): - args = self.args() if callable(self.args) else self.args - self.func(*args, **self.kwargs) + self.func(*(self.args() if callable(self.args) else self.args), **(self.kwargs() if callable(self.kwargs) else self.kwargs)) if self.runwith is None: run() else: @@ -237,6 +236,19 @@ def NoPrivatesTest(start, maxdepth, *, _depth=0, _failures=None, _namestack=None del _m, _func +# Tests for EventPopup.py + +TestRunner.Test()( + EventPopup.showPopup +) +TestRunner.Test()( + EventPopup.showPopup2 +) +TestRunner.Test(runwith=InGame, kwargs=EventPopup.EVENT_POPUP_DEMOARGS)( + EventPopup.showEventPopup +) + + #TODO: Add tests. Will probably require exception field in IPC protocol to use. #Basic IPC protocol specs and Pythonic operators. diff --git a/core/src/com/unciv/scripting/ScriptingBackend.kt b/core/src/com/unciv/scripting/ScriptingBackend.kt index 64583d6a1fcce..b2324c824c87a 100644 --- a/core/src/com/unciv/scripting/ScriptingBackend.kt +++ b/core/src/com/unciv/scripting/ScriptingBackend.kt @@ -40,7 +40,7 @@ abstract class ScriptingBackend_metadata { /** * @return A new instance of the parent class of which this object is a companion. */ - abstract fun new(scriptingScope: ScriptingScope): ScriptingBackendBase // TODO: Um, class references are totally a thing, and probably distinct from KClass, right? + abstract fun new(scriptingScope: ScriptingScope): ScriptingBackend // TODO: Um, class references are totally a thing, and probably distinct from KClass, right? abstract val displayName: String val syntaxHighlighting: SyntaxHighlighter? = null } @@ -96,7 +96,9 @@ interface ScriptingImplementation { // TODO: Add .userTerminable flag and per-instance display string to ScriptingBackendBase. Let mod command histories be seen on ConsoleScreen? -open class ScriptingBackendBase(val scriptingScope: ScriptingScope): ScriptingImplementation { +// TODO: Add note that methods should be called through ScriptingState, rather than directly. + +open class ScriptingBackend(val scriptingScope: ScriptingScope): ScriptingImplementation { //A little bit confusing. //Hm. Couldn't the metadata getter go on the interface? //That would mean this exists only as a code example for defining the companion object. @@ -108,7 +110,7 @@ open class ScriptingBackendBase(val scriptingScope: ScriptingScope): ScriptingIm * So every ScriptngBackend has a Metadata:ScriptingBackend_metadata companion object, which is stored in the ScriptingBackendType enums. */ companion object Metadata: ScriptingBackend_metadata() { - override fun new(scriptingScope: ScriptingScope) = ScriptingBackendBase(scriptingScope) + override fun new(scriptingScope: ScriptingScope) = ScriptingBackend(scriptingScope) // Trying to instantiate from a KClass seems messy when the constructors are expected to be called normally. This is easier. override val displayName: String = "Dummy" } @@ -127,7 +129,7 @@ open class ScriptingBackendBase(val scriptingScope: ScriptingScope): ScriptingIm //Has // Non-essential. Nothing should depend on this. Should always be removable from the game's code. -class HardcodedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackendBase(scriptingScope) { +class HardcodedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackend(scriptingScope) { companion object Metadata: ScriptingBackend_metadata() { override fun new(scriptingScope: ScriptingScope) = HardcodedScriptingBackend(scriptingScope) override val displayName: String = "Hardcoded" @@ -330,7 +332,7 @@ class HardcodedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacken //Nothing should depend on this. -class ReflectiveScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackendBase(scriptingScope) { +class ReflectiveScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackend(scriptingScope) { companion object Metadata: ScriptingBackend_metadata() { override fun new(scriptingScope: ScriptingScope) = ReflectiveScriptingBackend(scriptingScope) @@ -428,7 +430,7 @@ class ReflectiveScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacke } -abstract class EnvironmentedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackendBase(scriptingScope) { +abstract class EnvironmentedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackend(scriptingScope) { companion object Metadata: EnvironmentedScriptBackend_metadata() { // Need full metadata companion here, or else won't compile. @@ -569,7 +571,7 @@ class SluaScriptingBackend(scriptingScope: ScriptingScope): SubprocessScriptingB } -class DevToolsScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackendBase(scriptingScope) { +class DevToolsScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackend(scriptingScope) { //Probably redundant, and can probably be removed in favour of whatever mechanism is currently used to run the translation file generator. @@ -620,7 +622,7 @@ class DevToolsScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackend enum class ScriptingBackendType(val metadata: ScriptingBackend_metadata) { - Dummy(ScriptingBackendBase), + Dummy(ScriptingBackend), Hardcoded(HardcodedScriptingBackend), Reflective(ReflectiveScriptingBackend), //MicroPython(UpyScriptingBackend), diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index 18951bb88c963..3af7123f2a363 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -5,6 +5,8 @@ import com.unciv.ui.utils.clipIndexToBounds import com.unciv.ui.utils.enforceValidIndex import kotlin.collections.ArrayList +// TODO: Add .github/CODEOWNERS file for automatic PR notifications. + // TODO: Check for places to use Sequences. // Hm. It seems that Sequence performance isn't even a simple question of number of loops, and is also affected by boxed types and who know what else. // Premature optimization and such. Clearly long chains of loops can be rewritten as sequences. @@ -13,7 +15,7 @@ import kotlin.collections.ArrayList // TODO: There's probably some public vars that can/should be private set. -// TODO: Mods blacklist. +// TODO: Mods blacklist, for security threats. // See https://github.com/yairm210/Unciv/pull/5592/commits/a1f51e08ab782ab46bda220e0c4aaae2e8ba21a4 for example of running locking operation in separate thread. @@ -26,9 +28,11 @@ import kotlin.collections.ArrayList * @property scriptingScope ScriptingScope instance at the root of all scripting API. */ //TODO: Actually, probably should be only one instance in game, since various context changes set various ScriptingScope properties through it, and being able to view mod command history will also be useful. +// Yeah, singleton both this and ScriptingScope, I think?… There would be some benefits from having one ScriptingScoper per ScriptingBackend (namely: concurrent script executions), but it would come with a significant cost to how ScriptingScope is connected to ScriptingState and how the properties in it are currently updated (and that one benefit sounds like a can of Heisenbugs). +//This will be responsible for: Using the lock, threading, passing the entrypoint name to the lock, exposing context/running backend in scriptingScope, and setting handler context arguments. class ScriptingState(val scriptingScope: ScriptingScope) { - val scriptingBackends = ArrayList() + val scriptingBackends = ArrayList() private val outputHistory = ArrayList() private val commandHistory = ArrayList() @@ -43,10 +47,10 @@ class ScriptingState(val scriptingScope: ScriptingScope) { fun getOutputHistory() = outputHistory.toList() - data class BackendSpawnResult(val backend: ScriptingBackendBase, val motd: String) + data class BackendSpawnResult(val backend: ScriptingBackend, val motd: String) fun spawnBackend(backendtype: ScriptingBackendType): BackendSpawnResult { - val backend: ScriptingBackendBase = backendtype.metadata.new(scriptingScope) + val backend: ScriptingBackend = backendtype.metadata.new(scriptingScope) scriptingBackends.add(backend) activeBackend = scriptingBackends.size - 1 val motd = backend.motd() @@ -59,7 +63,7 @@ class ScriptingState(val scriptingScope: ScriptingScope) { activeBackend = index } - fun switchToBackend(backend: ScriptingBackendBase) { + fun switchToBackend(backend: ScriptingBackend) { // TODO: Apparently there's a bunch of extensions like .withIndex(), .indices, and .lastIndex that I can use to replace a lot of stuff currently done with .size. val index = scriptingBackends.indexOf(backend) if (index >= 0) @@ -96,7 +100,7 @@ class ScriptingState(val scriptingScope: ScriptingScope) { return scriptingBackends.isNotEmpty() } - fun getActiveBackend(): ScriptingBackendBase { + fun getActiveBackend(): ScriptingBackend { return scriptingBackends[activeBackend] } @@ -127,6 +131,7 @@ class ScriptingState(val scriptingScope: ScriptingScope) { } fun exec(command: String): String { // TODO: Allow "passing" args that get assigned to something under ScriptingScope here. + // TODO: Allow passing a name to use with ScriptingLock. //scriptingScope.scriptingBackend = if (command.length > 0) { if (command != commandHistory.lastOrNull()) @@ -145,14 +150,8 @@ class ScriptingState(val scriptingScope: ScriptingScope) { } // fun acquireScriptLock() { -// scriptingScope.worldScreen?.isPlayersTurn = false - //TODO: Move to ScriptingLock. - //Not perfect. I think scriptingScope also exposes mutating the GUI itself, and many things that aren't protected by this? Then again, a script that *wants* to cause a crash/ANR will always be able to do so by just assigning an invalid value or deleting a required node somewhere. Could make mod handlers outside of worldScreen blocking, with written stipulations on (dis)recommended size, and then - //https://github.com/yairm210/Unciv/pull/5592/commits/a1f51e08ab782ab46bda220e0c4aaae2e8ba21a4 // } // fun releaseScriptLock() { -// scriptingScope.worldScreen?.isPlayersTurn = true - //Hm. Should return to original value, not necessarily true. That means keeping a property, which means I'd rather put this in its own class. // } } diff --git a/core/src/com/unciv/scripting/api/ScriptingApiExecutionContext.kt b/core/src/com/unciv/scripting/api/ScriptingApiExecutionContext.kt new file mode 100644 index 0000000000000..5247bb152cceb --- /dev/null +++ b/core/src/com/unciv/scripting/api/ScriptingApiExecutionContext.kt @@ -0,0 +1,10 @@ +package com.unciv.scripting.api + +import com.unciv.scripting.ScriptingBackend + +class ScriptingApiExecutionContext { + var handlerParameters: Map? = null + // Why not just use a map? String keys will be clearer in scripts than integers anyway. + // Collection that gets replaced with any contextual parameters when running script handlers. E.G. Unit moved, city founded, tech researched, construction finished. + var scriptingBackend: ScriptingBackend? = null // TODO: Currently executing backend. +} diff --git a/core/src/com/unciv/scripting/api/ScriptingApiJvmHelpers.kt b/core/src/com/unciv/scripting/api/ScriptingApiJvmHelpers.kt index 1288b57703811..a96553362d710 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiJvmHelpers.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiJvmHelpers.kt @@ -28,7 +28,7 @@ object ScriptingApiJvmHelpers { val enumMapsByQualname = LazyMap(::enumQualnameToMap) - val kotlinClassByQualname = LazyMap({ qualName: String -> Class.forName(qualName).kotlin }, exposeState = exposeStates) + val kotlinClassByQualname = LazyMap({ qualName: String -> Class.forName(qualName).kotlin }, exposeState = exposeStates) // TODO: Maybe skip the "kotlin" prefix on these? It's already in the "Jvm" object, not everything has the prefix for some reason, and the first word after it is usually what you're actually looking for. val kotlinSingletonByQualname = LazyMap({ qualName: String -> kotlinClassByQualname[qualName]?.objectInstance }, exposeState = exposeStates) @@ -61,10 +61,21 @@ object ScriptingApiJvmHelpers { fun toString(obj: Any?) = obj.toString() -// fun arrayOf(elements: Collection): Array<*> = elements.toTypedArray() -// fun arrayOfAny(elements: Collection): Array = elements.toTypedArray() -// fun arrayOfString(elements: Collection): Array = elements.toTypedArray() - - //TODO: Heavily overloaded toList or some such converters for Arrays, Sets, Sequences, Iterators, etc. + fun arrayOfAny(elements: Collection): Array<*> = elements.toTypedArray() // Rename to toArray? Hm. Named for role, not for semantics— This seems more useful for making new arrays, whereas the toString, toList, etc, are for converting existing instances. + fun arrayOfTyped(elements: Collection): Array<*> = when (val item = elements.firstOrNull()) { + // For scripting API/reflection. Return type won't be known in IDE, but that's fine as it's erased at runtime anyway. Important thing is that the compiler uses the right functions, creating the right typed arrays at run time. + is String -> (elements as Collection).toTypedArray() + is Number -> (elements as Collection).toTypedArray() + else -> throw IllegalArgumentException("${item!!::class.qualifiedName}") + } + + fun arrayOfTyped1(item: Any?) = arrayOfTyped(listOf(item)) // The "Pathcode" DSL doesn't have any syntax for array or collection literals, and adding such would be beyond its scope. So these helper functions let small arrays be used (1) in the reflective scripting backend and (2), more importantly, in the programm-y and more speed-focused helper functions in ScriptingApiMappers and ModApiHelpers. + fun arrayOfTyped2(item1: Any?, item2: Any?) = arrayOfTyped(listOf(item1, item2)) + fun arrayOfTyped3(item1: Any?, item2: Any?, item3: Any?) = arrayOfTyped(listOf(item1, item2, item3)) + fun arrayOfTyped4(item1: Any?, item2: Any?, item3: Any?, item4: Any?) = arrayOfTyped(listOf(item1, item2, item3, item4)) + fun arrayOfTyped5(item1: Any?, item2: Any?, item3: Any?, item4: Any?, item5: Any?) = arrayOfTyped(listOf(item1, item2, item3, item4, item5)) + + fun toList(array: Array<*>) = array.toList() // sorted([real(m.getName()) for m in apiHelpers.Jvm.kotlinClassByQualname["kotlin.collections.ArraysKt"].jClass.getMethods()]) + fun toList(iterable: Iterable<*>) = iterable.toList() + fun toList(sequence: Sequence<*>) = sequence.toList() } - diff --git a/core/src/com/unciv/scripting/api/ScriptingApiUnciv.kt b/core/src/com/unciv/scripting/api/ScriptingApiUnciv.kt index 7399c1dd9dc00..4d28b0b4d29d5 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiUnciv.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiUnciv.kt @@ -1,6 +1,6 @@ package com.unciv.scripting.api -import com.unciv.ui.utils.UncivDateFormat +import com.unciv.scripting.utils.ScriptingDebugParameters object ScriptingApiUnciv { // These are also all accessible by qualified name through ScriptingApiJvmHelpers. @@ -9,5 +9,6 @@ object ScriptingApiUnciv { val GameStarter = com.unciv.logic.GameStarter val HexMath = com.unciv.logic.HexMath val MapSaver = com.unciv.logic.MapSaver + val ScriptingDebugParameters = com.unciv.scripting.utils.ScriptingDebugParameters val UncivDateFormat = com.unciv.ui.utils.UncivDateFormat } diff --git a/core/src/com/unciv/scripting/api/ScriptingModApiHelpers.kt b/core/src/com/unciv/scripting/api/ScriptingModApiHelpers.kt index cf57ccda6696e..6a927c85e7032 100644 --- a/core/src/com/unciv/scripting/api/ScriptingModApiHelpers.kt +++ b/core/src/com/unciv/scripting/api/ScriptingModApiHelpers.kt @@ -1,6 +1,8 @@ package com.unciv.scripting.api import com.unciv.scripting.reflection.Reflection +import com.unciv.scripting.utils.ScriptingLock +import com.unciv.ui.utils.stringifyException // Wrapper for a function that takes no arguments. // @param func The function to wrap. @@ -15,25 +17,21 @@ fun (() -> R).unwrapped() = if (this is LambdaWrapper0) this.lambda else thi class ScriptingModApiHelpers(val scriptingScope: ScriptingScope) { - // var handlerContext: NamedTuple? - // Why not just use a map? String keys will be clearer in scripts than integers anyway. - // Collection that gets replaced with any contextual parameters when running script handlers. E.G. Unit moved, city founded, tech researched, construction finished. - // TODO: Mods blacklist, for security threats. //fun lambdifyExecScript(code: String ): () -> Unit = fun(){ scriptingScope.uncivGame!!.scriptingState.exec(code) } // FIXME: Requires awareness of which scriptingState and which backend to use. //Directly invoking the resulting lambda from a running script will almost certainly break the REPL loop/IPC protocol. - //setTimeout? + //setTimeout? // Probably don't implement this. But see ToastPopup.startTimer() if you do. fun lambdifyReadPathcode(instance: Any?, pathcode: String): () -> Any? { val path = Reflection.parseKotlinPath(pathcode) - return LambdaWrapper0 { Reflection.resolveInstancePath(instance, path) } + return LambdaWrapper0 { Reflection.resolveInstancePath(instance ?: scriptingScope, path) } } fun lambdifyAssignPathcode(instance: Any?, pathcode: String, value: Any?): () -> Unit { val path = Reflection.parseKotlinPath(pathcode) - return LambdaWrapper0 { Reflection.setInstancePath(instance, path, value) } + return LambdaWrapper0 { Reflection.setInstancePath(instance ?: scriptingScope, path, value) } } fun lambdifyAssignPathcode(instance: Any?, pathcode: String, value: () -> Any?): () -> Unit { val path = Reflection.parseKotlinPath(pathcode) val getter = value.unwrapped() - return LambdaWrapper0 { Reflection.setInstancePath(instance, path, getter()) } + return LambdaWrapper0 { Reflection.setInstancePath(instance ?: scriptingScope, path, getter()) } } // fun lambdifyCallWithArgs() // Wouldn't it be easier to just use lambdifyExecScript? This is rapidly growing into its own, very verbose, functional programming language otherwise. // fun lambdifyCallWithDynamicArgs() // Okay, these aren't possible unless you type as Function, due to static arg counts, I think. @@ -41,13 +39,15 @@ class ScriptingModApiHelpers(val scriptingScope: ScriptingScope) { val lambda = func.unwrapped() return LambdaWrapper0 { lambda() } } - fun lambdifyIgnoreExceptions(func: () -> R): () -> R? { + fun lambdifyIgnoreExceptions(func: () -> R): () -> R? { // TODO: Probably implicitly do this for all lambdas from here. The host program doesn't have to make it easy for scripts to crash it. val lambda = func.unwrapped() return LambdaWrapper0 { try { lambda() } catch (e: Exception) { - println("Error in function from ${this::class.simpleName}.lambdifySilentFailure():\n${e.toString().prependIndent("\t")}") + ScriptingLock.notifyPlayerScriptFailure(e) +// println("Error in function from ${this::class.simpleName}.lambdifySilentFailure():\n${e.stringifyException().prependIndent("\t")}") //// TODO: Toast. + // Really these should all go to STDERR. null } } diff --git a/core/src/com/unciv/scripting/api/ScriptingScope.kt b/core/src/com/unciv/scripting/api/ScriptingScope.kt index 28bcd1cc45259..f7211d0e693bf 100644 --- a/core/src/com/unciv/scripting/api/ScriptingScope.kt +++ b/core/src/com/unciv/scripting/api/ScriptingScope.kt @@ -32,7 +32,6 @@ class ScriptingScope( var uncivGame: UncivGame? = null, var worldScreen: WorldScreen? = null, var mapEditorScreen: MapEditorScreen? = null, - //var scriptingBackend: ScriptingBackend? = null // TODO: Currently executing backend. ) { val Unciv = ScriptingApiUnciv @@ -41,29 +40,11 @@ class ScriptingScope( val modApiHelpers = ScriptingModApiHelpers(this) + val apiExecutionContext = ScriptingApiExecutionContext() + // TODO: Some way to clear the instancesaver? } // Does having one state manage multiple backends that all share the same scope really make sense? Mod handler dispatch, callbacks, etc might all be easier if the multi-backend functionality of ScriptingState were implemented only for ConsoleScreen. // ScriptingState also helps separate , keep the shared ScriptingScope between all of them (such that it only needs to be updated once on game context changes), and update - -/* -//class NamedTuple(val map: Map) { - //Default order-preserving implementation of Map is important. - //SortedMap? - val keys = map.keys.toList() - //Default Set implementation also preserver order, so hopefully fine. - fun contains(vararg args: Any?) { - throw UnsupportedOperationException() - //Check against values? Keys? - } - fun get() { - if (is Int) { - } - } -} -*/ - - - diff --git a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt index 8deb715222cc4..e0fa5ceeef67b 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt @@ -99,7 +99,7 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = * @property name Serialized string value of this flag. */ enum class KnownFlag { - PassMic, + PassMic, // Names of these must match Module.md spec. Exception } diff --git a/core/src/com/unciv/scripting/reflection/Reflection.kt b/core/src/com/unciv/scripting/reflection/Reflection.kt index 542c9c1592946..eae7392a1214a 100644 --- a/core/src/com/unciv/scripting/reflection/Reflection.kt +++ b/core/src/com/unciv/scripting/reflection/Reflection.kt @@ -8,6 +8,8 @@ import kotlin.reflect.* // I've noticed that the first time running a script is significantly slower than any subsequent times. Takes 50% longer to run the Python test suite the first time than the second time, and simple functions go from incurring a noticeable delay to being visually instant. // I don't think anything either can or needs to be done about that, but I assume it's the JVM JIT'ing. +// TODO: Show warning on accessing deprecated property? + object Reflection { @Suppress("UNCHECKED_CAST") @@ -17,8 +19,7 @@ object Reflection { @Suppress("UNCHECKED_CAST") fun readInstanceProperty(instance: Any, propertyName: String): R? { // From https://stackoverflow.com/a/35539628/12260302 - val kprop = (instance::class.members.first { it.name == propertyName } as KProperty1) - // TODO: Throw more helpful error on failure. + val kprop = (instance::class.members.first { it.name == propertyName } as KProperty1) // Memoization candidates? I already have LazyMap, which should work for this. return (if (kprop.isConst) kprop.getter.call() else @@ -233,7 +234,7 @@ object Reflection { components.add( when (element.type) { PathElementType.Property -> ".${element.name}" PathElementType.Key -> "[${if (element.doEval) element.name else element.params[0]!!}]" - PathElementType.Call -> "(${if (element.doEval) element.name else element.params.joinToString(", ")}])" + PathElementType.Call -> "(${if (element.doEval) element.name else element.params.joinToString(", ")})" }) } return components.joinToString() @@ -340,7 +341,7 @@ object Reflection { } if (trimmed.length > 1 && trimmed.startsWith('"') && trimmed.endsWith('"')) { return trimmed.slice(1..trimmed.length-2) - } + } // TODO: Allow single-quoted strings? val asint = trimmed.toIntOrNull() if (asint != null) { return asint diff --git a/core/src/com/unciv/scripting/serialization/TokenizingJson.kt b/core/src/com/unciv/scripting/serialization/TokenizingJson.kt index 540fb40fffc65..0d7502197afa8 100644 --- a/core/src/com/unciv/scripting/serialization/TokenizingJson.kt +++ b/core/src/com/unciv/scripting/serialization/TokenizingJson.kt @@ -108,7 +108,7 @@ object TokenizingJson { fun isNotPrimitive(value: Any?): Boolean { return when (value) { null -> false - is String -> false + is String -> false // TODO: Chars? is Boolean -> false is Number -> false else -> true diff --git a/core/src/com/unciv/scripting/utils/ScriptingDebugParameters.kt b/core/src/com/unciv/scripting/utils/ScriptingDebugParameters.kt index e12cb1b419816..b8b2e46a74954 100644 --- a/core/src/com/unciv/scripting/utils/ScriptingDebugParameters.kt +++ b/core/src/com/unciv/scripting/utils/ScriptingDebugParameters.kt @@ -7,4 +7,6 @@ object ScriptingDebugParameters { var printAccessForDebug = false // Whether to print out major token count changes and cleaning events in InstanceTokenizer for debug. var printTokenizerMilestones = false + // Whether to print out a warning when reflectively accessing definitions that have been deprecated. + var printReflectiveDeprecationWarnings = false // TODO } diff --git a/core/src/com/unciv/scripting/utils/ScriptingLock.kt b/core/src/com/unciv/scripting/utils/ScriptingLock.kt index 10097eb6e43bd..1878dfc00dc5f 100644 --- a/core/src/com/unciv/scripting/utils/ScriptingLock.kt +++ b/core/src/com/unciv/scripting/utils/ScriptingLock.kt @@ -1,6 +1,58 @@ package com.unciv.scripting.utils +import com.unciv.UncivGame +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.Popup +import com.unciv.ui.utils.ToastPopup +import com.unciv.ui.utils.stringifyException +import java.lang.IllegalArgumentException +import java.lang.IllegalStateException +import java.util.UUID +import java.util.concurrent.atomic.AtomicBoolean + object ScriptingLock { - private var isRunning = false + private val isRunning = AtomicBoolean(false) // Maybe make thread-safe just in case, but also officially disallow use by multiple threads at once? + var runningName: String? = null + private set + private var runningKey: String? = null // Unique key set each run to make sure + private var runQueue = ArrayDeque<() -> Unit>() + // @return A randomly generated string to pass to the release function. + fun acquire(name: String? = null): String? { + val success = isRunning.compareAndSet(false, true) + if (!success) throw IllegalStateException("Cannot acquire ScriptingLock for $name because already in use by $runningName.") + runningKey = UUID.randomUUID().toString() + return runningKey +// scriptingScope.worldScreen?.isPlayersTurn = false + //Hm. Should return to original value, not necessarily true. That means keeping a property, which means I'd rather put this in its own class. + //TODO: Move to ScriptingLock. + //Not perfect. I think scriptingScope also exposes mutating the GUI itself, and many things that aren't protected by this? Then again, a script that *wants* to cause a crash/ANR will always be able to do so by just assigning an invalid value or deleting a required node somewhere. Could make mod handlers outside of worldScreen blocking, with written stipulations on (dis)recommended size, and then + //https://github.com/yairm210/Unciv/pull/5592/commits/a1f51e08ab782ab46bda220e0c4aaae2e8ba21a4 + } + fun release(releaseKey: String) { + if (releaseKey != runningKey) throw IllegalArgumentException("Invalid key given to release ScriptingLock.") +// val success = isRunning.compareAndSet(true, false) + if (isRunning.get()) { + runningName = null + runningKey = null + isRunning.set(false) + runQueue.removeFirstOrNull()?.invoke() + } else { + throw IllegalStateException("Cannot release ScriptingLock because it has not been acquired.") + } + } // TODO: Prevent UI collision, and also prevent recursive script executions. + // Allow registering name text of running script, for error messages. + fun notifyPlayerScriptFailure(exception: Throwable) { + // Should this be in ScriptingState after that's been singleton'd? + val popup = Popup(UncivGame.Current.screen as BaseScreen) + val msg = "An error has occurred with the mod/script \"$runningName\":\n\n${exception.toString().prependIndent("\t")}\n\nSee system terminal output for details.\nConsider disabling mods if this keeps happening.\n" + popup.addGoodSizedLabel(msg).row() + popup.addOKButton{} + popup.open(true) + printConsolePlayerScriptFailure(exception) + } + fun printConsolePlayerScriptFailure(exception: Throwable) { + println("\nException with \"$runningName\" script:\n${exception.stringifyException().prependIndent("\t")}\n") + // Really these should all go to STDERR. + } } diff --git a/core/src/com/unciv/ui/consolescreen/IConsoleScreenAccessible.kt b/core/src/com/unciv/ui/consolescreen/IConsoleScreenAccessible.kt index b61a9d9363ef3..27b8c817a9d25 100644 --- a/core/src/com/unciv/ui/consolescreen/IConsoleScreenAccessible.kt +++ b/core/src/com/unciv/ui/consolescreen/IConsoleScreenAccessible.kt @@ -56,5 +56,5 @@ interface IConsoleScreenAccessible { } // .apply errors on compile with "val cannot be reassigned". } -// fun BaseScreen.updateScriptingState(){} // TODO: Same, but don't clear. +// fun BaseScreen.updateScriptingState(){} // TODO: Same, but don't clear. Or not? } diff --git a/core/src/com/unciv/ui/utils/ExtensionFunctions.kt b/core/src/com/unciv/ui/utils/ExtensionFunctions.kt index cb1653796765d..116f678ae530f 100644 --- a/core/src/com/unciv/ui/utils/ExtensionFunctions.kt +++ b/core/src/com/unciv/ui/utils/ExtensionFunctions.kt @@ -317,7 +317,7 @@ fun List.randomWeighted(weights: List, random: Random = Random): T /** * @return String of exception preceded by entire stack trace. */ -fun Exception.stringifyException(): String { +fun Throwable.stringifyException(): String { val causes = arrayListOf() var cause: Throwable? = this while (cause != null) { @@ -326,9 +326,9 @@ fun Exception.stringifyException(): String { //I swear this is okay to do. } return listOf( - "\n", + "", *this.stackTrace, - "\n", + "", *causes.asReversed().map { it.toString() }.toTypedArray() ).joinToString("\n") } From c35d8331ccb64bfb751a56a9c0bfb4483caecd5a Mon Sep 17 00:00:00 2001 From: will-ca Date: Fri, 10 Dec 2021 07:39:02 +0000 Subject: [PATCH 69/93] Make a lot of things into singletons. I didn't eat for most the day and I think it might have affected my judgement. --- .../enginefiles/python/unciv_lib/wrapping.py | 1 - .../unciv_scripting_examples/EventPopup.py | 18 +-- core/Module.md | 10 +- core/src/com/unciv/UncivGame.kt | 13 +-- .../com/unciv/scripting/ScriptingBackend.kt | 108 +++++++++--------- .../src/com/unciv/scripting/ScriptingState.kt | 12 +- .../api/ScriptingApiExecutionContext.kt | 2 +- .../scripting/api/ScriptingApiHelpers.kt | 6 +- .../api/ScriptingApiInstanceRegistry.kt | 2 +- .../scripting/api/ScriptingModApiHelpers.kt | 55 +++++---- .../com/unciv/scripting/api/ScriptingScope.kt | 20 ++-- .../unciv/scripting/utils/ApiSpecGenerator.kt | 6 +- .../unciv/scripting/utils/ScriptingLock.kt | 15 ++- .../unciv/ui/consolescreen/ConsoleScreen.kt | 24 ++-- .../consolescreen/IConsoleScreenAccessible.kt | 6 +- 15 files changed, 138 insertions(+), 160 deletions(-) diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py index 15e223872261f..33a603b10871e 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py @@ -253,7 +253,6 @@ def _bakereal_(self): def __getattr__(self, name, *, do_bake=True): # Due to lazy IPC calling, hasattr will never work with this. Instead, check for in dir(). # TODO: Shouldn't I special-casing get_help or _docstring_ here? Wait, no, I think I thought it would be accessed on the class. - # TODO: I suppose I could catch ForeignError to raise AttributeError here. attr = self._clone_(path=(*self._path, makePathElement(name=name))) if BIND_BY_REFERENCE and do_bake: try: diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EventPopup.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EventPopup.py index aebc5ea85eda2..745baca752c0c 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EventPopup.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EventPopup.py @@ -23,9 +23,7 @@ def showPopup(): closebutton = ExtensionFunctions['toTextButton'](Constants.close) ExtensionFunctions['onClick']( closebutton, - modApiHelpers.lambdifyIgnoreExceptions( - modApiHelpers.lambdifyReadPathcode(p, '.close()') - ) + modApiHelpers.lambdifyReadPathcode(p, '.close()') ) p.add(closebutton) p.open(False) @@ -38,9 +36,7 @@ def showPopup2(): closebutton = ExtensionFunctions['toTextButton'](Constants.close) ExtensionFunctions['onClick']( closebutton, - modApiHelpers.lambdifyIgnoreExceptions( - modApiHelpers.lambdifyReadPathcode(p, '.close()') - ) + modApiHelpers.lambdifyReadPathcode(p, '.close()') ) p.add(closebutton) p.open(False) @@ -90,14 +86,8 @@ def showEventPopup(title=None, image=None, text="No text event text provided!", ExtensionFunctions['onClick']( button, modApiHelpers.lambdifyCombine([ - modApiHelpers.lambdifyIgnoreExceptions( - action - ) - for action in - ( - *((clickaction,) if real(clickaction) else ()), - closeaction - ) + *((clickaction,) if real(clickaction) else ()), + closeaction ]) ) popup.add(button).row() diff --git a/core/Module.md b/core/Module.md index a5db0a230cc46..062d043034670 100644 --- a/core/Module.md +++ b/core/Module.md @@ -37,18 +37,18 @@ UncivGame(): uncivGame worldScreen? *ScriptingBackend(): - scriptingScope + ScriptingScope ?ScriptingReplManager(): Blackbox() // Common interface to wrap foreign interpreter with pipes, STDIN/STDOUT, queues, sockets, embedding, JNI, etc. - scriptingScope + ScriptingScope ScriptingProtocol(): - scriptingScope + ScriptingScope ?folderHandler: setupInterpreterEnvironment() // If used, a temporary directory with file structure copied from engine and shared folders in `assets/scripting`. ConsoleScreen(): // Persistent as long as window isn't resized. Recreates itself and restores most of its state from scriptingState if resized. scriptingState WorldScreen(): consoleScreen - scriptingState // ScriptingState has getters and setters that wrap scriptingScope, which WorldScreen uses to update game info. + scriptingState // ScriptingState has getters and setters that wrap ScriptingScope, which WorldScreen uses to update game info. MainMenuScreen(): consoleScreen scriptingState // Same as for worldScreen. @@ -80,7 +80,7 @@ fun ExecuteCommand(command:String): packet:Packet = ReceiveFromInterpreter().parsed() if isPropertyRequest(packet): UnlockGameInfo() - response:Packet = ResolvePacket(scriptingScope, packet) + response:Packet = ResolvePacket(ScriptingScope, packet) LockGameInfo() SendToInterpreter(response) else if isCommandEndPacket(packet): diff --git a/core/src/com/unciv/UncivGame.kt b/core/src/com/unciv/UncivGame.kt index fc21461a47e52..c3054ba992f2c 100644 --- a/core/src/com/unciv/UncivGame.kt +++ b/core/src/com/unciv/UncivGame.kt @@ -75,7 +75,6 @@ class UncivGame(parameters: UncivGameParameters) : Game() { val translations = Translations() - lateinit var scriptingState: ScriptingState lateinit var consoleScreen: ConsoleScreen // Keep same ConsoleScreen() when possible, to avoid having to manually persist/restore history, input field, etc. @@ -158,15 +157,9 @@ class UncivGame(parameters: UncivGameParameters) : Game() { } private fun createScripting() { - scriptingState = ScriptingState( - ScriptingScope( - uncivGame = this - ) - ) - consoleScreen = ConsoleScreen( - scriptingState, - { } - ) + ScriptingScope.uncivGame = this + + consoleScreen = ConsoleScreen { } } fun setScreen(screen: BaseScreen) { diff --git a/core/src/com/unciv/scripting/ScriptingBackend.kt b/core/src/com/unciv/scripting/ScriptingBackend.kt index b2324c824c87a..1a9a60710589b 100644 --- a/core/src/com/unciv/scripting/ScriptingBackend.kt +++ b/core/src/com/unciv/scripting/ScriptingBackend.kt @@ -40,7 +40,7 @@ abstract class ScriptingBackend_metadata { /** * @return A new instance of the parent class of which this object is a companion. */ - abstract fun new(scriptingScope: ScriptingScope): ScriptingBackend // TODO: Um, class references are totally a thing, and probably distinct from KClass, right? + abstract fun new(): ScriptingBackend // TODO: Um, class references are totally a thing, and probably distinct from KClass, right? abstract val displayName: String val syntaxHighlighting: SyntaxHighlighter? = null } @@ -98,7 +98,7 @@ interface ScriptingImplementation { // TODO: Add note that methods should be called through ScriptingState, rather than directly. -open class ScriptingBackend(val scriptingScope: ScriptingScope): ScriptingImplementation { +open class ScriptingBackend: ScriptingImplementation { //A little bit confusing. //Hm. Couldn't the metadata getter go on the interface? //That would mean this exists only as a code example for defining the companion object. @@ -110,7 +110,7 @@ open class ScriptingBackend(val scriptingScope: ScriptingScope): ScriptingImplem * So every ScriptngBackend has a Metadata:ScriptingBackend_metadata companion object, which is stored in the ScriptingBackendType enums. */ companion object Metadata: ScriptingBackend_metadata() { - override fun new(scriptingScope: ScriptingScope) = ScriptingBackend(scriptingScope) + override fun new() = ScriptingBackend() // Trying to instantiate from a KClass seems messy when the constructors are expected to be called normally. This is easier. override val displayName: String = "Dummy" } @@ -129,9 +129,9 @@ open class ScriptingBackend(val scriptingScope: ScriptingScope): ScriptingImplem //Has // Non-essential. Nothing should depend on this. Should always be removable from the game's code. -class HardcodedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackend(scriptingScope) { +class HardcodedScriptingBackend(): ScriptingBackend() { companion object Metadata: ScriptingBackend_metadata() { - override fun new(scriptingScope: ScriptingScope) = HardcodedScriptingBackend(scriptingScope) + override fun new() = HardcodedScriptingBackend() override val displayName: String = "Hardcoded" } @@ -190,19 +190,19 @@ class HardcodedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacken } } "countcities" -> { - if (!(scriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } - appendOut(scriptingScope.civInfo!!.cities.size.toString()) + if (!(ScriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } + appendOut(ScriptingScope.civInfo!!.cities.size.toString()) } "locatebuildings" -> { var buildingcities: List = listOf() - if (!(scriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } + if (!(ScriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } if (args.size > 1) { var searchfor = args.slice(1..args.size-1).joinToString(" ").trim(' ') - buildingcities = scriptingScope.civInfo!!.cities + buildingcities = ScriptingScope.civInfo!!.cities .filter { searchfor in it.cityConstructions.builtBuildings || it.cityConstructions.builtBuildings.any({ building -> - scriptingScope.gameInfo!!.ruleSet.buildings[building]!!.requiresResource(searchfor) + ScriptingScope.gameInfo!!.ruleSet.buildings[building]!!.requiresResource(searchfor) }) } .map { it.name } @@ -211,18 +211,18 @@ class HardcodedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacken } "missingbuildings" -> { var buildingcities: List = listOf() - if (!(scriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } + if (!(ScriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } if (args.size > 1) { var searchfor = args.slice(1..args.size-1).joinToString(" ").trim(' ') - buildingcities = scriptingScope.civInfo!!.cities + buildingcities = ScriptingScope.civInfo!!.cities .filter { !(searchfor in it.cityConstructions.builtBuildings) } .map { it.name } } appendOut(buildingcities.joinToString(", ")) } "listcities" -> { - if (!(scriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } - appendOut(scriptingScope.civInfo!!.cities + if (!(ScriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } + appendOut(ScriptingScope.civInfo!!.cities .map { city -> city.name } .joinToString(", ") ) @@ -236,33 +236,33 @@ class HardcodedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacken appendOut("Cheats disabled.") } "godmode" -> { - if (!(scriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } + if (!(ScriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } if (cheats) { - var godmode = if (args.size > 1) args[1].toBoolean() else !(scriptingScope.gameInfo!!.gameParameters.godMode) - scriptingScope.gameInfo!!.gameParameters.godMode = godmode + var godmode = if (args.size > 1) args[1].toBoolean() else !(ScriptingScope.gameInfo!!.gameParameters.godMode) + ScriptingScope.gameInfo!!.gameParameters.godMode = godmode appendOut("${if (godmode) "Enabled" else "Disabled"} godmode.") } else { appendOut("Cheats must be enabled to use this command!") } } "godview" -> { - if (!(scriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } + if (!(ScriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } if (cheats) { - var godview = if (args.size > 1) args[1].toBoolean() else !(scriptingScope.uncivGame!!.viewEntireMapForDebug) - scriptingScope.uncivGame!!.viewEntireMapForDebug = godview + var godview = if (args.size > 1) args[1].toBoolean() else !(ScriptingScope.uncivGame!!.viewEntireMapForDebug) + ScriptingScope.uncivGame!!.viewEntireMapForDebug = godview appendOut("${if (godview) "Enabled" else "Disabled"} whole map visibility.") } else { appendOut("Cheats must be enabled to use this command!") } } "inspectpath" -> { - if (!(scriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } + if (!(ScriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } if (cheats) { val detailed = args.size > 1 && args[1] == "detailed" val startindex = if (detailed) 2 else 1 val path = (if (args.size > startindex) args.slice(startindex..args.size-1) else listOf()).joinToString(" ") try { - var obj = Reflection.evalKotlinString(scriptingScope, path) + var obj = Reflection.evalKotlinString(ScriptingScope, path) val isnull = obj == null appendOut( if (detailed) @@ -281,9 +281,9 @@ class HardcodedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacken if (cheats) { try { val path = (if (args.size > 2) args.slice(2..args.size-1) else listOf()).joinToString(" ") - val value = Reflection.evalKotlinString(scriptingScope, args[1]) + val value = Reflection.evalKotlinString(ScriptingScope, args[1]) Reflection.setInstancePath( - scriptingScope, + ScriptingScope, Reflection.parseKotlinPath(path), value ) @@ -296,7 +296,7 @@ class HardcodedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacken } } "simulatetoturn" -> { - if (!(scriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } + if (!(ScriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } if (cheats) { var numturn = 0 if (args.size > 1) { @@ -306,17 +306,17 @@ class HardcodedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacken appendOut("Invalid number: ${args[1]}\n") } } - scriptingScope.uncivGame!!.simulateUntilTurnForDebug = numturn + ScriptingScope.uncivGame!!.simulateUntilTurnForDebug = numturn appendOut("Will automatically simulate game until turn ${numturn} after this turn.\nThe map will not update until completed.") } else { appendOut("Cheats must be enabled to use this command!") } } "supercharge" -> { - if (!(scriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } + if (!(ScriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } if (cheats) { - var supercharge = if (args.size > 1) args[1].toBoolean() else !(scriptingScope.uncivGame!!.superchargedForDebug) - scriptingScope.uncivGame!!.superchargedForDebug = supercharge + var supercharge = if (args.size > 1) args[1].toBoolean() else !(ScriptingScope.uncivGame!!.superchargedForDebug) + ScriptingScope.uncivGame!!.superchargedForDebug = supercharge appendOut("${if (supercharge) "Enabled" else "Disabled"} stats supercharge.") } else { appendOut("Cheats must be enabled to use this command!") @@ -332,10 +332,10 @@ class HardcodedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacken //Nothing should depend on this. -class ReflectiveScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackend(scriptingScope) { +class ReflectiveScriptingBackend(): ScriptingBackend() { companion object Metadata: ScriptingBackend_metadata() { - override fun new(scriptingScope: ScriptingScope) = ReflectiveScriptingBackend(scriptingScope) + override fun new() = ReflectiveScriptingBackend() override val displayName: String = "Reflective" } @@ -375,7 +375,7 @@ class ReflectiveScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacke } val leafname = if (workingpath.isNotEmpty()) workingpath[workingpath.size - 1].name else "" val prefix = command.dropLast(leafname.length) - val branchobj = Reflection.resolveInstancePath(scriptingScope, workingpath.slice(0..workingpath.size-2)) + val branchobj = Reflection.resolveInstancePath(ScriptingScope, workingpath.slice(0..workingpath.size-2)) return AutocompleteResults( branchobj!!::class.members .map { it.name } @@ -399,20 +399,20 @@ class ReflectiveScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacke try { when (parts[0]) { "get" -> { - appendOut("${Reflection.evalKotlinString(scriptingScope, parts[1])}") + appendOut("${Reflection.evalKotlinString(ScriptingScope, parts[1])}") } "set" -> { var setparts = parts[1].split(' ', limit=2) - var value = Reflection.evalKotlinString(scriptingScope, setparts[0]) + var value = Reflection.evalKotlinString(ScriptingScope, setparts[0]) Reflection.setInstancePath( - scriptingScope, + ScriptingScope, Reflection.parseKotlinPath(setparts[1]), value ) appendOut("Set ${setparts[1]} to ${value}") } "typeof" -> { - var obj = Reflection.evalKotlinString(scriptingScope, parts[1]) + var obj = Reflection.evalKotlinString(ScriptingScope, parts[1]) appendOut("${if (obj == null) null else obj!!::class.qualifiedName}") } "examples" -> { @@ -430,13 +430,13 @@ class ReflectiveScriptingBackend(scriptingScope: ScriptingScope): ScriptingBacke } -abstract class EnvironmentedScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackend(scriptingScope) { +abstract class EnvironmentedScriptingBackend(): ScriptingBackend() { companion object Metadata: EnvironmentedScriptBackend_metadata() { // Need full metadata companion here, or else won't compile. // Ideally would be able to just declare that subclasses must define a companion of the correct type, but ah well. override val displayName = "" - override fun new(scriptingScope: ScriptingScope) = throw UnsupportedOperationException("Base scripting backend class not meant to be instantiated.") + override fun new() = throw UnsupportedOperationException("Base scripting backend class not meant to be instantiated.") override val engine = "" } @@ -453,13 +453,13 @@ abstract class EnvironmentedScriptingBackend(scriptingScope: ScriptingScope): Sc } -abstract class BlackboxScriptingBackend(scriptingScope: ScriptingScope): EnvironmentedScriptingBackend(scriptingScope) { +abstract class BlackboxScriptingBackend(): EnvironmentedScriptingBackend() { companion object Metadata: EnvironmentedScriptBackend_metadata() { // Need full metadata companion here, or else won't compile. // Ideally would be able to just declare that subclasses must define a companion of the correct type, but ah well. override val displayName = "" - override fun new(scriptingScope: ScriptingScope) = throw UnsupportedOperationException("Base scripting backend class not meant to be instantiated.") + override fun new() = throw UnsupportedOperationException("Base scripting backend class not meant to be instantiated.") override val engine = "" } @@ -503,13 +503,13 @@ abstract class BlackboxScriptingBackend(scriptingScope: ScriptingScope): Environ } -abstract class SubprocessScriptingBackend(scriptingScope: ScriptingScope): BlackboxScriptingBackend(scriptingScope) { +abstract class SubprocessScriptingBackend(): BlackboxScriptingBackend() { abstract val processCmd: Array override val blackbox by lazy { SubprocessBlackbox(processCmd) } - override val replManager: ScriptingReplManager by lazy { ScriptingRawReplManager(scriptingScope, blackbox) } + override val replManager: ScriptingReplManager by lazy { ScriptingRawReplManager(ScriptingScope, blackbox) } override fun motd(): String { return """ @@ -525,17 +525,17 @@ abstract class SubprocessScriptingBackend(scriptingScope: ScriptingScope): Black } -abstract class ProtocolSubprocessScriptingBackend(scriptingScope: ScriptingScope): SubprocessScriptingBackend(scriptingScope) { +abstract class ProtocolSubprocessScriptingBackend(): SubprocessScriptingBackend() { - override val replManager by lazy { ScriptingProtocolReplManager(scriptingScope, blackbox) } + override val replManager by lazy { ScriptingProtocolReplManager(ScriptingScope, blackbox) } } -class SpyScriptingBackend(scriptingScope: ScriptingScope): ProtocolSubprocessScriptingBackend(scriptingScope) { +class SpyScriptingBackend(): ProtocolSubprocessScriptingBackend() { companion object Metadata: EnvironmentedScriptBackend_metadata() { - override fun new(scriptingScope: ScriptingScope) = SpyScriptingBackend(scriptingScope) + override fun new() = SpyScriptingBackend() override val displayName: String = "System Python" override val engine = "python" } @@ -545,10 +545,10 @@ class SpyScriptingBackend(scriptingScope: ScriptingScope): ProtocolSubprocessScr } -class SqjsScriptingBackend(scriptingScope: ScriptingScope): SubprocessScriptingBackend(scriptingScope) { +class SqjsScriptingBackend(): SubprocessScriptingBackend() { companion object Metadata: EnvironmentedScriptBackend_metadata() { - override fun new(scriptingScope: ScriptingScope) = SqjsScriptingBackend(scriptingScope) + override fun new() = SqjsScriptingBackend() override val displayName: String = "System QuickJS" override val engine = "qjs" } @@ -558,10 +558,10 @@ class SqjsScriptingBackend(scriptingScope: ScriptingScope): SubprocessScriptingB } -class SluaScriptingBackend(scriptingScope: ScriptingScope): SubprocessScriptingBackend(scriptingScope) { +class SluaScriptingBackend(): SubprocessScriptingBackend() { companion object Metadata: EnvironmentedScriptBackend_metadata() { - override fun new(scriptingScope: ScriptingScope) = SluaScriptingBackend(scriptingScope) + override fun new() = SluaScriptingBackend() override val displayName: String = "System Lua" override val engine = "lua" } @@ -571,12 +571,12 @@ class SluaScriptingBackend(scriptingScope: ScriptingScope): SubprocessScriptingB } -class DevToolsScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackend(scriptingScope) { +class DevToolsScriptingBackend(): ScriptingBackend() { //Probably redundant, and can probably be removed in favour of whatever mechanism is currently used to run the translation file generator. companion object Metadata: ScriptingBackend_metadata() { - override fun new(scriptingScope: ScriptingScope) = DevToolsScriptingBackend(scriptingScope) + override fun new() = DevToolsScriptingBackend() override val displayName: String = "DevTools" } @@ -603,10 +603,10 @@ class DevToolsScriptingBackend(scriptingScope: ScriptingScope): ScriptingBackend try { when (commv[0]) { "PrintFlatApiDefs" -> { - out += ApiSpecGenerator(scriptingScope).generateFlatApi().toString() + "\n" + out += ApiSpecGenerator().generateFlatApi().toString() + "\n" } "PrintClassApiDefs" -> { - out += ApiSpecGenerator(scriptingScope).generateClassApi().toString() + "\n" + out += ApiSpecGenerator().generateClassApi().toString() + "\n" } else -> { out += "Unknown command: ${commv[0]}\n" diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index 3af7123f2a363..8f31d7cd3954f 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -25,12 +25,12 @@ import kotlin.collections.ArrayList * Abstracts available scope, running backends, command history * Should be unique per isolated use of scripting. E.G. One for the [~]-key console screen, one for each mod/all mods per save file (or whatever works best), etc. * - * @property scriptingScope ScriptingScope instance at the root of all scripting API. + * @property ScriptingScope ScriptingScope instance at the root of all scripting API. */ //TODO: Actually, probably should be only one instance in game, since various context changes set various ScriptingScope properties through it, and being able to view mod command history will also be useful. // Yeah, singleton both this and ScriptingScope, I think?… There would be some benefits from having one ScriptingScoper per ScriptingBackend (namely: concurrent script executions), but it would come with a significant cost to how ScriptingScope is connected to ScriptingState and how the properties in it are currently updated (and that one benefit sounds like a can of Heisenbugs). -//This will be responsible for: Using the lock, threading, passing the entrypoint name to the lock, exposing context/running backend in scriptingScope, and setting handler context arguments. -class ScriptingState(val scriptingScope: ScriptingScope) { +//This will be responsible for: Using the lock, threading, passing the entrypoint name to the lock, exposing context/running backend in ScriptingScope, and setting handler context arguments. +object ScriptingState { val scriptingBackends = ArrayList() @@ -50,7 +50,7 @@ class ScriptingState(val scriptingScope: ScriptingScope) { data class BackendSpawnResult(val backend: ScriptingBackend, val motd: String) fun spawnBackend(backendtype: ScriptingBackendType): BackendSpawnResult { - val backend: ScriptingBackend = backendtype.metadata.new(scriptingScope) + val backend: ScriptingBackend = backendtype.metadata.new() scriptingBackends.add(backend) activeBackend = scriptingBackends.size - 1 val motd = backend.motd() @@ -132,7 +132,7 @@ class ScriptingState(val scriptingScope: ScriptingScope) { fun exec(command: String): String { // TODO: Allow "passing" args that get assigned to something under ScriptingScope here. // TODO: Allow passing a name to use with ScriptingLock. - //scriptingScope.scriptingBackend = + //ScriptingScope.scriptingBackend = if (command.length > 0) { if (command != commandHistory.lastOrNull()) commandHistory.add(command) @@ -145,7 +145,7 @@ class ScriptingState(val scriptingScope: ScriptingScope) { activeCommandHistory = 0 var out = if (hasBackend()) getActiveBackend().exec(command) else "" echo(out) - //scriptingScope.scriptingBackend = null // TODO + //ScriptingScope.scriptingBackend = null // TODO return out } diff --git a/core/src/com/unciv/scripting/api/ScriptingApiExecutionContext.kt b/core/src/com/unciv/scripting/api/ScriptingApiExecutionContext.kt index 5247bb152cceb..396ca4618b821 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiExecutionContext.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiExecutionContext.kt @@ -2,7 +2,7 @@ package com.unciv.scripting.api import com.unciv.scripting.ScriptingBackend -class ScriptingApiExecutionContext { +object ScriptingApiExecutionContext { var handlerParameters: Map? = null // Why not just use a map? String keys will be clearer in scripts than integers anyway. // Collection that gets replaced with any contextual parameters when running script handlers. E.G. Unit moved, city founded, tech researched, construction finished. diff --git a/core/src/com/unciv/scripting/api/ScriptingApiHelpers.kt b/core/src/com/unciv/scripting/api/ScriptingApiHelpers.kt index 3d4d7a0b4ee83..e051e0c2664b1 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiHelpers.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiHelpers.kt @@ -10,10 +10,10 @@ import java.io.ByteArrayOutputStream // TODO: Search core code for Transient lazy init caches (E.G. natural wonders saved in TileMap apparently, and TileMap.resources), and add functions to refresh them? -class ScriptingApiHelpers(val scriptingScope: ScriptingScope) { +object ScriptingApiHelpers { // This, and the classes of its members, should try to implement only the minimum number of helper functions that are needed for each type of functionality otherwise not possible in scripts. E.G. Don't add special "loadGame" functions or whatever here, but do expose the existing methods of UncivGame. E.G. Don't add factories to speed up making alert popups, because all the required constructors can already be called through constructorByQualname anyway. Let the rest of the codebase and the scripts themselves do the work— Maintenance of the API itself will be easier if all it does is expose existing Kotlin code to dynamic Python/JS/Lua code. val isInGame: Boolean - get() = (scriptingScope.civInfo != null && scriptingScope.gameInfo != null && scriptingScope.uncivGame != null) + get() = (ScriptingScope.civInfo != null && ScriptingScope.gameInfo != null && ScriptingScope.uncivGame != null) val App = ScriptingApiAppHelpers @@ -23,7 +23,7 @@ class ScriptingApiHelpers(val scriptingScope: ScriptingScope) { val Mappers = ScriptingApiMappers - val registeredInstances = ScriptingApiInstanceRegistry() + val registeredInstances = ScriptingApiInstanceRegistry val instancesAsInstances = FakeMap{obj: Any? -> obj} // TODO: Rename this, and singleton it. /// Scripting language bindings work by keeping track of the paths to values, and making Kotlin/the JVM resolve them only when needed. // This creates a dilemma: Resolving a path into a Kotlin value too early means that no further paths (E.G. attribute, keys, calls) can be built on top of it. But resolving it late means that expected side effects may not happen (E.G. function calls probably shouldn't be deferred). And values that *must* be resolved, like the results of function calls, cannot have their own members and method accessed until they themselves are assigned to a path, because they're just kinda floating around as far as the scripting-exposed semantics are concerned. diff --git a/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt b/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt index fa74cba33ac68..822a96b92a33f 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt @@ -38,7 +38,7 @@ object AllScriptingApiInstanceRegistries { * @throws IllegalArgumentException On an attempted assignment colliding with an existing key. * @throws NoSuchElementException For reads and removals at non-existent keys. */ -class ScriptingApiInstanceRegistry: MutableMap { +object ScriptingApiInstanceRegistry: MutableMap { private val backingMap = mutableMapOf() // fun init() { // AllScriptingApiInstanceRegistries.add(this) diff --git a/core/src/com/unciv/scripting/api/ScriptingModApiHelpers.kt b/core/src/com/unciv/scripting/api/ScriptingModApiHelpers.kt index 6a927c85e7032..1f01795efa5ee 100644 --- a/core/src/com/unciv/scripting/api/ScriptingModApiHelpers.kt +++ b/core/src/com/unciv/scripting/api/ScriptingModApiHelpers.kt @@ -2,57 +2,56 @@ package com.unciv.scripting.api import com.unciv.scripting.reflection.Reflection import com.unciv.scripting.utils.ScriptingLock -import com.unciv.ui.utils.stringifyException // Wrapper for a function that takes no arguments. // @param func The function to wrap. -class LambdaWrapper0(func: () -> R): () -> R { +class LambdaWrapper0(func: () -> R?): () -> R? { // Kotlin reflection has difficulties with the anonymous classes used for Lambdas, and this seems easier than trying to figure out why and cleaner than trying to work through it. - val lambda: () -> R = func.unwrapped() - override fun invoke() = lambda() + val lambda: () -> R? = func.unwrapped() + val suppressing: () -> R? = lambda.reportExceptionsAsScriptErrors() + override fun invoke() = suppressing() } // Extension function to strip a zero-argument function of a wrapping LambdaWrapper0 if present. -fun (() -> R).unwrapped() = if (this is LambdaWrapper0) this.lambda else this +fun (() -> R?).unwrapped() = if (this is LambdaWrapper0) this.lambda else this + +// Extension function to wrap a zero-argument function to suppress all exceptions, instead returning null and notifying the player. +fun (() -> R).reportExceptionsAsScriptErrors(asName: String? = null): () -> R? { + return { + try { + this() + } catch (e: Exception) { + ScriptingLock.notifyPlayerScriptFailure(e, asName = asName) + null + } + } +} -class ScriptingModApiHelpers(val scriptingScope: ScriptingScope) { - //fun lambdifyExecScript(code: String ): () -> Unit = fun(){ scriptingScope.uncivGame!!.scriptingState.exec(code) } // FIXME: Requires awareness of which scriptingState and which backend to use. +object ScriptingModApiHelpers { + //fun lambdifyExecScript(code: String ): () -> Unit = fun(){ ScriptingScope.uncivGame!!.scriptingState.exec(code) } // FIXME: Requires awareness of which scriptingState and which backend to use. //Directly invoking the resulting lambda from a running script will almost certainly break the REPL loop/IPC protocol. //setTimeout? // Probably don't implement this. But see ToastPopup.startTimer() if you do. fun lambdifyReadPathcode(instance: Any?, pathcode: String): () -> Any? { val path = Reflection.parseKotlinPath(pathcode) - return LambdaWrapper0 { Reflection.resolveInstancePath(instance ?: scriptingScope, path) } + return LambdaWrapper0 { Reflection.resolveInstancePath(instance ?: ScriptingScope, path) } } - fun lambdifyAssignPathcode(instance: Any?, pathcode: String, value: Any?): () -> Unit { + fun lambdifyAssignPathcode(instance: Any?, pathcode: String, value: Any?): () -> Unit? { val path = Reflection.parseKotlinPath(pathcode) - return LambdaWrapper0 { Reflection.setInstancePath(instance ?: scriptingScope, path, value) } + return LambdaWrapper0 { Reflection.setInstancePath(instance ?: ScriptingScope, path, value) } } - fun lambdifyAssignPathcode(instance: Any?, pathcode: String, value: () -> Any?): () -> Unit { + fun lambdifyAssignPathcode(instance: Any?, pathcode: String, value: () -> Any?): () -> Unit? { val path = Reflection.parseKotlinPath(pathcode) val getter = value.unwrapped() - return LambdaWrapper0 { Reflection.setInstancePath(instance ?: scriptingScope, path, getter()) } + return LambdaWrapper0 { Reflection.setInstancePath(instance ?: ScriptingScope, path, getter()) } } // fun lambdifyCallWithArgs() // Wouldn't it be easier to just use lambdifyExecScript? This is rapidly growing into its own, very verbose, functional programming language otherwise. // fun lambdifyCallWithDynamicArgs() // Okay, these aren't possible unless you type as Function, due to static arg counts, I think. - fun lambdifySuppressReturn(func: () -> R): () -> Unit { + fun lambdifySuppressReturn(func: () -> R): () -> Unit? { val lambda = func.unwrapped() - return LambdaWrapper0 { lambda() } - } - fun lambdifyIgnoreExceptions(func: () -> R): () -> R? { // TODO: Probably implicitly do this for all lambdas from here. The host program doesn't have to make it easy for scripts to crash it. - val lambda = func.unwrapped() - return LambdaWrapper0 { - try { - lambda() - } catch (e: Exception) { - ScriptingLock.notifyPlayerScriptFailure(e) -// println("Error in function from ${this::class.simpleName}.lambdifySilentFailure():\n${e.stringifyException().prependIndent("\t")}") //// TODO: Toast. - // Really these should all go to STDERR. - null - } - } + return LambdaWrapper0 { lambda(); null } } - fun lambdifyCombine(funcs: List<() -> Any?>): () -> Unit { + fun lambdifyCombine(funcs: List<() -> Any?>): () -> Unit? { val lambdas = funcs.map { it.unwrapped() } return LambdaWrapper0 { for (f in lambdas) { f() } } } diff --git a/core/src/com/unciv/scripting/api/ScriptingScope.kt b/core/src/com/unciv/scripting/api/ScriptingScope.kt index f7211d0e693bf..0265265a3cafa 100644 --- a/core/src/com/unciv/scripting/api/ScriptingScope.kt +++ b/core/src/com/unciv/scripting/api/ScriptingScope.kt @@ -23,24 +23,24 @@ import com.unciv.ui.worldscreen.WorldScreen * * To reduce the chance of E.G. name collisions in .apiHelpers.registeredInstances, or one misbehaving mod breaking everything by unassigning .gameInfo, different ScriptingState()s should each have their own ScriptingScope(). */ -class ScriptingScope( +object ScriptingScope // This entire API should still be considered unstable. It may be drastically changed at any time. //If this is going to be exposed to downloaded mods, then every declaration here, as well as *every* declaration that is safe for scripts to have access to, should probably be whitelisted with annotations and checked or errored at the point of reflection. - var civInfo: CivilizationInfo? = null, - var gameInfo: GameInfo? = null, - var uncivGame: UncivGame? = null, - var worldScreen: WorldScreen? = null, - var mapEditorScreen: MapEditorScreen? = null, - ) { + { + var civInfo: CivilizationInfo? = null + var gameInfo: GameInfo? = null + var uncivGame: UncivGame? = null + var worldScreen: WorldScreen? = null + var mapEditorScreen: MapEditorScreen? = null val Unciv = ScriptingApiUnciv - val apiHelpers = ScriptingApiHelpers(this) + val apiHelpers = ScriptingApiHelpers - val modApiHelpers = ScriptingModApiHelpers(this) + val modApiHelpers = ScriptingModApiHelpers - val apiExecutionContext = ScriptingApiExecutionContext() + val apiExecutionContext = ScriptingApiExecutionContext // TODO: Some way to clear the instancesaver? diff --git a/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt b/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt index 7cf021dd7cf9e..b701e0a70e550 100644 --- a/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt +++ b/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt @@ -61,14 +61,14 @@ fun makeMemberSpecDef(member: KCallable<*>): ApiSpecDef { } -class ApiSpecGenerator(val scriptingScope: ScriptingScope) { +class ApiSpecGenerator { fun isUncivClass(cls: KClass<*>): Boolean { return cls.qualifiedName!!.startsWith("com.unciv") } fun getAllUncivClasses(): Set> { - val searchclasses = mutableListOf>(scriptingScope::class) + val searchclasses = mutableListOf>(ScriptingScope::class) val encounteredclasses = mutableSetOf>() var i: Int = 0 while (i < searchclasses.size) { @@ -97,7 +97,7 @@ class ApiSpecGenerator(val scriptingScope: ScriptingScope) { } fun generateFlatApi(): List { - return scriptingScope::class.members.map { it.name } + return ScriptingScope::class.members.map { it.name } } fun generateClassApi(): Map> { diff --git a/core/src/com/unciv/scripting/utils/ScriptingLock.kt b/core/src/com/unciv/scripting/utils/ScriptingLock.kt index 1878dfc00dc5f..f1c9af7070028 100644 --- a/core/src/com/unciv/scripting/utils/ScriptingLock.kt +++ b/core/src/com/unciv/scripting/utils/ScriptingLock.kt @@ -3,7 +3,6 @@ package com.unciv.scripting.utils import com.unciv.UncivGame import com.unciv.ui.utils.BaseScreen import com.unciv.ui.utils.Popup -import com.unciv.ui.utils.ToastPopup import com.unciv.ui.utils.stringifyException import java.lang.IllegalArgumentException import java.lang.IllegalStateException @@ -22,10 +21,10 @@ object ScriptingLock { if (!success) throw IllegalStateException("Cannot acquire ScriptingLock for $name because already in use by $runningName.") runningKey = UUID.randomUUID().toString() return runningKey -// scriptingScope.worldScreen?.isPlayersTurn = false +// ScriptingScope.worldScreen?.isPlayersTurn = false //Hm. Should return to original value, not necessarily true. That means keeping a property, which means I'd rather put this in its own class. //TODO: Move to ScriptingLock. - //Not perfect. I think scriptingScope also exposes mutating the GUI itself, and many things that aren't protected by this? Then again, a script that *wants* to cause a crash/ANR will always be able to do so by just assigning an invalid value or deleting a required node somewhere. Could make mod handlers outside of worldScreen blocking, with written stipulations on (dis)recommended size, and then + //Not perfect. I think ScriptingScope also exposes mutating the GUI itself, and many things that aren't protected by this? Then again, a script that *wants* to cause a crash/ANR will always be able to do so by just assigning an invalid value or deleting a required node somewhere. Could make mod handlers outside of worldScreen blocking, with written stipulations on (dis)recommended size, and then //https://github.com/yairm210/Unciv/pull/5592/commits/a1f51e08ab782ab46bda220e0c4aaae2e8ba21a4 } fun release(releaseKey: String) { @@ -42,17 +41,17 @@ object ScriptingLock { } // TODO: Prevent UI collision, and also prevent recursive script executions. // Allow registering name text of running script, for error messages. - fun notifyPlayerScriptFailure(exception: Throwable) { + fun notifyPlayerScriptFailure(exception: Throwable, asName: String? = null) { // Should this be in ScriptingState after that's been singleton'd? val popup = Popup(UncivGame.Current.screen as BaseScreen) - val msg = "An error has occurred with the mod/script \"$runningName\":\n\n${exception.toString().prependIndent("\t")}\n\nSee system terminal output for details.\nConsider disabling mods if this keeps happening.\n" + val msg = "An error has occurred with the mod/script \"${asName ?: runningName}\":\n\n${exception.toString().prependIndent("\t")}\n\nSee system terminal output for details.\nConsider disabling mods if this keeps happening.\n" popup.addGoodSizedLabel(msg).row() popup.addOKButton{} popup.open(true) - printConsolePlayerScriptFailure(exception) + printConsolePlayerScriptFailure(exception, asName) } - fun printConsolePlayerScriptFailure(exception: Throwable) { - println("\nException with \"$runningName\" script:\n${exception.stringifyException().prependIndent("\t")}\n") + fun printConsolePlayerScriptFailure(exception: Throwable, asName: String? = null) { + println("\nException with \"${asName ?: runningName}\" script:\n${exception.stringifyException().prependIndent("\t")}\n") // Really these should all go to STDERR. } } diff --git a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt index 75ecdc998fde8..b295851f1c6c1 100644 --- a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt @@ -22,7 +22,7 @@ import kotlin.math.min //"I understand and wish to continue." // Probably grey this out for five seconds. //"Get me out of here!" -class ConsoleScreen(val scriptingState: ScriptingState, var closeAction: () -> Unit): BaseScreen() { +class ConsoleScreen(var closeAction: () -> Unit): BaseScreen() { private val layoutTable: Table = Table() @@ -65,7 +65,7 @@ class ConsoleScreen(val scriptingState: ScriptingState, var closeAction: () -> U for (backendtype in ScriptingBackendType.values()) { var backendadder = backendtype.metadata.displayName.toTextButton() backendadder.onClick { - echo(scriptingState.spawnBackend(backendtype).motd) + echo(ScriptingState.spawnBackend(backendtype).motd) updateRunning() } backendsAdders.add(backendadder) @@ -156,20 +156,20 @@ class ConsoleScreen(val scriptingState: ScriptingState, var closeAction: () -> U private fun updateRunning() { runningList.clearChildren() var i = 0 - for (backend in scriptingState.scriptingBackends) { + for (backend in ScriptingState.scriptingBackends) { var button = backend.metadata.displayName.toTextButton() val index = i runningList.add(button) - if (i == scriptingState.activeBackend) { + if (i == ScriptingState.activeBackend) { button.color = Color.GREEN } button.onClick { - scriptingState.switchToBackend(index) + ScriptingState.switchToBackend(index) updateRunning() } var termbutton = ImageGetter.getImage("OtherIcons/Stop") termbutton.onClick { - val exc: Exception? = scriptingState.termBackend(index) + val exc: Exception? = ScriptingState.termBackend(index) updateRunning() if (exc != null) { echo("Failed to stop ${backend.metadata.displayName} backend: ${exc.toString()}") @@ -206,7 +206,7 @@ class ConsoleScreen(val scriptingState: ScriptingState, var closeAction: () -> U private fun echoHistory() { // Doesn't restore autocompletion. I guess that's by design. Autocompletion is a protocol/UI-level feature IMO, and not part of the emulated STDIN/STDOUT. Call `echo()` in `ScriptingState`'s `autocomplete` method if that's a problem. - for (hist in scriptingState.getOutputHistory()) { + for (hist in ScriptingState.getOutputHistory()) { echo(hist) } } @@ -214,7 +214,7 @@ class ConsoleScreen(val scriptingState: ScriptingState, var closeAction: () -> U private fun autocomplete() { val original = inputText val cursorpos = cursorPos - var results = scriptingState.autocomplete(inputText, cursorpos) + var results = ScriptingState.autocomplete(inputText, cursorpos) if (results.isHelpText) { echo(results.helpText) return @@ -245,7 +245,7 @@ class ConsoleScreen(val scriptingState: ScriptingState, var closeAction: () -> U } private fun navigateHistory(increment: Int) { - setText(scriptingState.navigateHistory(increment), SetTextCursorMode.End) + setText(ScriptingState.navigateHistory(increment), SetTextCursorMode.End) } private fun echo(text: String) { @@ -255,7 +255,7 @@ class ConsoleScreen(val scriptingState: ScriptingState, var closeAction: () -> U label.setWrap(true) printHistory.add(label).left().bottom().width(width).padLeft(15f).row() val cells = printHistory.getCells() - while (cells.size > scriptingState.maxOutputHistory && cells.size > 0) { + while (cells.size > ScriptingState.maxOutputHistory && cells.size > 0) { val cell = cells.first() cell.getActor().remove() cells.removeValue(cell, true) @@ -266,12 +266,12 @@ class ConsoleScreen(val scriptingState: ScriptingState, var closeAction: () -> U } private fun run() { - echo(scriptingState.exec(inputText)) + echo(ScriptingState.exec(inputText)) setText("") } fun clone(): ConsoleScreen { - return ConsoleScreen(scriptingState, closeAction).also { + return ConsoleScreen(closeAction).also { it.inputText = inputText it.cursorPos = cursorPos it.setScroll(printScroll.getScrollX(), printScroll.getScrollY(), animate = false) diff --git a/core/src/com/unciv/ui/consolescreen/IConsoleScreenAccessible.kt b/core/src/com/unciv/ui/consolescreen/IConsoleScreenAccessible.kt index 27b8c817a9d25..cdf2ffda9999c 100644 --- a/core/src/com/unciv/ui/consolescreen/IConsoleScreenAccessible.kt +++ b/core/src/com/unciv/ui/consolescreen/IConsoleScreenAccessible.kt @@ -4,6 +4,7 @@ import com.badlogic.gdx.Input import com.unciv.logic.GameInfo import com.unciv.logic.civilization.CivilizationInfo import com.unciv.scripting.ScriptingState +import com.unciv.scripting.api.ScriptingScope import com.unciv.ui.consolescreen.ConsoleScreen import com.unciv.ui.utils.BaseScreen import com.unciv.ui.worldscreen.WorldScreen @@ -18,9 +19,6 @@ interface IConsoleScreenAccessible { val BaseScreen.consoleScreen: ConsoleScreen // TODO: Oh, apparently don't need to explicitly refer to this? Change in other extension functions too, I guess. get() = this.game.consoleScreen - val BaseScreen.scriptingState: ScriptingState - get() = this.game.scriptingState - //Set the console screen tilde hotkey. fun BaseScreen.setOpenConsoleScreenHotkey() { @@ -48,7 +46,7 @@ interface IConsoleScreenAccessible { worldScreen: WorldScreen? = null, mapEditorScreen: MapEditorScreen? = null ) { - this.scriptingState.scriptingScope.also { + ScriptingScope.also { it.gameInfo = gameInfo it.civInfo = civInfo it.worldScreen = worldScreen From 6340815e0a87d2cdc3fb6a00accf84fb3f9614bf Mon Sep 17 00:00:00 2001 From: will-ca Date: Fri, 10 Dec 2021 17:47:51 +0000 Subject: [PATCH 70/93] Scripting mutex. Script execution context. Script-to-JVM exception propagation. Script error popups. Cleanup, close lots of TODOs. Reflective invoke operator, genericize reflective tools. Threaded script runner (not used yet). --- .../enginefiles/python/unciv_lib/api.py | 11 +- .../enginefiles/python/unciv_lib/ipc.py | 4 +- .../enginefiles/python/unciv_lib/wrapping.py | 16 ++- .../unciv_scripting_examples/EventPopup.py | 15 +-- .../python/unciv_scripting_examples/Tests.py | 1 + .../python/unciv_scripting_examples/Utils.py | 11 +- core/Module.md | 9 -- .../com/unciv/scripting/ScriptingBackend.kt | 65 +++++------ .../src/com/unciv/scripting/ScriptingState.kt | 107 ++++++++++-------- .../api/ScriptingApiExecutionContext.kt | 2 +- .../api/ScriptingApiInstanceRegistry.kt | 2 +- .../scripting/api/ScriptingApiJvmHelpers.kt | 17 ++- .../scripting/api/ScriptingApiMappers.kt | 2 +- .../scripting/api/ScriptingModApiHelpers.kt | 53 ++++++--- .../com/unciv/scripting/api/ScriptingScope.kt | 2 + .../scripting/protocol/ScriptingProtocol.kt | 18 +-- .../protocol/ScriptingReplManager.kt | 8 +- .../reflection/FunctionDispatcher.kt | 5 +- .../unciv/scripting/reflection/Reflection.kt | 33 +++--- .../serialization/InstanceTokenizer.kt | 4 +- .../scripting/serialization/TokenizingJson.kt | 1 + .../src/com/unciv/scripting/utils/FakeMaps.kt | 1 + .../utils/ScriptingBackendException.kt | 4 + .../utils/ScriptingDebugParameters.kt | 30 +++++ .../unciv/scripting/utils/ScriptingLock.kt | 57 ---------- .../unciv/scripting/utils/ScriptingRunLock.kt | 47 ++++++++ .../scripting/utils/ScriptingRunThreader.kt | 43 +++++++ .../unciv/ui/consolescreen/ConsoleScreen.kt | 18 ++- 28 files changed, 343 insertions(+), 243 deletions(-) create mode 100644 core/src/com/unciv/scripting/utils/ScriptingBackendException.kt delete mode 100644 core/src/com/unciv/scripting/utils/ScriptingLock.kt create mode 100644 core/src/com/unciv/scripting/utils/ScriptingRunLock.kt create mode 100644 core/src/com/unciv/scripting/utils/ScriptingRunThreader.kt diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/api.py b/android/assets/scripting/enginefiles/python/unciv_lib/api.py index 233b6a134596c..105500eaaf36e 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/api.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/api.py @@ -130,19 +130,20 @@ def EvalForeignMotd(self, packet): Press [TAB] at any time to trigger autocompletion at the current cursor position, or display help text for an empty function call. -"""#TODO: Replace current imports with startup command managed by ConsoleScreen and GameSettings. +""", ()#TODO: Replace current imports with startup command managed by ConsoleScreen and GameSettings. @ipc.receiverMethod('autocomplete', 'autocomplete_response') def EvalForeignAutocomplete(self, packet): assert 'PassMic' in packet.flags, f"Expected 'PassMic' in packet flags: {packet}" res = self.autocompleter.GetAutocomplete(packet.data["command"], packet.data["cursorpos"]) if self.autocompleter else "No autocompleter set." self.passMic() - return res + return res, () @ipc.receiverMethod('exec', 'exec_response') def EvalForeignExec(self, packet): line = packet.data assert 'PassMic' in packet.flags, f"Expected 'PassMic' in packet flags: {packet}" with ipc.FakeStdout() as fakeout: print(f">>> {str(line)}") + isException = False try: try: code = compile(line, 'STDIN', 'eval') @@ -151,14 +152,14 @@ def EvalForeignExec(self, packet): else: print(repr(eval(code, self.scope, self.scope))) except Exception as e: - #TODO: Return 'Exception' flag in packet here. print(utils.formatException(e)) + isException = True finally: self.passMic() - return fakeout.getvalue() + return fakeout.getvalue(), (('Exception',) if isException else ()) @ipc.receiverMethod('terminate', 'terminate_response') def EvalForeignTerminate(self, packet): - return None + return None, () from . import wrapping diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/ipc.py b/android/assets/scripting/enginefiles/python/unciv_lib/ipc.py index 01e382e811851..1b764e0163e90 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/ipc.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/ipc.py @@ -97,10 +97,10 @@ def RespondForeignAction(self, request): rdata = None if action in self._responders: method, raction = self._responders[action] - rdata = method(decoded) + rdata, rflags = method(decoded) else: raise ForeignError("Unknown action type for foreign action request: " + repr(decoded)) - self.sender(ForeignPacket(raction, decoded.identifier, rdata).serialized()) + self.sender(ForeignPacket(raction, decoded.identifier, rdata, rflags).serialized()) def AwaitForeignAction(self):#, *, ignoreempty=True): self.RespondForeignAction(self.receiver()) def ForeignREPL(self): diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py index 33a603b10871e..3a96d04195a72 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py @@ -86,6 +86,8 @@ def stringPathList(pathlist): return "".join(items) +operator.hash = hash + _magicmeths = ( '__lt__', '__le__', @@ -114,15 +116,16 @@ def stringPathList(pathlist): '__pos__', '__pow__', '__rshift__', # I think one of these might be used for int(), which seems to mysteriously work? - '__sub__', # Further indication that a bitshift is used for int(): A wrapped foreign float can't be converted. + '__sub__', # Further indication that a bitshift or something is used for int(): A wrapped foreign float can't be converted. '__truediv__', '__xor__', '__concat__', '__contains__', # Implemented through foreign request. - '__delitem__', # Should be implemented through foreign request if it is to be supported. + '__delitem__', # Implemented through foreign request. '__getitem__', # Implemented through foreign request. # @indexOf # Not actually totally sure what this is. I thought it was implemented in lists and tuples as `.index()`? # '__setitem__', # Implemented through foreign request. + ('__hash__', 'hash') # Monkey-patched into operator module above. ) _rmagicmeths = ( @@ -158,10 +161,11 @@ def stringPathList(pathlist): '__ior__' ) -_tokensafemethods = { # TODO +_tokensafemethods = { '__eq__', - '__ne__' -} # TODO: Hash. + '__ne__', + '__hash__' +} def resolveForOperators(cls): """Decorator. Adds missing magic methods to a class, which resolve their arguments with `api.real(a)`.""" @@ -174,7 +178,7 @@ def alreadyhas(name): name, opname = meth if not alreadyhas(name): # Set the magic method only if neither it nor any of its base classes have already defined a custom implementation. - setattr(cls, name, resolvingFunction(getattr(operator, opname), allowforeigntokens=False)) + setattr(cls, name, resolvingFunction(getattr(operator, opname), allowforeigntokens=name in _tokensafemethods)) for rmeth in _rmagicmeths: normalname = rmeth.replace('__r', '__', 1) if not alreadyhas(rmeth) and hasattr(cls, normalname): diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EventPopup.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EventPopup.py index 745baca752c0c..63e9abf0271ad 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EventPopup.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EventPopup.py @@ -5,6 +5,8 @@ #from unciv_scripting_examples.EventPopup import *; r=showEventPopup(**EVENT_POPUP_DEMOARGS()) +# modApiHelpers.lambdifyReadPathcode(None, 'apiHelpers.Jvm.constructorByQualname["com.unciv.ui.utils.ToastPopup"]("Test", uncivGame.getScreen(), 8000)') + #apiHelpers.Jvm.constructorByQualname["com.unciv.ui.utils.ToastPopup"]("Test", uncivGame.getScreen(), 8000) #from . import Utils @@ -13,7 +15,7 @@ Constructors = apiHelpers.Jvm.constructorByQualname ExtensionFunctions = apiHelpers.Jvm.functionByQualClassAndName['com.unciv.ui.utils.ExtensionFunctionsKt'] -Singletons = apiHelpers.Jvm.kotlinSingletonByQualname +Singletons = apiHelpers.Jvm.singletonByQualname Constants = Singletons['com.unciv.Constants'] # With bind-by-reference, doing fewer key and attribute accesses by doing them earlier should actually be faster. def showPopup(): @@ -43,16 +45,7 @@ def showPopup2(): pass # Make button show toast and do something else. -def execCodeInModule(code): - exec(code, globals(), None) - -def makeLocalLambda(code): - assert isinstance(code, str) - f'import unciv_scripting_examples.EventPopup; unciv_scripting_examples.EventPopup.execCodeInModule({repr(code)})' - # Could cache a compile(code) if Python performance is a huge issue. - - -Companions = apiHelpers.Jvm.kotlinCompanionByQualClass +Companions = apiHelpers.Jvm.companionByQualClass Enums = apiHelpers.Jvm.enumMapsByQualname GdxColours = apiHelpers.Jvm.staticPropertyByQualClassAndName['com.badlogic.gdx.graphics.Color'] #StatColours = TODO diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py index 412bc119c1c1b..4e1935d676694 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py @@ -261,4 +261,5 @@ def NoPrivatesTest(start, maxdepth, *, _depth=0, _failures=None, _namestack=None #Probably don't bother with DOCTEST, or anything. Just use assert statements where needed, print out any errors, and check in the build tests that there's no exceptions (by flag, or by printout value). +#ForeignObject equality and hash comparisons. diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Utils.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Utils.py index e7d8c11366242..ad2406a8f4799 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Utils.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Utils.py @@ -1,4 +1,4 @@ -import os, atexit, random, time +import os, atexit, random, time, sys import unciv, unciv_pyhelpers @@ -51,6 +51,15 @@ def __exit__(self, *exc): self.memallocKeys.clear() +def execCodeInModule(moduleQualname, code): + exec(code, sys.modules[moduleQualname].__dict__, None) + +def makeLocalLambdaCode(moduleQualname, code): + """Return a Python code string that, when executed, will execute the given code string inside the module at the given qualified name.""" + return f'import {__name__}; {__name__}.execCodeInModule({repr(moduleQualname)}, {repr(code)})' + # Could cache a compile(code) if Python performance is a huge issue. + + @atexit.register def on_exit(): # I don't think this actually works. diff --git a/core/Module.md b/core/Module.md index 062d043034670..cf5212d36fba9 100644 --- a/core/Module.md +++ b/core/Module.md @@ -337,15 +337,6 @@ Flags are string values for communicating extra information that doesn't need a ``` 'Exception' //Indicates that this packet is associated with an error. - //Currently sent only by Kotlin side and handled only by Python backend. Modding API and build tests will need this to be implemented in the other direction too. - ``` - - ``` - //'BeginIteration' - //'EndIteration' - //Not implemented. Probably needed if iteration over non-sized objects is needed. Probably not worth the trouble. - //Deprecated without ever being implemented. Lack of indices for such objects means you wouldn't be able to directly do anything with their iterated results anyway in the same way that you can for "lists" "iterated" by appending indices to their paths, as you wouldn't have a path by which to refer back to them. - //Alternate solutions: Sets are already serialized as JSON arrays, which can be resolved in running scripts. Script-accessible Kotlin-side helper functions can be defined to convert other containers to lists, or if needed, to yield their values per call. Instance tokens arising from these operations can be assigned by running scripts to a name on the Kotlin side, creating a concrete path by which to reflectively access their own members. ``` --- diff --git a/core/src/com/unciv/scripting/ScriptingBackend.kt b/core/src/com/unciv/scripting/ScriptingBackend.kt index 1a9a60710589b..cdba5ea62a5f4 100644 --- a/core/src/com/unciv/scripting/ScriptingBackend.kt +++ b/core/src/com/unciv/scripting/ScriptingBackend.kt @@ -22,12 +22,9 @@ import kotlin.reflect.full.companionObjectInstance * @property matches List of valid matches. Each match should be a full input string after applying autocompletion (I.E. Don't truncate to the cursor position). * @property helpText String to print out instead of showing autocomplete matches. */ -//TODO: Probably replace isHelpText with a nullable helpText. -data class AutocompleteResults(val matches: List = listOf(), val isHelpText: Boolean = false, val helpText: String = "") - +data class AutocompleteResults(val matches: List = listOf(), val helpText: String? = null) data class ExecResult(val resultPrint: String, val isException: Boolean = false) -//TODO: Use this. /** @@ -76,9 +73,8 @@ interface ScriptingImplementation { * @param command Code to execute * @return REPL printout. */ - fun exec(command: String): String { - //TODO: To support modding (and more specifically, error catching in mod development and use), this (and everything that parallels/implements it) should eventually support returning a Boolean to flag the REPL printout as an error message, in addition to the fake STDOUT printout. - return command + fun exec(command: String): ExecResult { + return ExecResult(command) } /** @@ -99,11 +95,6 @@ interface ScriptingImplementation { // TODO: Add note that methods should be called through ScriptingState, rather than directly. open class ScriptingBackend: ScriptingImplementation { -//A little bit confusing. -//Hm. Couldn't the metadata getter go on the interface? -//That would mean this exists only as a code example for defining the companion object. -//TODO: Move getter, delete class or rename to DummyScriptingBackend? - /** * For the UI, a way is needed to list all available scripting backend types with 1. A readable display name and 2. A way to create new instances. * @@ -168,14 +159,14 @@ class HardcodedScriptingBackend(): ScriptingBackend() { } override fun autocomplete(command: String, cursorPos: Int?): AutocompleteResults{ - if (' ' in command) { - return AutocompleteResults(listOf(), true, getCommandHelpText(command.split(' ')[0])) + return if (' ' in command) { + AutocompleteResults(helpText = getCommandHelpText(command.split(' ')[0])) } else { - return AutocompleteResults(commandshelp.keys.filter({ c -> c.startsWith(command) }).map({ c -> c + " " })) + AutocompleteResults(commandshelp.keys.filter({ c -> c.startsWith(command) }).map({ c -> c + " " })) } } - override fun exec(command: String): String { + override fun exec(command: String): ExecResult { var args = command.split(' ') var out = "\n> ${command}\n" fun appendOut(text: String) { @@ -190,12 +181,12 @@ class HardcodedScriptingBackend(): ScriptingBackend() { } } "countcities" -> { - if (!(ScriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } + if (!(ScriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return ExecResult(out) } appendOut(ScriptingScope.civInfo!!.cities.size.toString()) } "locatebuildings" -> { var buildingcities: List = listOf() - if (!(ScriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } + if (!(ScriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return ExecResult(out) } if (args.size > 1) { var searchfor = args.slice(1..args.size-1).joinToString(" ").trim(' ') buildingcities = ScriptingScope.civInfo!!.cities @@ -211,7 +202,7 @@ class HardcodedScriptingBackend(): ScriptingBackend() { } "missingbuildings" -> { var buildingcities: List = listOf() - if (!(ScriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } + if (!(ScriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return ExecResult(out) } if (args.size > 1) { var searchfor = args.slice(1..args.size-1).joinToString(" ").trim(' ') buildingcities = ScriptingScope.civInfo!!.cities @@ -221,7 +212,7 @@ class HardcodedScriptingBackend(): ScriptingBackend() { appendOut(buildingcities.joinToString(", ")) } "listcities" -> { - if (!(ScriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } + if (!(ScriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return ExecResult(out) } appendOut(ScriptingScope.civInfo!!.cities .map { city -> city.name } .joinToString(", ") @@ -236,7 +227,7 @@ class HardcodedScriptingBackend(): ScriptingBackend() { appendOut("Cheats disabled.") } "godmode" -> { - if (!(ScriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } + if (!(ScriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return ExecResult(out) } if (cheats) { var godmode = if (args.size > 1) args[1].toBoolean() else !(ScriptingScope.gameInfo!!.gameParameters.godMode) ScriptingScope.gameInfo!!.gameParameters.godMode = godmode @@ -246,7 +237,7 @@ class HardcodedScriptingBackend(): ScriptingBackend() { } } "godview" -> { - if (!(ScriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } + if (!(ScriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return ExecResult(out) } if (cheats) { var godview = if (args.size > 1) args[1].toBoolean() else !(ScriptingScope.uncivGame!!.viewEntireMapForDebug) ScriptingScope.uncivGame!!.viewEntireMapForDebug = godview @@ -256,7 +247,7 @@ class HardcodedScriptingBackend(): ScriptingBackend() { } } "inspectpath" -> { - if (!(ScriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } + if (!(ScriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return ExecResult(out) } if (cheats) { val detailed = args.size > 1 && args[1] == "detailed" val startindex = if (detailed) 2 else 1 @@ -296,7 +287,7 @@ class HardcodedScriptingBackend(): ScriptingBackend() { } } "simulatetoturn" -> { - if (!(ScriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } + if (!(ScriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return ExecResult(out) } if (cheats) { var numturn = 0 if (args.size > 1) { @@ -313,7 +304,7 @@ class HardcodedScriptingBackend(): ScriptingBackend() { } } "supercharge" -> { - if (!(ScriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return out } + if (!(ScriptingScope.apiHelpers.isInGame)) { appendOut("Must be in-game for this command!"); return ExecResult(out) } if (cheats) { var supercharge = if (args.size > 1) args[1].toBoolean() else !(ScriptingScope.uncivGame!!.superchargedForDebug) ScriptingScope.uncivGame!!.superchargedForDebug = supercharge @@ -325,7 +316,7 @@ class HardcodedScriptingBackend(): ScriptingBackend() { appendOut("The command ${args[0]} is either not known or not implemented.") } } - return out + return ExecResult(out) //return "\n> ${command}\n${out}" } } @@ -371,7 +362,7 @@ class ReflectiveScriptingBackend(): ScriptingBackend() { val workingcode = params[params.size-1] val workingpath = Reflection.parseKotlinPath(workingcode) if (workingpath.any { it.type == Reflection.PathElementType.Call }) { - return AutocompleteResults(listOf(), true, "No autocomplete available for function calls.") + return AutocompleteResults(helpText = "No autocomplete available for function calls.") } val leafname = if (workingpath.isNotEmpty()) workingpath[workingpath.size - 1].name else "" val prefix = command.dropLast(leafname.length) @@ -386,11 +377,11 @@ class ReflectiveScriptingBackend(): ScriptingBackend() { return AutocompleteResults(commandparams.keys.filter { it.startsWith(command) }.map { it+" " }) } } catch (e: Exception) { - return AutocompleteResults(listOf(), true, "Could not get autocompletion: ${e}") + return AutocompleteResults(helpText = "Could not get autocompletion: ${e}") } } - override fun exec(command: String): String{ + override fun exec(command: String): ExecResult { var parts = command.split(' ', limit=2) var out = "\n> ${command}\n" fun appendOut(text: String) { @@ -425,7 +416,7 @@ class ReflectiveScriptingBackend(): ScriptingBackend() { } catch (e: Exception) { appendOut("Error evaluating command: ${e}") } - return out + return ExecResult(out) } } @@ -478,18 +469,18 @@ abstract class BlackboxScriptingBackend(): EnvironmentedScriptingBackend() { } override fun autocomplete(command: String, cursorPos: Int?): AutocompleteResults { - try { - return replManager.autocomplete(command, cursorPos) + return try { + replManager.autocomplete(command, cursorPos) } catch (e: Exception) { - return AutocompleteResults(isHelpText = true, helpText = "Autocomplete error: ${e}") + AutocompleteResults(helpText = "Autocomplete error: ${e}") } } - override fun exec(command: String): String { + override fun exec(command: String): ExecResult { try { return replManager.exec("${command}\n") } catch (e: RuntimeException) { - return "${e}" + return ExecResult("${e}") } } @@ -597,7 +588,7 @@ class DevToolsScriptingBackend(): ScriptingBackend() { override fun autocomplete(command: String, cursorPos: Int?) = AutocompleteResults(commands.filter { it.startsWith(command) }) - override fun exec(command: String): String { + override fun exec(command: String): ExecResult { val commv = command.split(' ', limit=2) var out = "> ${command}\n" try { @@ -616,7 +607,7 @@ class DevToolsScriptingBackend(): ScriptingBackend() { } catch (e: Exception) { out += e.toString() } - return out + return ExecResult(out) } } diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index 8f31d7cd3954f..7aa5bea6fc2f7 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -1,6 +1,8 @@ package com.unciv.scripting import com.unciv.scripting.api.ScriptingScope +import com.unciv.scripting.utils.ScriptingRunLock +import com.unciv.scripting.utils.makeScriptingRunName import com.unciv.ui.utils.clipIndexToBounds import com.unciv.ui.utils.enforceValidIndex import kotlin.collections.ArrayList @@ -11,8 +13,6 @@ import kotlin.collections.ArrayList // Hm. It seems that Sequence performance isn't even a simple question of number of loops, and is also affected by boxed types and who know what else. // Premature optimization and such. Clearly long chains of loops can be rewritten as sequences. -// TODO: Replace Exception types with Throwable? Wait, no. Apparently that just includes "serious problems that a reasonable application should not try to catch." - // TODO: There's probably some public vars that can/should be private set. // TODO: Mods blacklist, for security threats. @@ -27,10 +27,9 @@ import kotlin.collections.ArrayList * * @property ScriptingScope ScriptingScope instance at the root of all scripting API. */ -//TODO: Actually, probably should be only one instance in game, since various context changes set various ScriptingScope properties through it, and being able to view mod command history will also be useful. -// Yeah, singleton both this and ScriptingScope, I think?… There would be some benefits from having one ScriptingScoper per ScriptingBackend (namely: concurrent script executions), but it would come with a significant cost to how ScriptingScope is connected to ScriptingState and how the properties in it are currently updated (and that one benefit sounds like a can of Heisenbugs). //This will be responsible for: Using the lock, threading, passing the entrypoint name to the lock, exposing context/running backend in ScriptingScope, and setting handler context arguments. object ScriptingState { + // Singletons. Biggest benefit to having multiple ScriptingStates/ScriptingScopes would be concurrent execution of different engines with different states, which sounds more like a nightmare than a benefit. val scriptingBackends = ArrayList() @@ -38,6 +37,7 @@ object ScriptingState { private val commandHistory = ArrayList() var activeBackend: Int = 0 + private set val maxOutputHistory: Int = 511 val maxCommandHistory: Int = 511 @@ -52,36 +52,26 @@ object ScriptingState { fun spawnBackend(backendtype: ScriptingBackendType): BackendSpawnResult { val backend: ScriptingBackend = backendtype.metadata.new() scriptingBackends.add(backend) - activeBackend = scriptingBackends.size - 1 + activeBackend = scriptingBackends.lastIndex val motd = backend.motd() echo(motd) return BackendSpawnResult(backend, motd) } + fun getIndexOfBackend(backend: ScriptingBackend): Int? { + val index = scriptingBackends.indexOf(backend) + return if (index >= 0) + index + else + null + } + fun switchToBackend(index: Int) { scriptingBackends.enforceValidIndex(index) activeBackend = index } - fun switchToBackend(backend: ScriptingBackend) { - // TODO: Apparently there's a bunch of extensions like .withIndex(), .indices, and .lastIndex that I can use to replace a lot of stuff currently done with .size. - val index = scriptingBackends.indexOf(backend) - if (index >= 0) - return switchToBackend(index = index) -// for ((i, b) in scriptingBackends.withIndex()) { -// if (b == backend) { -// return switchToBackend(index = i) -// } -// } - throw IllegalArgumentException("Could not find scripting backend base: ${backend}") - } - -// fun switchToBackend(displayName: String) { -// // Are these really necessary? -// } -// -// fun switchToBackend(backendType: ScriptingBackendType) { -// } + fun switchToBackend(backend: ScriptingBackend) = switchToBackend(getIndexOfBackend(backend)!!) fun termBackend(index: Int): Exception? { scriptingBackends.enforceValidIndex(index) @@ -96,6 +86,8 @@ object ScriptingState { return result } + fun termBackend(backend: ScriptingBackend) = termBackend(getIndexOfBackend(backend)!!) + fun hasBackend(): Boolean { return scriptingBackends.isNotEmpty() } @@ -116,7 +108,7 @@ object ScriptingState { fun autocomplete(command: String, cursorPos: Int? = null): AutocompleteResults { // Deliberately not calling echo() to add into history because I consider autocompletion a protocol/API/UI level feature if (!(hasBackend())) { - return AutocompleteResults(listOf(), false, "") + return AutocompleteResults() } return getActiveBackend().autocomplete(command, cursorPos) } @@ -130,28 +122,53 @@ object ScriptingState { } } - fun exec(command: String): String { // TODO: Allow "passing" args that get assigned to something under ScriptingScope here. - // TODO: Allow passing a name to use with ScriptingLock. - //ScriptingScope.scriptingBackend = - if (command.length > 0) { - if (command != commandHistory.lastOrNull()) - commandHistory.add(command) - while (commandHistory.size > maxCommandHistory) { - commandHistory.removeAt(0) - // No need to restrict activeCommandHistory to valid indices here because it gets set to zero anyway. - // Also probably O(n) to remove from start.. + // @throws IllegalStateException On failure to acquire scripting lock. + @Synchronized fun exec(command: String, asName: String? = null, withParams: Map? = null): ExecResult { + val backend = getActiveBackend() + val name = asName ?: makeScriptingRunName(this::class.simpleName, backend) + val releaseKey: String + releaseKey = ScriptingRunLock.acquire(name) + // Lock acquisition failure gets propagated as Exception, rather than as return. E.G.: Lets lambdas (from modApiHelpers) fail and trigger their own error handling (exposing misbehaving mods to the user). + // isException in ExecResult return value means exception in completely opaque scripting backend. Kotlin exception should still be thrown and propagated like normal. + try { + ScriptingScope.apiExecutionContext.apply { + handlerParameters = withParams + scriptingBackend = backend + } + if (command.isNotEmpty()) { + if (command != commandHistory.lastOrNull()) + commandHistory.add(command) + while (commandHistory.size > maxCommandHistory) { + commandHistory.removeAt(0) + // No need to restrict activeCommandHistory to valid indices here because it gets set to zero anyway. + // Also probably O(n) to remove from start.. + } + } + activeCommandHistory = 0 + var out = if (hasBackend()) backend.exec(command) else ExecResult("") + echo(out.resultPrint) + return out + } finally { + ScriptingScope.apiExecutionContext.apply { + handlerParameters = null + scriptingBackend = null } + ScriptingRunLock.release(releaseKey) } - activeCommandHistory = 0 - var out = if (hasBackend()) getActiveBackend().exec(command) else "" - echo(out) - //ScriptingScope.scriptingBackend = null // TODO - return out } -// fun acquireScriptLock() { -// } - -// fun releaseScriptLock() { -// } + fun exec(command: String, asName: String? = null, withParams: Map? = null, withBackend: ScriptingBackend): ExecResult { + switchToBackend(withBackend) + return exec( + command = command, + asName = asName, + withParams = withParams + ) + } } + +// UI locking can honestly probably go into the mod script dispatcher thingy. +// ScriptingScope.worldScreen?.isPlayersTurn = false +//Hm. Should return to original value, not necessarily true. That means keeping a property, which means I'd rather put this in its own class. +//Not perfect. I think ScriptingScope also exposes mutating the GUI itself, and many things that aren't protected by this? Then again, a script that *wants* to cause a crash/ANR will always be able to do so by just assigning an invalid value or deleting a required node somewhere. Could make mod handlers outside of worldScreen blocking, with written stipulations on (dis)recommended size, and then +//https://github.com/yairm210/Unciv/pull/5592/commits/a1f51e08ab782ab46bda220e0c4aaae2e8ba21a4 diff --git a/core/src/com/unciv/scripting/api/ScriptingApiExecutionContext.kt b/core/src/com/unciv/scripting/api/ScriptingApiExecutionContext.kt index 396ca4618b821..f7a1524ee3f4f 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiExecutionContext.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiExecutionContext.kt @@ -6,5 +6,5 @@ object ScriptingApiExecutionContext { var handlerParameters: Map? = null // Why not just use a map? String keys will be clearer in scripts than integers anyway. // Collection that gets replaced with any contextual parameters when running script handlers. E.G. Unit moved, city founded, tech researched, construction finished. - var scriptingBackend: ScriptingBackend? = null // TODO: Currently executing backend. + var scriptingBackend: ScriptingBackend? = null } diff --git a/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt b/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt index 822a96b92a33f..578b02dafd947 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt @@ -53,7 +53,7 @@ object ScriptingApiInstanceRegistry: MutableMap { get() = backingMap.size override fun containsKey(key: String) = backingMap.containsKey(key) override fun containsValue(value: Any?) = backingMap.containsValue(value) - override fun get(key: String): Any? { //FIXME: Operator modifiers? + override fun get(key: String): Any? { if (key !in this) { throw NoSuchElementException("\"${key}\" not in ${this}.") } diff --git a/core/src/com/unciv/scripting/api/ScriptingApiJvmHelpers.kt b/core/src/com/unciv/scripting/api/ScriptingApiJvmHelpers.kt index a96553362d710..f55b890905d22 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiJvmHelpers.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiJvmHelpers.kt @@ -19,7 +19,7 @@ fun enumQualnameToMap(qualName: String) = Class.forName(qualName).enumConstants. // Always return a built-in Map class instance here, so its gets serialized as JSON object instead of tokenized, and scripts can refer directly to its items. // I cast to Enum<*> fully expecting it would crash because it felt metaclass-y. But apparently it's just a base class, so it works? -private const val exposeStates = true // TODO: Probably keep this false? +private const val exposeStates = false /** * For use in ScriptingScope. Allows interpreted scripts access Kotlin/JVM class functionality that isn't attached to any application instances. @@ -28,22 +28,21 @@ object ScriptingApiJvmHelpers { val enumMapsByQualname = LazyMap(::enumQualnameToMap) - val kotlinClassByQualname = LazyMap({ qualName: String -> Class.forName(qualName).kotlin }, exposeState = exposeStates) // TODO: Maybe skip the "kotlin" prefix on these? It's already in the "Jvm" object, not everything has the prefix for some reason, and the first word after it is usually what you're actually looking for. + val classByQualname = LazyMap({ qualName: String -> Class.forName(qualName).kotlin }, exposeState = exposeStates) - val kotlinSingletonByQualname = LazyMap({ qualName: String -> kotlinClassByQualname[qualName]?.objectInstance }, exposeState = exposeStates) + val singletonByQualname = LazyMap({ qualName: String -> classByQualname[qualName]?.objectInstance }, exposeState = exposeStates) - val kotlinCompanionByQualClass = LazyMap({ qualName: String -> kotlinClassByQualname[qualName]?.companionObjectInstance }, exposeState = exposeStates) + val companionByQualClass = LazyMap({ qualName: String -> classByQualname[qualName]?.companionObjectInstance }, exposeState = exposeStates) val functionByQualClassAndName = LazyMap({ jclassQualname: String -> val cls = Class.forName(jclassQualname) LazyMap({ methodName: String -> makeFunctionDispatcher(cls.getDeclaredMethods().asSequence().filter { it.name == methodName }.map { it.kotlinFunction }.filterNotNull().toList() as List>) }, exposeState = exposeStates) - // Could initialize the second LazyMap here by accessing for all names— Only benefit would be for autocomplete, at higher first-call time and memory use, though. }, exposeState = exposeStates) // apiHelpers.Jvm.functionByQualClassAndName["com.unciv.ui.utils.ExtensionFunctionsKt"]["toLabel"]("Test") // TODO: Right... Extension properties? // Class.forName("kotlin.reflect.full.KClasses").getMethods().map{it.name} - // apiHelpers.Jvm.functionByQualClassAndName["kotlin.reflect.full.KClasses"]["getFunctions"](apiHelpers.Jvm.kotlinClassByQualname["com.badlogic.gdx.scenes.scene2d.ui.Cell"]) + // apiHelpers.Jvm.functionByQualClassAndName["kotlin.reflect.full.KClasses"]["getFunctions"](apiHelpers.Jvm.classByQualname["com.badlogic.gdx.scenes.scene2d.ui.Cell"]) // Right. .kotlinFunction is null for extension property getters: // Class.forName("kotlin.reflect.full.KClasses").getDeclaredMethods().first{it.name == "getFunctions"}.kotlinFunction @@ -51,13 +50,13 @@ object ScriptingApiJvmHelpers { val kcls = Class.forName(jclassQualname).kotlin LazyMap({ name: String -> Reflection.readClassProperty(kcls, name) as Any? }, exposeState = exposeStates) }, exposeState = exposeStates) - // apiHelpers.Jvm.kotlinClassByQualname["com.badlogic.gdx.graphics.Color"].members[50].get() + // apiHelpers.Jvm.classByQualname["com.badlogic.gdx.graphics.Color"].members[50].get() // apiHelpers.Jvm.staticPropertyByQualClassAndName["com.badlogic.gdx.graphics.Color"]['WHITE'] val constructorByQualname = LazyMap({ qualName: String -> makeFunctionDispatcher(Class.forName(qualName).kotlin.constructors) }, exposeState = exposeStates) // TODO (Later, Maybe): This would actually be quite easy to whitelist by package paths. - val kotlinClassByInstance = FakeMap{ obj: Any? -> obj!!::class } + val classByInstance = FakeMap{ obj: Any? -> obj!!::class } fun toString(obj: Any?) = obj.toString() @@ -75,7 +74,7 @@ object ScriptingApiJvmHelpers { fun arrayOfTyped4(item1: Any?, item2: Any?, item3: Any?, item4: Any?) = arrayOfTyped(listOf(item1, item2, item3, item4)) fun arrayOfTyped5(item1: Any?, item2: Any?, item3: Any?, item4: Any?, item5: Any?) = arrayOfTyped(listOf(item1, item2, item3, item4, item5)) - fun toList(array: Array<*>) = array.toList() // sorted([real(m.getName()) for m in apiHelpers.Jvm.kotlinClassByQualname["kotlin.collections.ArraysKt"].jClass.getMethods()]) + fun toList(array: Array<*>) = array.toList() // sorted([real(m.getName()) for m in apiHelpers.Jvm.classByQualname["kotlin.collections.ArraysKt"].jClass.getMethods()]) fun toList(iterable: Iterable<*>) = iterable.toList() fun toList(sequence: Sequence<*>) = sequence.toList() } diff --git a/core/src/com/unciv/scripting/api/ScriptingApiMappers.kt b/core/src/com/unciv/scripting/api/ScriptingApiMappers.kt index 23f6e146d9e6d..a62195c33f93e 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiMappers.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiMappers.kt @@ -3,7 +3,7 @@ package com.unciv.scripting.api import com.unciv.scripting.reflection.Reflection import com.unciv.scripting.utils.LazyMap -// TODO: Rename this. +// Not sure about the name, but not sure what would be better. object ScriptingApiMappers { diff --git a/core/src/com/unciv/scripting/api/ScriptingModApiHelpers.kt b/core/src/com/unciv/scripting/api/ScriptingModApiHelpers.kt index 1f01795efa5ee..4f8d32974daf2 100644 --- a/core/src/com/unciv/scripting/api/ScriptingModApiHelpers.kt +++ b/core/src/com/unciv/scripting/api/ScriptingModApiHelpers.kt @@ -1,59 +1,78 @@ package com.unciv.scripting.api +import com.unciv.scripting.ScriptingState import com.unciv.scripting.reflection.Reflection -import com.unciv.scripting.utils.ScriptingLock +import com.unciv.scripting.utils.ScriptingBackendException +import com.unciv.scripting.utils.ScriptingErrorHandling +import com.unciv.scripting.utils.ScriptingRunLock // Wrapper for a function that takes no arguments. +// +// Presents as a reflection-friendly invokable instance +// Catches all thrown Exceptions and shows error dialogs instead. +// +// @param asName Textual identifier for this function to use in error messages. // @param func The function to wrap. -class LambdaWrapper0(func: () -> R?): () -> R? { - // Kotlin reflection has difficulties with the anonymous classes used for Lambdas, and this seems easier than trying to figure out why and cleaner than trying to work through it. +class LambdaWrapper0(asName: String? = null, func: () -> R?): () -> R? { // Making private messes with calling reflectively. val lambda: () -> R? = func.unwrapped() - val suppressing: () -> R? = lambda.reportExceptionsAsScriptErrors() + val suppressing: () -> R? = lambda.reportExceptionsAsScriptErrors(asName) override fun invoke() = suppressing() } // Extension function to strip a zero-argument function of a wrapping LambdaWrapper0 if present. -fun (() -> R?).unwrapped() = if (this is LambdaWrapper0) this.lambda else this +private fun (() -> R?).unwrapped() = if (this is LambdaWrapper0) this.lambda else this // Extension function to wrap a zero-argument function to suppress all exceptions, instead returning null and notifying the player. -fun (() -> R).reportExceptionsAsScriptErrors(asName: String? = null): () -> R? { +private fun (() -> R).reportExceptionsAsScriptErrors(asName: String? = null): () -> R? { return { try { this() } catch (e: Exception) { - ScriptingLock.notifyPlayerScriptFailure(e, asName = asName) + ScriptingErrorHandling.notifyPlayerScriptFailure(e, asName = asName) null } } } +private val alphanumeric = ('A'..'Z') + ('a'..'z') + ('0'..'9') +private fun (String?).lambdaName() = "${this}+λ${(1..3).map { alphanumeric.random() }.joinToString("")}" object ScriptingModApiHelpers { - //fun lambdifyExecScript(code: String ): () -> Unit = fun(){ ScriptingScope.uncivGame!!.scriptingState.exec(code) } // FIXME: Requires awareness of which scriptingState and which backend to use. - //Directly invoking the resulting lambda from a running script will almost certainly break the REPL loop/IPC protocol. + fun lambdifyExecScript(code: String): () -> Unit? { + val backend = ScriptingScope.apiExecutionContext.scriptingBackend!! + val name = ScriptingRunLock.runningName.lambdaName() + return LambdaWrapper0(name) { + val execResult = ScriptingState.exec( + command = code, + asName = name, + withBackend = backend + ) + if (execResult.isException) { + throw ScriptingBackendException(execResult.resultPrint) + } // Thrown exception will be caught by reportExceptionsAsScriptErrors, and show error dialog. + Unit + } + } //setTimeout? // Probably don't implement this. But see ToastPopup.startTimer() if you do. fun lambdifyReadPathcode(instance: Any?, pathcode: String): () -> Any? { val path = Reflection.parseKotlinPath(pathcode) - return LambdaWrapper0 { Reflection.resolveInstancePath(instance ?: ScriptingScope, path) } + return LambdaWrapper0(ScriptingRunLock.runningName.lambdaName()) { Reflection.resolveInstancePath(instance ?: ScriptingScope, path) } } fun lambdifyAssignPathcode(instance: Any?, pathcode: String, value: Any?): () -> Unit? { val path = Reflection.parseKotlinPath(pathcode) - return LambdaWrapper0 { Reflection.setInstancePath(instance ?: ScriptingScope, path, value) } + return LambdaWrapper0(ScriptingRunLock.runningName.lambdaName()) { Reflection.setInstancePath(instance ?: ScriptingScope, path, value) } } fun lambdifyAssignPathcode(instance: Any?, pathcode: String, value: () -> Any?): () -> Unit? { val path = Reflection.parseKotlinPath(pathcode) val getter = value.unwrapped() - return LambdaWrapper0 { Reflection.setInstancePath(instance ?: ScriptingScope, path, getter()) } + return LambdaWrapper0(ScriptingRunLock.runningName.lambdaName()) { Reflection.setInstancePath(instance ?: ScriptingScope, path, getter()) } } -// fun lambdifyCallWithArgs() // Wouldn't it be easier to just use lambdifyExecScript? This is rapidly growing into its own, very verbose, functional programming language otherwise. -// fun lambdifyCallWithDynamicArgs() // Okay, these aren't possible unless you type as Function, due to static arg counts, I think. fun lambdifySuppressReturn(func: () -> R): () -> Unit? { val lambda = func.unwrapped() - return LambdaWrapper0 { lambda(); null } + return LambdaWrapper0(ScriptingRunLock.runningName.lambdaName()) { lambda(); null } } fun lambdifyCombine(funcs: List<() -> Any?>): () -> Unit? { val lambdas = funcs.map { it.unwrapped() } - return LambdaWrapper0 { for (f in lambdas) { f() } } + return LambdaWrapper0(ScriptingRunLock.runningName.lambdaName()) { for (f in lambdas) { f() } } } - //fun lambdifyReduce // Not sure about this, since so far the model for script-created lambdas makes sense only for side effects, and not for return values. } diff --git a/core/src/com/unciv/scripting/api/ScriptingScope.kt b/core/src/com/unciv/scripting/api/ScriptingScope.kt index 0265265a3cafa..996cb16445f8c 100644 --- a/core/src/com/unciv/scripting/api/ScriptingScope.kt +++ b/core/src/com/unciv/scripting/api/ScriptingScope.kt @@ -44,6 +44,8 @@ object ScriptingScope // TODO: Some way to clear the instancesaver? + // TODO: Serialize containers to JSON? + } // Does having one state manage multiple backends that all share the same scope really make sense? Mod handler dispatch, callbacks, etc might all be easier if the multi-backend functionality of ScriptingState were implemented only for ConsoleScreen. diff --git a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt index e0fa5ceeef67b..8d6627677c772 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt @@ -1,6 +1,7 @@ package com.unciv.scripting.protocol import com.unciv.scripting.AutocompleteResults +import com.unciv.scripting.ExecResult import com.unciv.scripting.reflection.FunctionDispatcher import com.unciv.scripting.reflection.Reflection import com.unciv.scripting.utils.ScriptingDebugParameters @@ -203,9 +204,12 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = if (packet.data is JsonArray) AutocompleteResults((packet.data as List).map { (it as JsonPrimitive).content }) else - AutocompleteResults(listOf(), true, (packet.data as JsonPrimitive).content) + AutocompleteResults(helpText = (packet.data as JsonPrimitive).content) - fun exec(packet: ScriptingPacket): String = (packet.data as JsonPrimitive).content + fun exec(packet: ScriptingPacket) = ExecResult( + resultPrint = (packet.data as JsonPrimitive).content, + isException = packet.hasFlag(KnownFlag.Exception) + ) fun terminate(packet: ScriptingPacket): Exception? = if (packet.data == JsonNull || packet.data == null) @@ -220,10 +224,8 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = * @param obj Instance to save. * @return Same instance as given, unchanged. Allows this function to be chained, or used to pass through an anonymous instance. */ - fun trySaveInstance(obj: Any?): Any? { - if (instanceSaver != null) { - instanceSaver.add(obj) - }//TODO: I should use this in more of the request types. + private fun trySaveInstance(obj: T): T { + instanceSaver?.add(obj)//TODO: I should use this in more of the request types. return obj } @@ -328,10 +330,10 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = "keys" -> { try { val packetData = ScriptingPacketPathedData(packet) - val leaf = Reflection.resolveInstancePath( + val leaf = trySaveInstance(Reflection.resolveInstancePath( if (packetData.use_root) packetData.root else scope, packetData.path - ) + )) responseData = TokenizingJson.getJsonElement((leaf as Map).keys) } catch (e: Exception) { responseData = JsonPrimitive(e.stringifyException()) diff --git a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt index 15e57ee91f397..ae8b467cb44b4 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt @@ -1,6 +1,7 @@ package com.unciv.scripting.protocol import com.unciv.scripting.AutocompleteResults +import com.unciv.scripting.ExecResult import com.unciv.scripting.ScriptingImplementation import com.unciv.scripting.utils.ScriptingDebugParameters @@ -28,12 +29,12 @@ class ScriptingRawReplManager(scope: Any, blackbox: Blackbox): ScriptingReplMana return AutocompleteResults() } - override fun exec(command: String): String { + override fun exec(command: String): ExecResult { if (!blackbox.readyForWrite) { throw IllegalStateException("REPL not ready: ${blackbox}") } else { blackbox.write(command) - return blackbox.readAll(block=true).joinToString("\n") + return ExecResult(blackbox.readAll(block=true).joinToString("\n")) } } @@ -64,7 +65,6 @@ class ScriptingProtocolReplManager(scope: Any, blackbox: Blackbox): ScriptingRep fun getRequestResponse(packetToSend: ScriptingPacket, enforceValidity: Boolean = true, execLoop: () -> Unit = fun(){}): ScriptingPacket { // Please update the specifications in Module.md if you change the basic structure of this REPL loop. if (ScriptingDebugParameters.printPacketsForDebug) println("\nSending: ${packetToSend}") - // TODO: Move this to ScriptingProtocol? blackbox.write(packetToSend.toJson() + "\n") execLoop() val response = ScriptingPacket.fromJson(blackbox.read(block=true)) @@ -113,7 +113,7 @@ class ScriptingProtocolReplManager(scope: Any, blackbox: Blackbox): ScriptingRep ) } - override fun exec(command: String): String { + override fun exec(command: String): ExecResult { if (!blackbox.readyForWrite) { throw IllegalStateException("REPL not ready: ${blackbox}") } else { diff --git a/core/src/com/unciv/scripting/reflection/FunctionDispatcher.kt b/core/src/com/unciv/scripting/reflection/FunctionDispatcher.kt index eec27e7541f80..e75354ce7fdbe 100644 --- a/core/src/com/unciv/scripting/reflection/FunctionDispatcher.kt +++ b/core/src/com/unciv/scripting/reflection/FunctionDispatcher.kt @@ -147,7 +147,7 @@ open class FunctionDispatcher( * @return The result from dispatching the given arguments to the function definition with a compatible signature. * @throws IllegalArgumentException If no compatible signature was found, or if more than one compatible signature was found. */ - open fun call(arguments: Array): Any? { // TODO: Let return be typed. + open fun call(arguments: Array): R { // KCallable's .call() takes varargs instead of an array object. But spreads are expensive, so I'm not doing that. // To test from Python: // gameInfo.civilizations.add(1, civInfo) @@ -162,7 +162,6 @@ open class FunctionDispatcher( var match: KCallable? = null if (matches.isEmpty()) { throw IllegalArgumentException("No matching signatures found for calling ${nounifyFunctions()} with given arguments: (${arguments.map {if (it == null) "null" else it::class?.simpleName ?: "null"}.joinToString(", ")})") - //FIXME: A lot of non-null assertions and null checks (not here, but generally in the codebase) can probably be replaced with safe calls. } if (matches.size > 1) { if (resolveAmbiguousSpecificity) { @@ -179,7 +178,7 @@ open class FunctionDispatcher( } return match.call( *arguments - ) + ) as R } /** diff --git a/core/src/com/unciv/scripting/reflection/Reflection.kt b/core/src/com/unciv/scripting/reflection/Reflection.kt index eae7392a1214a..acad1baf621ad 100644 --- a/core/src/com/unciv/scripting/reflection/Reflection.kt +++ b/core/src/com/unciv/scripting/reflection/Reflection.kt @@ -24,8 +24,8 @@ object Reflection { kprop.getter.call() else kprop.get(instance)) as R? - // KProperty1().get(instance) Fails for consts: apiHelpers.Jvm.kotlinSingletonByQualname["com.unciv.Constants"].close - // m=next(m for m in apiHelpers.Jvm.kotlinClassByQualname["com.unciv.Constants"].members if m.name == 'close') + // KProperty1().get(instance) Fails for consts: apiHelpers.Jvm.singletonByQualname["com.unciv.Constants"].close + // m=next(m for m in apiHelpers.Jvm.classByQualname["com.unciv.Constants"].members if m.name == 'close') // object o {val a=1; const val b=2} // (o::class.members.first{it.name == "a"} as KProperty1).get(o) // (o::class.members.first{it.name == "b"} as KProperty1).getter.call() @@ -61,12 +61,12 @@ object Reflection { // TODO: .functions? Choose one that includes superclasses but excludes extensions. // FIXME: Right. Cell.row is an example of a name used as both a property and a function. // p=apiHelpers.Jvm.constructorByQualname["com.unciv.ui.utils.Popup"](uncivGame.consoleScreen); disp=p.add(apiHelpers.Jvm.functionByQualClassAndName["com.unciv.ui.utils.ExtensionFunctionsKt"]["toLabel"]("Test Text.")).row - // [apiHelpers.Jvm.kotlinClassByInstance[f] for f in disp.functions] + // [apiHelpers.Jvm.classByInstance[f] for f in disp.functions] // KFunctionImpl vs KMutableProperty1Impl, apparently. // Adding `is Function` to the filter should do it, I think? - // apiHelpers.Jvm.kotlinClassByQualname["com.badlogic.gdx.scenes.scene2d.ui.Cell"].members - // apiHelpers.Jvm.kotlinClassByQualname["com.badlogic.gdx.scenes.scene2d.ui.Cell"] - // apiHelpers.Jvm.functionByQualClassAndName["kotlin.reflect.full.KClasses"]["getFunctions"](apiHelpers.Jvm.kotlinClassByQualname["com.badlogic.gdx.scenes.scene2d.ui.Cell"]) + // apiHelpers.Jvm.classByQualname["com.badlogic.gdx.scenes.scene2d.ui.Cell"].members + // apiHelpers.Jvm.classByQualname["com.badlogic.gdx.scenes.scene2d.ui.Cell"] + // apiHelpers.Jvm.functionByQualClassAndName["kotlin.reflect.full.KClasses"]["getFunctions"](apiHelpers.Jvm.classByQualname["com.badlogic.gdx.scenes.scene2d.ui.Cell"]) matchNumbersLeniently = matchNumbersLeniently, matchClassesQualnames = matchClassesQualnames, resolveAmbiguousSpecificity = resolveAmbiguousSpecificity @@ -80,9 +80,9 @@ object Reflection { * @return Helpful representative text. */ override fun toString() = """${this::class.simpleName}(instance=${this.instance::class.simpleName}(), methodName="${this.methodName}") with ${this.functions.size} dispatch candidates""" - // Used by "docstring" packet action in ScriptingProtocol, which is in turn exposed in interpeters as help text. TODO: Could move to an extension method in ScriptingProtocol, I suppose. + // Used by "docstring" packet action in ScriptingProtocol, which is in turn exposed in interpreters as help text. - override fun call(arguments: Array): Any? { + override fun call(arguments: Array): R { return super.call(arrayOf(instance, *arguments)) // Add receiver to arguments. } @@ -91,13 +91,13 @@ object Reflection { } - fun readInstanceItem(instance: Any, keyOrIndex: Any): Any? { // TODO: Unify type param/casting behaviour across this, readInstanceProperty, and FunctionDispatcher.call. + fun readInstanceItem(instance: Any, keyOrIndex: Any): R { // TODO: Make this work with operator overloading. Though Map is already an interface that anything can implement, so maybe not. if (keyOrIndex is Int) { return try { (instance as List)[keyOrIndex] } - catch (e: ClassCastException) { (instance as Array)[keyOrIndex] } + catch (e: ClassCastException) { (instance as Array)[keyOrIndex] } as R } else { - return (instance as Map)[keyOrIndex] + return (instance as Map)[keyOrIndex] as R } } @@ -291,10 +291,6 @@ object Reflection { ) } PathElementType.Call -> { - // TODO: Handle invoke operator. Easy enough, just recurse to access the .invoke. - // object test{operator fun invoke(a:Any?,b:Any?){println("$a $b")}}; test(1,2) - // Test in Python: apiHelpers.Jvm.constructorByQualname('com.unciv.UncivGame'). Also do an object for multi-arg testing, I guess? - // Maybe TODO: Handle lambdas. E.G. WorldScreen.nextTurnAction. But honestly, it may be better to just expose wrapping non-lambdas. if (obj is FunctionDispatcher) { // Undocumented implicit behaviour: Using the last object means that this should work with explicitly created FunctionDispatcher()s. (obj).call( @@ -306,8 +302,7 @@ object Reflection { ).toTypedArray() ) } else { - throw UnsupportedOperationException() - resolveInstancePath( // TODO: Test this. + resolveInstancePath( // Might be a weird if this recurses… I think circular invoke properties would crash? obj, listOf( PathElement( @@ -356,7 +351,7 @@ object Reflection { fun setInstancePath(instance: Any?, path: List, value: Any?) { val leafobj = resolveInstancePath(instance, path.slice(0..path.size-2)) - val leafelement = path[path.size - 1] + val leafelement = path[path.lastIndex] when (leafelement.type) { PathElementType.Property -> { setInstanceProperty(leafobj!!, leafelement.name, value) @@ -379,7 +374,7 @@ object Reflection { fun removeInstancePath(instance: Any?, path: List) { val leafobj = resolveInstancePath(instance, path.slice(0..path.size-2)) - val leafelement = path[path.size - 1] + val leafelement = path[path.lastIndex] when (leafelement.type) { PathElementType.Property -> { throw UnsupportedOperationException("Cannot remove instance property.") diff --git a/core/src/com/unciv/scripting/serialization/InstanceTokenizer.kt b/core/src/com/unciv/scripting/serialization/InstanceTokenizer.kt index ada1a0c2d8bff..c67eb460ae971 100644 --- a/core/src/com/unciv/scripting/serialization/InstanceTokenizer.kt +++ b/core/src/com/unciv/scripting/serialization/InstanceTokenizer.kt @@ -115,7 +115,7 @@ object InstanceTokenizer { val badtokens = if (tokenReuse) tokensByInstances.clean(true)!! else - instancesByTokens.entries.asSequence().filter { it.value.get() == null }.map { it.key }.toList() // Technically the .toLists()'s not needed, but this is a legacy thing anyway— TODO: Wait, can't you type as Iterable? + instancesByTokens.entries.asSequence().filter { it.value.get() == null }.map { it.key }.toList() // Legacy mode. toList() is only for type. for (t in badtokens) { instancesByTokens.remove(t) } @@ -144,7 +144,7 @@ object InstanceTokenizer { * @param obj Instance to tokenize. * @return Token string that can later be detokenized back into the original instance. */ - fun getToken(obj: Any?): String { // TOOD: Switch to Any, since null will just be cleaned anyway? + fun getToken(obj: Any?): String { // TODO: Switch to Any, since null will just be cleaned anyway? tryClean() val token = tokenFromInstance(obj) instancesByTokens[token] = WeakReference(obj) diff --git a/core/src/com/unciv/scripting/serialization/TokenizingJson.kt b/core/src/com/unciv/scripting/serialization/TokenizingJson.kt index 0d7502197afa8..0e2ce69b47a15 100644 --- a/core/src/com/unciv/scripting/serialization/TokenizingJson.kt +++ b/core/src/com/unciv/scripting/serialization/TokenizingJson.kt @@ -136,6 +136,7 @@ object TokenizingJson { // Maybe just treat strings normally but stringify everything else? That would be consistent with Python behaviour and a superset of normal string-encoding behaviour. // Collisions would be tricky— Probably just either throw an exception or make real strings always take precedence. It's probably not worth it to specify that JSON keys have to be encoded. // TODO: More testing/documentation is needed. + // Well, now Maps just aren't ever serialized in normal use anymore, so it's fine I guess? } ) } if (value is Iterable<*>) { diff --git a/core/src/com/unciv/scripting/utils/FakeMaps.kt b/core/src/com/unciv/scripting/utils/FakeMaps.kt index 2d9f261c88dd9..30294cebd5374 100644 --- a/core/src/com/unciv/scripting/utils/FakeMaps.kt +++ b/core/src/com/unciv/scripting/utils/FakeMaps.kt @@ -16,6 +16,7 @@ abstract class StatelessMap: Map { override fun isEmpty(): Boolean = noStateError() } +// interface InvokableMap: Map { operator fun invoke(key: K): V? = get(key) } diff --git a/core/src/com/unciv/scripting/utils/ScriptingBackendException.kt b/core/src/com/unciv/scripting/utils/ScriptingBackendException.kt new file mode 100644 index 0000000000000..acb7478053725 --- /dev/null +++ b/core/src/com/unciv/scripting/utils/ScriptingBackendException.kt @@ -0,0 +1,4 @@ +package com.unciv.scripting.utils + +class ScriptingBackendException(message: String): RuntimeException(message) { +} diff --git a/core/src/com/unciv/scripting/utils/ScriptingDebugParameters.kt b/core/src/com/unciv/scripting/utils/ScriptingDebugParameters.kt index b8b2e46a74954..b49c342697850 100644 --- a/core/src/com/unciv/scripting/utils/ScriptingDebugParameters.kt +++ b/core/src/com/unciv/scripting/utils/ScriptingDebugParameters.kt @@ -1,8 +1,15 @@ package com.unciv.scripting.utils +import com.unciv.UncivGame +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.Popup +import com.unciv.ui.utils.stringifyException + object ScriptingDebugParameters { // Whether to print out all/most IPC packets for debug. var printPacketsForDebug = false + // Whether to print out all executed script code strings for debug (more readable than printing all packets). + var printCommandsForDebug = false // Whether to print out all/most IPC actions for debug (more readable than printing all packets). var printAccessForDebug = false // Whether to print out major token count changes and cleaning events in InstanceTokenizer for debug. @@ -10,3 +17,26 @@ object ScriptingDebugParameters { // Whether to print out a warning when reflectively accessing definitions that have been deprecated. var printReflectiveDeprecationWarnings = false // TODO } + +object ScriptingErrorHandling { + fun notifyPlayerScriptFailure(text: String, asName: String? = null, toConsole: Boolean = true) { + val popup = Popup(UncivGame.Current.screen as BaseScreen) + val msg = "{An error has occurred with the mod/script} ${asName ?: ScriptingRunLock.runningName}:\n\n${text.prependIndent("\t")}\n\n{See system terminal output for details.}\n{Consider disabling mods if this keeps happening.}\n" + popup.addGoodSizedLabel(msg).row() + popup.addOKButton{} + popup.open(true) + if (toConsole) + printConsolePlayerScriptFailure(text, asName) + } + fun notifyPlayerScriptFailure(exception: Throwable, asName: String? = null) { + notifyPlayerScriptFailure(exception.toString(), asName, false) + printConsolePlayerScriptFailure(exception, asName) + } + fun printConsolePlayerScriptFailure(text: String, asName: String? = null) { + println("\nException with <${asName ?: ScriptingRunLock.runningName}> script:\n${text.prependIndent("\t")}\n") + // Really these should all go to STDERR. + } + fun printConsolePlayerScriptFailure(exception: Throwable, asName: String? = null) { + printConsolePlayerScriptFailure(exception.stringifyException(), asName) + } +} diff --git a/core/src/com/unciv/scripting/utils/ScriptingLock.kt b/core/src/com/unciv/scripting/utils/ScriptingLock.kt deleted file mode 100644 index f1c9af7070028..0000000000000 --- a/core/src/com/unciv/scripting/utils/ScriptingLock.kt +++ /dev/null @@ -1,57 +0,0 @@ -package com.unciv.scripting.utils - -import com.unciv.UncivGame -import com.unciv.ui.utils.BaseScreen -import com.unciv.ui.utils.Popup -import com.unciv.ui.utils.stringifyException -import java.lang.IllegalArgumentException -import java.lang.IllegalStateException -import java.util.UUID -import java.util.concurrent.atomic.AtomicBoolean - -object ScriptingLock { - private val isRunning = AtomicBoolean(false) // Maybe make thread-safe just in case, but also officially disallow use by multiple threads at once? - var runningName: String? = null - private set - private var runningKey: String? = null // Unique key set each run to make sure - private var runQueue = ArrayDeque<() -> Unit>() - // @return A randomly generated string to pass to the release function. - fun acquire(name: String? = null): String? { - val success = isRunning.compareAndSet(false, true) - if (!success) throw IllegalStateException("Cannot acquire ScriptingLock for $name because already in use by $runningName.") - runningKey = UUID.randomUUID().toString() - return runningKey -// ScriptingScope.worldScreen?.isPlayersTurn = false - //Hm. Should return to original value, not necessarily true. That means keeping a property, which means I'd rather put this in its own class. - //TODO: Move to ScriptingLock. - //Not perfect. I think ScriptingScope also exposes mutating the GUI itself, and many things that aren't protected by this? Then again, a script that *wants* to cause a crash/ANR will always be able to do so by just assigning an invalid value or deleting a required node somewhere. Could make mod handlers outside of worldScreen blocking, with written stipulations on (dis)recommended size, and then - //https://github.com/yairm210/Unciv/pull/5592/commits/a1f51e08ab782ab46bda220e0c4aaae2e8ba21a4 - } - fun release(releaseKey: String) { - if (releaseKey != runningKey) throw IllegalArgumentException("Invalid key given to release ScriptingLock.") -// val success = isRunning.compareAndSet(true, false) - if (isRunning.get()) { - runningName = null - runningKey = null - isRunning.set(false) - runQueue.removeFirstOrNull()?.invoke() - } else { - throw IllegalStateException("Cannot release ScriptingLock because it has not been acquired.") - } - } - // TODO: Prevent UI collision, and also prevent recursive script executions. - // Allow registering name text of running script, for error messages. - fun notifyPlayerScriptFailure(exception: Throwable, asName: String? = null) { - // Should this be in ScriptingState after that's been singleton'd? - val popup = Popup(UncivGame.Current.screen as BaseScreen) - val msg = "An error has occurred with the mod/script \"${asName ?: runningName}\":\n\n${exception.toString().prependIndent("\t")}\n\nSee system terminal output for details.\nConsider disabling mods if this keeps happening.\n" - popup.addGoodSizedLabel(msg).row() - popup.addOKButton{} - popup.open(true) - printConsolePlayerScriptFailure(exception, asName) - } - fun printConsolePlayerScriptFailure(exception: Throwable, asName: String? = null) { - println("\nException with \"${asName ?: runningName}\" script:\n${exception.stringifyException().prependIndent("\t")}\n") - // Really these should all go to STDERR. - } -} diff --git a/core/src/com/unciv/scripting/utils/ScriptingRunLock.kt b/core/src/com/unciv/scripting/utils/ScriptingRunLock.kt new file mode 100644 index 0000000000000..0387f2d60670c --- /dev/null +++ b/core/src/com/unciv/scripting/utils/ScriptingRunLock.kt @@ -0,0 +1,47 @@ +package com.unciv.scripting.utils + +import com.unciv.scripting.ScriptingBackend +import com.unciv.scripting.ScriptingState +import java.util.UUID +import java.util.concurrent.atomic.AtomicBoolean + +// Does not prevent concurrent run attempts. Just tries to make them to error immediately instead of messing anything up. + + + +// Lock to prevent multiple scripts from trying to run at the same time. +object ScriptingRunLock { + private val isRunning = + AtomicBoolean(false) + var runningName: String? = null + private set + private var runningKey: String? = null // Unique key set each run to make sure + // @param name An informative string to identify this acquisition and subsequent activities. + // @throws IllegalStateException if lock is already in use. + // @return A randomly generated string to pass to the release function. + @Synchronized fun acquire(name: String? = null): String { + // Different *threads* that try to run scripts concurrently should already queue up nicely without this due to the use of the Synchronized annotation on ScriptingState.exec(), I think. + // But because of the risk of recursive script runs (which will deadlock if trying to acquire a lock that must be released by the same thread, and which break the already-in-use IPC loop for a backend if allowed to continue without a lock), the behaviour here if or when that fails is to throw an exception. + val success = isRunning.compareAndSet(false, true) + if (!success) throw IllegalStateException("Cannot acquire ${this::class.simpleName} for $name because already in use by $runningName.") + val key = UUID.randomUUID().toString() + runningKey = key + runningName = name + return key + } + // @param releaseKey The string previously returned by the immediately preceding succesful acquire(). + // @throws IllegalArgumentException If given the incorrect releaseKey. + // @throws IllegalStateException If not currently acquired. + @Synchronized fun release(releaseKey: String) { + if (releaseKey != runningKey) throw IllegalArgumentException("Invalid key given to release ${this::class.simpleName}.") + if (isRunning.get()) { + runningName = null + runningKey = null + isRunning.set(false) + } else { + throw IllegalStateException("Cannot release ${this::class.simpleName} because it has not been acquired.") + } + } +} + +fun makeScriptingRunName(executor: String?, backend: ScriptingBackend) = "$executor/${backend.metadata.displayName}@${ScriptingState.getIndexOfBackend(backend)}" diff --git a/core/src/com/unciv/scripting/utils/ScriptingRunThreader.kt b/core/src/com/unciv/scripting/utils/ScriptingRunThreader.kt new file mode 100644 index 0000000000000..52aa29463c097 --- /dev/null +++ b/core/src/com/unciv/scripting/utils/ScriptingRunThreader.kt @@ -0,0 +1,43 @@ +package com.unciv.scripting.utils + +import com.badlogic.gdx.Gdx +import java.util.concurrent.ConcurrentLinkedDeque +import java.util.concurrent.atomic.AtomicBoolean +import kotlin.concurrent.thread + +// Utility to run multiple actions related to scripting in separate threads, sequentially. +object ScriptingRunThreader { + private val isDoingRuns = AtomicBoolean(false) + @Suppress("NewApi") // FIXME: Bump API level up. + private var runQueue = ConcurrentLinkedDeque<() -> Unit>() + fun queueRun(toRun: () -> Unit) { + runQueue.add(toRun) + } + fun queueRuns(runs: Iterable<() -> Unit>) { + for (run in runs) runQueue.add(run) + } + fun queueRuns(runs: Sequence<() -> Unit>) { + for (run in runs) runQueue.add(run) + } + @Synchronized fun doRuns(begin: Boolean = true) { + if (begin) { + val success = isDoingRuns.compareAndSet(false, true) + if (!success) { + throw IllegalStateException("${this::class.simpleName} is already consuming its queued runs!") + } + } + val run: (() -> Unit)? = runQueue.poll() + if (run != null) { + thread { + try { + run() + } finally { + Gdx.app.postRunnable { doRuns(false) } + } + } + } else { + isDoingRuns.set(false) + } + } +} + diff --git a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt index b295851f1c6c1..9775bcd706352 100644 --- a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt @@ -11,6 +11,8 @@ import com.badlogic.gdx.scenes.scene2d.ui.TextField import com.unciv.Constants import com.unciv.scripting.ScriptingBackendType import com.unciv.scripting.ScriptingState +import com.unciv.scripting.utils.ScriptingErrorHandling +import com.unciv.scripting.utils.makeScriptingRunName import com.unciv.ui.utils.* import com.unciv.ui.utils.AutoScrollPane as ScrollPane import kotlin.math.max @@ -215,8 +217,8 @@ class ConsoleScreen(var closeAction: () -> Unit): BaseScreen() { val original = inputText val cursorpos = cursorPos var results = ScriptingState.autocomplete(inputText, cursorpos) - if (results.isHelpText) { - echo(results.helpText) + if (results.helpText != null) { + echo(results.helpText!!) return } if (results.matches.size < 1) { @@ -231,7 +233,7 @@ class ConsoleScreen(var closeAction: () -> Unit): BaseScreen() { //var minmatch = original //Checking against the current input would prevent autoinsertion from working for autocomplete backends that support getting results from the middle of the current input. var minmatch = original.slice(0..cursorpos-1) var chosenresult = results.matches.first({true}) - for (l in original.length-1..chosenresult.length-1) { + for (l in original.lastIndex..chosenresult.lastIndex) { var longer = chosenresult.slice(0..l) if (results.matches.all { it.startsWith(longer) }) { minmatch = longer @@ -239,7 +241,7 @@ class ConsoleScreen(var closeAction: () -> Unit): BaseScreen() { break } } - setText(minmatch + original.slice(cursorpos..original.length-1), SetTextCursorMode.Insert) + setText(minmatch + original.slice(cursorpos..original.lastIndex), SetTextCursorMode.Insert) // TODO: Splice the longest starting substring with the text after the cursor, to let autocomplete implementations work on the middle of current input. } } @@ -266,8 +268,14 @@ class ConsoleScreen(var closeAction: () -> Unit): BaseScreen() { } private fun run() { - echo(ScriptingState.exec(inputText)) + val name = makeScriptingRunName(this::class.simpleName, ScriptingState.getActiveBackend()) + val execResult = ScriptingState.exec(inputText, asName = name) + echo(execResult.resultPrint) setText("") + if (execResult.isException) { + ScriptingErrorHandling.printConsolePlayerScriptFailure(execResult.resultPrint, asName = name) + ToastPopup("Exception in ${name}.", this) + } } fun clone(): ConsoleScreen { From c8d60b4288c173732b28278e9b394037a471a72c Mon Sep 17 00:00:00 2001 From: will-ca Date: Fri, 10 Dec 2021 21:29:50 +0000 Subject: [PATCH 71/93] New parser for Pathcode DSL. Reflective scripting backend tests. --- .../python/unciv_scripting_examples/Tests.py | 2 +- .../com/unciv/scripting/ScriptingBackend.kt | 68 ++++++++++++++++--- .../scripting/api/ScriptingApiJvmHelpers.kt | 2 + .../com/unciv/scripting/api/ScriptingScope.kt | 4 ++ .../unciv/scripting/reflection/Reflection.kt | 63 +++++++++++------ .../unciv/scripting/utils/ApiSpecGenerator.kt | 2 +- .../unciv/ui/consolescreen/ConsoleScreen.kt | 2 +- 7 files changed, 108 insertions(+), 35 deletions(-) diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py index 4e1935d676694..da9d2c38ae752 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py @@ -129,7 +129,7 @@ def _print(*args, **kwargs): failcounts[exc_name] = 0 failcounts[exc_name] += 1 del exc_name - exc = AssertionError(f"{len(failures)} Python tests FAILED: {[test.name for test in failures]}\nFailure types: {failcounts}\n\n") + exc = AssertionError(f"{len(failures)}/{len(self._tests)} Python tests FAILED: {[test.name for test in failures]}\nFailure types: {failcounts}\n\n") _print(exc) raise exc else: diff --git a/core/src/com/unciv/scripting/ScriptingBackend.kt b/core/src/com/unciv/scripting/ScriptingBackend.kt index cdba5ea62a5f4..1dee5763103d0 100644 --- a/core/src/com/unciv/scripting/ScriptingBackend.kt +++ b/core/src/com/unciv/scripting/ScriptingBackend.kt @@ -330,22 +330,61 @@ class ReflectiveScriptingBackend(): ScriptingBackend() { override val displayName: String = "Reflective" } - private val commandparams = mapOf("get" to 1, "set" to 2, "typeof" to 1) //showprivates? - private val examples = listOf( + private val commandparams = mapOf("get" to 1, "set" to 2, "typeof" to 1, "examples" to 0, "runtests" to 0) //showprivates? + private val examples = listOf( // The new splitToplevelExprs means set can safely use equals sign for assignment. + "get uncivGame.loadGame(Unciv.GameStarter.startNewGame(apiHelpers.Jvm.companionByQualClass[\"com.unciv.models.metadata.GameSetupInfo\"].fromSettings(\"Chieftain\")))", "get gameInfo.civilizations[1].policies.adoptedPolicies", "set 5 civInfo.tech.freeTechs", - "set 1 civInfo.cities[0].health", +// "set 1 civInfo.cities[0].health", // Doesn't work as test due to new game, no city. "set 5 gameInfo.turns", "get civInfo.addGold(1337)", + "get civInfo.addNotification(\"Here's a notification!\", apiHelpers.Jvm.arrayOfTyped1(\"StatIcons/Resistance\"))", "set 2000 worldScreen.bottomUnitTable.selectedUnit.promotions.XP", - "get worldScreen.bottomUnitTable.selectedCity.population.setPopulation(25)", +// "get worldScreen.bottomUnitTable.selectedCity.population.setPopulation(25)", // Doesn't work as test due to new game, no city. "set \"Cattle\" worldScreen.mapHolder.selectedTile.resource", "set \"Krakatoa\" worldScreen.mapHolder.selectedTile.naturalWonder", + "get apiHelpers.Jvm.constructorByQualname[\"com.unciv.ui.worldscreen.AlertPopup\"](worldScreen, apiHelpers.Jvm.constructorByQualname[\"com.unciv.logic.civilization.PopupAlert\"](apiHelpers.Jvm.enumMapsByQualname[\"com.unciv.logic.civilization.AlertType\"][\"StartIntro\"], \"Text text.\")).open(false)", "get civInfo.addGold(civInfo.tech.techsResearched.size)", "get uncivGame.setScreen(apiHelpers.Jvm.constructorByQualname[\"com.unciv.ui.mapeditor.MapEditorScreen\"](gameInfo.tileMap))", - "get apiHelpers.Jvm.constructorByQualname[\"com.unciv.ui.utils.ToastPopup\"](\"This is a popup!\", uncivGame.consoleScreen, 2000)" - //apiHelpers.Jvm.constructorByQualname["com.unciv.ui.worldscreen.AlertPopup"](worldScreen, apiHelpers.Jvm.constructorByQualname["com.unciv.logic.civilization.PopupAlert"](apiHelpers.Enums.enumMapsByQualname["com.unciv.logic.civilization.AlertType"]["FirstContact"], "Carthage")) + "get apiHelpers.Jvm.constructorByQualname[\"com.unciv.ui.utils.ToastPopup\"](\"This is a popup!\", apiHelpers.Jvm.companionByQualClass[\"com.unciv.UncivGame\"].Current.getScreen(), 2000)", + "get apiHelpers.Jvm.singletonByQualname[\"com.unciv.ui.utils.Fonts\"].turn", + "get apiHelpers.App.assetImageB64(\"StatIcons/Resistance\")", + "get apiHelpers.App.assetFileString(\"jsons/Civ V - Gods & Kings/Terrains.json\")", + "get apiHelpers.App.assetFileB64(\"jsons/Tutorials.json\")", + "get apiHelpers.Jvm.staticPropertyByQualClassAndName[\"com.badlogic.gdx.graphics.Color\"][\"WHITE\"]", + "get apiHelpers.Jvm.constructorByQualname[\"com.unciv.ui.utils.ToastPopup\"](\"This is a popup!\", apiHelpers.Jvm.companionByQualClass[\"com.unciv.UncivGame\"].Current.getScreen(), 2000).add(apiHelpers.Jvm.functionByQualClassAndName[\"com.unciv.ui.utils.ExtensionFunctionsKt\"][\"toLabel\"](\"With Scarlet text! \", apiHelpers.Jvm.staticPropertyByQualClassAndName[\"com.badlogic.gdx.graphics.Color\"][\"SCARLET\"], 24))", + "set true Unciv.ScriptingDebugParameters.printCommandsForDebug", + "set false Unciv.ScriptingDebugParameters.printCommandsForDebug" ) + private val tests = listOf( + "get modApiHelpers.lambdifyExecScript(\"get uncivGame\")", + "get apiHelpers.Jvm.functionByQualClassAndName[\"com.unciv.ui.utils.ExtensionFunctionsKt\"][\"onClick\"](apiHelpers.Jvm.constructorByQualname[\"com.unciv.ui.utils.ToastPopup\"](\"Click to add gold!\", apiHelpers.Jvm.companionByQualClass[\"com.unciv.UncivGame\"].Current.getScreen(), 5000), modApiHelpers.lambdifyReadPathcode(null, \"civInfo.addGold(1000)\"))", // The click action doesn't work, but this still tests extension/static function access. +// "get fFeiltali.stastIRFI" // Force a failure. + ) + + private fun runTests(): ExecResult { // TODO: Run through examples as tests, add to unit tests. + val failResult = exec("get This.Command[Should](Fail)!") + if (!failResult.isException) { + throw AssertionError("ERROR in reflective scripting tests: Unable to detect failures.") + } + val failures = ArrayList() + val tests = sequenceOf(examples.filterNot { it.startsWith("runtests") }, tests).flatten().toList() + for (command in tests) { + val execResult = exec(command) + if (execResult.isException) { + failures.add(command) + } + } + return if (failures.isEmpty()) {ExecResult( + "${tests.size} reflective scripting tests PASSED!" + )} else {ExecResult( + listOf( + "${failures.size}/${tests.size} reflective scripting tests FAILED:", + *failures.map { it.prependIndent("\t") }.toTypedArray() + ).joinToString("\n"), + true + )} + } override fun motd(): String { return "\n\nWelcome to the reflective Unciv CLI backend.\n\nCommands you enter will be parsed as a path consisting of property reads, key and index accesses, function calls, and string, numeric, boolean, and null literals.\nKeys, indices, and function arguments are parsed recursively.\nProperties can be both read from and written to.\n\nExamples:\n${examples.map({"> ${it}"}).joinToString("\n")}\n\nPress [TAB] at any time to trigger autocompletion for all known leaf names at the currently entered path.\n" @@ -384,8 +423,9 @@ class ReflectiveScriptingBackend(): ScriptingBackend() { override fun exec(command: String): ExecResult { var parts = command.split(' ', limit=2) var out = "\n> ${command}\n" + var isException = false fun appendOut(text: String) { - out += text + "\n" + out += text + "\n" // Slow? Meh. The user will always be the bottleneck in this code. } try { when (parts[0]) { @@ -409,14 +449,20 @@ class ReflectiveScriptingBackend(): ScriptingBackend() { "examples" -> { throw RuntimeException("Not implemented.") } + "runtests" -> { + val testResults = runTests() + appendOut(testResults.resultPrint) + isException = testResults.isException + } else -> { - appendOut("Unknown command: ${parts[0]}") + throw Exception("Unknown command:\n${parts[0].prependIndent("\t")}") } } - } catch (e: Exception) { - appendOut("Error evaluating command: ${e}") + } catch (e: Throwable) { // The runtest command is meant to catch breakage from isMinifyEnabled=true removing scripting API functions, which would be NoSuchElementError, I think, so not an Exception subclass. + appendOut("Error evaluating command:\n${e.toString().prependIndent("\t")}") + isException = true } - return ExecResult(out) + return ExecResult(out, isException) } } diff --git a/core/src/com/unciv/scripting/api/ScriptingApiJvmHelpers.kt b/core/src/com/unciv/scripting/api/ScriptingApiJvmHelpers.kt index f55b890905d22..53cec002b8a52 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiJvmHelpers.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiJvmHelpers.kt @@ -77,4 +77,6 @@ object ScriptingApiJvmHelpers { fun toList(array: Array<*>) = array.toList() // sorted([real(m.getName()) for m in apiHelpers.Jvm.classByQualname["kotlin.collections.ArraysKt"].jClass.getMethods()]) fun toList(iterable: Iterable<*>) = iterable.toList() fun toList(sequence: Sequence<*>) = sequence.toList() + + //fun toChar(string: CharSequence) } diff --git a/core/src/com/unciv/scripting/api/ScriptingScope.kt b/core/src/com/unciv/scripting/api/ScriptingScope.kt index 996cb16445f8c..aac663491bd53 100644 --- a/core/src/com/unciv/scripting/api/ScriptingScope.kt +++ b/core/src/com/unciv/scripting/api/ScriptingScope.kt @@ -6,6 +6,9 @@ import com.unciv.logic.civilization.CivilizationInfo import com.unciv.ui.mapeditor.MapEditorScreen import com.unciv.ui.worldscreen.WorldScreen +// Could use annotations to expose documentation. Probably not worth it unless integrable with KDoc. + +// TODO: Making ((…) -> Unit) functions in the core codebase return the instance itself would significantly increase the power of the Pathcode DSL. /** * Holds references to all internal game data that scripting backends have access to. @@ -44,6 +47,7 @@ object ScriptingScope // TODO: Some way to clear the instancesaver? + // fun containersToJson(root: Any?) // TODO: Serialize containers to JSON? } diff --git a/core/src/com/unciv/scripting/reflection/Reflection.kt b/core/src/com/unciv/scripting/reflection/Reflection.kt index acad1baf621ad..7c202643b90d7 100644 --- a/core/src/com/unciv/scripting/reflection/Reflection.kt +++ b/core/src/com/unciv/scripting/reflection/Reflection.kt @@ -158,7 +158,7 @@ object Reflection { "()" to PathElementType.Call ) - fun parseKotlinPath(code: String): List { + fun parseKotlinPath(code: String): List { // Probably don't need unit tests specifically for this. Any scripting backend unit tests will be implicitly using it anyway, and in this case, the test cases for ReflectiveScriptingBackend are basically reference inputs. var path: MutableList = ArrayList() //var curr_type = PathElementType.Property var curr_name = ArrayList() @@ -240,29 +240,50 @@ object Reflection { return components.joinToString() } -// private val closingbrackets = null -// data class OpenBracket( -// val char: Char, -// var offset: Int -// ) - - //class OpenBracketIterator() { - //} - - - //fun getOpenBracketStack() { - //} - - fun splitToplevelExprs(code: String, delimiters: String = ","): List { - return if (code.isBlank()) listOf() else code.split(',').map { it.trim(' ') } - var segs = ArrayList() - val bracketdepths = mutableMapOf( - *brackettypes.keys.map { it to 0 }.toTypedArray() - ) - //TODO: Actually try to parse for parenthesization, strings, etc. + fun splitToplevelExprs( // Probably don't need unit tests specifically for this. Any scripting backend unit tests will be implicitly using it anyway, and in this case, the test cases for ReflectiveScriptingBackend are basically reference inputs. + code: String, + delimiters: CharSequence = ",", + bracketPairs: Map = mapOf('(' to ')', '[' to ']'), // Move defaults outside, so callers can E.G. flip them for a flipped string/maxParts. + // Don't give quote marks as brackets, because they act differently: First opening quote gets mistaken for unexpected closing bracket, and stuff inside them still won't be escaped. + maxParts: Int = 0, + backSlashEscape: Boolean = false // IDK about this. Simplicity, clarity, and reliability are more important here than being correct by an arbitrary and complicated standard— Point is to be able to parse an optimally useful and easy common subset of a lot of programming languages, in which context escapes are always a headache. + ): List { + if (code.isBlank()) + return listOf() + val subExprs = ArrayList() + var currentIndex = 0 + val bracketClosersStack = ArrayList() + val currExpr = ArrayList() + var lastChar: Char? = null + for ((i, char) in code.withIndex()) { + if ((backSlashEscape && lastChar == '\\') || (maxParts > 0 && subExprs.size >= maxParts - 1)) { + currExpr.add(char) + continue + } + if (bracketClosersStack.isEmpty() && char in delimiters) { + subExprs.add(currExpr.joinToString("")) + currExpr.clear() + continue + } + currExpr.add(char) + if (char in bracketPairs.values) { + if (char == bracketClosersStack.lastOrNull()) { + bracketClosersStack.removeLast() + continue + } else { + throw IllegalArgumentException("Unexpected bracket $char at index $i in code: $code") + } + } + val closingBracket = bracketPairs[char] + if (closingBracket != null) bracketClosersStack.add(closingBracket) + } + subExprs.add(currExpr.joinToString("")) + return subExprs } + fun splitToplevelExprs(code: String): List = splitToplevelExprs(code, ",") // For reflective use and debug— FunctionDispatcher doesn't use default args. + fun resolveInstancePath(instance: Any?, path: List): Any? { //TODO: Allow passing an ((Any?)->Unit)? (or maybe Boolean) function as a parameter that gets called at every stage of resolution, to let exceptions be thrown if accessing something not whitelisted. diff --git a/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt b/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt index b701e0a70e550..1a1bf1689a9ff 100644 --- a/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt +++ b/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt @@ -71,7 +71,7 @@ class ApiSpecGenerator { val searchclasses = mutableListOf>(ScriptingScope::class) val encounteredclasses = mutableSetOf>() var i: Int = 0 - while (i < searchclasses.size) { + while (i < searchclasses.size) { // withIndex var cls = searchclasses[i] for (m in cls.members) { var kclass: KClass<*>? diff --git a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt index 9775bcd706352..cb69448ccb473 100644 --- a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt @@ -257,7 +257,7 @@ class ConsoleScreen(var closeAction: () -> Unit): BaseScreen() { label.setWrap(true) printHistory.add(label).left().bottom().width(width).padLeft(15f).row() val cells = printHistory.getCells() - while (cells.size > ScriptingState.maxOutputHistory && cells.size > 0) { + while (cells.size > ScriptingState.maxOutputHistory && cells.size > 0) { // TODO: Move to separate method. val cell = cells.first() cell.getActor().remove() cells.removeValue(cell, true) From decf4864d0c46996953d618c3892c7a0aaa0964d Mon Sep 17 00:00:00 2001 From: will-ca Date: Sat, 11 Dec 2021 06:01:38 +0000 Subject: [PATCH 72/93] Disable `-XstartOnFirstThread` when not on Mac. From 6d0f8d67be69420aec0872bad6adab2af97cf021 Mon Sep 17 00:00:00 2001 From: will-ca Date: Sat, 11 Dec 2021 17:48:21 +0000 Subject: [PATCH 73/93] Scripting OptionsPopup tab. Warning labels and confirmation dialogs. User startup scripts. --- android/Images/OtherIcons/Code.png | Bin 0 -> 2110 bytes android/Images/OtherIcons/Script.png | Bin 0 -> 1836 bytes .../unciv_scripting_examples/EventPopup.py | 2 +- .../assets/scripting/enginefiles/qjs/main.js | 1 + .../com/unciv/models/metadata/GameSettings.kt | 12 ++ .../com/unciv/scripting/ScriptingBackend.kt | 15 +- .../src/com/unciv/scripting/ScriptingState.kt | 2 +- .../api/ScriptingApiExecutionContext.kt | 3 +- .../scripting/api/ScriptingApiMappers.kt | 4 +- .../protocol/ScriptingReplManager.kt | 2 +- .../unciv/ui/consolescreen/ConsoleScreen.kt | 69 +++++--- .../ui/worldscreen/mainmenu/OptionsPopup.kt | 150 ++++++++++++++++-- docs/Credits.md | 2 + 13 files changed, 210 insertions(+), 52 deletions(-) create mode 100644 android/Images/OtherIcons/Code.png create mode 100644 android/Images/OtherIcons/Script.png diff --git a/android/Images/OtherIcons/Code.png b/android/Images/OtherIcons/Code.png new file mode 100644 index 0000000000000000000000000000000000000000..e6e3bbc442cfc2460c92288cf5c54bfc8777f9d6 GIT binary patch literal 2110 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 X+Vj prop == 'real' ? target : new Proxy([...target, prop], handlers)}; let p=new Proxy([], handlers) // Chrome, Node, and SpiderMonkey can print this fine. QuickJS and Deno use inspection in their REPL. // TODO: Implement JS bindings. +// Basically port over the subset of the Python behaviour that implements bindings. I don't want to bother with adding many richer features like autocompletion, though— And there's a benefit to that because the language bindings/library get copied over for every // https://stackoverflow.com/questions/9781285/specify-scope-for-eval-in-javascript // https://www.figma.com/blog/how-we-built-the-figma-plugin-system/#attempt-3-realms diff --git a/core/src/com/unciv/models/metadata/GameSettings.kt b/core/src/com/unciv/models/metadata/GameSettings.kt index 91f7b5df0720a..8df37843bb32b 100644 --- a/core/src/com/unciv/models/metadata/GameSettings.kt +++ b/core/src/com/unciv/models/metadata/GameSettings.kt @@ -4,13 +4,20 @@ import com.badlogic.gdx.Application import com.badlogic.gdx.Gdx import com.unciv.UncivGame import com.unciv.logic.GameSaver +import com.unciv.scripting.ScriptingBackendType import java.text.Collator import java.util.* import kotlin.collections.HashSet +import kotlin.collections.LinkedHashMap data class WindowState (val width: Int = 900, val height: Int = 600) class GameSettings { + + companion object { + val scriptingConsoleStartupDefaults = ScriptingBackendType.values().associate { it.metadata.displayName to it.suggestedStartup } + } + var showWorkedTiles: Boolean = false var showResourcesAndImprovements: Boolean = true var showTileYields: Boolean = false @@ -54,6 +61,10 @@ class GameSettings { var showExperimentalWorldWrap = false // We're keeping this as a config due to ANR problems on Android phones for people who don't know what they're doing :/ var enableScriptingConsole = false + var showScriptingConsoleWarning = true + var scriptingConsoleStartups = scriptingConsoleStartupDefaults.toMutableMap() + + var enableModScripting = false var lastOverviewPage: String = "Cities" @@ -146,3 +157,4 @@ enum class LocaleCode(var language: String, var country: String) { Ukrainian("uk", "UA"), Vietnamese("vi", "VN"), } + diff --git a/core/src/com/unciv/scripting/ScriptingBackend.kt b/core/src/com/unciv/scripting/ScriptingBackend.kt index 1dee5763103d0..20f12fce6601a 100644 --- a/core/src/com/unciv/scripting/ScriptingBackend.kt +++ b/core/src/com/unciv/scripting/ScriptingBackend.kt @@ -362,7 +362,7 @@ class ReflectiveScriptingBackend(): ScriptingBackend() { // "get fFeiltali.stastIRFI" // Force a failure. ) - private fun runTests(): ExecResult { // TODO: Run through examples as tests, add to unit tests. + private fun runTests(): ExecResult { // TODO: Add to unit tests. val failResult = exec("get This.Command[Should](Fail)!") if (!failResult.isException) { throw AssertionError("ERROR in reflective scripting tests: Unable to detect failures.") @@ -510,7 +510,7 @@ abstract class BlackboxScriptingBackend(): EnvironmentedScriptingBackend() { try { return replManager.motd() } catch (e: Exception) { - return "No MOTD for ${metadata.engine} backend: ${e}\n" + return "No MOTD for ${metadata.engine} backend: ${e}\n" // TODO: Hm.. } } @@ -523,10 +523,10 @@ abstract class BlackboxScriptingBackend(): EnvironmentedScriptingBackend() { } override fun exec(command: String): ExecResult { - try { - return replManager.exec("${command}\n") + return try { + replManager.exec("${command}\n") } catch (e: RuntimeException) { - return ExecResult("${e}") + ExecResult("${e}", true) } } @@ -658,12 +658,13 @@ class DevToolsScriptingBackend(): ScriptingBackend() { } -enum class ScriptingBackendType(val metadata: ScriptingBackend_metadata) { +// @property suggestedStartup Default startup code to run when started by ConsoleScreen *only*. +enum class ScriptingBackendType(val metadata: ScriptingBackend_metadata, val suggestedStartup: String = "") { // Not sure how I feel about having suggestedStartup here— Kinda breaks separation of functionality and UI— But keeping a separate Map in the UI files would be too messy, and it's not as bad as putting it in the companion objects— Really, this entire Enum is a mash of stuff needed to launch and use all the backend types by anything else, so that fits. Dummy(ScriptingBackend), Hardcoded(HardcodedScriptingBackend), Reflective(ReflectiveScriptingBackend), //MicroPython(UpyScriptingBackend), - SystemPython(SpyScriptingBackend), + SystemPython(SpyScriptingBackend, "from unciv import *; from unciv_pyhelpers import *"), SystemQuickJS(SqjsScriptingBackend), SystemLua(SluaScriptingBackend), // DevTools(DevToolsScriptingBackend), diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index 7aa5bea6fc2f7..a89dc92705e43 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -128,7 +128,7 @@ object ScriptingState { val name = asName ?: makeScriptingRunName(this::class.simpleName, backend) val releaseKey: String releaseKey = ScriptingRunLock.acquire(name) - // Lock acquisition failure gets propagated as Exception, rather than as return. E.G.: Lets lambdas (from modApiHelpers) fail and trigger their own error handling (exposing misbehaving mods to the user). + // Lock acquisition failure gets propagated as thrown Exception, rather than as return. E.G.: Lets lambdas (from modApiHelpers) fail and trigger their own error handling (exposing misbehaving mods to the user). // isException in ExecResult return value means exception in completely opaque scripting backend. Kotlin exception should still be thrown and propagated like normal. try { ScriptingScope.apiExecutionContext.apply { diff --git a/core/src/com/unciv/scripting/api/ScriptingApiExecutionContext.kt b/core/src/com/unciv/scripting/api/ScriptingApiExecutionContext.kt index f7a1524ee3f4f..3bd86a7cc3b60 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiExecutionContext.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiExecutionContext.kt @@ -4,7 +4,6 @@ import com.unciv.scripting.ScriptingBackend object ScriptingApiExecutionContext { var handlerParameters: Map? = null - // Why not just use a map? String keys will be clearer in scripts than integers anyway. - // Collection that gets replaced with any contextual parameters when running script handlers. E.G. Unit moved, city founded, tech researched, construction finished. + // Collection that gets replaced with any contextual parameters when running script handlers. E.G. The Unit moved, the city founded, the tech researched, the construction finished. var scriptingBackend: ScriptingBackend? = null } diff --git a/core/src/com/unciv/scripting/api/ScriptingApiMappers.kt b/core/src/com/unciv/scripting/api/ScriptingApiMappers.kt index a62195c33f93e..41ac8cf4f051d 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiMappers.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiMappers.kt @@ -47,5 +47,5 @@ object ScriptingApiMappers { } // st=time.time(); [real(t.baseTerrain) for t in gameInfo.tileMap.values]; print(time.time()-st) // st=time.time(); apiHelpers.Mappers.mapPathCodes(gameInfo.tileMap.values, ['baseTerrain']); print(time.time()-st) -// FIXME: Gets slower each time run (presumably due to InstanceTokenizer.clean()'s leak). -// Actually around the same speed. Or wait: Is that the InstanceTokenizer slowdown? +// Holy shizzle that's faster. +// (It shows a lot of time went to serialization before though, I think.) diff --git a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt index ae8b467cb44b4..2e0cc5fd71aa1 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt @@ -115,7 +115,7 @@ class ScriptingProtocolReplManager(scope: Any, blackbox: Blackbox): ScriptingRep override fun exec(command: String): ExecResult { if (!blackbox.readyForWrite) { - throw IllegalStateException("REPL not ready: ${blackbox}") + throw IllegalStateException("REPL not ready: ${blackbox}") // Switch to ExecResult() return? } else { return ScriptingProtocol.parseActionResponses.exec( getRequestResponse( diff --git a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt index cb69448ccb473..5efd05d9d759f 100644 --- a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt @@ -7,7 +7,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.Label import com.badlogic.gdx.scenes.scene2d.ui.SplitPane import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.TextButton -import com.badlogic.gdx.scenes.scene2d.ui.TextField +import com.badlogic.gdx.scenes.scene2d.ui.TextField // Ooh. TextArea could be nice. (Probably overkill and horrible if done messily, though.) import com.unciv.Constants import com.unciv.scripting.ScriptingBackendType import com.unciv.scripting.ScriptingState @@ -18,7 +18,7 @@ import com.unciv.ui.utils.AutoScrollPane as ScrollPane import kotlin.math.max import kotlin.math.min -//TODO: "WARNING\n\nThe Unciv scripting API is a HIGHLY EXPERIMENTAL feature intended for advanced users!\nIt may be possible to damage your device and files by running malicious or poorly designed code!" +//TODO: //"Show this warning next time." //"I understand and wish to continue." // Probably grey this out for five seconds. @@ -51,6 +51,8 @@ class ConsoleScreen(var closeAction: () -> Unit): BaseScreen() { private val layoutUpdators = ArrayList<() -> Unit>() private var isOpen = false + private var warningAccepted = false + var inputText: String get() = inputField.text set(value: String) { inputField.setText(value) } @@ -65,9 +67,15 @@ class ConsoleScreen(var closeAction: () -> Unit): BaseScreen() { backendsAdders.add("Launch new backend:".toLabel()).padRight(30f).padLeft(20f) for (backendtype in ScriptingBackendType.values()) { - var backendadder = backendtype.metadata.displayName.toTextButton() + var backendadder = backendtype.metadata.displayName.toTextButton() // Hm. Should this be translated/translatable? I suppose it already is translatable in OptionsPopup too. And in the running list— So basically everywhere it's shown. backendadder.onClick { - echo(ScriptingState.spawnBackend(backendtype).motd) + val spawned = ScriptingState.spawnBackend(backendtype) + echo(spawned.motd) + val startup = game.settings.scriptingConsoleStartups[backendtype.metadata.displayName]!! + if (startup.isNotBlank()) { + ScriptingState.switchToBackend(spawned.backend) + exec(startup) + } updateRunning() } backendsAdders.add(backendadder) @@ -111,20 +119,20 @@ class ConsoleScreen(var closeAction: () -> Unit): BaseScreen() { layoutTable.add(inputBar) - runButton.onClick({ run() }) - keyPressDispatcher[Input.Keys.ENTER] = { run() } - keyPressDispatcher[Input.Keys.NUMPAD_ENTER] = { run() } + runButton.onClick(::run) + keyPressDispatcher[Input.Keys.ENTER] = ::run + keyPressDispatcher[Input.Keys.NUMPAD_ENTER] = ::run - tabButton.onClick({ autocomplete() }) - keyPressDispatcher[Input.Keys.TAB] = { autocomplete() } + tabButton.onClick(::autocomplete) + keyPressDispatcher[Input.Keys.TAB] = ::autocomplete - upButton.onClick({ navigateHistory(1) }) + upButton.onClick { navigateHistory(1) } keyPressDispatcher[Input.Keys.UP] = { navigateHistory(1) } - downButton.onClick({ navigateHistory(-1) }) + downButton.onClick { navigateHistory(-1) } keyPressDispatcher[Input.Keys.DOWN] = { navigateHistory(-1) } - onBackButtonClicked({ closeConsole() }) - closeButton.onClick({ closeConsole() }) + onBackButtonClicked(::closeConsole) + closeButton.onClick(::closeConsole) updateLayout() @@ -141,12 +149,30 @@ class ConsoleScreen(var closeAction: () -> Unit): BaseScreen() { } } + fun showWarningPopup() { + YesNoPopup( + "{WARNING}\n\n{The Unciv scripting API is a HIGHLY EXPERIMENTAL feature intended for advanced users!}\n{It may be possible to damage your device and files by running malicious or poorly designed code!}\n\n{Do you wish to continue?}", + { + warningAccepted = true + }, + this, + { + closeConsole() + game.settings.enableScriptingConsole = false + game.settings.save() + } + ).open(true) + } + fun openConsole() { game.setScreen(this) keyPressDispatcher.install(stage) //TODO: Can this be moved to UncivGame.setScreen? stage.setKeyboardFocus(inputField) inputField.getOnscreenKeyboard().show(true) this.isOpen = true + if (game.settings.showScriptingConsoleWarning && !warningAccepted) { + showWarningPopup() + } } fun closeConsole() { @@ -157,28 +183,25 @@ class ConsoleScreen(var closeAction: () -> Unit): BaseScreen() { private fun updateRunning() { runningList.clearChildren() - var i = 0 - for (backend in ScriptingState.scriptingBackends) { + for ((i, backend) in ScriptingState.scriptingBackends.withIndex()) { var button = backend.metadata.displayName.toTextButton() - val index = i runningList.add(button) if (i == ScriptingState.activeBackend) { button.color = Color.GREEN } button.onClick { - ScriptingState.switchToBackend(index) + ScriptingState.switchToBackend(i) updateRunning() } var termbutton = ImageGetter.getImage("OtherIcons/Stop") termbutton.onClick { - val exc: Exception? = ScriptingState.termBackend(index) + val exc: Exception? = ScriptingState.termBackend(i) updateRunning() if (exc != null) { echo("Failed to stop ${backend.metadata.displayName} backend: ${exc.toString()}") } } runningList.add(termbutton.surroundWithCircle(40f)).row() - i += 1 } } @@ -267,9 +290,9 @@ class ConsoleScreen(var closeAction: () -> Unit): BaseScreen() { setScroll(0f,0f) } - private fun run() { + private fun exec(command: String) { val name = makeScriptingRunName(this::class.simpleName, ScriptingState.getActiveBackend()) - val execResult = ScriptingState.exec(inputText, asName = name) + val execResult = ScriptingState.exec(command, asName = name) echo(execResult.resultPrint) setText("") if (execResult.isException) { @@ -278,6 +301,10 @@ class ConsoleScreen(var closeAction: () -> Unit): BaseScreen() { } } + private fun run() { + exec(inputText) + } + fun clone(): ConsoleScreen { return ConsoleScreen(closeAction).also { it.inputText = inputText diff --git a/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt b/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt index dd06bb51008ae..fe8506a5c3294 100644 --- a/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt +++ b/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt @@ -5,6 +5,7 @@ import com.badlogic.gdx.Gdx import com.badlogic.gdx.Input import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.scenes.scene2d.Actor +import com.badlogic.gdx.scenes.scene2d.Group import com.badlogic.gdx.scenes.scene2d.ui.* import com.badlogic.gdx.utils.Align import com.unciv.Constants @@ -13,6 +14,7 @@ import com.unciv.UncivGame import com.unciv.logic.MapSaver import com.unciv.logic.civilization.PlayerType import com.unciv.models.UncivSound +import com.unciv.models.metadata.GameSettings import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.RulesetCache import com.unciv.models.ruleset.tile.ResourceType @@ -73,6 +75,7 @@ class OptionsPopup(val previousScreen: BaseScreen) : Popup(previousScreen) { if (Gdx.app.type == Application.ApplicationType.Android) tabs.addPage("Multiplayer", getMultiplayerTab(), ImageGetter.getImage("OtherIcons/Multiplayer"), 24f) tabs.addPage("Advanced", getAdvancedTab(), ImageGetter.getImage("OtherIcons/Settings"), 24f) + tabs.addPage("Scripting", getScriptingTab(), ImageGetter.getImage("OtherIcons/Code"), 24f) if (RulesetCache.size > 1) { tabs.addPage("Locate mod errors", getModCheckTab(), ImageGetter.getImage("OtherIcons/Mods"), 24f) { _, _ -> if (modCheckFirstRun) runModChecker() @@ -246,17 +249,6 @@ class OptionsPopup(val previousScreen: BaseScreen) : Popup(previousScreen) { settings.showExperimentalWorldWrap = it } - addYesNoRow("Enable scripting console\nEXPERIMENTAL - PRESS ~ TO ACTIVATE.", - settings.enableScriptingConsole) { - settings.enableScriptingConsole = it - } - ////TODO: Disable on Android. - //TODO (Later): Persist command history? - //TODO: Move to separate tab. - //TODO: Startup macros per backend type. - //TODO: ConsoleScreen warning toggle. - //https://thenounproject.com/icon/code-787514/ - if (previousScreen.game.limitOrientationsHelper != null) { addYesNoRow("Enable portrait orientation", settings.allowAndroidPortrait) { settings.allowAndroidPortrait = it @@ -270,6 +262,86 @@ class OptionsPopup(val previousScreen: BaseScreen) : Popup(previousScreen) { addSetUserId() } + private fun getScriptingTab() = Table(BaseScreen.skin).apply { + pad(10f) + defaults().pad(5f) + + add( + WrappableLabel( + "{WARNING: The features on this page are all experimental, and very powerful. It may be possible to damage your device using them if you do not know what you are doing.}\n\n{You have been warned!}", + tabs.prefWidth * 0.5f + ).apply { + wrap = true + setAlignment(Align.center) + } + ).padTop(20f).row() + + addSeparator().padTop(20f) + + add("Scripting Console".toLabel(fontSize = 24)).padTop(20f).row() + + val consoleBasicTable = Table(skin).apply { + pad(5f) + defaults().pad(2.5f) + addYesNoRow("{Enable scripting console}\n{Press ~ to activate}", + settings.enableScriptingConsole) { + settings.enableScriptingConsole = it + } + addYesNoRow("Show warning when opening scripting console", + settings.showScriptingConsoleWarning) { + settings.showScriptingConsoleWarning = it + } // Two affirmative actions to start using scripting console: Enable in click options button, accept ConsoleScreen warning. + } + + add(consoleBasicTable).row() + + add("Console Startup Commands".toLabel(fontSize = 24)).padTop(20f).row() + + val consoleStartupsTable = Table(skin).apply { + pad(5f) + defaults().pad(2.5f) + settings.scriptingConsoleStartups.keys.removeAll { it !in GameSettings.scriptingConsoleStartupDefaults } + + for ((displayName, startup) in settings.scriptingConsoleStartups) { + addTextFieldRow(displayName, startup, GameSettings.scriptingConsoleStartupDefaults[displayName]) { + settings.scriptingConsoleStartups[displayName] = it + } + } + } + + add(consoleStartupsTable).row() + + addSeparator().padTop(20f) + + add("Mod Scripting API".toLabel(fontSize = 24)).padTop(20f).row() + + val modScriptingTable = Table(skin).apply { + pad(5f) + defaults().pad(2.5f) + val buttonSetter = object { var set = { _: Boolean -> println("Uninitialized button setting wrapper for enableModScripting.") } } // Need access to the button setter from inside the lambda passed to addYesNoRow, but only get it from the return value of addYesNoRow. It's… Not *that* messy, and doing this here is probably cleaner than what I would end up with if I tried to change addYesNoRow any further (which is also used a lot elsewhere). + buttonSetter.set = addYesNoRow( + "{Allow mods to run scripts}\n{CAUTION!}", + settings.enableModScripting + ) { // Probably not worth adding the confirmation behaviour to addYesNoRow… Standardizing it sounds like a quick path to warning fatigue. + settings.enableModScripting = false + if (it) { // Aside from not wanting to get anyone hacked, should probably also check if Google Play has liability (or third-party distribution) issues— Not any more dangerous than a web browser, in fact arguably safer/more controlled, and trapped in Android sandbox regardless, but still. + YesNoPopup( // Four affirmative actions to enable a mod: Click options button, accept options warning, tick mod checkbox, accept mod warning. + "{Enabling this feature will allow mods you choose to run code on your device.}\n\n{Malicious code may be able to harm your device or steal your data!}\n{Never enable scripting for any mods you don't trust.}\n\n{Do you wish to continue?}", + { settings.enableModScripting = it }, + UncivGame.Current.screen as BaseScreen, + { buttonSetter.set(false) } + ).open(true) + } + } + } + + add(modScriptingTable).row() + //TODO: Move to separate tab. + //TODO: Startup macros per backend type. + //TODO: ConsoleScreen warning toggle. + //https://thenounproject.com/icon/code-787514/ + } + private fun getModCheckTab() = Table(BaseScreen.skin).apply { defaults().pad(10f).align(Align.top) modCheckCheckBox = "Check extension mods based on vanilla".toCheckBox { @@ -624,8 +696,8 @@ class OptionsPopup(val previousScreen: BaseScreen) : Popup(previousScreen) { } } - - private fun Table.addYesNoRow(text: String, initialValue: Boolean, updateWorld: Boolean = false, action: ((Boolean) -> Unit)) { + // @return Lambda function for setting the button state (triggers action, world update, etc). + private fun Table.addYesNoRow(text: String, initialValue: Boolean, updateWorld: Boolean = false, action: ((Boolean) -> Unit)): (Boolean) -> Unit { val wrapWidth = tabs.prefWidth - 60f add(WrappableLabel(text, wrapWidth).apply { wrap = true }) .left().fillX() @@ -637,6 +709,50 @@ class OptionsPopup(val previousScreen: BaseScreen) : Popup(previousScreen) { previousScreen.shouldUpdate = true } add(button).row() + return { button.value = it } + } + + + // @param labelText Label to show next to the text field. + // @param initialValue Text in the text field. + // @param defaultValue If non-null, add a button to reset the text field to this value. + // @param action Function given the updated value of the text field when it changes. Called before saving game settings. + private fun Table.addTextFieldRow(labelText: String, initialValue: String, defaultValue: String? = null, action: (String) -> Unit) { + val wrapWidth = tabs.prefWidth * 0.2f + add(WrappableLabel(labelText, wrapWidth).apply { wrap = true }) + .right().fillX() + .maxWidth(wrapWidth) + val input = TextField(initialValue, skin) + input.onChange { + action(input.text) + settings.save() // Don't currently see a need for updateWorld. + } + val fieldWidth = tabs.prefWidth * 0.5f + var lastcell: Cell<*> = add(input) + .left().fillX() + .minWidth(fieldWidth) + + if (defaultValue != null) { + val resetIcon = ImageGetter.getImage("OtherIcons/Close").apply { + setSize(20f, 20f) + } + val iconWrapper = Group().apply { // Stolen from, TODO: Unify with code in TabbedPager.addPage(). + isTransform = + false // performance helper - nothing here is rotated or scaled + setSize(20f, 20f) + resetIcon.center(this) + addActor(resetIcon) + } + val resetButton = Button(skin).apply { add(iconWrapper) }.onClick { + input.text = defaultValue + action(input.text) + settings.save() + } + + lastcell = add(resetButton) + } + + lastcell.row() } //endregion @@ -650,12 +766,13 @@ class OptionsPopup(val previousScreen: BaseScreen) : Popup(previousScreen) { private class YesNoButton( initialValue: Boolean, skin: Skin, - action: (Boolean) -> Unit + private val action: (Boolean) -> Unit ) : TextButton (initialValue.toYesNo(), skin ) { - + // State of the button. Triggers action and updates text when changed. var value = initialValue - private set(value) { + set(value) { field = value + action(value) setText(value.toYesNo()) } @@ -663,7 +780,6 @@ class OptionsPopup(val previousScreen: BaseScreen) : Popup(previousScreen) { color = ImageGetter.getBlue() onClick { value = !value - action.invoke(value) } } diff --git a/docs/Credits.md b/docs/Credits.md index d7c2bcc9156a3..fde3972fc128e 100644 --- a/docs/Credits.md +++ b/docs/Credits.md @@ -684,6 +684,8 @@ Unless otherwise specified, all the following are from [the Noun Project](https: * [Multiplayer](https://thenounproject.com/search/?q=multiplayer&i=1215652) by Roy Charles * [Options](https://thenounproject.com/search/?q=options&i=866090) By Thengakola * [Package](https://thenounproject.com/search/?q=package&i=1886048) by shashank singh +* [Code](https://thenounproject.com/icon/code-787514/) by Júlia Korčoková (CC-BY-3.0, Modified) +* [Script](https://thenounproject.com/icon/script-2690195/) by Iconbox (CC-BY-3.0) # Sound credits From 6ef4984110a9175525d0f8c5f287c2a3608d2318 Mon Sep 17 00:00:00 2001 From: will-ca Date: Sat, 11 Dec 2021 17:51:02 +0000 Subject: [PATCH 74/93] Lower Gradle RAM limit again. From 361eabd74b1c41b624fe87fde0f7e928b6eaa7ad Mon Sep 17 00:00:00 2001 From: will-ca Date: Sat, 11 Dec 2021 17:52:11 +0000 Subject: [PATCH 75/93] Auto-generated atlas and docs. From 669044edb17865bd67056a3a4638c25487d1239e Mon Sep 17 00:00:00 2001 From: will-ca Date: Sat, 11 Dec 2021 17:37:51 +0000 Subject: [PATCH 76/93] Update desktop Mac arguments. From de2c35bdcfd510a6f07e88642ca438227493ee9e Mon Sep 17 00:00:00 2001 From: will-ca Date: Sat, 11 Dec 2021 19:25:21 +0000 Subject: [PATCH 77/93] Kotlin reorganization and Python tests for scripting lock. --- .../python/unciv_scripting_examples/Tests.py | 47 +++++++++++++++---- .../src/com/unciv/scripting/ScriptingState.kt | 4 +- .../scripting/api/ScriptingModApiHelpers.kt | 9 +++- .../unciv/scripting/reflection/Reflection.kt | 5 +- .../{utils => sync}/ScriptingRunLock.kt | 5 +- .../{utils => sync}/ScriptingRunThreader.kt | 5 +- .../utils/ScriptingBackendException.kt | 1 + .../utils/ScriptingDebugParameters.kt | 1 + .../unciv/ui/consolescreen/ConsoleScreen.kt | 2 +- 9 files changed, 57 insertions(+), 22 deletions(-) rename core/src/com/unciv/scripting/{utils => sync}/ScriptingRunLock.kt (90%) rename core/src/com/unciv/scripting/{utils => sync}/ScriptingRunThreader.kt (90%) diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py index da9d2c38ae752..35aeb60f4a0ad 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py @@ -27,15 +27,6 @@ def tryRun(func): except Exception as e: return e -try: - assert False - # Can also check __debug__. Meh. Explicit(ly using the behaviour) is better here than implicit(ly relying on related behaviour). -except: - pass -else: - raise RuntimeError("Assertions must be enabled to run Python tests.") - - with open(Utils.exampleAssetPath("Elizabeth300"), 'r') as save: # TODO: Compress this. Unciv uses Base64 and GZIP. Elizabeth300 = save.read() @@ -140,6 +131,17 @@ def _print(*args, **kwargs): #### Tests begin here. #### +@TestRunner.Test() +def AssertionsEnabledTest(): + try: + assert False + # Can also check __debug__. Meh. Explicit(ly using the behaviour) is better here than implicit(ly relying on related behaviour). + except: + pass + else: + raise RuntimeError("Assertions must be enabled to run Python tests. Results may contain false passes.") + + @TestRunner.Test(runwith=InGame) def LoadGameTest(): """Example test. Explicitly tests that the InGame context manager is working.""" @@ -152,7 +154,7 @@ def LoadGameTest(): # @TestRunner.Test(runwith=InGame, name="NoPrivatesTest-InGame", args=(unciv, 2)) # @TestRunner.Test(runwith=InMapEditor, name="NoPrivatesTest-InMapEditor", args=(unciv, 2)) -# Enable this if it's ever decided to guarantee that the 'dir' IPC action type won't return inaccessible names. +# Enable this if it's ever decided to guarantee that the 'dir' IPC action type won't return inaccessible names. (I don't think that's necssary, though. I mean, I don't think successful property access is guaranteed by Python semantics either? And if it's *really* an issue, the AttributeError raised by ForeignObject.__getattr__ can be checked for in ForeignObject.__dir__.) def NoPrivatesTest(start, maxdepth, *, _depth=0, _failures=None, _namestack=None): # Would have to differentiate between unitialized properties and the like, and privates. if _failures is None: @@ -176,6 +178,31 @@ def NoPrivatesTest(start, maxdepth, *, _depth=0, _failures=None, _namestack=None if _depth == 0: assert not _failures, _failures + +@TestRunner.Test() +def NoRecursiveScriptingTest(): + try: + unciv.modApiHelpers.callLambdaAllowException( + unciv.modApiHelpers.lambdifySuppressReturn( + unciv.modApiHelpers.lambdifyReadPathcode(None, "") + ) + ) + # Should work, because those lambdas are run in JVM. + except Exception as e: + raise AssertionException(f"Normal lambda failed: {e}") + try: + unciv.modApiHelpers.callLambdaAllowException( + unciv.modApiHelpers.lambdifyExecScript("print()") + ) + # Should fail, because recursive scripting is forbidden. + except Exception as e: + re = repr(e) + assert ('already in use' in re and 'Cannot acquire' in re), f"Recursive scripting failed like expected, but did not produce the expected exception: {repr(e)}" + else: + raise AssertionError(f"Recursive scripting succeeded, but it isn't supposed to.") + # If it ever gets here, the entire IPC loop will almost definitely be broken, so this will never actually make it to the JVM. But oh well. Let's still keep an explicit test nonetheless for semantic clarity. + + # Tests for PlayerMacros.py. TestRunner.Test(runwith=InGame, args=lambda: (unciv.civInfo.cities, 0.5))( diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index a89dc92705e43..dfc7901a393ea 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -1,8 +1,8 @@ package com.unciv.scripting import com.unciv.scripting.api.ScriptingScope -import com.unciv.scripting.utils.ScriptingRunLock -import com.unciv.scripting.utils.makeScriptingRunName +import com.unciv.scripting.sync.ScriptingRunLock +import com.unciv.scripting.sync.makeScriptingRunName import com.unciv.ui.utils.clipIndexToBounds import com.unciv.ui.utils.enforceValidIndex import kotlin.collections.ArrayList diff --git a/core/src/com/unciv/scripting/api/ScriptingModApiHelpers.kt b/core/src/com/unciv/scripting/api/ScriptingModApiHelpers.kt index 4f8d32974daf2..961b38f884d8b 100644 --- a/core/src/com/unciv/scripting/api/ScriptingModApiHelpers.kt +++ b/core/src/com/unciv/scripting/api/ScriptingModApiHelpers.kt @@ -4,7 +4,7 @@ import com.unciv.scripting.ScriptingState import com.unciv.scripting.reflection.Reflection import com.unciv.scripting.utils.ScriptingBackendException import com.unciv.scripting.utils.ScriptingErrorHandling -import com.unciv.scripting.utils.ScriptingRunLock +import com.unciv.scripting.sync.ScriptingRunLock // Wrapper for a function that takes no arguments. // @@ -57,6 +57,7 @@ object ScriptingModApiHelpers { fun lambdifyReadPathcode(instance: Any?, pathcode: String): () -> Any? { val path = Reflection.parseKotlinPath(pathcode) return LambdaWrapper0(ScriptingRunLock.runningName.lambdaName()) { Reflection.resolveInstancePath(instance ?: ScriptingScope, path) } + // Lambda generated by using lambdifyReadPathCode (or something in Mappers) from inside a lambdifyReadPathCode() during a JVM-space callback could have incorrect names, but oh well? } fun lambdifyAssignPathcode(instance: Any?, pathcode: String, value: Any?): () -> Unit? { val path = Reflection.parseKotlinPath(pathcode) @@ -75,4 +76,10 @@ object ScriptingModApiHelpers { val lambdas = funcs.map { it.unwrapped() } return LambdaWrapper0(ScriptingRunLock.runningName.lambdaName()) { for (f in lambdas) { f() } } } + + // Due to potential and purpose of being used in GUI callbacks, returns from all the lambdafication functions suppress exceptions and show an error dialog instead. + // This function calls one of those lambdas without the universal exception catching, in case a script wants to use a JVM-space lambda itself and be able to catch JVM-space propagated exceptions for whatever reason. + fun callLambdaAllowException(func: () -> Unit?) = func.unwrapped()() + + } diff --git a/core/src/com/unciv/scripting/reflection/Reflection.kt b/core/src/com/unciv/scripting/reflection/Reflection.kt index 7c202643b90d7..d1672bf13b628 100644 --- a/core/src/com/unciv/scripting/reflection/Reflection.kt +++ b/core/src/com/unciv/scripting/reflection/Reflection.kt @@ -1,6 +1,7 @@ package com.unciv.scripting.reflection import com.unciv.scripting.serialization.TokenizingJson +import com.unciv.ui.utils.stringifyException import kotlin.collections.ArrayList import kotlinx.serialization.Serializable import kotlin.reflect.* @@ -322,7 +323,7 @@ object Reflection { element.params ).toTypedArray() ) - } else { + } else { // Actual lambdas still don't work. Detecting functions with any number of args is apparently impossible in static Kotlin. resolveInstancePath( // Might be a weird if this recurses… I think circular invoke properties would crash? obj, listOf( @@ -337,7 +338,7 @@ object Reflection { } } } catch (e: Exception) { - throw IllegalAccessException("Cannot access $element on $obj:\n${e.toString().prependIndent("\t")}") + throw IllegalAccessException("Cannot access $element on $obj:\n${e.stringifyException().prependIndent("\t")}") } } return obj diff --git a/core/src/com/unciv/scripting/utils/ScriptingRunLock.kt b/core/src/com/unciv/scripting/sync/ScriptingRunLock.kt similarity index 90% rename from core/src/com/unciv/scripting/utils/ScriptingRunLock.kt rename to core/src/com/unciv/scripting/sync/ScriptingRunLock.kt index 0387f2d60670c..aad61e523dc3e 100644 --- a/core/src/com/unciv/scripting/utils/ScriptingRunLock.kt +++ b/core/src/com/unciv/scripting/sync/ScriptingRunLock.kt @@ -1,4 +1,4 @@ -package com.unciv.scripting.utils +package com.unciv.scripting.sync import com.unciv.scripting.ScriptingBackend import com.unciv.scripting.ScriptingState @@ -22,8 +22,7 @@ object ScriptingRunLock { @Synchronized fun acquire(name: String? = null): String { // Different *threads* that try to run scripts concurrently should already queue up nicely without this due to the use of the Synchronized annotation on ScriptingState.exec(), I think. // But because of the risk of recursive script runs (which will deadlock if trying to acquire a lock that must be released by the same thread, and which break the already-in-use IPC loop for a backend if allowed to continue without a lock), the behaviour here if or when that fails is to throw an exception. - val success = isRunning.compareAndSet(false, true) - if (!success) throw IllegalStateException("Cannot acquire ${this::class.simpleName} for $name because already in use by $runningName.") + if (!isRunning.compareAndSet(false, true)) throw IllegalStateException("Cannot acquire ${this::class.simpleName} for $name because it is already in use by $runningName.") val key = UUID.randomUUID().toString() runningKey = key runningName = name diff --git a/core/src/com/unciv/scripting/utils/ScriptingRunThreader.kt b/core/src/com/unciv/scripting/sync/ScriptingRunThreader.kt similarity index 90% rename from core/src/com/unciv/scripting/utils/ScriptingRunThreader.kt rename to core/src/com/unciv/scripting/sync/ScriptingRunThreader.kt index 52aa29463c097..b1910056ca9ae 100644 --- a/core/src/com/unciv/scripting/utils/ScriptingRunThreader.kt +++ b/core/src/com/unciv/scripting/sync/ScriptingRunThreader.kt @@ -1,4 +1,4 @@ -package com.unciv.scripting.utils +package com.unciv.scripting.sync import com.badlogic.gdx.Gdx import java.util.concurrent.ConcurrentLinkedDeque @@ -21,8 +21,7 @@ object ScriptingRunThreader { } @Synchronized fun doRuns(begin: Boolean = true) { if (begin) { - val success = isDoingRuns.compareAndSet(false, true) - if (!success) { + if (!isDoingRuns.compareAndSet(false, true)) { throw IllegalStateException("${this::class.simpleName} is already consuming its queued runs!") } } diff --git a/core/src/com/unciv/scripting/utils/ScriptingBackendException.kt b/core/src/com/unciv/scripting/utils/ScriptingBackendException.kt index acb7478053725..91178eb1659cb 100644 --- a/core/src/com/unciv/scripting/utils/ScriptingBackendException.kt +++ b/core/src/com/unciv/scripting/utils/ScriptingBackendException.kt @@ -1,4 +1,5 @@ package com.unciv.scripting.utils class ScriptingBackendException(message: String): RuntimeException(message) { + // TODO: Use this more? } diff --git a/core/src/com/unciv/scripting/utils/ScriptingDebugParameters.kt b/core/src/com/unciv/scripting/utils/ScriptingDebugParameters.kt index b49c342697850..a8758400eed30 100644 --- a/core/src/com/unciv/scripting/utils/ScriptingDebugParameters.kt +++ b/core/src/com/unciv/scripting/utils/ScriptingDebugParameters.kt @@ -1,6 +1,7 @@ package com.unciv.scripting.utils import com.unciv.UncivGame +import com.unciv.scripting.sync.ScriptingRunLock import com.unciv.ui.utils.BaseScreen import com.unciv.ui.utils.Popup import com.unciv.ui.utils.stringifyException diff --git a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt index 5efd05d9d759f..300097835f219 100644 --- a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt @@ -12,7 +12,7 @@ import com.unciv.Constants import com.unciv.scripting.ScriptingBackendType import com.unciv.scripting.ScriptingState import com.unciv.scripting.utils.ScriptingErrorHandling -import com.unciv.scripting.utils.makeScriptingRunName +import com.unciv.scripting.sync.makeScriptingRunName import com.unciv.ui.utils.* import com.unciv.ui.utils.AutoScrollPane as ScrollPane import kotlin.math.max From daff295a6647e42e9940334415591fe54302b008 Mon Sep 17 00:00:00 2001 From: will-ca Date: Sat, 11 Dec 2021 19:26:49 +0000 Subject: [PATCH 78/93] 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? From ae46e3e8462c7f1a5d9ad226d3e80bdf4f8cbbef Mon Sep 17 00:00:00 2001 From: will-ca Date: Sun, 12 Dec 2021 05:40:33 +0000 Subject: [PATCH 79/93] Soft-code default Python imports. --- android/assets/scripting/enginefiles/python/main.py | 6 +++--- .../assets/scripting/enginefiles/python/unciv_lib/api.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/android/assets/scripting/enginefiles/python/main.py b/android/assets/scripting/enginefiles/python/main.py index c142cdafaafaf..215122a0d43c7 100644 --- a/android/assets/scripting/enginefiles/python/main.py +++ b/android/assets/scripting/enginefiles/python/main.py @@ -36,11 +36,11 @@ # Let the entire API be imported from external scripts. # None of this will work on Upy. - uncivModule.help = lambda thing=None: print(__doc__) if thing is None else print(unciv_lib.api.get_doc(thing)) if isinstance(thing, unciv_lib.wrapping.ForeignObject) else help(thing) + # uncivModule.help = lambda thing=None: print(__doc__) if thing is None else print(unciv_lib.api.get_doc(thing)) if isinstance(thing, unciv_lib.wrapping.ForeignObject) else help(thing) - replScope = {} + replScope = {'help': lambda thing=None: print(__doc__) if thing is None else print(unciv_lib.api.get_doc(thing)) if isinstance(thing, unciv_lib.wrapping.ForeignObject) else help(thing)} - exec('from unciv_pyhelpers import *', replScope, replScope) + # exec('from unciv_pyhelpers import *', replScope, replScope) # TODO: This, and the scope update in UncivReplTransceiver, should probably be a default exec in Kotlin-side game options instead. diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/api.py b/android/assets/scripting/enginefiles/python/unciv_lib/api.py index 105500eaaf36e..ecd6fed92b799 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/api.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/api.py @@ -105,8 +105,8 @@ def populateApiScope(self): for n in dir(uncivscope): if n not in self.apiscope: self.apiscope[n] = wrapping.ForeignObject(path=n, foreignrequester=self.GetForeignActionResponse) - self.scope.update({**self.apiscope, **self.scope}) - # TODO: Replace this update with Kotlin-side init? + # self.scope.update({**self.apiscope, **self.scope}) + # TODO: Populate module, let scripts import it themselves. def passMic(self): """Send a 'PassMic' packet.""" self.SendForeignAction({'action':None, 'identifier': None, 'data':None, 'flags':('PassMic',)}) From 8fd7ef1e8b17be2e4a50882d30d0e1b5dc1172cd Mon Sep 17 00:00:00 2001 From: will-ca Date: Sun, 12 Dec 2021 09:00:24 +0000 Subject: [PATCH 80/93] Disable scripting in multiplayer. --- core/src/com/unciv/scripting/ScriptingState.kt | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index dfc7901a393ea..fb6c4c713539a 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -1,8 +1,11 @@ package com.unciv.scripting +import com.unciv.UncivGame import com.unciv.scripting.api.ScriptingScope import com.unciv.scripting.sync.ScriptingRunLock import com.unciv.scripting.sync.makeScriptingRunName +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.ToastPopup import com.unciv.ui.utils.clipIndexToBounds import com.unciv.ui.utils.enforceValidIndex import kotlin.collections.ArrayList @@ -124,15 +127,20 @@ object ScriptingState { // @throws IllegalStateException On failure to acquire scripting lock. @Synchronized fun exec(command: String, asName: String? = null, withParams: Map? = null): ExecResult { + if (UncivGame.Current.isGameInfoInitialized() && UncivGame.Current.gameInfo.gameParameters.isOnlineMultiplayer) { + ToastPopup("Scripting not allowed in multiplayer.", UncivGame.Current.screen as BaseScreen) // TODO: Translation. + return ExecResult("", true) + } val backend = getActiveBackend() val name = asName ?: makeScriptingRunName(this::class.simpleName, backend) - val releaseKey: String - releaseKey = ScriptingRunLock.acquire(name) - // Lock acquisition failure gets propagated as thrown Exception, rather than as return. E.G.: Lets lambdas (from modApiHelpers) fail and trigger their own error handling (exposing misbehaving mods to the user). + val releaseKey = ScriptingRunLock.acquire(name) + // Lock acquisition failure gets propagated as thrown Exception, rather than as return. E.G.: Lets lambdas (from modApiHelpers) fail and trigger their own error handling (exposing misbehaving mods to the user via popup). // isException in ExecResult return value means exception in completely opaque scripting backend. Kotlin exception should still be thrown and propagated like normal. try { ScriptingScope.apiExecutionContext.apply { - handlerParameters = withParams + handlerParameters = withParams?.toMap() + // 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. scriptingBackend = backend } if (command.isNotEmpty()) { From 06575e39102a1437ed7692c0b3d607a377f0d048 Mon Sep 17 00:00:00 2001 From: will-ca Date: Sun, 12 Dec 2021 10:51:54 +0000 Subject: [PATCH 81/93] Let shutdown/dispose handlers be registered in UncivGame. --- core/src/com/unciv/UncivGame.kt | 6 +++- .../api/ScriptingApiInstanceRegistry.kt | 29 ++++++++----------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/core/src/com/unciv/UncivGame.kt b/core/src/com/unciv/UncivGame.kt index c3054ba992f2c..85f158914b805 100644 --- a/core/src/com/unciv/UncivGame.kt +++ b/core/src/com/unciv/UncivGame.kt @@ -13,7 +13,6 @@ import com.unciv.models.metadata.GameSettings import com.unciv.models.ruleset.RulesetCache import com.unciv.models.tilesets.TileSetCache import com.unciv.models.translations.Translations -import com.unciv.scripting.ScriptingState import com.unciv.scripting.api.ScriptingScope import com.unciv.scripting.utils.ScriptingDebugParameters import com.unciv.ui.consolescreen.ConsoleScreen @@ -78,6 +77,9 @@ class UncivGame(parameters: UncivGameParameters) : Game() { lateinit var consoleScreen: ConsoleScreen // Keep same ConsoleScreen() when possible, to avoid having to manually persist/restore history, input field, etc. + // Set of functions to call when disposing/closing the game. + val disposeCallbacks = HashSet<() -> Unit>() + override fun create() { Gdx.input.setCatchKey(Input.Keys.BACK, true) @@ -222,6 +224,8 @@ class UncivGame(parameters: UncivGameParameters) : Game() { } settings.save() + for (callback in disposeCallbacks) callback() + threadList.filter { it !== Thread.currentThread() && it.name != "DestroyJavaVM"}.forEach { println (" Thread ${it.name} still running in UncivGame.dispose().") } diff --git a/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt b/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt index 578b02dafd947..5a3a60c29153d 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt @@ -1,26 +1,21 @@ package com.unciv.scripting.api +import com.unciv.UncivGame import java.util.Collections.newSetFromMap import java.util.WeakHashMap import kotlin.NoSuchElementException -import kotlin.concurrent.thread object AllScriptingApiInstanceRegistries { private val registries = newSetFromMap(WeakHashMap()) // Apparently this will be a WeakSet? Oh, all sets just wrap Maps? - fun init() { - Runtime.getRuntime().addShutdownHook( - // TODO: Move uses of this into UncivGame.dispose()? - // TODO: Allow arbitrary callbacks to be added for UncivGame.dispose(). - // TODO: Also, doesn't actually work. - thread(start = false, name = "Check ScriptingApiInstanceRegistry()s are empty.") { - val allkeys = getAllKeys() - if (allkeys.isNotEmpty()) { - println("WARNING: ${allkeys.size} ScriptingApiInstanceRegistry()s still have keys in them:") - println("\t" + allkeys.map { "${it.value.size} keys in ${it.key}\n\t\t"+it.value.joinToString("\n\t\t") }.joinToString("\n\t")) - } + init { + UncivGame.Current.disposeCallbacks.add { // Runtime.getRuntime().addShutdownHook() also works, but I probably prefer to unify with existing shutdown behaviour. + val allKeys = getAllKeys() + if (allKeys.isNotEmpty()) { + println("WARNING: ${allKeys.size} ScriptingApiInstanceRegistry()s still have keys in them:") + println("\t" + allKeys.map { "${it.value.size} keys in ${it.key}\n\t\t"+it.value.joinToString("\n\t\t") }.joinToString("\n\t")) } - ) + } } fun add(registry: ScriptingApiInstanceRegistry) { registries.add(registry) @@ -38,11 +33,11 @@ object AllScriptingApiInstanceRegistries { * @throws IllegalArgumentException On an attempted assignment colliding with an existing key. * @throws NoSuchElementException For reads and removals at non-existent keys. */ -object ScriptingApiInstanceRegistry: MutableMap { +object ScriptingApiInstanceRegistry: MutableMap { // This is a singleton as ScriptingScope is a singleton now, but it's probably best to keep it with the same semantics as a class. + init { + AllScriptingApiInstanceRegistries.add(this) + } private val backingMap = mutableMapOf() -// fun init() { -// AllScriptingApiInstanceRegistries.add(this) -// } override val entries get() = backingMap.entries override val keys From 04856b8ed247b920db3615eb07b0ccfde243b790 Mon Sep 17 00:00:00 2001 From: will-ca Date: Sun, 12 Dec 2021 10:56:15 +0000 Subject: [PATCH 82/93] Delete scripting environment directories on backend termination. --- .../com/unciv/scripting/ScriptingBackend.kt | 75 +++++++++++++------ .../utils/ScriptingDebugParameters.kt | 31 +------- .../scripting/utils/ScriptingErrorHandling.kt | 30 ++++++++ .../unciv/scripting/utils/SourceManager.kt | 11 +-- 4 files changed, 90 insertions(+), 57 deletions(-) create mode 100644 core/src/com/unciv/scripting/utils/ScriptingErrorHandling.kt diff --git a/core/src/com/unciv/scripting/ScriptingBackend.kt b/core/src/com/unciv/scripting/ScriptingBackend.kt index 20f12fce6601a..26e4527846c46 100644 --- a/core/src/com/unciv/scripting/ScriptingBackend.kt +++ b/core/src/com/unciv/scripting/ScriptingBackend.kt @@ -2,6 +2,7 @@ package com.unciv.scripting //import com.badlogic.gdx.Gdx import com.badlogic.gdx.files.FileHandle +import com.unciv.UncivGame import com.unciv.scripting.api.ScriptingScope import com.unciv.scripting.reflection.Reflection import com.unciv.scripting.protocol.Blackbox @@ -10,6 +11,7 @@ import com.unciv.scripting.protocol.ScriptingProtocolReplManager import com.unciv.scripting.protocol.ScriptingRawReplManager import com.unciv.scripting.protocol.SubprocessBlackbox import com.unciv.scripting.utils.ApiSpecGenerator +import com.unciv.scripting.utils.ScriptingDebugParameters import com.unciv.scripting.utils.SourceManager import com.unciv.scripting.utils.SyntaxHighlighter import kotlin.reflect.full.companionObjectInstance @@ -24,6 +26,11 @@ import kotlin.reflect.full.companionObjectInstance */ data class AutocompleteResults(val matches: List = listOf(), val helpText: String? = null) + +// Data class representing the result of executing a command or script in an opaque ScriptingImplementation/ScriptingBackend. + +// @property resultPrint Unstructured output text of command. Analogous to STDOUT, or STDERR if isException is set. +// @property isException Whether the resultPrint represents an uncaught exception. Should only be used for errors that occur inside of a ScriptingImplementation/ScriptingBackend; For errors that occur in Kotlin code outside of a running ScriptingImplementation/ScriptingBackend, an Exception() should be thrown as usual. data class ExecResult(val resultPrint: String, val isException: Boolean = false) @@ -38,7 +45,7 @@ abstract class ScriptingBackend_metadata { * @return A new instance of the parent class of which this object is a companion. */ abstract fun new(): ScriptingBackend // TODO: Um, class references are totally a thing, and probably distinct from KClass, right? - abstract val displayName: String + abstract val displayName: String // TODO: Translations on all these? val syntaxHighlighting: SyntaxHighlighter? = null } @@ -103,7 +110,7 @@ open class ScriptingBackend: ScriptingImplementation { companion object Metadata: ScriptingBackend_metadata() { override fun new() = ScriptingBackend() // Trying to instantiate from a KClass seems messy when the constructors are expected to be called normally. This is easier. - override val displayName: String = "Dummy" + override val displayName = "Dummy" } /** @@ -123,7 +130,7 @@ open class ScriptingBackend: ScriptingImplementation { class HardcodedScriptingBackend(): ScriptingBackend() { companion object Metadata: ScriptingBackend_metadata() { override fun new() = HardcodedScriptingBackend() - override val displayName: String = "Hardcoded" + override val displayName = "Hardcoded" } val commandshelp = mapOf( @@ -170,7 +177,7 @@ class HardcodedScriptingBackend(): ScriptingBackend() { var args = command.split(' ') var out = "\n> ${command}\n" fun appendOut(text: String) { - out += text + "\n" + out += (text + "\n").prependIndent(" ") } when (args[0]) { "help" -> { @@ -298,7 +305,7 @@ class HardcodedScriptingBackend(): ScriptingBackend() { } } ScriptingScope.uncivGame!!.simulateUntilTurnForDebug = numturn - appendOut("Will automatically simulate game until turn ${numturn} after this turn.\nThe map will not update until completed.") + appendOut("Will automatically simulate game until turn ${numturn} the next time you press Next Turn.\nThe map will not update until completed.") } else { appendOut("Cheats must be enabled to use this command!") } @@ -327,7 +334,7 @@ class ReflectiveScriptingBackend(): ScriptingBackend() { companion object Metadata: ScriptingBackend_metadata() { override fun new() = ReflectiveScriptingBackend() - override val displayName: String = "Reflective" + override val displayName = "Reflective" } private val commandparams = mapOf("get" to 1, "set" to 2, "typeof" to 1, "examples" to 0, "runtests" to 0) //showprivates? @@ -466,7 +473,8 @@ class ReflectiveScriptingBackend(): ScriptingBackend() { } } - +// Uses SourceManager to copy library files for engine type into a temporary directory per instance. +// Deletes abstract class EnvironmentedScriptingBackend(): ScriptingBackend() { companion object Metadata: EnvironmentedScriptBackend_metadata() { @@ -477,16 +485,38 @@ abstract class EnvironmentedScriptingBackend(): ScriptingBackend() { override val engine = "" } + val folderHandle = SourceManager.setupInterpreterEnvironment(metadata.engine) // Temporary directories are often RAM-backed, so, meh. Alternative to copying entire interpreter library/bindings would be to either implement a virtual filesystem (complicated and sounds brittle) or make scripts share files and thus let them interfere with each other if they have filesystem access (as is deliberately the case in the Python backend)… A couple hundred lines of text and a small, compressed example JPEG or three won't kill anything. + fun deleteFolder(): Unit { + if (ScriptingDebugParameters.printEnvironmentFolderCreation) { + println("Deleting interpreter environment for ${metadata.engine} scripting engine: ${folderHandle.path()}") + } + folderHandle.deleteDirectory() + } + + init { + UncivGame.Current.disposeCallbacks.add(::deleteFolder) + } + override val metadata // Since the companion object type is different, we have to define a new getter for the subclass instance companion getter to get its new members. get() = this::class.companionObjectInstance as EnvironmentedScriptBackend_metadata - val folderHandle: FileHandle by lazy { SourceManager.setupInterpreterEnvironment(metadata.engine) } // This requires the overridden values for engine, so setting it in the constructor causes a null error... May be fixed since moving engine to the companions. // Also, BlackboxScriptingBackend inherits from this, but not all subclasses of BlackboxScriptingBackend might need it. So as long as it's not accessed, it won't be initialized. // TODO: Probably implement a the Disposable interface method here to clean up the folder. + override fun terminate(): Exception? { + return try { + deleteFolder() + null + } catch (e: Exception) { + e + } finally { + UncivGame.Current.disposeCallbacks.remove(::deleteFolder) // Looks like value equality, but not referential equality, is preserved between different references to the same function… Good enough. + } + } + } @@ -531,11 +561,12 @@ abstract class BlackboxScriptingBackend(): EnvironmentedScriptingBackend() { } override fun terminate(): Exception? { - try { - return replManager.terminate() + val deleteResult = super.terminate() + return (try { + replManager.terminate() } catch (e: Exception) { - return e - } + e + }) ?: deleteResult } } @@ -550,14 +581,16 @@ abstract class SubprocessScriptingBackend(): BlackboxScriptingBackend() { override fun motd(): String { return """ - + + Welcome to the Unciv '${metadata.displayName}' API. This backend relies on running the system ${processCmd.firstOrNull()} command as a subprocess. - + If you do not have an interactive REPL below, then please make sure the following command is valid on your system: - + ${processCmd.joinToString(" ")} - - """.trimIndent() + super.motd() + + + """.trimIndent() + super.motd() // I don't think trying to translate this (or its subcomponents— Although I guess translations are going to be available for displayName anyway) would be the best idea. } } @@ -573,7 +606,7 @@ class SpyScriptingBackend(): ProtocolSubprocessScriptingBackend() { companion object Metadata: EnvironmentedScriptBackend_metadata() { override fun new() = SpyScriptingBackend() - override val displayName: String = "System Python" + override val displayName = "System Python" override val engine = "python" } @@ -586,7 +619,7 @@ class SqjsScriptingBackend(): SubprocessScriptingBackend() { companion object Metadata: EnvironmentedScriptBackend_metadata() { override fun new() = SqjsScriptingBackend() - override val displayName: String = "System QuickJS" + override val displayName = "System QuickJS" override val engine = "qjs" } @@ -599,7 +632,7 @@ class SluaScriptingBackend(): SubprocessScriptingBackend() { companion object Metadata: EnvironmentedScriptBackend_metadata() { override fun new() = SluaScriptingBackend() - override val displayName: String = "System Lua" + override val displayName = "System Lua" override val engine = "lua" } @@ -614,7 +647,7 @@ class DevToolsScriptingBackend(): ScriptingBackend() { companion object Metadata: ScriptingBackend_metadata() { override fun new() = DevToolsScriptingBackend() - override val displayName: String = "DevTools" + override val displayName = "DevTools" } val commands = listOf( diff --git a/core/src/com/unciv/scripting/utils/ScriptingDebugParameters.kt b/core/src/com/unciv/scripting/utils/ScriptingDebugParameters.kt index a8758400eed30..0967a6f7cd619 100644 --- a/core/src/com/unciv/scripting/utils/ScriptingDebugParameters.kt +++ b/core/src/com/unciv/scripting/utils/ScriptingDebugParameters.kt @@ -1,11 +1,5 @@ package com.unciv.scripting.utils -import com.unciv.UncivGame -import com.unciv.scripting.sync.ScriptingRunLock -import com.unciv.ui.utils.BaseScreen -import com.unciv.ui.utils.Popup -import com.unciv.ui.utils.stringifyException - object ScriptingDebugParameters { // Whether to print out all/most IPC packets for debug. var printPacketsForDebug = false @@ -17,27 +11,8 @@ object ScriptingDebugParameters { var printTokenizerMilestones = false // Whether to print out a warning when reflectively accessing definitions that have been deprecated. var printReflectiveDeprecationWarnings = false // TODO + // Whether to print out when creating and deleting temporary + var printEnvironmentFolderCreation = false + // TODO: Add to gameIsNotRunWithDebugModes unit tests. } -object ScriptingErrorHandling { - fun notifyPlayerScriptFailure(text: String, asName: String? = null, toConsole: Boolean = true) { - val popup = Popup(UncivGame.Current.screen as BaseScreen) - val msg = "{An error has occurred with the mod/script} ${asName ?: ScriptingRunLock.runningName}:\n\n${text.prependIndent("\t")}\n\n{See system terminal output for details.}\n{Consider disabling mods if this keeps happening.}\n" - popup.addGoodSizedLabel(msg).row() - popup.addOKButton{} - popup.open(true) - if (toConsole) - printConsolePlayerScriptFailure(text, asName) - } - fun notifyPlayerScriptFailure(exception: Throwable, asName: String? = null) { - notifyPlayerScriptFailure(exception.toString(), asName, false) - printConsolePlayerScriptFailure(exception, asName) - } - fun printConsolePlayerScriptFailure(text: String, asName: String? = null) { - println("\nException with <${asName ?: ScriptingRunLock.runningName}> script:\n${text.prependIndent("\t")}\n") - // Really these should all go to STDERR. - } - fun printConsolePlayerScriptFailure(exception: Throwable, asName: String? = null) { - printConsolePlayerScriptFailure(exception.stringifyException(), asName) - } -} diff --git a/core/src/com/unciv/scripting/utils/ScriptingErrorHandling.kt b/core/src/com/unciv/scripting/utils/ScriptingErrorHandling.kt new file mode 100644 index 0000000000000..b29177836fba5 --- /dev/null +++ b/core/src/com/unciv/scripting/utils/ScriptingErrorHandling.kt @@ -0,0 +1,30 @@ +package com.unciv.scripting.utils + +import com.unciv.UncivGame +import com.unciv.scripting.sync.ScriptingRunLock +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.Popup +import com.unciv.ui.utils.stringifyException + +object ScriptingErrorHandling { + fun notifyPlayerScriptFailure(text: String, asName: String? = null, toConsole: Boolean = true) { + val popup = Popup(UncivGame.Current.screen as BaseScreen) + val msg = "{An error has occurred with the mod/script} ${asName ?: ScriptingRunLock.runningName}:\n\n${text.prependIndent("\t")}\n\n{See system terminal output for details.}\n{Consider disabling mods if this keeps happening.}\n" // TODO: Translation. + popup.addGoodSizedLabel(msg).row() + popup.addOKButton{} + popup.open(true) + if (toConsole) + printConsolePlayerScriptFailure(text, asName) + } + fun notifyPlayerScriptFailure(exception: Throwable, asName: String? = null) { + notifyPlayerScriptFailure(exception.toString(), asName, false) + printConsolePlayerScriptFailure(exception, asName) + } + fun printConsolePlayerScriptFailure(text: String, asName: String? = null) { + println("\nException with <${asName ?: ScriptingRunLock.runningName}> script:\n${text.prependIndent("\t")}\n") + // Really these should all go to STDERR. + } + fun printConsolePlayerScriptFailure(exception: Throwable, asName: String? = null) { + printConsolePlayerScriptFailure(exception.stringifyException(), asName) + } +} diff --git a/core/src/com/unciv/scripting/utils/SourceManager.kt b/core/src/com/unciv/scripting/utils/SourceManager.kt index 5241ab7ce6003..2cc1a805f660c 100644 --- a/core/src/com/unciv/scripting/utils/SourceManager.kt +++ b/core/src/com/unciv/scripting/utils/SourceManager.kt @@ -33,6 +33,9 @@ object SourceManager { fun setupInterpreterEnvironment(engine: String): FileHandle { val enginedir = getEngineLibraries(engine) val outdir = FileHandle.tempDirectory("unciv-${engine}_") + if (ScriptingDebugParameters.printEnvironmentFolderCreation) { + println("Created interpreter environment for $engine scripting engine: ${outdir.path()}") + } fun addfile(sourcedir: FileHandle, path: String) { val target = outdir.child(path) if (path.endsWith("/")) { @@ -47,14 +50,6 @@ object SourceManager { for (fp in ScriptingConstants.engines[engine]!!.files) { addfile(enginedir, fp) } - Runtime.getRuntime().addShutdownHook( - // Delete temporary directory on JVM shutdown, not on backend object destruction/termination. The copied files shouldn't be huge anyway, there's no reference to a ScriptingBackend() here, and I trust the shutdown hook to be run more reliably. - // I guess you could wrap the outdir folder handler in something with a .finalize(), then keep it around for the duration of each backend, if you wanted to clear scripting runtimes when they're no longer in use. May become more pressing if a modding API is implemented and it involves spinning up new ScriptingStates/ScriptingBackends for every loaded game, I guess. - thread(start = false, name = "Delete ${outdir.toString()}.") { - outdir.deleteDirectory() - } - // Will JVM outlive app on Android? Hopefully temporary directory will get cleaned long-term, but may need more robust behaviour short-term. - ) return outdir } } From 2d21675f64ccbaa0b92ce58fd6f332c47b63498b Mon Sep 17 00:00:00 2001 From: will-ca Date: Sun, 12 Dec 2021 10:59:11 +0000 Subject: [PATCH 83/93] Add Python shadow module to enable some semblance of emulated script functionality and testability even without Unciv. --- .../scripting/ScriptingEngineConstants.json | 1 + .../enginefiles/python/unciv_lib/shadow.py | 118 +++++++++++++++++- .../unciv_scripting_examples/EventPopup.py | 2 +- .../MapEditingMacros.py | 30 +++-- .../unciv_scripting_examples/PlayerMacros.py | 2 + .../python/unciv_scripting_examples/Tests.py | 4 +- 6 files changed, 140 insertions(+), 17 deletions(-) diff --git a/android/assets/scripting/ScriptingEngineConstants.json b/android/assets/scripting/ScriptingEngineConstants.json index 7868573dbf285..f5ef5fec0a6f3 100644 --- a/android/assets/scripting/ScriptingEngineConstants.json +++ b/android/assets/scripting/ScriptingEngineConstants.json @@ -7,6 +7,7 @@ unciv_lib/api.py unciv_lib/autocompletion.py unciv_lib/ipc.py + unciv_lib/shadow.py unciv_lib/utils.py unciv_lib/wrapping.py PythonScripting.md diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/shadow.py b/android/assets/scripting/enginefiles/python/unciv_lib/shadow.py index 4f0d1dd31ee4c..4487ab7c8227e 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/shadow.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/shadow.py @@ -1,3 +1,119 @@ +""" +Micro-library meant to allow Unciv scripts to be run even without running the game, by implementing fake versions of as many Python operators as possible. Script logic may error, but the basic control flow shouldn't. +The objects resulting from the shadowed API keep track of how they were created and any further access to them. +The repr() of each object visualizes the tree of everything done under it. -# TODO: Implement a class/object that just returns itself for all magic methods. +E.G.: Shadowed result from a function that shows an event popup when run in Unciv: + + )> + │ + ├ .addGoodSizedLabel + │ ├ (Something has happened in your empire!, 24) + │ │ ├ .row + │ │ │ ├ () + ├ .addSeparator + │ ├ () + │ │ ├ .row + │ │ │ ├ () + ├ .addGoodSizedLabel + │ ├ (A societally and politically s...lay a political decision: , 18) + │ │ ├ .row + │ │ │ ├ () + ├ .add + │ ├ ()>) + │ │ ├ .row + │ │ │ ├ () + ├ .add + │ ├ ()>) + │ │ ├ .row + │ │ │ ├ () + ├ .open + │ ├ (False) +""" + +import sys, random, re + +def rep(obj): + if isinstance(obj, FakeApi): + return str(obj) + else: + return repr(obj) + +def strCall(a, kw): + return ", ".join(str(p) for l in (a, (f"{k}={rep(v)}" for k, v in kw.items())) for p in l) + + +def doAction(self, action): + res = self.__class__(preceding=self, action=action) + self._following.append(res) + return res + + +MagicNames = {'__getattr__', *(n for t in __builtins__.values() if isinstance(t, type) for n, m in t.__dict__.items() if n.startswith('__') and callable(m) and n not in object.__dict__)} + +class FakeApiMetaclass(type): + def __new__(meta, name, bases, namespace, **kwds): + for n in MagicNames: + if n not in namespace: + namespace[n] = lambda self, *a, **kw: doAction(self, f".{n}({strCall(a, kw)})") + return super(FakeApiMetaclass, meta).__new__(meta, name, bases, namespace, **kwds) + + +class FakeApi(str, metaclass=FakeApiMetaclass): + __all__ = ('Unciv', 'apiExecutionContext', 'apiHelpers', 'civInfo', 'gameInfo', 'mapEditorScreen', 'modApiHelpers', 'toString', 'uncivGame', 'worldScreen') + def _init(self, *, preceding=None, action=None): + self._preceding = preceding + self._action = action + self._following = [] + def __new__(cls, preceding=None, action=""): + self = str.__new__(cls, "") + self._init(preceding=preceding, action=action) + return self + def __repr__(self): + lines = [self.__str__(), "│"] + def traverseChildren(node, depth): + action = re.sub("\s+", " ", re.sub("[│├]", "", node._action)).strip() + if len(action) > 65: + action = action[:31] + "..." + action[-31:] + lines.append("│ "*(depth-1) + "├ " + action) + for child in node._following: + traverseChildren(child, depth+1) + for child in self._following: + traverseChildren(child, 1) + return "\n".join(lines) + def __str__(self): + def path(): + x = self + while x is not None: + yield str(x._action) + x = x._preceding + return f"" + def __bool__(self): + # doAction(".__bool__()") + return True + def __int__(self): + # doAction(".__int__()") + return random.getrandbits(5) + def __getattr__(self, name): + return doAction(self, f".{name}") + def __getitem__(self, key): + return doAction(self, f"[{rep(key)}]") + def __call__(self, *a, **kw): + return doAction(self, f"({strCall(a, kw)})") + def __next__(self): + if not random.getrandbits(2): + raise StopIteration() + return self + def __iter__(self): + yield self + while not random.getrandbits(2): + yield self + def __length_hint__(self): + return NotImplemented + + +FAKE_API = FakeApi() + +if 'unciv' not in sys.modules: + sys.modules['unciv'] = FAKE_API diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EventPopup.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EventPopup.py index c9f73be6cd403..f169c672b05a4 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EventPopup.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EventPopup.py @@ -85,7 +85,7 @@ def showEventPopup(title=None, image=None, text="No text event text provided!", ) popup.add(button).row() popup.open(False) - return {**locals()} + return popup def EVENT_POPUP_DEMOARGS(): diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py index ba5fea13903ec..3260112f72404 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py @@ -19,14 +19,16 @@ # If you modify this file, please add any new functions to Tests.py. - -t = re.sub("//.*", "", re.sub('/\*.*\*/', "", real(unciv.apiHelpers.App.assetFileString("jsons/Civ V - Gods & Kings/Terrains.json")), flags=re.DOTALL)) -terrainsjson = json.loads(t) -# In an actual implementation, you would want to read from the ruleset instead of the JSON. But this is eaiser for me. -del t - -terrainbases = {t['name']: t for t in terrainsjson if t['type'] in ('Water', 'Land')} -terrainfeatures = {t['name']: t for t in terrainsjson if t['type'] == 'TerrainFeature'} +try: + t = re.sub("//.*", "", re.sub('/\*.*\*/', "", real(unciv.apiHelpers.App.assetFileString("jsons/Civ V - Gods & Kings/Terrains.json")), flags=re.DOTALL)) + terrainsjson = json.loads(t) + # In an actual implementation, you would want to read from the ruleset instead of the JSON. But this is easier for me. + del t +except Exception as e: + print("Couldn't load terrains Terrains.json") +else: + terrainbases = {t['name']: t for t in terrainsjson if t['type'] in ('Water', 'Land')} + terrainfeatures = {t['name']: t for t in terrainsjson if t['type'] == 'TerrainFeature'} def genValidTerrains(*, forbid=('Fallout',)): @@ -45,9 +47,9 @@ def genValidTerrains(*, forbid=('Fallout',)): otherparams = terrainfeatures[otherfeature] if terrain in otherparams['occursOn']: terrains.add(f"{terrain}/{otherfeature},{feature}") - return terrains + return tuple(sorted(terrains)) -naturalterrains = tuple(sorted(genValidTerrains())) +# naturalterrains = tuple(sorted(genValidTerrains())) _altitudeterrainsequence = ( @@ -150,7 +152,7 @@ def floodFillSelected(start=None, fillas=None, *, alsopropagateto=()): } def _mandelbrot(x, y, iterations=100, *, expo=2, escaperadius=12, innervalue=None): - c=complex(x,y) + c = complex(x,y) z = 0+0j dist = 0 if innervalue is None: @@ -302,7 +304,9 @@ def getImageAverageRgb(image): return tuple(c/total_alpha for c in (r_sum, g_sum, b_sum)) -def computeTerrainAverageColours(terrains=naturalterrains): +def computeTerrainAverageColours(terrains=None): + if terrains is None: + terrains = genValidTerrains() def terraincol(terrain): with compositedTerrainImage(terrain) as i: return getImageAverageRgb(i) @@ -349,7 +353,7 @@ def loadImageColours(tileMap=None, imagepath="EarthTerrainFantasyHex.jpg", terra imagepath = _imageFallbackPath(imagepath) if terraincolours is None: print(f"\nNo terrain colours given. Computing average tile colours based on FantasyHex tileset. This may take several seconds.") - terraincolours = computeTerrainAverageColours(naturalterrains) + terraincolours = computeTerrainAverageColours(genValidTerrains()) print(f"\nTerrain colours computed:\n{repr(terraincolours)}") pixinterp = _TerrainColourInterpreter(terraincolours, maxdither=maxdither) with PIL.Image.open(imagepath) as image: diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/PlayerMacros.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/PlayerMacros.py index b61296a870938..42b0012c5712b 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/PlayerMacros.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/PlayerMacros.py @@ -29,6 +29,7 @@ def gatherBiggestCities(cities, ratio, stat='production'): def clearCitiesProduction(cities): """Clear given cities of all queued and current production, and return the iterable of cities.""" + i = 0 # Loop never run when no cities? for i, city in enumerate(cities): city.cityConstructions.constructionQueue.clear() print(f"Cleared production from {i+1} cities.") @@ -36,6 +37,7 @@ def clearCitiesProduction(cities): def addCitiesProduction(cities, queue=()): """Add given construction items to the construction queues of given cities, and return the iterable of cities.""" + i = 0 for i, city in enumerate(cities): for build in queue: city.cityConstructions.addToQueue(build) diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py index 35aeb60f4a0ad..890d723ded239 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py @@ -197,10 +197,10 @@ def NoRecursiveScriptingTest(): # Should fail, because recursive scripting is forbidden. except Exception as e: re = repr(e) - assert ('already in use' in re and 'Cannot acquire' in re), f"Recursive scripting failed like expected, but did not produce the expected exception: {repr(e)}" + assert (isinstance(e, unciv_pyhelpers.ForeignError) and 'already in use' in re and 'Cannot acquire' in re), f"Recursive scripting failed like expected, but did not produce the expected exception: {repr(e)}" else: raise AssertionError(f"Recursive scripting succeeded, but it isn't supposed to.") - # If it ever gets here, the entire IPC loop will almost definitely be broken, so this will never actually make it to the JVM. But oh well. Let's still keep an explicit test nonetheless for semantic clarity. + # If it ever gets here, the entire IPC loop will almost definitely be broken, so this will never actually make it to the JVM. But oh well. Let's still keep an explicit test nonetheless for semantic/spec clarity. # Tests for PlayerMacros.py. From 3beaef31724731bf3c753b1ddb9de26e9a6cf0ab Mon Sep 17 00:00:00 2001 From: will-ca Date: Sun, 12 Dec 2021 11:02:41 +0000 Subject: [PATCH 84/93] Tag needed translations as TODOs. --- .../scripting/api/ScriptingApiHelpers.kt | 2 +- .../scripting/protocol/SubprocessBlackbox.kt | 4 +-- .../unciv/scripting/sync/ScriptingRunLock.kt | 4 +-- .../scripting/sync/ScriptingRunThreader.kt | 17 ++++++------ .../unciv/ui/consolescreen/ConsoleScreen.kt | 26 +++++++------------ .../consolescreen/IConsoleScreenAccessible.kt | 12 +++------ .../ui/worldscreen/mainmenu/OptionsPopup.kt | 19 +++++++------- 7 files changed, 38 insertions(+), 46 deletions(-) diff --git a/core/src/com/unciv/scripting/api/ScriptingApiHelpers.kt b/core/src/com/unciv/scripting/api/ScriptingApiHelpers.kt index e051e0c2664b1..7e79da7475d94 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiHelpers.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiHelpers.kt @@ -24,7 +24,7 @@ object ScriptingApiHelpers { val Mappers = ScriptingApiMappers val registeredInstances = ScriptingApiInstanceRegistry - val instancesAsInstances = FakeMap{obj: Any? -> obj} // TODO: Rename this, and singleton it. + val instancesAsInstances = FakeMap{obj: Any? -> obj} /// Scripting language bindings work by keeping track of the paths to values, and making Kotlin/the JVM resolve them only when needed. // This creates a dilemma: Resolving a path into a Kotlin value too early means that no further paths (E.G. attribute, keys, calls) can be built on top of it. But resolving it late means that expected side effects may not happen (E.G. function calls probably shouldn't be deferred). And values that *must* be resolved, like the results of function calls, cannot have their own members and method accessed until they themselves are assigned to a path, because they're just kinda floating around as far as the scripting-exposed semantics are concerned. // So this fake Map works around that, by providing a way for any random object to appear to have a path. diff --git a/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt b/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt index 350e485120666..667359caa0a88 100644 --- a/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt +++ b/core/src/com/unciv/scripting/protocol/SubprocessBlackbox.kt @@ -65,7 +65,7 @@ class SubprocessBlackbox(val processCmd: Array): Blackbox { */ override fun start() { if (isAlive) { - throw RuntimeException("Process is already running: ${process}") + throw RuntimeException("Process is already running: ${process}") // Could translate. Probably shouldn't. } try { process = Runtime.getRuntime().exec(processCmd) @@ -102,7 +102,7 @@ class SubprocessBlackbox(val processCmd: Array): Blackbox { if (block || readyForRead > 0) { return inStream!!.readLine() } else { - throw IllegalStateException("Empty STDOUT for ${process}.") + throw IllegalStateException("Empty STDOUT for ${process}.") // Could translate. Probably shouldn't. } } diff --git a/core/src/com/unciv/scripting/sync/ScriptingRunLock.kt b/core/src/com/unciv/scripting/sync/ScriptingRunLock.kt index aad61e523dc3e..93d94996729e4 100644 --- a/core/src/com/unciv/scripting/sync/ScriptingRunLock.kt +++ b/core/src/com/unciv/scripting/sync/ScriptingRunLock.kt @@ -22,13 +22,13 @@ object ScriptingRunLock { @Synchronized fun acquire(name: String? = null): String { // Different *threads* that try to run scripts concurrently should already queue up nicely without this due to the use of the Synchronized annotation on ScriptingState.exec(), I think. // But because of the risk of recursive script runs (which will deadlock if trying to acquire a lock that must be released by the same thread, and which break the already-in-use IPC loop for a backend if allowed to continue without a lock), the behaviour here if or when that fails is to throw an exception. - if (!isRunning.compareAndSet(false, true)) throw IllegalStateException("Cannot acquire ${this::class.simpleName} for $name because it is already in use by $runningName.") + if (!isRunning.compareAndSet(false, true)) throw IllegalStateException("Cannot acquire ${this::class.simpleName} for $name because it is already in use by $runningName.") // Prooobably don't translate? val key = UUID.randomUUID().toString() runningKey = key runningName = name return key } - // @param releaseKey The string previously returned by the immediately preceding succesful acquire(). + // @param releaseKey The string previously returned by the immediately preceding successful acquire(). // @throws IllegalArgumentException If given the incorrect releaseKey. // @throws IllegalStateException If not currently acquired. @Synchronized fun release(releaseKey: String) { diff --git a/core/src/com/unciv/scripting/sync/ScriptingRunThreader.kt b/core/src/com/unciv/scripting/sync/ScriptingRunThreader.kt index b1910056ca9ae..323a2be7d9194 100644 --- a/core/src/com/unciv/scripting/sync/ScriptingRunThreader.kt +++ b/core/src/com/unciv/scripting/sync/ScriptingRunThreader.kt @@ -9,7 +9,7 @@ import kotlin.concurrent.thread object ScriptingRunThreader { private val isDoingRuns = AtomicBoolean(false) @Suppress("NewApi") // FIXME: Bump API level up. - private var runQueue = ConcurrentLinkedDeque<() -> Unit>() + private val runQueue = ConcurrentLinkedDeque<() -> Unit>() fun queueRun(toRun: () -> Unit) { runQueue.add(toRun) } @@ -19,24 +19,25 @@ object ScriptingRunThreader { fun queueRuns(runs: Sequence<() -> Unit>) { for (run in runs) runQueue.add(run) } - @Synchronized fun doRuns(begin: Boolean = true) { - if (begin) { - if (!isDoingRuns.compareAndSet(false, true)) { - throw IllegalStateException("${this::class.simpleName} is already consuming its queued runs!") - } - } + @Synchronized private fun doNextRunRecursive() { val run: (() -> Unit)? = runQueue.poll() if (run != null) { thread { try { run() } finally { - Gdx.app.postRunnable { doRuns(false) } + Gdx.app.postRunnable { doNextRunRecursive() } } } } else { isDoingRuns.set(false) } } + @Synchronized fun doRuns() { + if (!isDoingRuns.compareAndSet(false, true)) { + throw IllegalStateException("${this::class.simpleName} is already consuming its queued runs!") + } + doNextRunRecursive() + } } diff --git a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt index 300097835f219..8776e89b7b455 100644 --- a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt @@ -18,11 +18,7 @@ import com.unciv.ui.utils.AutoScrollPane as ScrollPane import kotlin.math.max import kotlin.math.min -//TODO: -//"Show this warning next time." - -//"I understand and wish to continue." // Probably grey this out for five seconds. -//"Get me out of here!" +// Could also abstract this entire class as an extension on Table, letting it be used as screen, popup, or random widget, but don't really need to. class ConsoleScreen(var closeAction: () -> Unit): BaseScreen() { @@ -43,10 +39,10 @@ class ConsoleScreen(var closeAction: () -> Unit): BaseScreen() { private val inputField: TextField = TextField("", skin) private val inputControls: Table = Table() - private val tabButton: TextButton = "TAB".toTextButton() + private val tabButton: TextButton = "TAB".toTextButton() // TODO: Translation. private val upButton: Image = ImageGetter.getImage("OtherIcons/Up") private val downButton: Image = ImageGetter.getImage("OtherIcons/Down") - private val runButton: TextButton = "ENTER".toTextButton() + private val runButton: TextButton = "ENTER".toTextButton() // TODO: Translation. private val layoutUpdators = ArrayList<() -> Unit>() private var isOpen = false @@ -65,7 +61,7 @@ class ConsoleScreen(var closeAction: () -> Unit): BaseScreen() { init { - backendsAdders.add("Launch new backend:".toLabel()).padRight(30f).padLeft(20f) + backendsAdders.add("Launch new backend:".toLabel()).padRight(30f).padLeft(20f) // TODO: Translation. for (backendtype in ScriptingBackendType.values()) { var backendadder = backendtype.metadata.displayName.toTextButton() // Hm. Should this be translated/translatable? I suppose it already is translatable in OptionsPopup too. And in the running list— So basically everywhere it's shown. backendadder.onClick { @@ -92,7 +88,7 @@ class ConsoleScreen(var closeAction: () -> Unit): BaseScreen() { printHistory.bottom() printScroll = ScrollPane(printHistory) - runningContainer.add("Active Backends:".toLabel()).row() + runningContainer.add("Active Backends:".toLabel()).row() // TODO: Translation. runningContainer.add(runningList) middleSplit = SplitPane(printScroll, runningContainer, false, skin) @@ -143,16 +139,16 @@ class ConsoleScreen(var closeAction: () -> Unit): BaseScreen() { updateRunning() } - fun updateLayout() { + private fun updateLayout() { for (func in layoutUpdators) { func() } } - fun showWarningPopup() { + private fun showWarningPopup() { YesNoPopup( "{WARNING}\n\n{The Unciv scripting API is a HIGHLY EXPERIMENTAL feature intended for advanced users!}\n{It may be possible to damage your device and files by running malicious or poorly designed code!}\n\n{Do you wish to continue?}", - { + { // TODO: Translation. warningAccepted = true }, this, @@ -198,7 +194,7 @@ class ConsoleScreen(var closeAction: () -> Unit): BaseScreen() { val exc: Exception? = ScriptingState.termBackend(i) updateRunning() if (exc != null) { - echo("Failed to stop ${backend.metadata.displayName} backend: ${exc.toString()}") + echo("Failed to stop ${backend.metadata.displayName} backend: ${exc.toString()}") // TODO: Translation? I mean, probably not, really. } } runningList.add(termbutton.surroundWithCircle(40f)).row() @@ -297,7 +293,7 @@ class ConsoleScreen(var closeAction: () -> Unit): BaseScreen() { setText("") if (execResult.isException) { ScriptingErrorHandling.printConsolePlayerScriptFailure(execResult.resultPrint, asName = name) - ToastPopup("Exception in ${name}.", this) + ToastPopup("{Exception in} ${name}.", this) // TODO: Translation. } } @@ -331,5 +327,3 @@ class ConsoleScreen(var closeAction: () -> Unit): BaseScreen() { SelectAfter } } - -// Screen, widget, or popup? diff --git a/core/src/com/unciv/ui/consolescreen/IConsoleScreenAccessible.kt b/core/src/com/unciv/ui/consolescreen/IConsoleScreenAccessible.kt index cdf2ffda9999c..97f11d868013e 100644 --- a/core/src/com/unciv/ui/consolescreen/IConsoleScreenAccessible.kt +++ b/core/src/com/unciv/ui/consolescreen/IConsoleScreenAccessible.kt @@ -16,21 +16,19 @@ import com.unciv.ui.mapeditor.MapEditorScreen //Interface that extends BaseScreen with methods for exposing the global ConsoleScreen. interface IConsoleScreenAccessible { - val BaseScreen.consoleScreen: ConsoleScreen // TODO: Oh, apparently don't need to explicitly refer to this? Change in other extension functions too, I guess. - get() = this.game.consoleScreen - + val BaseScreen.consoleScreen: ConsoleScreen + get() = game.consoleScreen //Set the console screen tilde hotkey. fun BaseScreen.setOpenConsoleScreenHotkey() { - this.keyPressDispatcher[Input.Keys.GRAVE] = { this.game.setConsoleScreen() } + this.keyPressDispatcher[Input.Keys.GRAVE] = { game.setConsoleScreen() } } //Set the console screen to return to the right screen when closed. //Defaults to setting the game's screen to this instance. Can also use a lambda, for E.G. WorldScreen and UncivGame.setWorldScreen(). fun BaseScreen.setConsoleScreenCloseAction(closeAction: (() -> Unit)? = null) { - // TODO: This can probably be combined with setOpenConsoleScreenHotkey. - this.consoleScreen.closeAction = closeAction ?: { this.game.setScreen(this) } + this.consoleScreen.closeAction = closeAction ?: { game.setScreen(this) } } //Extension method to update scripting API scope variables that are expected to change over the lifetime of a ScriptingState. @@ -53,6 +51,4 @@ interface IConsoleScreenAccessible { it.mapEditorScreen = mapEditorScreen } // .apply errors on compile with "val cannot be reassigned". } - -// fun BaseScreen.updateScriptingState(){} // TODO: Same, but don't clear. Or not? } diff --git a/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt b/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt index fe8506a5c3294..54fa6203d9db5 100644 --- a/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt +++ b/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt @@ -268,7 +268,7 @@ class OptionsPopup(val previousScreen: BaseScreen) : Popup(previousScreen) { add( WrappableLabel( - "{WARNING: The features on this page are all experimental, and very powerful. It may be possible to damage your device using them if you do not know what you are doing.}\n\n{You have been warned!}", + "{WARNING: The features on this page are all experimental, and very powerful. It may be possible to damage your device using them if you do not know what you are doing.}\n\n{You have been warned!}", // TODO: Translation. tabs.prefWidth * 0.5f ).apply { wrap = true @@ -278,16 +278,16 @@ class OptionsPopup(val previousScreen: BaseScreen) : Popup(previousScreen) { addSeparator().padTop(20f) - add("Scripting Console".toLabel(fontSize = 24)).padTop(20f).row() + add("Scripting Console".toLabel(fontSize = 24)).padTop(20f).row() // TODO: Translation. val consoleBasicTable = Table(skin).apply { pad(5f) defaults().pad(2.5f) - addYesNoRow("{Enable scripting console}\n{Press ~ to activate}", + addYesNoRow("{Enable scripting console}\n{Press ~ to activate}", // TODO: Translation. settings.enableScriptingConsole) { settings.enableScriptingConsole = it } - addYesNoRow("Show warning when opening scripting console", + addYesNoRow("Show warning when opening scripting console", // TODO: Translation. settings.showScriptingConsoleWarning) { settings.showScriptingConsoleWarning = it } // Two affirmative actions to start using scripting console: Enable in click options button, accept ConsoleScreen warning. @@ -295,7 +295,7 @@ class OptionsPopup(val previousScreen: BaseScreen) : Popup(previousScreen) { add(consoleBasicTable).row() - add("Console Startup Commands".toLabel(fontSize = 24)).padTop(20f).row() + add("Console Startup Commands".toLabel(fontSize = 24)).padTop(20f).row() // TODO: Translation. val consoleStartupsTable = Table(skin).apply { pad(5f) @@ -313,20 +313,20 @@ class OptionsPopup(val previousScreen: BaseScreen) : Popup(previousScreen) { addSeparator().padTop(20f) - add("Mod Scripting API".toLabel(fontSize = 24)).padTop(20f).row() + add("Mod Scripting API".toLabel(fontSize = 24)).padTop(20f).row() // TODO: Translation. val modScriptingTable = Table(skin).apply { pad(5f) defaults().pad(2.5f) val buttonSetter = object { var set = { _: Boolean -> println("Uninitialized button setting wrapper for enableModScripting.") } } // Need access to the button setter from inside the lambda passed to addYesNoRow, but only get it from the return value of addYesNoRow. It's… Not *that* messy, and doing this here is probably cleaner than what I would end up with if I tried to change addYesNoRow any further (which is also used a lot elsewhere). buttonSetter.set = addYesNoRow( - "{Allow mods to run scripts}\n{CAUTION!}", + "{Allow mods to run scripts}\n{CAUTION!}", // TODO: Translation. settings.enableModScripting ) { // Probably not worth adding the confirmation behaviour to addYesNoRow… Standardizing it sounds like a quick path to warning fatigue. settings.enableModScripting = false if (it) { // Aside from not wanting to get anyone hacked, should probably also check if Google Play has liability (or third-party distribution) issues— Not any more dangerous than a web browser, in fact arguably safer/more controlled, and trapped in Android sandbox regardless, but still. YesNoPopup( // Four affirmative actions to enable a mod: Click options button, accept options warning, tick mod checkbox, accept mod warning. - "{Enabling this feature will allow mods you choose to run code on your device.}\n\n{Malicious code may be able to harm your device or steal your data!}\n{Never enable scripting for any mods you don't trust.}\n\n{Do you wish to continue?}", + "{Enabling this feature will allow mods you choose to run code on your device.}\n\n{Malicious code may be able to harm your device or steal your data!}\n{Never enable scripting for any mods you don't trust.}\n\n{Do you wish to continue?}", // TODO: Translation. { settings.enableModScripting = it }, UncivGame.Current.screen as BaseScreen, { buttonSetter.set(false) } @@ -715,7 +715,7 @@ class OptionsPopup(val previousScreen: BaseScreen) : Popup(previousScreen) { // @param labelText Label to show next to the text field. // @param initialValue Text in the text field. - // @param defaultValue If non-null, add a button to reset the text field to this value. + // @param defaultValue If non-null, a button is added to reset the text field to this value. // @param action Function given the updated value of the text field when it changes. Called before saving game settings. private fun Table.addTextFieldRow(labelText: String, initialValue: String, defaultValue: String? = null, action: (String) -> Unit) { val wrapWidth = tabs.prefWidth * 0.2f @@ -751,6 +751,7 @@ class OptionsPopup(val previousScreen: BaseScreen) : Popup(previousScreen) { lastcell = add(resetButton) } + // This is a table, so different rows should be aligned even if they have different sizes. (Don't need to add empty cell if not adding reset button.) lastcell.row() } From 531d947b282abaf7a623661adb6a7ba883f75655 Mon Sep 17 00:00:00 2001 From: will-ca Date: Sun, 12 Dec 2021 11:06:01 +0000 Subject: [PATCH 85/93] Declarative type specification and runtime type checking for mod scripting handler types. --- .../modscripting/ModScriptingHandlerTypes.kt | 66 ++++++++++++++----- .../ModScriptingRegistrationManager.kt | 6 +- .../modscripting/ModScriptingRunManager.kt | 30 ++++++--- 3 files changed, 73 insertions(+), 29 deletions(-) diff --git a/core/src/com/unciv/models/modscripting/ModScriptingHandlerTypes.kt b/core/src/com/unciv/models/modscripting/ModScriptingHandlerTypes.kt index 2fe1672111d1c..df381dd437cc4 100644 --- a/core/src/com/unciv/models/modscripting/ModScriptingHandlerTypes.kt +++ b/core/src/com/unciv/models/modscripting/ModScriptingHandlerTypes.kt @@ -1,57 +1,91 @@ +@file:Suppress("RemoveExplicitTypeArguments") + package com.unciv.models.modscripting +import kotlin.reflect.KType +import kotlin.reflect.full.isSubclassOf +//import kotlin.reflect.full.isSubtypeOf +//import kotlin.reflect.full.starProjectedType +import kotlin.reflect.jvm.jvmErasure +import kotlin.reflect.typeOf + // 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. +// Likewise, ModScriptingHandlerTypes is only for defining handlerTypes, 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. +// Some handlerTypes may have parameters that should be set universally; Others may use or simply pass on parameters from where they're called. -interface Context { +interface HandlerContext { val name: String - val handlers: Collection + val handlerTypes: Collection } -interface Handler { + +interface HandlerType { val name: String + val paramTypes: Map? val paramGetter: ParamGetter + fun checkParamsValid(checkParams: Params): Boolean { // If this becomes a major performance sink, it could be disabled in release builds. But + if (paramTypes == null || checkParams == null) { + return checkParams == paramTypes + } + if (paramTypes!!.keys != checkParams.keys) { + return false + } + return checkParams.all { (k, v) -> + if (v == null) { + paramTypes!![k]!!.isMarkedNullable + } else { + //v::class.starProjectedType.isSubtypeOf(paramTypes!![k]!!) + // In FunctionDispatcher I compare the .jvmErasure KClass instead of the erased type. + // Right. Erased/star-projected types probably aren't subclasses of the argumented types from typeOf(). Could implement custom typeOfInstance that looks for most specific common element in allSuperTypes for collection contents, but sounds excessive and expensive. + v::class.isSubclassOf(paramTypes!![k]!!.jvmErasure) + } + } + } } -// For defining the types and behaviours of available handlers. +// For defining the types and behaviours of available handlerTypes. object ModScriptingHandlerTypes { private val defaultParamGetter: ParamGetter = { it } +// @Suppress("EnumEntryName") // Handler names are not Kotlin code, but more like JSON keys in mod configuration files. + @OptIn(ExperimentalStdlibApi::class) // For typeOf(). Technically isn't needed for functional release builds (see HandlerType.checkParamsValid). object All { // Not a great name for imports. - enum class UncivGame(override val paramGetter: ParamGetter = defaultParamGetter): Handler { - after_enter(), - before_exit() + enum class UncivGame(override val paramGetter: ParamGetter = defaultParamGetter): HandlerType { + after_enter() { + override val paramTypes = mapOf( + "testArg" to typeOf() + ) + } ; - companion object: Context { + companion object: HandlerContext { override val name = "UncivGame" - override val handlers = values().toList() + override val handlerTypes = values().toList() } } - enum class GameInfo(override val paramGetter: ParamGetter = defaultParamGetter): Handler { + enum class GameInfo(override val paramGetter: ParamGetter = defaultParamGetter): HandlerType { ; - companion object: Context { + companion object: HandlerContext { override val name = "GameInfo" - override val handlers = values().toList() + override val handlerTypes = values().toList() } } } - val all = listOf( // Explicitly type so anything missing companion raises compile/IDE error. + val all = listOf( // Explicitly type so anything with wrong or missing companion raises compile/IDE error. All.UncivGame, All.GameInfo ) val byContextAndName = all.associate { context -> - context.name to context.handlers.associateWith { handler -> + context.name to context.handlerTypes.associateWith { handler -> handler.name } } diff --git a/core/src/com/unciv/models/modscripting/ModScriptingRegistrationManager.kt b/core/src/com/unciv/models/modscripting/ModScriptingRegistrationManager.kt index b594dd6a623b4..643efbf507db2 100644 --- a/core/src/com/unciv/models/modscripting/ModScriptingRegistrationManager.kt +++ b/core/src/com/unciv/models/modscripting/ModScriptingRegistrationManager.kt @@ -3,10 +3,10 @@ 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. +// For organizing and associating script handlerTypes with specific mods. +// Uses ModScriptingRunManager and ModScriptingHandlerTypes. object ModScriptingRegistrationManager { - private fun register(mod: ScriptedMod, backend: ScriptingBackend, handler: Handler, code: String) { + private fun register(mod: ScriptedMod, backend: ScriptingBackend, handlerType: HandlerType, code: String) { } diff --git a/core/src/com/unciv/models/modscripting/ModScriptingRunManager.kt b/core/src/com/unciv/models/modscripting/ModScriptingRunManager.kt index 1a407b0e463c0..f9712210bbb69 100644 --- a/core/src/com/unciv/models/modscripting/ModScriptingRunManager.kt +++ b/core/src/com/unciv/models/modscripting/ModScriptingRunManager.kt @@ -7,25 +7,24 @@ import com.unciv.scripting.ScriptingState import com.unciv.scripting.utils.ScriptingErrorHandling import com.unciv.scripting.sync.ScriptingRunThreader import com.unciv.scripting.sync.makeScriptingRunName +import java.lang.IllegalStateException data class RegisteredHandler(val backend: ScriptingBackend, val code: String, val mod: ScriptedMod?) -// For organizing and running script handlers during gameplay. +// For organizing and running script handlerTypes during gameplay. // Uses ModScriptingHandlerTypes. object ModScriptingRunManager { - private val registeredHandlers: Map> = + private val registeredHandlers: Map> = ModScriptingHandlerTypes.all.asSequence() - .map { it.handlers } + .map { it.handlerTypes } .flatten() .associateWith { HashSet() } - fun getRegisteredForHandler(handler: Handler) = registeredHandlers[handler]!! + fun getRegisteredForHandler(handlerType: HandlerType) = registeredHandlers[handlerType]!! 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() { @@ -34,7 +33,7 @@ object ModScriptingRunManager { execResult = ScriptingState.exec( command = registeredHandler.code, asName = name, - withParams = params, + withParams = withParams, withBackend = registeredHandler.backend ) } catch(e: Throwable) { @@ -47,10 +46,21 @@ object ModScriptingRunManager { } } - fun runHandler(handler: Handler, baseParams: Params?) { // Not sure how to keep caller from advancing… (I wonder if qualified return syntax - val registeredHandlers = getRegisteredForHandler(handler) + fun runHandler(handlerType: HandlerType, baseParams: Params?, after: () -> Unit = {}) { + val registeredHandlers = getRegisteredForHandler(handlerType) if (registeredHandlers.isNotEmpty()) { - val params = handler.paramGetter(baseParams) + val params = handlerType.paramGetter(baseParams) + if (!handlerType.checkParamsValid(params)) { + throw IllegalStateException( + """ + Incorrect parameter signature for running mod script handlerTypes: + handlerType = ${handlerType.name} + handlerType.paramTypes = ${handlerType.paramTypes} + baseParams = $baseParams + params = $params + """.trimIndent() + ) + } ScriptingRunThreader.queueRuns( registeredHandlers.asSequence().map { lambdaRegisteredHandlerRunner(it, params) } ) From 0ce52dbb70f9d5fee187ee1b3ddf49585ffdd6b0 Mon Sep 17 00:00:00 2001 From: will-ca Date: Sun, 12 Dec 2021 18:58:43 +0000 Subject: [PATCH 86/93] Run scripted tests in JUnit tests. Requires OpenGL, system Python, etc. --- build.gradle.kts | 2 + core/src/com/unciv/UncivGame.kt | 13 ++++ core/src/com/unciv/UncivGameParameters.kt | 5 +- .../com/unciv/scripting/ScriptingBackend.kt | 18 ++--- ...kIdentityHashmap.kt => WeakIdentityMap.kt} | 0 .../src/com/unciv/scripting/ScriptedTests.kt | 70 +++++++++++++++++++ 6 files changed, 99 insertions(+), 9 deletions(-) rename core/src/com/unciv/scripting/utils/{WeakIdentityHashmap.kt => WeakIdentityMap.kt} (100%) diff --git a/build.gradle.kts b/build.gradle.kts index f87d3f0380b23..8c241ac777d1e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -143,6 +143,8 @@ project(":core") { "testImplementation"("com.badlogicgames.gdx:gdx-backend-headless:$gdxVersion") "testImplementation"("com.badlogicgames.gdx:gdx:$gdxVersion") "testImplementation"("com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop") + + "implementation"(project(":desktop")) } } } diff --git a/core/src/com/unciv/UncivGame.kt b/core/src/com/unciv/UncivGame.kt index 85f158914b805..578d7196fc5ad 100644 --- a/core/src/com/unciv/UncivGame.kt +++ b/core/src/com/unciv/UncivGame.kt @@ -13,6 +13,7 @@ import com.unciv.models.metadata.GameSettings import com.unciv.models.ruleset.RulesetCache import com.unciv.models.tilesets.TileSetCache import com.unciv.models.translations.Translations +import com.unciv.scripting.ScriptingState import com.unciv.scripting.api.ScriptingScope import com.unciv.scripting.utils.ScriptingDebugParameters import com.unciv.ui.consolescreen.ConsoleScreen @@ -36,6 +37,8 @@ class UncivGame(parameters: UncivGameParameters) : Game() { val consoleMode = parameters.consoleMode val customSaveLocationHelper = parameters.customSaveLocationHelper val limitOrientationsHelper = parameters.limitOrientationsHelper + val runScriptAndExit = parameters.runScriptAndExit + lateinit var gameInfo: GameInfo fun isGameInfoInitialized() = this::gameInfo.isInitialized @@ -141,6 +144,16 @@ class UncivGame(parameters: UncivGameParameters) : Game() { crashController = CrashController.Impl(crashReportSender) createScripting() + + if (runScriptAndExit != null) { + val backend = ScriptingState.spawnBackend(runScriptAndExit.first).backend + val execResult = ScriptingState.exec( + command = runScriptAndExit.second, + withBackend = backend + ) + runScriptAndExit.third?.invoke(execResult) + Gdx.app.exit() + } } fun loadGame(gameInfo: GameInfo) { diff --git a/core/src/com/unciv/UncivGameParameters.kt b/core/src/com/unciv/UncivGameParameters.kt index 9ee7153b36f0d..a38acb8c7be8f 100644 --- a/core/src/com/unciv/UncivGameParameters.kt +++ b/core/src/com/unciv/UncivGameParameters.kt @@ -1,6 +1,8 @@ package com.unciv import com.unciv.logic.CustomSaveLocationHelper +import com.unciv.scripting.ExecResult +import com.unciv.scripting.ScriptingBackendType import com.unciv.ui.utils.CrashReportSender import com.unciv.ui.utils.LimitOrientationsHelper import com.unciv.ui.utils.NativeFontImplementation @@ -11,5 +13,6 @@ class UncivGameParameters(val version: String, val fontImplementation: NativeFontImplementation? = null, val consoleMode: Boolean = false, val customSaveLocationHelper: CustomSaveLocationHelper? = null, - val limitOrientationsHelper: LimitOrientationsHelper? = null + val limitOrientationsHelper: LimitOrientationsHelper? = null, + val runScriptAndExit: Triple Unit)?>? = null ) { } diff --git a/core/src/com/unciv/scripting/ScriptingBackend.kt b/core/src/com/unciv/scripting/ScriptingBackend.kt index 26e4527846c46..e077344882b19 100644 --- a/core/src/com/unciv/scripting/ScriptingBackend.kt +++ b/core/src/com/unciv/scripting/ScriptingBackend.kt @@ -352,7 +352,9 @@ class ReflectiveScriptingBackend(): ScriptingBackend() { "set \"Krakatoa\" worldScreen.mapHolder.selectedTile.naturalWonder", "get apiHelpers.Jvm.constructorByQualname[\"com.unciv.ui.worldscreen.AlertPopup\"](worldScreen, apiHelpers.Jvm.constructorByQualname[\"com.unciv.logic.civilization.PopupAlert\"](apiHelpers.Jvm.enumMapsByQualname[\"com.unciv.logic.civilization.AlertType\"][\"StartIntro\"], \"Text text.\")).open(false)", "get civInfo.addGold(civInfo.tech.techsResearched.size)", - "get uncivGame.setScreen(apiHelpers.Jvm.constructorByQualname[\"com.unciv.ui.mapeditor.MapEditorScreen\"](gameInfo.tileMap))", + //"get uncivGame.setScreen(apiHelpers.Jvm.constructorByQualname[\"com.unciv.ui.mapeditor.MapEditorScreen\"](gameInfo.tileMap))", + // FIXME: This was working, but now hits an uinitialized .ruleset in the screen constructor. + // Still works in the .JAR. "get apiHelpers.Jvm.constructorByQualname[\"com.unciv.ui.utils.ToastPopup\"](\"This is a popup!\", apiHelpers.Jvm.companionByQualClass[\"com.unciv.UncivGame\"].Current.getScreen(), 2000)", "get apiHelpers.Jvm.singletonByQualname[\"com.unciv.ui.utils.Fonts\"].turn", "get apiHelpers.App.assetImageB64(\"StatIcons/Resistance\")", @@ -369,26 +371,26 @@ class ReflectiveScriptingBackend(): ScriptingBackend() { // "get fFeiltali.stastIRFI" // Force a failure. ) - private fun runTests(): ExecResult { // TODO: Add to unit tests. + private fun runTests(): ExecResult { // TODO: Could add suppress flag to disable printing in unit tests. val failResult = exec("get This.Command[Should](Fail)!") if (!failResult.isException) { - throw AssertionError("ERROR in reflective scripting tests: Unable to detect failures.") + throw AssertionError("ERROR in reflective scripting tests: Unable to detect failures.".also { println(it) }) } - val failures = ArrayList() + val failures = mutableMapOf() val tests = sequenceOf(examples.filterNot { it.startsWith("runtests") }, tests).flatten().toList() for (command in tests) { val execResult = exec(command) if (execResult.isException) { - failures.add(command) + failures[command] = execResult.resultPrint } } return if (failures.isEmpty()) {ExecResult( - "${tests.size} reflective scripting tests PASSED!" + "${tests.size} reflective scripting tests PASSED!".also { println(it) } )} else {ExecResult( listOf( "${failures.size}/${tests.size} reflective scripting tests FAILED:", - *failures.map { it.prependIndent("\t") }.toTypedArray() - ).joinToString("\n"), + *failures.map { "\t${it.key}\n\t\t${it.value }" }.toTypedArray() + ).joinToString("\n").also { println(it) }, true )} } diff --git a/core/src/com/unciv/scripting/utils/WeakIdentityHashmap.kt b/core/src/com/unciv/scripting/utils/WeakIdentityMap.kt similarity index 100% rename from core/src/com/unciv/scripting/utils/WeakIdentityHashmap.kt rename to core/src/com/unciv/scripting/utils/WeakIdentityMap.kt diff --git a/tests/src/com/unciv/scripting/ScriptedTests.kt b/tests/src/com/unciv/scripting/ScriptedTests.kt index e69de29bb2d1d..de9b5b9db9ab9 100644 --- a/tests/src/com/unciv/scripting/ScriptedTests.kt +++ b/tests/src/com/unciv/scripting/ScriptedTests.kt @@ -0,0 +1,70 @@ +package com.unciv.scripting + +import com.badlogic.gdx.Application +import com.badlogic.gdx.Gdx +import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application +import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration +import com.badlogic.gdx.graphics.glutils.HdpiMode +import com.unciv.UncivGame +import com.unciv.UncivGameParameters +import com.unciv.app.desktop.NativeFontDesktop +import com.unciv.testing.GdxTestRunner +import com.unciv.ui.utils.Fonts +import org.junit.After +import org.junit.Assert +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +// The scripting API, by definition and by nature, works with the entire application state. +// Most of the risk of instability comes from its dynamism. It's neither particularly difficult nor terribly useful to guarantee and test that any specific set of edge cases will work consistently, but it's much more concerning whether any practical usage patterns involving large amounts of code have broken, in all the non-statically-checked IPC protocols, tokenization logic, and Python/JS operator overloading. + +// So... The best way to have useful tests of the scripting API is going to be to launch an entire instance of the application, I think. +// There are/is seemingly (a) Github Action(s) to enable an OpenGL environment through software rendering. + +@RunWith(GdxTestRunner::class) +class ScriptedTests { + + // @return The ExecResult from running the command with the backendType, or null if something went wrong. + private fun runScript(backendType: ScriptingBackendType, command: String): ExecResult? { + var execResult: ExecResult? = null + val uncivGame = UncivGame(UncivGameParameters( + "ScriptedTests", + fontImplementation = NativeFontDesktop(Fonts.ORIGINAL_FONT_SIZE.toInt()), + runScriptAndExit = Triple( + backendType, + command, + { execResult = it } + ) + )) + val application = Lwjgl3Application( + uncivGame, + Lwjgl3ApplicationConfiguration() + ) + return execResult + } + + private fun runScriptedTest(backendType: ScriptingBackendType, command: String) { + val execResult = runScript(backendType, command) + Assert.assertFalse( + execResult?.resultPrint?.prependIndent("\t") ?: "No execResult.", + execResult?.isException ?: true + ) + } + + @Test + fun scriptedPythonTests() { + runScriptedTest( + ScriptingBackendType.SystemPython, + "from unciv_scripting_examples.Tests import *; TestRunner.run_tests(debugprint=False)" + ) + } + + @Test + fun scriptedReflectiveTests() { + runScriptedTest( + ScriptingBackendType.Reflective, + "runtests" + ) + } +} From b9e0030a142fe2e98cd17f85a37f244fb6fdf7c0 Mon Sep 17 00:00:00 2001 From: will-ca Date: Sun, 12 Dec 2021 19:01:07 +0000 Subject: [PATCH 87/93] Unit tests for InstanceMethodDispatcher. --- .../InstanceMethodDispatcherTests.kt | 174 ++++++++++++++++++ ...cherTests.kt => InstanceTokenizerTests.kt} | 0 2 files changed, 174 insertions(+) create mode 100644 tests/src/com/unciv/scripting/InstanceMethodDispatcherTests.kt rename tests/src/com/unciv/scripting/{reflection/InstanceMethodDispatcherTests.kt => InstanceTokenizerTests.kt} (100%) diff --git a/tests/src/com/unciv/scripting/InstanceMethodDispatcherTests.kt b/tests/src/com/unciv/scripting/InstanceMethodDispatcherTests.kt new file mode 100644 index 0000000000000..27b479629399b --- /dev/null +++ b/tests/src/com/unciv/scripting/InstanceMethodDispatcherTests.kt @@ -0,0 +1,174 @@ +package com.unciv.scripting + +import com.unciv.scripting.reflection.Reflection.InstanceMethodDispatcher +import com.unciv.testing.GdxTestRunner +import org.junit.Assert +import org.junit.Test +import org.junit.runner.RunWith + + +private fun info(message: Any?) { } // Originally this was a KTS where I would just print the args. Replacing the printlns with noops keeps around the info passed to them, and looks cleaner/is better understood by IDEs than commenting. + +private typealias TestMethodResult = Unit // To make it easier to switch out in case it's decided to use the Signature class defined below and its assertion methods. + +//private fun error(detailMessage: Any? = null): Nothing = throw AssertionError(detailMessage) // To save a tiny bit of (keyboard) typing + + +@RunWith(GdxTestRunner::class) +class InstanceMethodDispatcherTests { + + private fun assertError(call: () -> Unit) { + Assert.assertThrows(IllegalArgumentException::class.java, call) + } + + private val testInstance = TestClasses.TestInstance() + + private val testInstanceDispatcherMeth = InstanceMethodDispatcher(testInstance, "meth") + private val testInstanceDipsatcherMeth2 = InstanceMethodDispatcher(testInstance, "meth2") + private val testInstanceDispatcherMeth3 = InstanceMethodDispatcher(testInstance, "meth3") + + private val testInstanceGenericInt = TestClasses.TestInstanceGeneric() + + private val testInstanceGenericIntDispatcherMeth = InstanceMethodDispatcher(testInstanceGenericInt, "meth") + private val testInstanceGenericIntDispatcherMeth2 = InstanceMethodDispatcher(testInstanceGenericInt, "meth2") + + private val testInstanceDispatcherLenient = InstanceMethodDispatcher(testInstance, "meth", true) + + private val testInstanceDispatcherMeth3ResolveAmbiguous = InstanceMethodDispatcher(testInstance, "meth3", resolveAmbiguousSpecificity=true) + + + // Regular tests. + + @Test + fun testBasicInt() = testInstanceDispatcherMeth.call(arrayOf(1)) + @Test + fun testBasicString() = testInstanceDispatcherMeth.call(arrayOf("A")) + @Test + fun testBasicIntStringlist() = testInstanceDispatcherMeth.call(arrayOf(2, listOf("Test", "List"))) + @Test + fun testBasicStringStringlist() = testInstanceDispatcherMeth.call(arrayOf("B", listOf("Test", "List"))) + @Test + fun testBasicStringNull() = testInstanceDispatcherMeth.call(arrayOf("B", null)) + + //@Test + //fun testBasic2Null() = testInstanceDipsatcherMeth2.call(arrayOf(null)) // Worked usually but not always when these tests were still a KTS. Possible compiler heisenbug here. Seems to be an internal Kotlin error caused when saving KParameter.type in a list under checkParameterMatches. + @Test + fun testBasic2StringFailAmbiguous() = assertError { + testInstanceDipsatcherMeth2.call(arrayOf("Fail")) + } + + @Test + fun testBasic3Stringlist() = testInstanceDispatcherMeth3.call(arrayOf(listOf("A"))) // Should always work, as only the signature for the base class should match. + @Test + fun testBasic3StringarraylistFailAmbiguous() = assertError { + testInstanceDispatcherMeth3.call(arrayOf(arrayListOf("B"))) + } // Requires subtype resolution. + @Test + fun testBasic3StringcustomlistFailAmbiguous() = assertError { + testInstanceDispatcherMeth3.call(arrayOf(TestClasses.CustomList())) + } + + + // Generic tests. + + @Test + fun testGenericInt() = testInstanceGenericIntDispatcherMeth.call(arrayOf(1)) + @Test + fun testGenericIntInt() = testInstanceGenericIntDispatcherMeth.call(arrayOf(2, 5)) + @Test + fun testGenericStringInt() = testInstanceGenericIntDispatcherMeth.call(arrayOf("A", 5)) + @Test + fun testGenericStringNull() = testInstanceGenericIntDispatcherMeth.call(arrayOf("B", null)) + //@Test //Huh. Apparently typing the object to Int doesn't stop a String from being used at its generic place. So, what? Erased generics have no influence at runtime, and typing using them is just a code-hinting/code-linting sham? + fun testGenericStringStringFailNomatch() = assertError { + testInstanceGenericIntDispatcherMeth.call(arrayOf("B", "Fail")) + } + + @Test + fun testGeneric2IntFailAmbiguous() = assertError { + testInstanceGenericIntDispatcherMeth2.call(arrayOf(3)) + } + @Test //This last one seems to work or break when you recompile. Actually I'm not sure dispatch resolution in this case is supported by the language. + fun testGeneric2Null() = testInstanceGenericIntDispatcherMeth2.call(arrayOf(null)) + + + // Numeric tests. + + @Test + fun testNumericFloatFailNomatch() = assertError { + testInstanceDispatcherMeth.call(arrayOf(10.0f)) + } + @Test + fun testNumericLongFailNomatch() = assertError { + testInstanceDispatcherMeth.call(arrayOf(11L)) + } + @Test + fun testNumericDoubleNomatch() = assertError { + testInstanceDispatcherMeth.call(arrayOf(12.5)) + } + @Test + fun testLenientInt() = testInstanceDispatcherLenient.call(arrayOf(15)) + /*dispnmeth.call(arrayOf(16.0f)) // TODO: Test casts and order? + dispnmeth.call(arrayOf(17L)) + dispnmeth.call(arrayOf(18.5))*/ // + + + // Ambiguous tests. + + @Test + fun testAmbiguous3Stringlist() = testInstanceDispatcherMeth3ResolveAmbiguous.call(arrayOf(listOf("A"))) // Only one method compatible at all. + @Test + fun testAmbiguous3Stringarraylist() = testInstanceDispatcherMeth3ResolveAmbiguous.call(arrayOf(arrayListOf("B"))) // Two methods compatible, but one is most specific. + @Test + fun testAmbiguous3Stringcustomlist() = testInstanceDispatcherMeth3ResolveAmbiguous.call(arrayOf(TestClasses.CustomList())) // Three methods compatible, but one is most specific. + +} + +object TestClasses { + + class CustomList : ArrayList() + +// class Signature(val types: List) { // TODO: Could use this to make sure the ambiguity tests are all going to the right method. +// companion object { +// fun fromArgs(vararg args: Any?) = Signature(args.map { if (it == null) null else it::class.starProjectedType }.toList()) +// fun fromFunction(func: KFunction<*>) = func.parameters.map { it.type } +// } +// fun assertEquals(vararg checkTypes: KType?) = checkTypes.size == types.size && (checkTypes.toList() zip types).all { (arg, param) -> arg == param } +// fun assertIsSuperOf(vararg checkTypes: KType?) = checkTypes.size == types.size && (checkTypes.toList() zip types).all { (arg, param) -> if (arg == null || param == null) arg == param else arg.isSubtypeOf(param) } +// } + + + @Suppress("unused") + class TestInstance { + fun meth(a:Int) = info("Int: ${a::class} $a") + fun meth(a:String) = info("String: ${a::class} $a") + fun meth(a:Int, b:Any) = info("Int: ${a::class} ${a}; Any: ${b::class} $b") + // fun meth(a:Int, b:Any?) { +// //This should fail if enabled at the same time as the one above. due to multiple signatures. // TODO: JvmName? +// info("Int: ${a::class} ${a}; Any?: ${b!!::class} ${b}") +// } + fun meth(a:String, b:Any?) = info("String: ${a::class} ${a}; Any?: ${if (b == null) null else b::class} $b") + + fun meth2(a: Any) = info("Any: ${a::class} $a") + @JvmName("meth2Nullable") // Wasn't needed when running with kotlinc -script. + fun meth2(a: Any?) = info("Any?: ${if (a == null) null else a::class} $a") + + fun meth3(a: List<*>) = info("List<*>: ${a::class} $a") + // fun meth3(a: MutableList) { // Apparently the jvmErasure for MutableList is just List. So having both makes resolutions ambiguous. +// info("MutableList<*>: ${a::class} ${a}") +// } + fun meth3(a: ArrayList<*>) = info("ArrayList<*>: ${a::class} $a") + fun meth3(a: CustomList<*>) = info("testlist<*>: ${a::class} $a") + } + + @Suppress("unused") + class TestInstanceGeneric { //Non-nullable upper bounds on T to test that T? is recognized as a distinct signature from T when calling with null. + fun meth(a: T) = info("T: ${a::class} $a") + fun meth(a:Int, b:T) = info("Int: ${a::class} ${a}; T: ${b::class} $b") + fun meth(a:String, b:T?) = info("String: ${a::class} ${a}; T?: ${if (b == null) null else b::class} $b") + + fun meth2(a: T) = info("T: ${a::class} $a") + @JvmName("meth2Nullable") // Wasn't needed when running with kotlinc -script. + fun meth2(b: T?) = info("T?: ${if (b == null) null else b::class} $b") + } +} diff --git a/tests/src/com/unciv/scripting/reflection/InstanceMethodDispatcherTests.kt b/tests/src/com/unciv/scripting/InstanceTokenizerTests.kt similarity index 100% rename from tests/src/com/unciv/scripting/reflection/InstanceMethodDispatcherTests.kt rename to tests/src/com/unciv/scripting/InstanceTokenizerTests.kt From db3de2b49dc5236caec362da9d46dca0a467637b Mon Sep 17 00:00:00 2001 From: will-ca Date: Sun, 12 Dec 2021 19:02:29 +0000 Subject: [PATCH 88/93] Tiny change. Println a multiline string instead of escapes in InstanceRegistry. --- .../unciv/scripting/api/ScriptingApiInstanceRegistry.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt b/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt index 5a3a60c29153d..15ac7873f560f 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt @@ -57,7 +57,13 @@ object ScriptingApiInstanceRegistry: MutableMap { // This is a sin override fun isEmpty() = backingMap.isEmpty() override fun clear() = backingMap.clear() override fun put(key: String, value: Any?): Any? { - println("\nAssigning ${key} directly in ScriptingApiInstanceRegistry(). It is recommended that every script/mod do this only once per application lifespan, creating its own mapping under the registry for further assignments named according to the following format:\n\t-<'mod'|'module'|'package'>:/\n\tE.G.: \"python-module:myName/myCoolScript\"\n") + println(""" + + Assigning ${key} directly in ScriptingApiInstanceRegistry(). It is recommended that every script/mod do this only once per application lifespan, creating its own mapping under the registry for further assignments named according to the following format: + -<'mod'|'module'|'package'>:/ + E.G.: registeredInstances["python-module:myName/myCoolScript"] = {"some_name": 5} + + """.trimIndent()) if (key in this) { throw IllegalArgumentException("\"${key}\" already in ${this}.") } From e92e7dbcce73cea02ff101a06c959d4de2a6885d Mon Sep 17 00:00:00 2001 From: will-ca Date: Mon, 13 Dec 2021 17:14:24 +0000 Subject: [PATCH 89/93] Comments, notes, add some TODOs. Runtime collection type parameter detection. --- .../unciv_scripting_examples/EventPopup.py | 10 +++---- .../modscripting/ModScriptingHandlerTypes.kt | 1 + .../ModScriptingRegistrationManager.kt | 16 ++++++---- .../modscripting/ModScriptingRunManager.kt | 10 +++++-- .../com/unciv/scripting/ScriptingBackend.kt | 18 +++++------ .../src/com/unciv/scripting/ScriptingState.kt | 4 ++- .../api/ScriptingApiInstanceRegistry.kt | 6 ++-- .../scripting/api/ScriptingApiJvmHelpers.kt | 4 +-- .../unciv/scripting/reflection/Reflection.kt | 30 +++++++++++++++++++ .../scripting/sync/ScriptingRunThreader.kt | 6 ++-- .../ui/worldscreen/mainmenu/OptionsPopup.kt | 6 ++-- .../unciv/scripting/InstanceTokenizerTests.kt | 6 ++++ .../src/com/unciv/scripting/ScriptedTests.kt | 6 ++-- .../unciv/scripting/WeakIdentityMapTests.kt | 1 + 14 files changed, 87 insertions(+), 37 deletions(-) create mode 100644 tests/src/com/unciv/scripting/WeakIdentityMapTests.kt diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EventPopup.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EventPopup.py index f169c672b05a4..ef1e4f6534be0 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EventPopup.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EventPopup.py @@ -3,7 +3,7 @@ """ -#from unciv_scripting_examples.EventPopup import *; r=showEventPopup(**EVENT_POPUP_DEMOARGS()) +#from unciv_scripting_examples.EventPopup import *; uncivGame.setWorldScreen(); r=showEventPopup(**EVENT_POPUP_DEMOARGS()) # modApiHelpers.lambdifyReadPathcode(None, 'apiHelpers.Jvm.constructorByQualname["com.unciv.ui.utils.ToastPopup"]("Test", uncivGame.getScreen(), 8000)') @@ -85,7 +85,7 @@ def showEventPopup(title=None, image=None, text="No text event text provided!", ) popup.add(button).row() popup.open(False) - return popup + return popup # TODO: Need to update worldScreen. def EVENT_POPUP_DEMOARGS(): @@ -108,11 +108,11 @@ def EVENT_POPUP_DEMOARGS(): This is your chance to roleplay a political decision: """, 'options': { # TODO: Serialize Chars as string? - (f"I'll take a Gold stat bonus. (+{goldboost} {real(Fonts.gold.toString())})", GdxColours['GOLD']): + (f"I'll take a Gold stat boost. (+{goldboost} {real(Fonts.gold.toString())})", GdxColours['GOLD']): modApiHelpers.lambdifyReadPathcode(civInfo, f'.addGold({goldboost})'), # Can actually just read addGold for this. - (f"I'll take a Culture stat bonus. (+{cultureboost} {real(Fonts.culture.toString())})", GdxColours['VIOLET']): + (f"I'll take a Culture stat boost. (+{cultureboost} {real(Fonts.culture.toString())})", GdxColours['VIOLET']): modApiHelpers.lambdifyReadPathcode(civInfo, f'.policies.addCulture({cultureboost})'), - (f"I'll take a Science stat bonus. (+{scienceboost} {real(Fonts.science.toString())})", GdxColours['CYAN']): + (f"I'll take a Science stat boost. (+{scienceboost} {real(Fonts.science.toString())})", GdxColours['CYAN']): modApiHelpers.lambdifyReadPathcode(civInfo, f'.tech.addScience({scienceboost})'), ( (f"Let Chaos reign! (+{omniboost} {real(Fonts.gold.toString())}, {real(Fonts.culture.toString())}, {real(Fonts.science.toString())})", None), diff --git a/core/src/com/unciv/models/modscripting/ModScriptingHandlerTypes.kt b/core/src/com/unciv/models/modscripting/ModScriptingHandlerTypes.kt index df381dd437cc4..47ff041d0ca5b 100644 --- a/core/src/com/unciv/models/modscripting/ModScriptingHandlerTypes.kt +++ b/core/src/com/unciv/models/modscripting/ModScriptingHandlerTypes.kt @@ -57,6 +57,7 @@ object ModScriptingHandlerTypes { private val defaultParamGetter: ParamGetter = { it } // @Suppress("EnumEntryName") // Handler names are not Kotlin code, but more like JSON keys in mod configuration files. + @Suppress("EnumEntryName") @OptIn(ExperimentalStdlibApi::class) // For typeOf(). Technically isn't needed for functional release builds (see HandlerType.checkParamsValid). object All { // Not a great name for imports. enum class UncivGame(override val paramGetter: ParamGetter = defaultParamGetter): HandlerType { diff --git a/core/src/com/unciv/models/modscripting/ModScriptingRegistrationManager.kt b/core/src/com/unciv/models/modscripting/ModScriptingRegistrationManager.kt index 643efbf507db2..bf5476ec8db20 100644 --- a/core/src/com/unciv/models/modscripting/ModScriptingRegistrationManager.kt +++ b/core/src/com/unciv/models/modscripting/ModScriptingRegistrationManager.kt @@ -19,13 +19,17 @@ object ModScriptingRegistrationManager { 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…) + @Suppress("EnumEntryName") + enum class ModScriptingLanguages(val backendType: ScriptingBackendType) { + pathcode(ScriptingBackendType.Reflective), + python(ScriptingBackendType.SystemPython), + javascript(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 + var pathcode: ScriptingBackend? = null, + var python: ScriptingBackend? = null, + var javascript: ScriptingBackend? = null ) } diff --git a/core/src/com/unciv/models/modscripting/ModScriptingRunManager.kt b/core/src/com/unciv/models/modscripting/ModScriptingRunManager.kt index f9712210bbb69..90ee0f4ba07a3 100644 --- a/core/src/com/unciv/models/modscripting/ModScriptingRunManager.kt +++ b/core/src/com/unciv/models/modscripting/ModScriptingRunManager.kt @@ -1,12 +1,14 @@ package com.unciv.models.modscripting import com.badlogic.gdx.Gdx +import com.unciv.UncivGame 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 +import com.unciv.ui.utils.BaseScreen import java.lang.IllegalStateException @@ -70,8 +72,12 @@ object ModScriptingRunManager { } } - private fun lockGame() {} - private fun unlockGame() {} + private fun lockGame() { + Gdx.input.inputProcessor = null + } + private fun unlockGame() { + Gdx.input.inputProcessor = (UncivGame.Current.screen as BaseScreen).stage + } } diff --git a/core/src/com/unciv/scripting/ScriptingBackend.kt b/core/src/com/unciv/scripting/ScriptingBackend.kt index e077344882b19..397b2ba32f35f 100644 --- a/core/src/com/unciv/scripting/ScriptingBackend.kt +++ b/core/src/com/unciv/scripting/ScriptingBackend.kt @@ -429,7 +429,7 @@ class ReflectiveScriptingBackend(): ScriptingBackend() { } } - override fun exec(command: String): ExecResult { + override fun exec(command: String): ExecResult { // TODO: Treat multiple lines as consecutive commands, for modding. var parts = command.split(' ', limit=2) var out = "\n> ${command}\n" var isException = false @@ -441,7 +441,7 @@ class ReflectiveScriptingBackend(): ScriptingBackend() { "get" -> { appendOut("${Reflection.evalKotlinString(ScriptingScope, parts[1])}") } - "set" -> { + "set" -> { // TODO: Use the new pathcode splitter to accept equals sign format. var setparts = parts[1].split(' ', limit=2) var value = Reflection.evalKotlinString(ScriptingScope, setparts[0]) Reflection.setInstancePath( @@ -456,7 +456,7 @@ class ReflectiveScriptingBackend(): ScriptingBackend() { appendOut("${if (obj == null) null else obj!!::class.qualifiedName}") } "examples" -> { - throw RuntimeException("Not implemented.") + throw RuntimeException("Not implemented.") // TODO: Remove from MOTD, move to default startup. } "runtests" -> { val testResults = runTests() @@ -583,15 +583,15 @@ abstract class SubprocessScriptingBackend(): BlackboxScriptingBackend() { override fun motd(): String { return """ - - + + Welcome to the Unciv '${metadata.displayName}' API. This backend relies on running the system ${processCmd.firstOrNull()} command as a subprocess. - + If you do not have an interactive REPL below, then please make sure the following command is valid on your system: - + ${processCmd.joinToString(" ")} - - + + """.trimIndent() + super.motd() // I don't think trying to translate this (or its subcomponents— Although I guess translations are going to be available for displayName anyway) would be the best idea. } } diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index fb6c4c713539a..e04007f7965e9 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -48,6 +48,8 @@ object ScriptingState { var activeCommandHistory: Int = 0 // Actually inverted, because history items are added to end of list and not start. 0 means nothing, 1 means most recent command at end of list. + //var consoleScreenListener: ((String) -> Unit)? = null // TODO: Switch to push instead of pull for ConsoleScreen. + fun getOutputHistory() = outputHistory.toList() data class BackendSpawnResult(val backend: ScriptingBackend, val motd: String) @@ -128,7 +130,7 @@ object ScriptingState { // @throws IllegalStateException On failure to acquire scripting lock. @Synchronized fun exec(command: String, asName: String? = null, withParams: Map? = null): ExecResult { if (UncivGame.Current.isGameInfoInitialized() && UncivGame.Current.gameInfo.gameParameters.isOnlineMultiplayer) { - ToastPopup("Scripting not allowed in multiplayer.", UncivGame.Current.screen as BaseScreen) // TODO: Translation. + ToastPopup("Scripting not allowed in online multiplayer.", UncivGame.Current.screen as BaseScreen) // TODO: Translation. return ExecResult("", true) } val backend = getActiveBackend() diff --git a/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt b/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt index 15ac7873f560f..5ebc04a41092a 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt @@ -58,11 +58,11 @@ object ScriptingApiInstanceRegistry: MutableMap { // This is a sin override fun clear() = backingMap.clear() override fun put(key: String, value: Any?): Any? { println(""" - + Assigning ${key} directly in ScriptingApiInstanceRegistry(). It is recommended that every script/mod do this only once per application lifespan, creating its own mapping under the registry for further assignments named according to the following format: -<'mod'|'module'|'package'>:/ - E.G.: registeredInstances["python-module:myName/myCoolScript"] = {"some_name": 5} - + E.G.: registeredInstances["python-module:myName/myCoolScript"] = {"some_name": someToken} + """.trimIndent()) if (key in this) { throw IllegalArgumentException("\"${key}\" already in ${this}.") diff --git a/core/src/com/unciv/scripting/api/ScriptingApiJvmHelpers.kt b/core/src/com/unciv/scripting/api/ScriptingApiJvmHelpers.kt index 53cec002b8a52..a615ba5fcdba8 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiJvmHelpers.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiJvmHelpers.kt @@ -26,7 +26,7 @@ private const val exposeStates = false */ object ScriptingApiJvmHelpers { - val enumMapsByQualname = LazyMap(::enumQualnameToMap) + val enumMapsByQualname = LazyMap(::enumQualnameToMap) // TODO: Rename to enumsByQualClassAndName? val classByQualname = LazyMap({ qualName: String -> Class.forName(qualName).kotlin }, exposeState = exposeStates) @@ -63,7 +63,7 @@ object ScriptingApiJvmHelpers { fun arrayOfAny(elements: Collection): Array<*> = elements.toTypedArray() // Rename to toArray? Hm. Named for role, not for semantics— This seems more useful for making new arrays, whereas the toString, toList, etc, are for converting existing instances. fun arrayOfTyped(elements: Collection): Array<*> = when (val item = elements.firstOrNull()) { // For scripting API/reflection. Return type won't be known in IDE, but that's fine as it's erased at runtime anyway. Important thing is that the compiler uses the right functions, creating the right typed arrays at run time. - is String -> (elements as Collection).toTypedArray() + is String -> (elements as Collection).toTypedArray() // TODO: Use mostSpecificCommonSupertypeOrNull? is Number -> (elements as Collection).toTypedArray() else -> throw IllegalArgumentException("${item!!::class.qualifiedName}") } diff --git a/core/src/com/unciv/scripting/reflection/Reflection.kt b/core/src/com/unciv/scripting/reflection/Reflection.kt index d1672bf13b628..b01ae98be8753 100644 --- a/core/src/com/unciv/scripting/reflection/Reflection.kt +++ b/core/src/com/unciv/scripting/reflection/Reflection.kt @@ -5,10 +5,40 @@ import com.unciv.ui.utils.stringifyException import kotlin.collections.ArrayList import kotlinx.serialization.Serializable import kotlin.reflect.* +import kotlin.reflect.full.* // I've noticed that the first time running a script is significantly slower than any subsequent times. Takes 50% longer to run the Python test suite the first time than the second time, and simple functions go from incurring a noticeable delay to being visually instant. // I don't think anything either can or needs to be done about that, but I assume it's the JVM JIT'ing. + +fun allCommonSuperclasses(classes: Iterable>): Set> { + val allSupers = classes.asSequence().map { it.allSuperclasses.asSequence() }.flatMap { it }.toSet() + return allSupers.asSequence().filter { checkSuper -> classes.all { checkSuper.isSuperclassOf(it) } }.toSet() // Sequence, even though only one loop, because .filter returns a List() and I want a Set(). +} + +fun mostSpecificClassOrNull(classes: Set>) = classes.firstOrNull { checkCls -> classes.all { checkCls.isSubclassOf(it) } } + +fun Iterable.mostSpecificCommonSuperclassOrNull() = mostSpecificClassOrNull(allCommonSuperclasses(this.map { it::class } )) + +fun Iterable.mostSpecificCommonSupertypeOrNull(): KType? { + var isNullable = false + val nonNull = mutableSetOf() + for (v in this) { // I've been coding under the Pythonic assumption that iterable comprehensions will be much faster than loops, but I guess that probably doesn't actually apply on the JVM. + if (v == null) { + isNullable = true + } else { + nonNull.add(v) + } + } + val mostSpecific = nonNull.mostSpecificCommonSuperclassOrNull() + return if (mostSpecific == null) { + mostSpecific + } else { + mostSpecific.starProjectedType.withNullability(isNullable) + } +} + + // TODO: Show warning on accessing deprecated property? object Reflection { diff --git a/core/src/com/unciv/scripting/sync/ScriptingRunThreader.kt b/core/src/com/unciv/scripting/sync/ScriptingRunThreader.kt index 323a2be7d9194..46d6895da8538 100644 --- a/core/src/com/unciv/scripting/sync/ScriptingRunThreader.kt +++ b/core/src/com/unciv/scripting/sync/ScriptingRunThreader.kt @@ -10,13 +10,13 @@ object ScriptingRunThreader { private val isDoingRuns = AtomicBoolean(false) @Suppress("NewApi") // FIXME: Bump API level up. private val runQueue = ConcurrentLinkedDeque<() -> Unit>() - fun queueRun(toRun: () -> Unit) { + @Synchronized fun queueRun(toRun: () -> Unit) { runQueue.add(toRun) } - fun queueRuns(runs: Iterable<() -> Unit>) { + @Synchronized fun queueRuns(runs: Iterable<() -> Unit>) { for (run in runs) runQueue.add(run) } - fun queueRuns(runs: Sequence<() -> Unit>) { + @Synchronized fun queueRuns(runs: Sequence<() -> Unit>) { for (run in runs) runQueue.add(run) } @Synchronized private fun doNextRunRecursive() { diff --git a/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt b/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt index 54fa6203d9db5..d041f3b9f361e 100644 --- a/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt +++ b/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt @@ -268,7 +268,7 @@ class OptionsPopup(val previousScreen: BaseScreen) : Popup(previousScreen) { add( WrappableLabel( - "{WARNING: The features on this page are all experimental, and very powerful. It may be possible to damage your device using them if you do not know what you are doing.}\n\n{You have been warned!}", // TODO: Translation. + "{WARNING: The features on this page are all experimental, and very powerful. It may be possible to damage your device with them if you do not know what you are doing.}\n\n{You have been warned!}", // TODO: Translation. tabs.prefWidth * 0.5f ).apply { wrap = true @@ -290,7 +290,7 @@ class OptionsPopup(val previousScreen: BaseScreen) : Popup(previousScreen) { addYesNoRow("Show warning when opening scripting console", // TODO: Translation. settings.showScriptingConsoleWarning) { settings.showScriptingConsoleWarning = it - } // Two affirmative actions to start using scripting console: Enable in click options button, accept ConsoleScreen warning. + } // Two affirmative actions to start using scripting console: Enable in OptionsPopup button, accept ConsoleScreen warning. } add(consoleBasicTable).row() @@ -325,7 +325,7 @@ class OptionsPopup(val previousScreen: BaseScreen) : Popup(previousScreen) { ) { // Probably not worth adding the confirmation behaviour to addYesNoRow… Standardizing it sounds like a quick path to warning fatigue. settings.enableModScripting = false if (it) { // Aside from not wanting to get anyone hacked, should probably also check if Google Play has liability (or third-party distribution) issues— Not any more dangerous than a web browser, in fact arguably safer/more controlled, and trapped in Android sandbox regardless, but still. - YesNoPopup( // Four affirmative actions to enable a mod: Click options button, accept options warning, tick mod checkbox, accept mod warning. + YesNoPopup( // Four affirmative actions to enable a mod: Click OptionsPopup button, accept options warning, tick mod checkbox, accept mod warning. "{Enabling this feature will allow mods you choose to run code on your device.}\n\n{Malicious code may be able to harm your device or steal your data!}\n{Never enable scripting for any mods you don't trust.}\n\n{Do you wish to continue?}", // TODO: Translation. { settings.enableModScripting = it }, UncivGame.Current.screen as BaseScreen, diff --git a/tests/src/com/unciv/scripting/InstanceTokenizerTests.kt b/tests/src/com/unciv/scripting/InstanceTokenizerTests.kt index e69de29bb2d1d..fea3b65107bc1 100644 --- a/tests/src/com/unciv/scripting/InstanceTokenizerTests.kt +++ b/tests/src/com/unciv/scripting/InstanceTokenizerTests.kt @@ -0,0 +1,6 @@ +// TODO +// Check: +// Same instance produces same token. +// Different instances produce different tokens even if they have equal values. +// Lots of instances kept around builds up lots of tokens. +// Lots of instances allowed to get garbage collected doesn't leave many tokens behind. diff --git a/tests/src/com/unciv/scripting/ScriptedTests.kt b/tests/src/com/unciv/scripting/ScriptedTests.kt index de9b5b9db9ab9..4c523edf7e8a1 100644 --- a/tests/src/com/unciv/scripting/ScriptedTests.kt +++ b/tests/src/com/unciv/scripting/ScriptedTests.kt @@ -17,15 +17,15 @@ import org.junit.Test import org.junit.runner.RunWith // The scripting API, by definition and by nature, works with the entire application state. -// Most of the risk of instability comes from its dynamism. It's neither particularly difficult nor terribly useful to guarantee and test that any specific set of edge cases will work consistently, but it's much more concerning whether any practical usage patterns involving large amounts of code have broken, in all the non-statically-checked IPC protocols, tokenization logic, and Python/JS operator overloading. +// Most of the risk of instability comes from its dynamism. It's neither particularly difficult nor terribly useful to guarantee and test that any specific set of edge cases will work consistently, but it's much more concerning whether any practical usage patterns involving large amounts of code have broken. With all the non-statically-checked IPC protocols, tokenization logic, and Python/JS operator overloading, hitting as much of the API surface as possible in as realistic a setup as possible is probably the easiest and most useful way to catch breaking changes. // So... The best way to have useful tests of the scripting API is going to be to launch an entire instance of the application, I think. -// There are/is seemingly (a) Github Action(s) to enable an OpenGL environment through software rendering. +// There are/is seemingly (a) Github Action(s) to enable an OpenGL environment through software rendering, so that should hopefully be fine. @RunWith(GdxTestRunner::class) class ScriptedTests { - // @return The ExecResult from running the command with the backendType, or null if something went wrong. + // @return The ExecResult from running the command with the backendType in a new Unciv application, or null if something went wrong. private fun runScript(backendType: ScriptingBackendType, command: String): ExecResult? { var execResult: ExecResult? = null val uncivGame = UncivGame(UncivGameParameters( diff --git a/tests/src/com/unciv/scripting/WeakIdentityMapTests.kt b/tests/src/com/unciv/scripting/WeakIdentityMapTests.kt new file mode 100644 index 0000000000000..70b786d12ed05 --- /dev/null +++ b/tests/src/com/unciv/scripting/WeakIdentityMapTests.kt @@ -0,0 +1 @@ +// TODO From 1ee62be35a0ae1c88d67f46af4577262fcfbfefa Mon Sep 17 00:00:00 2001 From: will-ca Date: Tue, 14 Dec 2021 20:39:04 +0000 Subject: [PATCH 90/93] Implement script handlers/mod script execution. Handler definition through builders. JSON script handlers for mods. Threading tweaks, IDK. Hardcoded backend update. --- core/src/com/unciv/JsonParser.kt | 6 +- core/src/com/unciv/UncivGame.kt | 2 + .../ModScriptingDebugParameters.kt | 11 + .../modscripting/ModScriptingHandlerTypes.kt | 308 ++++++++++++++---- .../ModScriptingRegistrationManager.kt | 100 +++++- .../modscripting/ModScriptingRunManager.kt | 84 +++-- .../unciv/models/modscripting/ScriptedMod.kt | 3 - .../models/modscripting/ScriptedModRules.kt | 116 +++++++ .../com/unciv/scripting/ScriptingBackend.kt | 61 +++- .../src/com/unciv/scripting/ScriptingState.kt | 29 +- .../scripting/api/ScriptingApiHelpers.kt | 2 + .../api/ScriptingApiInstanceRegistry.kt | 2 +- .../scripting/api/ScriptingApiMathHelpers.kt | 52 +++ .../unciv/scripting/sync/ScriptingRunLock.kt | 13 +- .../scripting/sync/ScriptingRunThreader.kt | 71 +++- .../utils/ScriptingDebugParameters.kt | 4 + .../scripting/utils/ScriptingErrorHandling.kt | 38 ++- .../unciv/ui/consolescreen/ConsoleScreen.kt | 100 ++++-- 18 files changed, 815 insertions(+), 187 deletions(-) create mode 100644 core/src/com/unciv/models/modscripting/ModScriptingDebugParameters.kt delete mode 100644 core/src/com/unciv/models/modscripting/ScriptedMod.kt create mode 100644 core/src/com/unciv/models/modscripting/ScriptedModRules.kt create mode 100644 core/src/com/unciv/scripting/api/ScriptingApiMathHelpers.kt diff --git a/core/src/com/unciv/JsonParser.kt b/core/src/com/unciv/JsonParser.kt index 794e6f9b7dc9b..15d4e7e92b9d5 100644 --- a/core/src/com/unciv/JsonParser.kt +++ b/core/src/com/unciv/JsonParser.kt @@ -14,4 +14,8 @@ class JsonParser { val jsonText = file.readString(Charsets.UTF_8.name()) return json.fromJson(tClass, jsonText) } -} \ No newline at end of file + +// fun getFromJsonString(tClass: Class, jsonText: String): T { // Mostly for debug. +// return json.fromJson(tClass, jsonText) +// } +} diff --git a/core/src/com/unciv/UncivGame.kt b/core/src/com/unciv/UncivGame.kt index 578d7196fc5ad..2671e7ea16821 100644 --- a/core/src/com/unciv/UncivGame.kt +++ b/core/src/com/unciv/UncivGame.kt @@ -247,6 +247,8 @@ class UncivGame(parameters: UncivGameParameters) : Game() { companion object { lateinit var Current: UncivGame fun isCurrentInitialized() = this::Current.isInitialized + // The main loop thread of the game, from which the OpenGL context is available and in which blocking actions can interrupt rendering. + val MainThread = Thread.currentThread() // Originaly thought I'd be clever and set this from a postRunnable for an explicit guarantee of getting the loop thread, but that does a NPE. } } diff --git a/core/src/com/unciv/models/modscripting/ModScriptingDebugParameters.kt b/core/src/com/unciv/models/modscripting/ModScriptingDebugParameters.kt new file mode 100644 index 0000000000000..7cd1d4773e3ff --- /dev/null +++ b/core/src/com/unciv/models/modscripting/ModScriptingDebugParameters.kt @@ -0,0 +1,11 @@ +package com.unciv.models.modscripting + +object ModScriptingDebugParameters { + // Whether to print when script handlers are triggered. + var printHandlerRun = false // Const val? Faster. But I like the idea of the scripting API itself being able to change these. // Bah. Premature optimization. There are far slower places to worry about speed. + // + var printHandlerRegister = false + var printModRead = false + var printModRegister = false + +} diff --git a/core/src/com/unciv/models/modscripting/ModScriptingHandlerTypes.kt b/core/src/com/unciv/models/modscripting/ModScriptingHandlerTypes.kt index 47ff041d0ca5b..919a014c1e8e3 100644 --- a/core/src/com/unciv/models/modscripting/ModScriptingHandlerTypes.kt +++ b/core/src/com/unciv/models/modscripting/ModScriptingHandlerTypes.kt @@ -2,14 +2,16 @@ package com.unciv.models.modscripting +import com.unciv.UncivGame +import com.unciv.logic.GameInfo +import com.unciv.logic.civilization.CivilizationInfo +import com.unciv.ui.consolescreen.ConsoleScreen +import com.unciv.ui.utils.stringifyException import kotlin.reflect.KType -import kotlin.reflect.full.isSubclassOf //import kotlin.reflect.full.isSubtypeOf //import kotlin.reflect.full.starProjectedType -import kotlin.reflect.jvm.jvmErasure import kotlin.reflect.typeOf - // Dependency/feature stack: // ModScriptingHandlerTypes —> ModScriptingRunManager —> ModScriptingRegistrationHandler // com.unciv.scripting —> com.unciv.models.modscripting @@ -17,77 +19,271 @@ import kotlin.reflect.typeOf // 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 handlerTypes, ModScriptingRunManager is only for using that during gameplay, and anything to do with parsing mod structures should go into the level of ModScriptingRegistrationHandler. +val HANDLER_DEFINITIONS = handlerDefinitions { + handlerContext(ContextId.UncivGame) { + addInstantiationHandlers() + } + handlerContext(ContextId.GameInfo) { + handler>(HandlerId.after_instantiate){ + param("gameInfo") { it.first } + param("civInfo") { it.second } + } + handler>(HandlerId.before_discard) { + param("gameInfo") { it.first } + param("civInfo") { it.second } + } + addSingleParamHandlers( + HandlerId.after_open, + HandlerId.before_close + ) + } + handlerContext(ContextId.ConsoleScreen) { + addInstantiationHandlers() + addSingleParamHandlers( + HandlerId.after_open, + HandlerId.before_close + ) + } +} + +private inline fun HandlerDefinitionsBuilderScope.HandlerContextBuilderScope.addSingleParamHandlers(vararg handlerTypes: HandlerId, paramName: String? = null) { + for (handlerType in handlerTypes) { + handler(handlerType) { + param(paramName ?: V::class.simpleName!!.replaceFirstChar { it.lowercase()[0] }) + } + } +} + +private inline fun HandlerDefinitionsBuilderScope.HandlerContextBuilderScope.addInstantiationHandlers(paramName: String? = null) { + addSingleParamHandlers(HandlerId.after_instantiate, HandlerId.before_discard, paramName = paramName) +} + +val ALL_HANDLER_TYPES = HANDLER_DEFINITIONS.handlerContexts.values.asSequence() + .map { it.handlerTypes.values } + .flatten().toSet() + + +// Enum of identifiers for a scripting handler context namespace. +enum class ContextId { // These are mostly so autocompletion, typodetection, usage search, etc will work. + UncivGame, + MainMenuScreen, + PausePopup, + GameInfo, + WorldScreen, + DiplomacyScreen, + PolicyPickerScreen, + TradeScreen, + MapEditor, + ConsoleScreen, +} + +// Enum of identifiers for names +@Suppress("EnumEntryName", "SpellCheckingInspection") +enum class HandlerId { + // After creation of a new object with the same name as the context. + after_instantiate, + // Before destruction of each object with the same name as the context. Not guaranteed to be run. + before_discard, + // After data represented by object is exposed as context to player, including if such happens multiple times. + after_open, + // After data represented by object is hidden replaced as context to player, including if such happens multiple times. + before_close, + before_gamesave, + after_turnautostart, + after_unitmove, + after_cityconstruction, + after_cityfound, + after_techfinished, + after_policyadopted, + before_turnend, + after_tileclicked, + after_modload, + before_modunload, +} + typealias Params = Map? -typealias ParamGetter = (Params?) -> Params +typealias ParamGetter = (Any?) -> Params // Some handlerTypes may have parameters that should be set universally; Others may use or simply pass on parameters from where they're called. +interface HandlerDefinitions { + val handlerContexts: Map + fun get(key: ContextId): HandlerContext { + val handlerContext = handlerContexts[key] + if (handlerContext == null) { + val exc = NullPointerException("Unknown HandlerContext $key!") + println(exc.stringifyException()) + throw exc + } + return handlerContext + } + operator fun get(contextKey: ContextId, handlerKey: HandlerId) = get(contextKey).get(handlerKey) +} + interface HandlerContext { - val name: String - val handlerTypes: Collection + val name: ContextId + val handlerTypes: Map + fun get(key: HandlerId): HandlerType { + val handlerType = handlerTypes[key] + if (handlerType == null) { + val exc = NullPointerException("Unknown HandlerType $key for $name context!") + println(exc.stringifyException()) + throw exc + } + return handlerType + } } interface HandlerType { - val name: String + val name: HandlerId val paramTypes: Map? val paramGetter: ParamGetter - fun checkParamsValid(checkParams: Params): Boolean { // If this becomes a major performance sink, it could be disabled in release builds. But - if (paramTypes == null || checkParams == null) { - return checkParams == paramTypes - } - if (paramTypes!!.keys != checkParams.keys) { - return false - } - return checkParams.all { (k, v) -> - if (v == null) { - paramTypes!![k]!!.isMarkedNullable - } else { - //v::class.starProjectedType.isSubtypeOf(paramTypes!![k]!!) - // In FunctionDispatcher I compare the .jvmErasure KClass instead of the erased type. - // Right. Erased/star-projected types probably aren't subclasses of the argumented types from typeOf(). Could implement custom typeOfInstance that looks for most specific common element in allSuperTypes for collection contents, but sounds excessive and expensive. - v::class.isSubclassOf(paramTypes!![k]!!.jvmErasure) - } - } - } +// fun checkParamsValid(checkParams: Params): Boolean { // If this becomes a major performance sink, it could be disabled in release builds. But +// if (paramTypes == null || checkParams == null) { +// return checkParams == paramTypes +// } +// if (paramTypes!!.keys != checkParams.keys) { +// return false +// } +// return checkParams.all { (k, v) -> +// if (v == null) { +// paramTypes!![k]!!.isMarkedNullable +// } else { +// //v::class.starProjectedType.isSubtypeOf(paramTypes!![k]!!) +// // In FunctionDispatcher I compare the .jvmErasure KClass instead of the erased type. +// // Right. Erased/star-projected types probably aren't subclasses of the argumented types from typeOf(). Could implement custom typeOfInstance that looks for most specific common element in allSuperTypes for collection contents, but sounds excessive and expensive. +// v::class.isSubclassOf(paramTypes!![k]!!.jvmErasure) +// } +// } +// } } -// For defining the types and behaviours of available handlerTypes. -object ModScriptingHandlerTypes { - - private val defaultParamGetter: ParamGetter = { it } - -// @Suppress("EnumEntryName") // Handler names are not Kotlin code, but more like JSON keys in mod configuration files. - @Suppress("EnumEntryName") - @OptIn(ExperimentalStdlibApi::class) // For typeOf(). Technically isn't needed for functional release builds (see HandlerType.checkParamsValid). - object All { // Not a great name for imports. - enum class UncivGame(override val paramGetter: ParamGetter = defaultParamGetter): HandlerType { - after_enter() { - override val paramTypes = mapOf( - "testArg" to typeOf() - ) - } - ; - companion object: HandlerContext { - override val name = "UncivGame" - override val handlerTypes = values().toList() + +@DslMarker +private annotation class HandlerBuilder + +// Type-safe builder scope for the root hierarchy of all handler context and handler type definitions. +@HandlerBuilder +private class HandlerDefinitionsBuilderScope { + + val handlerContexts = mutableMapOf() + + // Type-safe builder scope for a handler context namespace. + + // @param name Name of the context. + @HandlerBuilder + class HandlerContextBuilderScope(val name: ContextId) { + + val handlers = mutableMapOf() + + // Type-safe builder scope for a handler type. + + // @param V The type that may be provided as an argument when running this handler type. + // @param name Name of the handler type. + @HandlerBuilder + class HandlerTypeBuilderScope(val name: HandlerId) { + + val paramTypes = mutableMapOf() + val paramGetters = mutableMapOf Any?>() + + // Type-safe builder method for a parameter that a handler type accepts and then sets in a Map accessible by the scripting API while the handler type is running. + + // @param R The type that this value is to be set to in the script's execution context. + // @param name The key of this parameter in the scripting-accessible Map. + // @param getter A function that returns the value of this parameter in the scripting-accessible Map, when given the argument passed to this handler type. + @OptIn(ExperimentalStdlibApi::class) + inline fun param(name: String, noinline getter: (V) -> R = { it as R }) { + paramTypes[name] = typeOf() + paramGetters[name] = getter } + } - enum class GameInfo(override val paramGetter: ParamGetter = defaultParamGetter): HandlerType { - ; - companion object: HandlerContext { - override val name = "GameInfo" - override val handlerTypes = values().toList() + // Type-safe builder method for a handler type. + + // @param V The type that may be provided as an argument when running this handler type. + // @param name The name of this handler type. + fun handler(name: HandlerId, init: HandlerTypeBuilderScope.() -> Unit) { + val unconfigured = HandlerTypeBuilderScope(name) + unconfigured.init() + handlers[name] = object: HandlerType { + override val name = name + val context = this@HandlerContextBuilderScope + override val paramTypes = unconfigured.paramTypes.toMap() + override val paramGetter: ParamGetter = { given: Any? -> unconfigured.paramGetters.entries.associate { it.key to it.value(given as V) } } + override fun toString() = "HandlerType:${context.name}/${name}" } } + } - val all = listOf( // Explicitly type so anything with wrong or missing companion raises compile/IDE error. - All.UncivGame, - All.GameInfo - ) - val byContextAndName = all.associate { context -> - context.name to context.handlerTypes.associateWith { handler -> - handler.name + + // Type-safe builder method for a handler context namespace. + + // @param name The name of this context. + fun handlerContext(name: ContextId, init: HandlerContextBuilderScope.() -> Unit) { + val unconfigured = HandlerContextBuilderScope(name) + unconfigured.init() + handlerContexts[name] = object: HandlerContext { + override val name = unconfigured.name + override val handlerTypes = unconfigured.handlers.toMap() } } + +} + +// Type-safe builder function for the root hierarchy of all handler context and handler type definitions. +private fun handlerDefinitions(init: HandlerDefinitionsBuilderScope.() -> Unit): HandlerDefinitions { + val unconfigured = HandlerDefinitionsBuilderScope() + unconfigured.init() + return object: HandlerDefinitions { + override val handlerContexts = unconfigured.handlerContexts.toMap() + } } + + +//object ModScripthingHandlerTypes2 { +//} + +// For defining the types and behaviours of available handlerTypes. +//object ModScriptingHandlerTypes { +// +// +// +//// private val defaultParamGetter: ParamGetter = { it } +// private val defaultParamGetter: ParamGetter = { mapOf() } +// +//// @Suppress("EnumEntryName") // Handler names are not Kotlin code, but more like JSON keys in mod configuration files. +// @Suppress("EnumEntryName") +// @OptIn(ExperimentalStdlibApi::class) // For typeOf(). Technically isn't needed for functional release builds (see HandlerType.checkParamsValid). +// object All { // Not a great name for imports. +// enum class UncivGame(override val paramGetter: ParamGetter = defaultParamGetter): HandlerType { +// after_instantiate() { +// override val paramTypes = mapOf( +// "testArg" to typeOf() +// ) +// } +// ; +// companion object: HandlerContext { +// override val name = "UncivGame" +// override val handlerTypes = values().toList() +// } +// } +// +// enum class GameInfo(override val paramGetter: ParamGetter = defaultParamGetter): HandlerType { +// ; +// companion object: HandlerContext { +// override val name = "GameInfo" +// override val handlerTypes = values().toList() +// } +// } +// } +// val all = listOf( // Explicitly type so anything with wrong or missing companion raises compile/IDE error. +// All.UncivGame, +// All.GameInfo +// ) +// val byContextAndName = all.associate { context -> +// context.name to context.handlerTypes.associateWith { handler -> +// handler.name +// } +// } +//} diff --git a/core/src/com/unciv/models/modscripting/ModScriptingRegistrationManager.kt b/core/src/com/unciv/models/modscripting/ModScriptingRegistrationManager.kt index bf5476ec8db20..cb3ea48b1dccd 100644 --- a/core/src/com/unciv/models/modscripting/ModScriptingRegistrationManager.kt +++ b/core/src/com/unciv/models/modscripting/ModScriptingRegistrationManager.kt @@ -1,35 +1,101 @@ package com.unciv.models.modscripting -import com.unciv.scripting.ScriptingBackend import com.unciv.scripting.ScriptingBackendType // For organizing and associating script handlerTypes with specific mods. // Uses ModScriptingRunManager and ModScriptingHandlerTypes. object ModScriptingRegistrationManager { - private fun register(mod: ScriptedMod, backend: ScriptingBackend, handlerType: HandlerType, code: String) { - } + val activeMods = mutableSetOf() - fun registerMod(mod: ScriptedMod) { + init { + registerMod( + ScriptedModRules.fromJsonString( + """ + { + "name": "Test Mod", + "language": "pathcode", + "handlers": { + "ConsoleScreen": { + "after_open": { + "background": ["examples", "get apiExecutionContext.handlerParameters", "get apiExecutionContext.scriptingBackend", "faef"] + } + } + } + } + """ + ) + ) + registerMod( + ScriptedModRules.fromJsonString( + """ + { + "name": "Test Mod2", + "language": "python", + "handlers": { + "ConsoleScreen": { + "before_close": { + "background": ["from unciv_scripting_examples.MapEditingMacros import *; (makeMandelbrot() if unciv.apiHelpers.isInGame else None)"], + "foreground": ["from unciv_scripting_examples.EventPopup import *; (showEventPopup(**EVENT_POPUP_DEMOARGS()) if apiHelpers.isInGame else None)"] + } + } + } + } + """ + ) + ) } - private fun unregister(registeredHandler: RegisteredHandler) { + fun registerMod(modRules: ScriptedModRules) { + if (ModScriptingDebugParameters.printModRegister) { + println("Registering ${modRules.handlersByType.values.map { it.background.size + it.foreground.size }.sum() } script handlers for mod ${modRules.name}.") + } + if (modRules in activeMods) { + throw IllegalArgumentException("Mod ${modRules.name} is already registered.") + } + val backend = modRules.backend + if (backend != null) { + backend.apply { + userTerminable = false + displayNote = modRules.name + } + activeMods.add(modRules) + for ((handlerType, modHandlers) in modRules.handlersByType) { + for (command in modHandlers.background) { + ModScriptingRunManager.registerHandler( + handlerType, + RegisteredHandler( + backend, + command, + modRules, + mainThread = false + ) + ) + } + for (command in modHandlers.foreground) { + ModScriptingRunManager.registerHandler( + handlerType, + RegisteredHandler( + backend, + command, + modRules, + mainThread = true + ) + ) + } + } + } } - fun unregisterMod(mod: ScriptedMod) { - } - @Suppress("EnumEntryName") - enum class ModScriptingLanguages(val backendType: ScriptingBackendType) { - pathcode(ScriptingBackendType.Reflective), - python(ScriptingBackendType.SystemPython), - javascript(ScriptingBackendType.SystemQuickJS) + fun unregisterMod(modRules: ScriptedModRules) { } // …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 pathcode: ScriptingBackend? = null, - var python: ScriptingBackend? = null, - var javascript: ScriptingBackend? = null - ) +@Suppress("EnumEntryName") +enum class ModScriptingLanguage(val backendType: ScriptingBackendType) { + pathcode(ScriptingBackendType.Reflective), + python(ScriptingBackendType.SystemPython), + javascript(ScriptingBackendType.SystemQuickJS) } diff --git a/core/src/com/unciv/models/modscripting/ModScriptingRunManager.kt b/core/src/com/unciv/models/modscripting/ModScriptingRunManager.kt index 90ee0f4ba07a3..279d45d541ae9 100644 --- a/core/src/com/unciv/models/modscripting/ModScriptingRunManager.kt +++ b/core/src/com/unciv/models/modscripting/ModScriptingRunManager.kt @@ -7,31 +7,50 @@ 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.blockingConcurrentRun import com.unciv.scripting.sync.makeScriptingRunName import com.unciv.ui.utils.BaseScreen -import java.lang.IllegalStateException +import java.lang.IllegalArgumentException +import kotlin.concurrent.thread -data class RegisteredHandler(val backend: ScriptingBackend, val code: String, val mod: ScriptedMod?) +data class RegisteredHandler(val backend: ScriptingBackend, val code: String, val modRules: ScriptedModRules?, val mainThread: Boolean = false) // For organizing and running script handlerTypes during gameplay. // Uses ModScriptingHandlerTypes. object ModScriptingRunManager { - private val registeredHandlers: Map> = - ModScriptingHandlerTypes.all.asSequence() - .map { it.handlerTypes } - .flatten() - .associateWith { HashSet() } + private val registeredHandlers = ALL_HANDLER_TYPES.associateWith { mutableSetOf() } // Let's specify that the registration and functions must maintain the order, so mods can run multiple interacting commands predictably. + + fun registerHandler(handlerType: HandlerType, registeredHandler: RegisteredHandler) { + if (ModScriptingDebugParameters.printHandlerRegister) { + println("Registering $handlerType handler:\n${registeredHandler.toString().prependIndent("\t")}") + } + val registry = registeredHandlers[handlerType]!! + if (registeredHandler in registry) { + throw IllegalArgumentException("$registeredHandler is already registered for $handlerType!") + } + registry.add(registeredHandler) + } + + fun unregisterHandler(handlerType: HandlerType, registeredHandler: RegisteredHandler) { + if (ModScriptingDebugParameters.printHandlerRegister) { + println("Unregistering $handlerType handler:\n${registeredHandler.toString().prependIndent("\t")}") + } + val registry = registeredHandlers[handlerType]!! + if (registeredHandler !in registry) { + throw IllegalArgumentException("$registeredHandler isn't registered for $handlerType!") + } + registry.remove(registeredHandler) + } fun getRegisteredForHandler(handlerType: HandlerType) = registeredHandlers[handlerType]!! fun lambdaRegisteredHandlerRunner(registeredHandler: RegisteredHandler, withParams: Params): () -> Unit { - val params = HashMap(withParams) - val name = makeScriptingRunName(registeredHandler.mod?.name, registeredHandler.backend) - return fun() { + val name = makeScriptingRunName(registeredHandler.modRules?.name, registeredHandler.backend) + val nakedRunnable = fun() { val execResult: ExecResult? - try{ + try { execResult = ScriptingState.exec( command = registeredHandler.code, asName = name, @@ -46,33 +65,52 @@ object ModScriptingRunManager { ScriptingErrorHandling.notifyPlayerScriptFailure(text = execResult.resultPrint, asName = name) } } + return if (registeredHandler.mainThread) fun() { + blockingConcurrentRun(Gdx.app::postRunnable, nakedRunnable) + // Just running Gdx.app.postRunnable would bypass ScriptingRunThreader's sequential locking, I think. It still kinda works, because I put @Synchronized on a lot of things, but it seems too unpredictable/uncontrolled/unmaintainable to me, especially with lots of modded scripts. + } else + fun() { nakedRunnable() } + // Both scripts and the error reporting mechanism for scripts can involve UI widgets, which means OpenGL calls, which means crashes unless they're on the main thread. + // …I think the easiest way to avoid those while still letting scripts run in the background might be to let scripts specify a set of "background" and "foreground" commands per handler… Also the game will probably thread-lock if another run is attempted/waited for while the current one's been posted to and is waiting on the main thread, so I should move the lock acquisition to a worker thread too. —Except that postRunnable's done concurrently, so it might be fine? } - fun runHandler(handlerType: HandlerType, baseParams: Params?, after: () -> Unit = {}) { + fun runHandler(handlerType: HandlerType, baseParams: Any?, after: () -> Unit = {}) { val registeredHandlers = getRegisteredForHandler(handlerType) + if (ModScriptingDebugParameters.printHandlerRun) { + println("Running ${registeredHandlers.size} handlers for $handlerType with $baseParams.") + } if (registeredHandlers.isNotEmpty()) { val params = handlerType.paramGetter(baseParams) - if (!handlerType.checkParamsValid(params)) { - throw IllegalStateException( - """ - Incorrect parameter signature for running mod script handlerTypes: - handlerType = ${handlerType.name} - handlerType.paramTypes = ${handlerType.paramTypes} - baseParams = $baseParams - params = $params - """.trimIndent() - ) + if (ModScriptingDebugParameters.printHandlerRun) { + println("\tFinal parameters:\n\t\t${params?.map { "${it.key} = ${it.value}" }?.joinToString("\n\t\t") }") } +// if (!handlerType.checkParamsValid(params)) { +// throw IllegalStateException( +// """ +// Incorrect parameter signature for running mod script handlerTypes: +// handlerType = ${handlerType.name} +// handlerType.paramTypes = ${handlerType.paramTypes} +// baseParams = $baseParams +// params = $params +// """.trimIndent() +// ) +// } ScriptingRunThreader.queueRuns( registeredHandlers.asSequence().map { lambdaRegisteredHandlerRunner(it, params) } ) lockGame() ScriptingRunThreader.queueRun { Gdx.app.postRunnable(::unlockGame) } - ScriptingRunThreader.doRuns() + ScriptingRunThreader.queueRun { after() } + thread { // A locking operation. I have the thought that calling from a short-lived thread will be more resilient to threadlocking in the long term. Consider: Main thread waits on the lock. But then releasing lock ends up getting posted to main thread, so lock won't be released ever, as main thread needs to finish waiting on it before it can execute the release runnable. + ScriptingRunThreader.doRuns() + } + } else { + after() } } private fun lockGame() { + //isPlayersTurn. Gdx.input.inputProcessor = null } private fun unlockGame() { diff --git a/core/src/com/unciv/models/modscripting/ScriptedMod.kt b/core/src/com/unciv/models/modscripting/ScriptedMod.kt deleted file mode 100644 index b65aff0399032..0000000000000 --- a/core/src/com/unciv/models/modscripting/ScriptedMod.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.unciv.models.modscripting - -class ScriptedMod(val name: String) // See, try to follow conventions of, TilesetAndMod maybe? diff --git a/core/src/com/unciv/models/modscripting/ScriptedModRules.kt b/core/src/com/unciv/models/modscripting/ScriptedModRules.kt new file mode 100644 index 0000000000000..440593aa23f04 --- /dev/null +++ b/core/src/com/unciv/models/modscripting/ScriptedModRules.kt @@ -0,0 +1,116 @@ +package com.unciv.models.modscripting + +import com.badlogic.gdx.utils.Json +import com.badlogic.gdx.utils.JsonReader +import com.badlogic.gdx.utils.JsonValue +import com.unciv.scripting.ScriptingState + +class ScriptedModLoadable(val modRules: ScriptedModRules) { + +} + +class ScriptedModRules {// See, try to follow conventions of, TilesetAndMod maybe? + + // Getting this deserializing from JSON was a humongous, humongous pain. GDX seems to take only three levels of nested mappings before it stops knowing what to do with the arrays at the deepest level. I could have just used KotlinX instead as I'm bringing in the dependency anyway, but for some reason I seem to want to align the parts of the scripting API that interact with the existing codebase with the tools that are already used. + + // Recommend switching to KotlinX if anyone else decides to change this. See: ScriptingPacket, List, .decodeFromJsonElement, TokenizingJson— It's *so* easy (once you have it set up). + + companion object { + fun fromJsonString(jsonText: String): ScriptedModRules { + val mod = ScriptedModRules() + val jsonValue = JsonReader().parse(jsonText) + val json = Json().apply { setEnumNames(true) } + mod.apply { + name = jsonValue.getString("name") ?: "?" + language = json.fromJson(ModScriptingLanguage::class.java, jsonValue.getString("language")) + handlers = ScriptedModHandlerRoot.fromJson(jsonValue.get("handlers")) + } + return mod + } + } + + var name = "" + var language: ModScriptingLanguage? = null + var handlers = ScriptedModHandlerRoot() + + val backend by lazy { // TODO: This should be discarded when unloading. + val backendType = language?.backendType + if (backendType == null) + null + else + ScriptingState.spawnBackend(backendType).backend + } + + val handlersByType by lazy { + val flatmap = mutableMapOf() +// if (handlers == null) { +// return@lazy flatmap.toMap() +// } + for ((contextId, contextHandlers) in handlers.entries) { + for ((handlerId, handlerSet) in contextHandlers) { + val handlerType = HANDLER_DEFINITIONS[ContextId.valueOf(contextId), HandlerId.valueOf(handlerId)] + if (handlerType in flatmap) { + throw IllegalArgumentException("Handler type $handlerType defined more than once in mod $name!") + } + flatmap[handlerType] = handlerSet + } + } + return@lazy flatmap.toMap() + } +} + + + +class ScriptedModHandlerRoot: HashMap() { + companion object { + fun fromJson(jsonValue: JsonValue?): ScriptedModHandlerRoot { + val root = ScriptedModHandlerRoot() + if (jsonValue == null) { + return root + } + for (context in jsonValue) { + root[context.name] = ScriptedModHandlerContext.fromJson(context) + } + return root + } + } +} + +class ScriptedModHandlerContext: HashMap() { + companion object { + fun fromJson(jsonValue: JsonValue?): ScriptedModHandlerContext { + val context = ScriptedModHandlerContext() + if (jsonValue == null) { + return context + } + for (handler in jsonValue) { + context[handler.name] = ScriptedModHandlerSet.fromJson(handler) + } + return context + } + } +} + +class ScriptedModHandlerSet { + + companion object { + fun fromJson(jsonValue: JsonValue?): ScriptedModHandlerSet { + val handlerSet = ScriptedModHandlerSet() + if (jsonValue == null) { + return handlerSet + } + handlerSet.apply { + background = jsonValue.get("background")?.asStringArray()?.toList() ?: listOf() + foreground = jsonValue.get("foreground")?.asStringArray()?.toList() ?: listOf() + } + return handlerSet + } + } + + var background = listOf() // Run first, in worker threads. + + var foreground = listOf() // Run after, in main thread. UI-affecting + +} + +// I mean, seriously. It's over 100 lines with poorly documented GDX functions just to deserialize three properties. Maybe there's an easier way… Rewrite it please if you know what that is. diff --git a/core/src/com/unciv/scripting/ScriptingBackend.kt b/core/src/com/unciv/scripting/ScriptingBackend.kt index 397b2ba32f35f..60d5dd31587bb 100644 --- a/core/src/com/unciv/scripting/ScriptingBackend.kt +++ b/core/src/com/unciv/scripting/ScriptingBackend.kt @@ -121,6 +121,13 @@ open class ScriptingBackend: ScriptingImplementation { open val metadata get() = this::class.companionObjectInstance as ScriptingBackend_metadata + // Flag marking whether or not the user should be allowed to manually terminate this backend. + // Meant to be set externally. + var userTerminable = true + // Optional short text conveying further information to show the user alongside the displayName. + // Meant to be set externally. + var displayNote: String? = null + } //Test, reference, example, and backup @@ -341,15 +348,15 @@ class ReflectiveScriptingBackend(): ScriptingBackend() { private val examples = listOf( // The new splitToplevelExprs means set can safely use equals sign for assignment. "get uncivGame.loadGame(Unciv.GameStarter.startNewGame(apiHelpers.Jvm.companionByQualClass[\"com.unciv.models.metadata.GameSetupInfo\"].fromSettings(\"Chieftain\")))", "get gameInfo.civilizations[1].policies.adoptedPolicies", - "set 5 civInfo.tech.freeTechs", -// "set 1 civInfo.cities[0].health", // Doesn't work as test due to new game, no city. - "set 5 gameInfo.turns", + "set civInfo.tech.freeTechs = 5", +// "set civInfo.cities[0].health = 1", // Doesn't work as test due to new game, no city. + "set gameInfo.turns = 5", "get civInfo.addGold(1337)", "get civInfo.addNotification(\"Here's a notification!\", apiHelpers.Jvm.arrayOfTyped1(\"StatIcons/Resistance\"))", - "set 2000 worldScreen.bottomUnitTable.selectedUnit.promotions.XP", + "set worldScreen.bottomUnitTable.selectedUnit.promotions.XP = 2000", // "get worldScreen.bottomUnitTable.selectedCity.population.setPopulation(25)", // Doesn't work as test due to new game, no city. - "set \"Cattle\" worldScreen.mapHolder.selectedTile.resource", - "set \"Krakatoa\" worldScreen.mapHolder.selectedTile.naturalWonder", + "set worldScreen.mapHolder.selectedTile.resource = \"Cattle\"", + "set worldScreen.mapHolder.selectedTile.naturalWonder = \"Krakatoa\"", "get apiHelpers.Jvm.constructorByQualname[\"com.unciv.ui.worldscreen.AlertPopup\"](worldScreen, apiHelpers.Jvm.constructorByQualname[\"com.unciv.logic.civilization.PopupAlert\"](apiHelpers.Jvm.enumMapsByQualname[\"com.unciv.logic.civilization.AlertType\"][\"StartIntro\"], \"Text text.\")).open(false)", "get civInfo.addGold(civInfo.tech.techsResearched.size)", //"get uncivGame.setScreen(apiHelpers.Jvm.constructorByQualname[\"com.unciv.ui.mapeditor.MapEditorScreen\"](gameInfo.tileMap))", @@ -362,8 +369,8 @@ class ReflectiveScriptingBackend(): ScriptingBackend() { "get apiHelpers.App.assetFileB64(\"jsons/Tutorials.json\")", "get apiHelpers.Jvm.staticPropertyByQualClassAndName[\"com.badlogic.gdx.graphics.Color\"][\"WHITE\"]", "get apiHelpers.Jvm.constructorByQualname[\"com.unciv.ui.utils.ToastPopup\"](\"This is a popup!\", apiHelpers.Jvm.companionByQualClass[\"com.unciv.UncivGame\"].Current.getScreen(), 2000).add(apiHelpers.Jvm.functionByQualClassAndName[\"com.unciv.ui.utils.ExtensionFunctionsKt\"][\"toLabel\"](\"With Scarlet text! \", apiHelpers.Jvm.staticPropertyByQualClassAndName[\"com.badlogic.gdx.graphics.Color\"][\"SCARLET\"], 24))", - "set true Unciv.ScriptingDebugParameters.printCommandsForDebug", - "set false Unciv.ScriptingDebugParameters.printCommandsForDebug" + "set Unciv.ScriptingDebugParameters.printCommandsForDebug = true", + "set Unciv.ScriptingDebugParameters.printCommandsForDebug = false" ) private val tests = listOf( "get modApiHelpers.lambdifyExecScript(\"get uncivGame\")", @@ -395,8 +402,21 @@ class ReflectiveScriptingBackend(): ScriptingBackend() { )} } + private fun examplesPrintable() = "\nExamples:\n${examples.map({"> ${it}"}).joinToString("\n")}\n" + override fun motd(): String { - return "\n\nWelcome to the reflective Unciv CLI backend.\n\nCommands you enter will be parsed as a path consisting of property reads, key and index accesses, function calls, and string, numeric, boolean, and null literals.\nKeys, indices, and function arguments are parsed recursively.\nProperties can be both read from and written to.\n\nExamples:\n${examples.map({"> ${it}"}).joinToString("\n")}\n\nPress [TAB] at any time to trigger autocompletion for all known leaf names at the currently entered path.\n" + return """ + + + Welcome to the reflective Unciv CLI backend. + + Commands you enter will be parsed as a path consisting of property reads, key and index accesses, function calls, and string, numeric, boolean, and null literals. + Keys, indices, and function arguments are parsed recursively. + Properties can be both read from and written to. + + Press [TAB] at any time to trigger autocompletion for all known leaf names at the currently entered path. + + """.trimIndent() } override fun autocomplete(command: String, cursorPos: Int?): AutocompleteResults { @@ -442,21 +462,28 @@ class ReflectiveScriptingBackend(): ScriptingBackend() { appendOut("${Reflection.evalKotlinString(ScriptingScope, parts[1])}") } "set" -> { // TODO: Use the new pathcode splitter to accept equals sign format. - var setparts = parts[1].split(' ', limit=2) - var value = Reflection.evalKotlinString(ScriptingScope, setparts[0]) +// var setparts = parts[1].split(' ', limit=2) + val setparts = Reflection.splitToplevelExprs( + parts[1], + delimiters = " " + ).filter { it.isNotBlank() } + if (setparts.size != 3 || setparts.elementAtOrNull(1) != "=") { + throw IllegalArgumentException("Expected two expressions separated by an equals sign with spaces. Got:\n" + setparts.joinToString("\n")) + } + var value = Reflection.evalKotlinString(ScriptingScope, setparts[2]) Reflection.setInstancePath( ScriptingScope, - Reflection.parseKotlinPath(setparts[1]), - value + path = Reflection.parseKotlinPath(setparts[0]), + value = value ) - appendOut("Set ${setparts[1]} to ${value}") + appendOut("Set ${setparts[0]} to ${value}") } "typeof" -> { var obj = Reflection.evalKotlinString(ScriptingScope, parts[1]) appendOut("${if (obj == null) null else obj!!::class.qualifiedName}") } "examples" -> { - throw RuntimeException("Not implemented.") // TODO: Remove from MOTD, move to default startup. + appendOut(examplesPrintable()) } "runtests" -> { val testResults = runTests() @@ -696,8 +723,8 @@ class DevToolsScriptingBackend(): ScriptingBackend() { // @property suggestedStartup Default startup code to run when started by ConsoleScreen *only*. enum class ScriptingBackendType(val metadata: ScriptingBackend_metadata, val suggestedStartup: String = "") { // Not sure how I feel about having suggestedStartup here— Kinda breaks separation of functionality and UI— But keeping a separate Map in the UI files would be too messy, and it's not as bad as putting it in the companion objects— Really, this entire Enum is a mash of stuff needed to launch and use all the backend types by anything else, so that fits. Dummy(ScriptingBackend), - Hardcoded(HardcodedScriptingBackend), - Reflective(ReflectiveScriptingBackend), + Hardcoded(HardcodedScriptingBackend, "help"), + Reflective(ReflectiveScriptingBackend, "examples"), //MicroPython(UpyScriptingBackend), SystemPython(SpyScriptingBackend, "from unciv import *; from unciv_pyhelpers import *"), SystemQuickJS(SqjsScriptingBackend), diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index e04007f7965e9..909f98bce1a2c 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -1,6 +1,6 @@ package com.unciv.scripting -import com.unciv.UncivGame +import com.unciv.UncivGame // Only for blocking execution in multiplayer. import com.unciv.scripting.api.ScriptingScope import com.unciv.scripting.sync.ScriptingRunLock import com.unciv.scripting.sync.makeScriptingRunName @@ -8,6 +8,7 @@ import com.unciv.ui.utils.BaseScreen import com.unciv.ui.utils.ToastPopup import com.unciv.ui.utils.clipIndexToBounds import com.unciv.ui.utils.enforceValidIndex +import com.unciv.ui.worldscreen.WorldScreen // Only for blocking execution in multiplayer. import kotlin.collections.ArrayList // TODO: Add .github/CODEOWNERS file for automatic PR notifications. @@ -39,8 +40,9 @@ object ScriptingState { private val outputHistory = ArrayList() private val commandHistory = ArrayList() - var activeBackend: Int = 0 + var activeBackendIndex: Int = 0 private set + val activeBackend get() = scriptingBackends[activeBackendIndex] val maxOutputHistory: Int = 511 val maxCommandHistory: Int = 511 @@ -48,7 +50,7 @@ object ScriptingState { var activeCommandHistory: Int = 0 // Actually inverted, because history items are added to end of list and not start. 0 means nothing, 1 means most recent command at end of list. - //var consoleScreenListener: ((String) -> Unit)? = null // TODO: Switch to push instead of pull for ConsoleScreen. + var consoleScreenListener: ((String) -> Unit)? = null // TODO: Switch to push instead of pull for ConsoleScreen. fun getOutputHistory() = outputHistory.toList() @@ -57,7 +59,7 @@ object ScriptingState { fun spawnBackend(backendtype: ScriptingBackendType): BackendSpawnResult { val backend: ScriptingBackend = backendtype.metadata.new() scriptingBackends.add(backend) - activeBackend = scriptingBackends.lastIndex + activeBackendIndex = scriptingBackends.lastIndex val motd = backend.motd() echo(motd) return BackendSpawnResult(backend, motd) @@ -73,7 +75,7 @@ object ScriptingState { fun switchToBackend(index: Int) { scriptingBackends.enforceValidIndex(index) - activeBackend = index + activeBackendIndex = index } fun switchToBackend(backend: ScriptingBackend) = switchToBackend(getIndexOfBackend(backend)!!) @@ -83,10 +85,10 @@ object ScriptingState { val result = scriptingBackends[index].terminate() if (result == null) { scriptingBackends.removeAt(index) - if (index < activeBackend) { - activeBackend -= 1 + if (index < activeBackendIndex) { + activeBackendIndex -= 1 } - activeBackend = scriptingBackends.clipIndexToBounds(activeBackend) + activeBackendIndex = scriptingBackends.clipIndexToBounds(activeBackendIndex) } return result } @@ -97,12 +99,9 @@ object ScriptingState { return scriptingBackends.isNotEmpty() } - fun getActiveBackend(): ScriptingBackend { - return scriptingBackends[activeBackend] - } - fun echo(text: String) { outputHistory.add(text) + consoleScreenListener?.invoke(text) while (outputHistory.size > maxOutputHistory) { outputHistory.removeAt(0) // If these are ArrayLists, performance will probably be O(n) relative to maxOutputHistory. @@ -115,7 +114,7 @@ object ScriptingState { if (!(hasBackend())) { return AutocompleteResults() } - return getActiveBackend().autocomplete(command, cursorPos) + return activeBackend.autocomplete(command, cursorPos) } fun navigateHistory(increment: Int): String { @@ -129,11 +128,11 @@ object ScriptingState { // @throws IllegalStateException On failure to acquire scripting lock. @Synchronized fun exec(command: String, asName: String? = null, withParams: Map? = null): ExecResult { - if (UncivGame.Current.isGameInfoInitialized() && UncivGame.Current.gameInfo.gameParameters.isOnlineMultiplayer) { + if (UncivGame.Current.screen is WorldScreen && UncivGame.Current.isGameInfoInitialized() && UncivGame.Current.gameInfo.gameParameters.isOnlineMultiplayer) { // TODO: After leaving game? ToastPopup("Scripting not allowed in online multiplayer.", UncivGame.Current.screen as BaseScreen) // TODO: Translation. return ExecResult("", true) } - val backend = getActiveBackend() + val backend = activeBackend val name = asName ?: makeScriptingRunName(this::class.simpleName, backend) val releaseKey = ScriptingRunLock.acquire(name) // Lock acquisition failure gets propagated as thrown Exception, rather than as return. E.G.: Lets lambdas (from modApiHelpers) fail and trigger their own error handling (exposing misbehaving mods to the user via popup). diff --git a/core/src/com/unciv/scripting/api/ScriptingApiHelpers.kt b/core/src/com/unciv/scripting/api/ScriptingApiHelpers.kt index 7e79da7475d94..c81b26dd1e22d 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiHelpers.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiHelpers.kt @@ -23,6 +23,8 @@ object ScriptingApiHelpers { val Mappers = ScriptingApiMappers + val Math = ScriptingApiMathHelpers + val registeredInstances = ScriptingApiInstanceRegistry val instancesAsInstances = FakeMap{obj: Any? -> obj} /// Scripting language bindings work by keeping track of the paths to values, and making Kotlin/the JVM resolve them only when needed. diff --git a/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt b/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt index 5ebc04a41092a..617c8ee8fb7db 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiInstanceRegistry.kt @@ -59,7 +59,7 @@ object ScriptingApiInstanceRegistry: MutableMap { // This is a sin override fun put(key: String, value: Any?): Any? { println(""" - Assigning ${key} directly in ScriptingApiInstanceRegistry(). It is recommended that every script/mod do this only once per application lifespan, creating its own mapping under the registry for further assignments named according to the following format: + INFO: Assigning ${key} directly in ScriptingApiInstanceRegistry(). It is recommended that every script/mod do this only once per application lifespan, creating its own mapping under the registry for further assignments named according to the following format: -<'mod'|'module'|'package'>:/ E.G.: registeredInstances["python-module:myName/myCoolScript"] = {"some_name": someToken} diff --git a/core/src/com/unciv/scripting/api/ScriptingApiMathHelpers.kt b/core/src/com/unciv/scripting/api/ScriptingApiMathHelpers.kt new file mode 100644 index 0000000000000..c77de707978ba --- /dev/null +++ b/core/src/com/unciv/scripting/api/ScriptingApiMathHelpers.kt @@ -0,0 +1,52 @@ +package com.unciv.scripting.api + +object ScriptingApiMathHelpers { + + fun add() {} + + fun subtract() {} + + fun multiply() {} + + fun divide() {} + + fun exponentiate() {} + + fun logarithm() {} + + fun floor() {} + + fun ceiling() {} + + fun truncateToInt() {} + + fun truncateToLong() {} + + fun round() {} + + fun roundToMultiple() {} + + fun modulo() {} + + fun sum() {} + + fun geometric() {} + + fun min() {} + + fun max() {} + + fun greaterThan() {} + + fun lessThan() {} + + fun equals() {} + + fun not() {} + + fun ifSwitch() {} + + fun interpolate() {} + + fun contains() {} +} diff --git a/core/src/com/unciv/scripting/sync/ScriptingRunLock.kt b/core/src/com/unciv/scripting/sync/ScriptingRunLock.kt index 93d94996729e4..f8a200fe225cc 100644 --- a/core/src/com/unciv/scripting/sync/ScriptingRunLock.kt +++ b/core/src/com/unciv/scripting/sync/ScriptingRunLock.kt @@ -2,6 +2,7 @@ package com.unciv.scripting.sync import com.unciv.scripting.ScriptingBackend import com.unciv.scripting.ScriptingState +import com.unciv.scripting.utils.ScriptingDebugParameters import java.util.UUID import java.util.concurrent.atomic.AtomicBoolean @@ -11,11 +12,15 @@ import java.util.concurrent.atomic.AtomicBoolean // Lock to prevent multiple scripts from trying to run at the same time. object ScriptingRunLock { - private val isRunning = + + private val isRunning = // Why is this separate from ScriptingRunThreader? Well, for one thing, ScriptingRunThreader doesn't let return values from scripts be used AtomicBoolean(false) + var runningName: String? = null private set + private var runningKey: String? = null // Unique key set each run to make sure + // @param name An informative string to identify this acquisition and subsequent activities. // @throws IllegalStateException if lock is already in use. // @return A randomly generated string to pass to the release function. @@ -23,6 +28,9 @@ object ScriptingRunLock { // Different *threads* that try to run scripts concurrently should already queue up nicely without this due to the use of the Synchronized annotation on ScriptingState.exec(), I think. // But because of the risk of recursive script runs (which will deadlock if trying to acquire a lock that must be released by the same thread, and which break the already-in-use IPC loop for a backend if allowed to continue without a lock), the behaviour here if or when that fails is to throw an exception. if (!isRunning.compareAndSet(false, true)) throw IllegalStateException("Cannot acquire ${this::class.simpleName} for $name because it is already in use by $runningName.") // Prooobably don't translate? + if (ScriptingDebugParameters.printLockAcquisition) { + println("${this::class.simpleName} acquired by $name.") + } val key = UUID.randomUUID().toString() runningKey = key runningName = name @@ -34,6 +42,9 @@ object ScriptingRunLock { @Synchronized fun release(releaseKey: String) { if (releaseKey != runningKey) throw IllegalArgumentException("Invalid key given to release ${this::class.simpleName}.") if (isRunning.get()) { + if (ScriptingDebugParameters.printLockAcquisition) { + println("${this::class.simpleName} released by $runningName.") + } runningName = null runningKey = null isRunning.set(false) diff --git a/core/src/com/unciv/scripting/sync/ScriptingRunThreader.kt b/core/src/com/unciv/scripting/sync/ScriptingRunThreader.kt index 46d6895da8538..fb46fc678e9c7 100644 --- a/core/src/com/unciv/scripting/sync/ScriptingRunThreader.kt +++ b/core/src/com/unciv/scripting/sync/ScriptingRunThreader.kt @@ -1,43 +1,92 @@ package com.unciv.scripting.sync import com.badlogic.gdx.Gdx +import com.unciv.UncivGame +import com.unciv.scripting.utils.ScriptingDebugParameters import java.util.concurrent.ConcurrentLinkedDeque -import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.Semaphore import kotlin.concurrent.thread -// Utility to run multiple actions related to scripting in separate threads, sequentially. +// Utility to run multiple actions related to scripting in separate threads, but still sequentially. object ScriptingRunThreader { - private val isDoingRuns = AtomicBoolean(false) - @Suppress("NewApi") // FIXME: Bump API level up. + private val runLock = Semaphore(1, true) + //// Should only ever be called from main thread actually, so fairness shouldn't matter. Nevermind. // Switched from AtomicBoolean to ReentrantLock, and now going to switch to a semaphore of some kind. private val runQueue = ConcurrentLinkedDeque<() -> Unit>() @Synchronized fun queueRun(toRun: () -> Unit) { + if (ScriptingDebugParameters.printThreadingStatus) { + println("Add $toRun to script thread queue.") + } runQueue.add(toRun) } @Synchronized fun queueRuns(runs: Iterable<() -> Unit>) { - for (run in runs) runQueue.add(run) + for (run in runs) queueRun(run) } @Synchronized fun queueRuns(runs: Sequence<() -> Unit>) { - for (run in runs) runQueue.add(run) + for (run in runs) queueRun(run) } - @Synchronized private fun doNextRunRecursive() { + private fun doNextRunRecursive() { val run: (() -> Unit)? = runQueue.poll() if (run != null) { + if (ScriptingDebugParameters.printThreadingStatus) { + println("Continuing to consume script thread queue.") + } thread { try { run() } finally { - Gdx.app.postRunnable { doNextRunRecursive() } + Gdx.app.postRunnable(::doNextRunRecursive) } } } else { - isDoingRuns.set(false) + if (ScriptingDebugParameters.printThreadingStatus) { + println("Finished consuming script thread queue.") + } + runLock.release() } } @Synchronized fun doRuns() { - if (!isDoingRuns.compareAndSet(false, true)) { - throw IllegalStateException("${this::class.simpleName} is already consuming its queued runs!") + runLock.acquire() + if (Thread.currentThread() == UncivGame.MainThread) { + println("WARNING: Calling ${this::class.simpleName}.doRuns() from the main thread. I think using a short-lived worker thread for this may be more durable in terms of avoiding thread-locking bugs.") + } + if (ScriptingDebugParameters.printThreadingStatus) { + println("Starting to consume script thread queue.") } doNextRunRecursive() } } +// When run the given function toRun, using the given concurrentRunner to run it, and block until it's done. + +// @throws IllegalStateException If already on the main thread. +fun blockingConcurrentRun(concurrentRunner: (Runnable) -> Unit, toRun: () -> Unit) { + if (concurrentRunner == Gdx.app::postRunnable + && Thread.currentThread() == UncivGame.MainThread) { + throw IllegalStateException("Tried to use lockingConcurrentRun to target the main thread with Gdx.app.postRunnable, while already on the main thread.") + } + val lock = Semaphore(1) + lock.acquire() + concurrentRunner { + toRun() + lock.release() + } + lock.acquire() +} + + +// Callable that takes a function toRun, a concurrentRunner that will execute it outside of the current thread, and, when called, returns the result of its execution in the foreign thread. + +// @property concurrentRunner First-order function that runs its argument concurrently (but with access to the same memory). +// @property toRun Function with a return result. +// @returns Result of the function toRun after it's run concurrently by the concurrentRunner. +class BlockingConcurrentRunReturn(val concurrentRunner: (Runnable) -> Unit, val toRun: () -> R) { + var result: R? = null + fun invoke(): R { + blockingConcurrentRun(concurrentRunner) { + result = toRun() + } + return result as R + } +} + + diff --git a/core/src/com/unciv/scripting/utils/ScriptingDebugParameters.kt b/core/src/com/unciv/scripting/utils/ScriptingDebugParameters.kt index 0967a6f7cd619..25514d69aa93a 100644 --- a/core/src/com/unciv/scripting/utils/ScriptingDebugParameters.kt +++ b/core/src/com/unciv/scripting/utils/ScriptingDebugParameters.kt @@ -13,6 +13,10 @@ object ScriptingDebugParameters { var printReflectiveDeprecationWarnings = false // TODO // Whether to print out when creating and deleting temporary var printEnvironmentFolderCreation = false + // Whether to print out when the lock for stopping simultaneous script run attempts is acquired and released. + var printLockAcquisition = false + // Whether to print out when the queue for running mod-controlled scripts in sequence is expanded or consumed. + var printThreadingStatus =false // TODO: Add to gameIsNotRunWithDebugModes unit tests. } diff --git a/core/src/com/unciv/scripting/utils/ScriptingErrorHandling.kt b/core/src/com/unciv/scripting/utils/ScriptingErrorHandling.kt index b29177836fba5..c418b653866b9 100644 --- a/core/src/com/unciv/scripting/utils/ScriptingErrorHandling.kt +++ b/core/src/com/unciv/scripting/utils/ScriptingErrorHandling.kt @@ -1,20 +1,38 @@ package com.unciv.scripting.utils +import com.badlogic.gdx.Gdx +import com.badlogic.gdx.scenes.scene2d.ui.Label +import com.badlogic.gdx.scenes.scene2d.ui.Table +import com.badlogic.gdx.utils.Align import com.unciv.UncivGame import com.unciv.scripting.sync.ScriptingRunLock -import com.unciv.ui.utils.BaseScreen -import com.unciv.ui.utils.Popup -import com.unciv.ui.utils.stringifyException +import com.unciv.ui.utils.* +import com.unciv.ui.utils.AutoScrollPane as ScrollPane object ScriptingErrorHandling { fun notifyPlayerScriptFailure(text: String, asName: String? = null, toConsole: Boolean = true) { - val popup = Popup(UncivGame.Current.screen as BaseScreen) - val msg = "{An error has occurred with the mod/script} ${asName ?: ScriptingRunLock.runningName}:\n\n${text.prependIndent("\t")}\n\n{See system terminal output for details.}\n{Consider disabling mods if this keeps happening.}\n" // TODO: Translation. - popup.addGoodSizedLabel(msg).row() - popup.addOKButton{} - popup.open(true) - if (toConsole) - printConsolePlayerScriptFailure(text, asName) + Gdx.app.postRunnable { // This can potentially be run for scripts in worker threads, so in that case needs to go to the main thread to have OpenGL context and not crash. + val popup = Popup(UncivGame.Current.screen as BaseScreen) + val widthTarget = popup.screen.stage.width / 2 + val msg1 = "{An error has occurred with the mod/script} \"${asName ?: ScriptingRunLock.runningName}\".\n\n{See system terminal output for details.}\n{Consider disabling mods if this keeps happening.}" // TODO: Translation. + popup.add(msg1.toLabel().apply + { + setAlignment(Align.center) + wrap = true + } ).width(widthTarget).row() + val contentTable = Table() + val msg2 = "\n\n${text.prependIndent("\t")}" + contentTable.add(Label(msg2, BaseScreen.skin).apply { + setFontSize(15) + wrap = true + }).width(widthTarget).row() + val scrollPane = ScrollPane(contentTable) + popup.add(scrollPane).row() + popup.addOKButton{} + popup.open(true) + if (toConsole) + printConsolePlayerScriptFailure(text, asName) + } } fun notifyPlayerScriptFailure(exception: Throwable, asName: String? = null) { notifyPlayerScriptFailure(exception.toString(), asName, false) diff --git a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt index 8776e89b7b455..ea54afd943e2a 100644 --- a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt @@ -1,14 +1,16 @@ package com.unciv.ui.consolescreen +import com.badlogic.gdx.Gdx import com.badlogic.gdx.Input import com.badlogic.gdx.graphics.Color -import com.badlogic.gdx.scenes.scene2d.ui.Image +import com.badlogic.gdx.scenes.scene2d.Event +import com.badlogic.gdx.scenes.scene2d.InputListener import com.badlogic.gdx.scenes.scene2d.ui.Label import com.badlogic.gdx.scenes.scene2d.ui.SplitPane import com.badlogic.gdx.scenes.scene2d.ui.Table -import com.badlogic.gdx.scenes.scene2d.ui.TextButton import com.badlogic.gdx.scenes.scene2d.ui.TextField // Ooh. TextArea could be nice. (Probably overkill and horrible if done messily, though.) import com.unciv.Constants +import com.unciv.models.modscripting.* import com.unciv.scripting.ScriptingBackendType import com.unciv.scripting.ScriptingState import com.unciv.scripting.utils.ScriptingErrorHandling @@ -22,27 +24,28 @@ import kotlin.math.min class ConsoleScreen(var closeAction: () -> Unit): BaseScreen() { - private val layoutTable: Table = Table() + private val layoutTable = Table() - private val topBar: Table = Table() - private var backendsScroll: ScrollPane - private val backendsAdders: Table = Table() - private val closeButton: TextButton = Constants.close.toTextButton() + private val topBar = Table() + private val backendsScroll: ScrollPane + private val backendsAdders = Table() + private val closeButton = Constants.close.toTextButton() - private var middleSplit: SplitPane - private var printScroll: ScrollPane - private val printHistory: Table = Table() - private val runningContainer: Table = Table() - private val runningList: Table = Table() + private val middleSplit: SplitPane + private val printScroll: ScrollPane + private val printHistory = Table() + private val runningScroll: ScrollPane + private val runningContainer = Table() + private val runningList = Table() // TODO: Scroll. - private val inputBar: Table = Table() - private val inputField: TextField = TextField("", skin) + private val inputBar = Table() + private val inputField = TextField("", skin) - private val inputControls: Table = Table() - private val tabButton: TextButton = "TAB".toTextButton() // TODO: Translation. - private val upButton: Image = ImageGetter.getImage("OtherIcons/Up") - private val downButton: Image = ImageGetter.getImage("OtherIcons/Down") - private val runButton: TextButton = "ENTER".toTextButton() // TODO: Translation. + private val inputControls = Table() + private val tabButton = "TAB".toTextButton() // TODO: Translation. + private val upButton = ImageGetter.getImage("OtherIcons/Up") + private val downButton = ImageGetter.getImage("OtherIcons/Down") + private val runButton = "ENTER".toTextButton() // TODO: Translation. private val layoutUpdators = ArrayList<() -> Unit>() private var isOpen = false @@ -59,6 +62,8 @@ class ConsoleScreen(var closeAction: () -> Unit): BaseScreen() { inputField.setCursorPosition(max(0, min(inputText.length, value))) } + private var lastSeenScriptingBackendCount = 0 + init { backendsAdders.add("Launch new backend:".toLabel()).padRight(30f).padLeft(20f) // TODO: Translation. @@ -66,7 +71,7 @@ class ConsoleScreen(var closeAction: () -> Unit): BaseScreen() { var backendadder = backendtype.metadata.displayName.toTextButton() // Hm. Should this be translated/translatable? I suppose it already is translatable in OptionsPopup too. And in the running list— So basically everywhere it's shown. backendadder.onClick { val spawned = ScriptingState.spawnBackend(backendtype) - echo(spawned.motd) +// echo(spawned.motd) val startup = game.settings.scriptingConsoleStartups[backendtype.metadata.displayName]!! if (startup.isNotBlank()) { ScriptingState.switchToBackend(spawned.backend) @@ -88,10 +93,11 @@ class ConsoleScreen(var closeAction: () -> Unit): BaseScreen() { printHistory.bottom() printScroll = ScrollPane(printHistory) - runningContainer.add("Active Backends:".toLabel()).row() // TODO: Translation. + runningContainer.add("Active Backends:".toLabel()).padBottom(5f).row() // TODO: Translation. runningContainer.add(runningList) + runningScroll = ScrollPane(runningContainer) - middleSplit = SplitPane(printScroll, runningContainer, false, skin) + middleSplit = SplitPane(printScroll, runningScroll, false, skin) middleSplit.setSplitAmount(0.8f) inputControls.add(tabButton) @@ -137,6 +143,19 @@ class ConsoleScreen(var closeAction: () -> Unit): BaseScreen() { echoHistory() updateRunning() + + layoutTable.addListener( // Check whenever receiving input event if there's any super obvious need to update the running backends list. Really I don't see the list changing on its own except in the mod manager screen, so this shouldn't ever be used in that case, but as I'm developing the mod script loader I'm noticing some missed backends, and a single equality check every couple hundred seconds on average doesn't feel expensive to make sure it's always up to date. + object: InputListener() { + override fun handle(e: Event): Boolean { + val size = ScriptingState.scriptingBackends.size + if (lastSeenScriptingBackendCount != size) { + lastSeenScriptingBackendCount = size + updateRunning() + } + return false + } + } + ) } private fun updateLayout() { @@ -163,15 +182,22 @@ class ConsoleScreen(var closeAction: () -> Unit): BaseScreen() { fun openConsole() { game.setScreen(this) keyPressDispatcher.install(stage) //TODO: Can this be moved to UncivGame.setScreen? + ScriptingState.consoleScreenListener = { + Gdx.app.postRunnable { echo(it) } // Calling ::echo directly triggers OpenGL stuff, which means crashes when ScriptingState isn't running in the main thread. + } stage.setKeyboardFocus(inputField) inputField.getOnscreenKeyboard().show(true) + updateRunning() this.isOpen = true if (game.settings.showScriptingConsoleWarning && !warningAccepted) { showWarningPopup() } + ModScriptingRegistrationManager.activeMods + ModScriptingRunManager.runHandler(HANDLER_DEFINITIONS[ContextId.ConsoleScreen, HandlerId.after_open], this) } fun closeConsole() { + ModScriptingRunManager.runHandler(HANDLER_DEFINITIONS[ContextId.ConsoleScreen, HandlerId.before_close], this) closeAction() keyPressDispatcher.uninstall() this.isOpen = false @@ -179,25 +205,35 @@ class ConsoleScreen(var closeAction: () -> Unit): BaseScreen() { private fun updateRunning() { runningList.clearChildren() - for ((i, backend) in ScriptingState.scriptingBackends.withIndex()) { + for (backend in ScriptingState.scriptingBackends) { var button = backend.metadata.displayName.toTextButton() + if (backend.displayNote != null) { + button.cells.last().row() + button.add(backend.displayNote.toString().toLabel(fontColor = Color.LIGHT_GRAY, fontSize = 16)) + } runningList.add(button) - if (i == ScriptingState.activeBackend) { + if (backend === ScriptingState.activeBackend) { button.color = Color.GREEN } button.onClick { - ScriptingState.switchToBackend(i) + ScriptingState.switchToBackend(backend) updateRunning() } var termbutton = ImageGetter.getImage("OtherIcons/Stop") + val terminable = backend.userTerminable + if (!terminable) { + termbutton.setColor(0.7f, 0.7f, 0.7f, 0.3f) + } termbutton.onClick { - val exc: Exception? = ScriptingState.termBackend(i) - updateRunning() - if (exc != null) { - echo("Failed to stop ${backend.metadata.displayName} backend: ${exc.toString()}") // TODO: Translation? I mean, probably not, really. + if (backend.userTerminable) { + val exc: Exception? = ScriptingState.termBackend(backend) + if (exc != null) { + echo("Failed to stop ${backend.metadata.displayName} backend: ${exc.toString()}") // TODO: Translation? I mean, probably not, really. + } } + updateRunning() } - runningList.add(termbutton.surroundWithCircle(40f)).row() + runningList.add(termbutton.surroundWithCircle(40f, color = if (terminable) Color.WHITE else Color(1f, 1f, 1f, 0.5f))).row() } } @@ -287,9 +323,9 @@ class ConsoleScreen(var closeAction: () -> Unit): BaseScreen() { } private fun exec(command: String) { - val name = makeScriptingRunName(this::class.simpleName, ScriptingState.getActiveBackend()) + val name = makeScriptingRunName(this::class.simpleName, ScriptingState.activeBackend) val execResult = ScriptingState.exec(command, asName = name) - echo(execResult.resultPrint) +// echo(execResult.resultPrint) setText("") if (execResult.isException) { ScriptingErrorHandling.printConsolePlayerScriptFailure(execResult.resultPrint, asName = name) From 627beb39dddd102bc12adfa915608d01b61d9fea Mon Sep 17 00:00:00 2001 From: will-ca Date: Mon, 20 Dec 2021 20:00:48 +0000 Subject: [PATCH 91/93] Comments/notes, examples. Bash-autogeneration handler definition boilerplates. --- .../enginefiles/python/unciv_lib/wrapping.py | 2 +- .../MapEditingMacros.py | 11 +- .../ProceduralTechtree.py | 45 +- .../python/unciv_scripting_examples/Tests.py | 34 + .../UniqueInjection.py | 1 + .../python/unciv_scripting_examples/Utils.py | 7 +- core/src/com/unciv/UncivGameParameters.kt | 2 +- .../ModScriptingDebugParameters.kt | 6 +- .../modscripting/ModScriptingHandlerTypes.kt | 1028 +++++++++++++++-- .../ModScriptingRegistrationManager.kt | 52 +- .../modscripting/ModScriptingRunManager.kt | 6 +- .../HandlerDefinitionsBuilders.kt | 2 + .../handlertypes/HandlerIdentifiers.kt | 2 + .../modscripting/handlertypes/HandlerType.kt | 2 + .../com/unciv/scripting/ScriptingBackend.kt | 3 +- .../com/unciv/scripting/ScriptingConstants.kt | 2 +- .../src/com/unciv/scripting/ScriptingState.kt | 53 +- .../scripting/api/ScriptingApiAppHelpers.kt | 10 + .../api/ScriptingApiExecutionContext.kt | 9 +- .../scripting/api/ScriptingApiGlHelpers.kt | 6 + .../scripting/sync/ScriptingRunThreader.kt | 5 +- .../src/com/unciv/scripting/utils/FakeMaps.kt | 24 +- .../utils/ScriptingDebugParameters.kt | 6 +- .../scripting/utils/ScriptingErrorHandling.kt | 4 +- .../unciv/scripting/utils/SourceManager.kt | 10 +- .../unciv/ui/consolescreen/ConsoleScreen.kt | 7 +- 26 files changed, 1174 insertions(+), 165 deletions(-) create mode 100644 core/src/com/unciv/models/modscripting/handlertypes/HandlerDefinitionsBuilders.kt create mode 100644 core/src/com/unciv/models/modscripting/handlertypes/HandlerIdentifiers.kt create mode 100644 core/src/com/unciv/models/modscripting/handlertypes/HandlerType.kt create mode 100644 core/src/com/unciv/scripting/api/ScriptingApiGlHelpers.kt diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py index 3a96d04195a72..1cf6bd9de9bfc 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py @@ -126,7 +126,7 @@ def stringPathList(pathlist): # @indexOf # Not actually totally sure what this is. I thought it was implemented in lists and tuples as `.index()`? # '__setitem__', # Implemented through foreign request. ('__hash__', 'hash') # Monkey-patched into operator module above. -) +) # TODO: __int__, __float__, and other stuff missing from operator. https://docs.python.org/3/reference/datamodel.html _rmagicmeths = ( '__radd__', diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py index 3260112f72404..ef9894f39143f 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py @@ -19,11 +19,12 @@ # If you modify this file, please add any new functions to Tests.py. +def stripJsonComments(text): + re.sub("//.*", "", re.sub('/\*.*?\*/', "", text, flags=re.DOTALL)) + try: - t = re.sub("//.*", "", re.sub('/\*.*\*/', "", real(unciv.apiHelpers.App.assetFileString("jsons/Civ V - Gods & Kings/Terrains.json")), flags=re.DOTALL)) - terrainsjson = json.loads(t) + terrainsjson = json.loads(stripJsonComments(real(unciv.apiHelpers.App.assetFileString("jsons/Civ V - Gods & Kings/Terrains.json")))) # In an actual implementation, you would want to read from the ruleset instead of the JSON. But this is easier for me. - del t except Exception as e: print("Couldn't load terrains Terrains.json") else: @@ -31,6 +32,10 @@ terrainfeatures = {t['name']: t for t in terrainsjson if t['type'] == 'TerrainFeature'} +def showProgress(): + return progressupdater, finisher + + def genValidTerrains(*, forbid=('Fallout',)): #Searches only two layers deep. I.E. Only combinations with at most two TerrainFeatures will be found. terrains = set() diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/ProceduralTechtree.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/ProceduralTechtree.py index aaddd1ca8bb14..9a819c0842820 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/ProceduralTechtree.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/ProceduralTechtree.py @@ -9,17 +9,20 @@ Call clearTechTree() and then extendTechTree(iterations=20) at any time to replace all undiscovered parts of the base tech tree with an entire new randomly generated one. Call scrambleTechTree() to keep all current technologies but randomize the order in which they are unlocked. + +Call reorgTechTree(webifyTechTree()) to turn the current tech tree into a tech web. """ # from unciv_scripting_examples.ProceduralTechtree import * # from unciv_scripting_examples.ProceduralTechtree import *; scrambleTechTree() +# from unciv_scripting_examples.ProceduralTechtree import *; reorgTechTree(webifyTechTree()) # tuple(real(t) for t in apiHelpers.instancesAsInstances[gameInfo.ruleSet.technologies['Civil Service'].prerequisites.toArray()]) # This means that some kind of handler for the modding API, once it's implemented, would have to be called before the JVM has a chance to crash, so the script can read its own serialized data out of GameInfo and inject items into the ruleset… You can already provably have a GameInfo with invalid values that doesn't crash until WorldScreen tries to render it, so running onGameLoad immediately after deserializing the save might be good enough. # Or I guess there could be a Kotlin-side mechanism for serializing injected rules. But on the Kotlin side, I think it would be cleaner to just let the script handle everything. Such a mechanism still may not cover the entire range of wild behaviours that can be done by scripts, and the entire point of having a dynamic scripting API is to avoid having to statically hard-code niche or esoteric uses. -import random +import random, math from unciv import * from unciv_pyhelpers import * @@ -135,3 +138,43 @@ def scrambleTechTree(): except: print(tname, ot) tech.prerequisites.addAll([techreplacements[ot] for ot in originalpreqs[techpositions[tname]]]) # toprereqs, oprereqs = (real(t.prerequisites) for t in (tech, other)) + +def reorgTechTree(techmap): + Constructors = apiHelpers.Jvm.constructorByQualname + techs = techtree() + columns = {x: Constructors['com.unciv.models.ruleset.tech.TechColumn']() for x in set(x for (x, y, era) in techmap.values())} + columneras = {x: era for (x, y, era) in techmap.values()} + for x, column in columns.items(): + column.era = columneras[x] + column.columnNumber = x + for name, (x, y, era) in techmap.items(): + techs[name].row = y + techs[name].column = columns[x] + +def webifyTechTree(*, coordscale=(0.5, 2.0), fuzz=1.0): + techlocs = {} + for name, tech in techtree().items(): + techlocs[name] = (tech.column.columnNumber, tech.row) + if tech.hasUnique("Starting tech", None): + era = tech.column.era + startingpos = techlocs[name] + oldmaxrow = max(y for (x, y) in techlocs.values()) + techlocs = {name: tuple(round(trig((y-startingpos[1]+fuz)/oldmaxrow*math.pi*2)*(x-startingpos[0])*cscale) for trig, cscale in zip((math.sin, math.cos), coordscale)) for name, (x, y) in techlocs.items() for fuz in (fuzz and random.random()*fuzz,)} + def inc(n, chance=1.0): + return (round(n-1) if n < 0 else round(n+1) if n > 0 else round(n+random.getrandbits(1)*2-1)) if random.random() < chance else n + mutatechances = max(coordscale) + mutatechances = tuple(c/mutatechances for c in coordscale) + for i in range(100): + if len(set(techlocs.values())) == len(techlocs): + break + for name, pos in techlocs.items(): + if tuple(techlocs.values()).count(pos) > 1: + techlocs[name] = tuple(inc(c, cscale) for c, cscale in zip(pos, mutatechances)) + else: + raise RuntimeError(f"Couldn't generate unique tech positions:\n{locals()}") + mincolumn = min(x for (x, y) in techlocs.values()) + minrow = min(y for (x, y) in techlocs.values()) + techmap = {name: (x-mincolumn, y-minrow+1, era) for name, (x, y) in techlocs.items()} + return techmap + + diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py index 890d723ded239..a3f0546db0356 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Tests.py @@ -16,10 +16,44 @@ # from unciv_scripting_examples.Tests import *; TestRunner.run_tests() +# import cProfile; cProfile.run("from unciv_scripting_examples.Tests import *; TestRunner.run_tests()", sort='tottime') # from unciv_scripting_examples.Tests import *; InMapEditor.__enter__() # from unciv_scripting_examples.Tests import *; import time; Start=time.time(); tryRun(TestRunner.run_tests); print(time.time()-Start) # from unciv_scripting_examples.Tests import *; import timeit; x=timeit.repeat(stmt='tryRun(TestRunner.run_tests)', setup='tryRun(TestRunner.run_tests)', repeat=3, number=3, globals=globals()); print(x); unciv.apiHelpers.Sys.copyToClipboard(repr(x)); unciv.apiHelpers.Sys.printLine(repr(x)) +# TODO: Protocol desync: +# import cProfile; cProfile.run("from unciv_scripting_examples.Tests import *; TestRunner.run_tests()", sort='cumtime') +# Ran first without sort, then with. +# Or just run twice. +# Wait, there's erroring due to unrelated handlers too… + + +# I have no idea how it managed to print out to the system terminal. + # 4932599 function calls (4929366 primitive calls) in 23.806 seconds + + # Ordered by: internal time + + # ncalls tottime percall cumtime percall filename:lineno(function) + # 40610 17.682 0.000 17.822 0.000 {method 'readline' of '_io.TextIOWrapper' objects} + # 1624812/1623564 0.764 0.000 0.869 0.000 wrapping.py:267(__getattribute__) + # 40610/40604 0.534 0.000 0.540 0.000 encoder.py:204(iterencode) + # 40657 0.440 0.000 0.440 0.000 {built-in method builtins.print} + # 75512 0.381 0.000 0.865 0.000 wrapping.py:231(__init__) + # 631561 0.358 0.000 0.358 0.000 wrapping.py:207(__setattr__) + # 75512 0.244 0.000 1.263 0.000 wrapping.py:244(_clone_) + # 35849 0.206 0.000 21.154 0.001 wrapping.py:250(_bakereal_) + # 40610/40604 0.183 0.000 20.482 0.001 ipc.py:70(GetForeignActionResponse) + # 40611 0.178 0.000 0.178 0.000 decoder.py:343(raw_decode) + # 40610 0.156 0.000 0.695 0.000 ipc.py:30(deserialized) + # 40610/40604 0.155 0.000 20.890 0.001 wrapping.py:17(meth) + # 40610/40604 0.135 0.000 0.859 0.000 __init__.py:183(dumps) + # 40611 0.116 0.000 0.392 0.000 decoder.py:332(decode) + # 40610/40604 0.114 0.000 0.682 0.000 encoder.py:182(encode) + # 27010 0.100 0.000 7.731 0.000 wrapping.py:257(__getattr__) + # 40610 0.099 0.000 0.134 0.000 ipc.py:13(makeUniqueId) + +# Is it going to turn out that Kotlin, not Python, is the bottleneck? I suppose that wouldn't be wholly a bad thing; It also means that the API design is sanely keeping the heavier lifting in the compiled part. + def tryRun(func): # For debug and manual runs. Not used otherwise. diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/UniqueInjection.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/UniqueInjection.py index 2b7f49b30cf75..0a99197d73a4c 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/UniqueInjection.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/UniqueInjection.py @@ -4,3 +4,4 @@ # Precedent for injected unique: "Requires a [] in all cities". # Since uniques are transient and such, probably inject into promotions. (Except that checks against the ruleset.) +# Inject into temporaryUniques. diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Utils.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Utils.py index ad2406a8f4799..e929c676af7ae 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Utils.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Utils.py @@ -5,9 +5,12 @@ RegistryKey = "python-package:Unciv/unciv_scripting_examples" -unciv.apiHelpers.registeredInstances[RegistryKey] = {} +registeredInstances = unciv.apiHelpers.registeredInstances -memalloc = unciv.apiHelpers.registeredInstances[RegistryKey] +if RegistryKey not in registeredInstances: + registeredInstances[RegistryKey] = {} + +memalloc = registeredInstances[RegistryKey] def singleton(*args, **kwargs): diff --git a/core/src/com/unciv/UncivGameParameters.kt b/core/src/com/unciv/UncivGameParameters.kt index a38acb8c7be8f..3ed44c518e13e 100644 --- a/core/src/com/unciv/UncivGameParameters.kt +++ b/core/src/com/unciv/UncivGameParameters.kt @@ -14,5 +14,5 @@ class UncivGameParameters(val version: String, val consoleMode: Boolean = false, val customSaveLocationHelper: CustomSaveLocationHelper? = null, val limitOrientationsHelper: LimitOrientationsHelper? = null, - val runScriptAndExit: Triple Unit)?>? = null + val runScriptAndExit: Triple Unit)?>? = null // TODO: Probably make exit optional? Or just make the scripts do it themselves. ) { } diff --git a/core/src/com/unciv/models/modscripting/ModScriptingDebugParameters.kt b/core/src/com/unciv/models/modscripting/ModScriptingDebugParameters.kt index 7cd1d4773e3ff..53eb4fad5beeb 100644 --- a/core/src/com/unciv/models/modscripting/ModScriptingDebugParameters.kt +++ b/core/src/com/unciv/models/modscripting/ModScriptingDebugParameters.kt @@ -1,11 +1,11 @@ package com.unciv.models.modscripting -object ModScriptingDebugParameters { +object ModScriptingDebugParameters { // Enum-ifying this could let tests iterate through them exhaustively. // Whether to print when script handlers are triggered. - var printHandlerRun = false // Const val? Faster. But I like the idea of the scripting API itself being able to change these. // Bah. Premature optimization. There are far slower places to worry about speed. + var printHandlerRun = true // Const val? Faster. But I like the idea of the scripting API itself being able to change these. // Bah. Premature optimization. There are far slower places to worry about speed. // var printHandlerRegister = false var printModRead = false var printModRegister = false - + var typeCheckHandlerParamsAtRuntime = true } diff --git a/core/src/com/unciv/models/modscripting/ModScriptingHandlerTypes.kt b/core/src/com/unciv/models/modscripting/ModScriptingHandlerTypes.kt index 919a014c1e8e3..bf70bfc26e026 100644 --- a/core/src/com/unciv/models/modscripting/ModScriptingHandlerTypes.kt +++ b/core/src/com/unciv/models/modscripting/ModScriptingHandlerTypes.kt @@ -2,11 +2,40 @@ package com.unciv.models.modscripting +import com.unciv.MainMenuScreen import com.unciv.UncivGame import com.unciv.logic.GameInfo import com.unciv.logic.civilization.CivilizationInfo +import com.unciv.scripting.utils.StatelessMap +import com.unciv.ui.AddMultiplayerGameScreen +import com.unciv.ui.EditMultiplayerGameInfoScreen +import com.unciv.ui.LanguagePickerScreen +import com.unciv.ui.MultiplayerScreen +import com.unciv.ui.cityscreen.* +import com.unciv.ui.civilopedia.CivilopediaScreen import com.unciv.ui.consolescreen.ConsoleScreen -import com.unciv.ui.utils.stringifyException +import com.unciv.ui.mapeditor.* +import com.unciv.ui.newgamescreen.* +import com.unciv.ui.overviewscreen.* +import com.unciv.ui.pickerscreens.* +import com.unciv.ui.saves.LoadGameScreen +import com.unciv.ui.saves.SaveGameScreen +import com.unciv.ui.tilegroups.CityButton +import com.unciv.ui.trade.DiplomacyScreen +import com.unciv.ui.trade.LeaderIntroTable +import com.unciv.ui.trade.OfferColumnsTable +import com.unciv.ui.trade.TradeTable +import com.unciv.ui.utils.* +import com.unciv.ui.victoryscreen.VictoryScreen +import com.unciv.ui.worldscreen.* +import com.unciv.ui.worldscreen.bottombar.BattleTable +import com.unciv.ui.worldscreen.bottombar.TileInfoTable +import com.unciv.ui.worldscreen.mainmenu.OptionsPopup +import com.unciv.ui.worldscreen.mainmenu.WorldScreenCommunityPopup +import com.unciv.ui.worldscreen.mainmenu.WorldScreenMenuPopup +import com.unciv.ui.worldscreen.unit.IdleUnitButton +import com.unciv.ui.worldscreen.unit.UnitActionsTable +import com.unciv.ui.worldscreen.unit.UnitTable import kotlin.reflect.KType //import kotlin.reflect.full.isSubtypeOf //import kotlin.reflect.full.starProjectedType @@ -19,7 +48,11 @@ import kotlin.reflect.typeOf // 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 handlerTypes, ModScriptingRunManager is only for using that during gameplay, and anything to do with parsing mod structures should go into the level of ModScriptingRegistrationHandler. -val HANDLER_DEFINITIONS = handlerDefinitions { +// Code generators setup: $ function GetScreens() ( shopt -s globstar && (grep -ohP '(?(\n$(for default in ${@:2}; do echo "$tab//$tab HandlerId.$default,"; done)\n$tab// )"; ); function HandlerBoilerplates() (export tab=" "; export defaults=(after_open before_close after_rebuild); while read name; do echo -e "handlerContext(ContextId.$name) {\n${tab}addInstantiationHandlers<$name>()\n$(MakeSingles $name ${defaults[@]})\n}"; done) + +// TODO: Autogenerate handler type documentation from this, similarly to UniqueType documentation. + +val HANDLER_DEFINITIONS = handlerDefinitions { // TODO: Definitely put this in its own package to preserve the namespace. handlerContext(ContextId.UncivGame) { addInstantiationHandlers() } @@ -37,16 +70,720 @@ val HANDLER_DEFINITIONS = handlerDefinitions { HandlerId.before_close ) } +// handlerContext(ContextId.ConsoleScreen) { +// addInstantiationHandlers() +// addSingleParamHandlers( +// HandlerId.after_open, +// HandlerId.before_close +// ) +// } + + + // Boilerplate generator: $ GetScreens | HandlerBoilerplates + handlerContext(ContextId.AddMultiplayerGameScreen) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.BaseScreen) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.CityScreen) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.CivilopediaScreen) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } handlerContext(ContextId.ConsoleScreen) { addInstantiationHandlers() - addSingleParamHandlers( - HandlerId.after_open, - HandlerId.before_close - ) + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.DiplomacyScreen) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.DiplomaticVotePickerScreen) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.DiplomaticVoteResultScreen) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.EditMultiplayerGameInfoScreen) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.EmpireOverviewScreen) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.GameParametersScreen) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.GreatPersonPickerScreen) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.ImprovementPickerScreen) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.LanguagePickerScreen) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.LoadGameScreen) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.MainMenuScreen) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.MapEditorScreen) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.ModManagementScreen) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.MultiplayerScreen) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.NewGameScreen) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.NewMapScreen) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.PantheonPickerScreen) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.PickerScreen) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.PlayerReadyScreen) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.PolicyPickerScreen) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.PromotionPickerScreen) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.ReligiousBeliefsPickerScreen) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.SaveAndLoadMapScreen) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.SaveGameScreen) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.TechPickerScreen) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.VictoryScreen) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.WorldScreen) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + + + // Boilerplate generator: $ GetPopups | HandlerBoilerplates + handlerContext(ContextId.AlertPopup) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.AskNumberPopup) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.AskTextPopup) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.ExitGamePopup) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.MapEditorMainScreenPopup) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.MapEditorMenuPopup) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.MapEditorRulesetPopup) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.OptionsPopup) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.Popup) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.ToastPopup) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.TradePopup) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.TradeThanksPopup) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) } + handlerContext(ContextId.WorldScreenCommunityPopup) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.WorldScreenMenuPopup) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.YesNoPopup) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + + + + // Boilerplate generator: $ (export names=(); (for name in ${names[@]}; do echo "$name"; done) | HandlerBoilerplates) + // Fill the parentheses inside names=() with a space-delimited list copied from HandlerId to use a Bash array. + + handlerContext(ContextId.BattleTable) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.CityButton) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.CityInfoTable) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.CityOverviewTable) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.CityReligionInfoTable) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.CityScreenCityPickerTable) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.CityScreenTileTable) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.CityStatsTable) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.ConstructionInfoTable) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.DiplomacyOverviewTable) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.ExpanderTab) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.GameOptionsTable) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.IdleUnitButton) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.LanguageTable) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.LeaderIntroTable) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.MapEditorOptionsTable) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.MapOptionsTable) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.MapParametersTable) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.Minimap) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.MinimapHolder) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.ModCheckboxTable) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.NationTable) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.OfferColumnsTable) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.PlayerPickerTable) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.ReligionOverviewTable) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.ResourcesOverviewTable) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.SpecialistAllocationTable) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.StatsOverviewTable) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.TabbedPager) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.TechButton) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.TileInfoTable) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.TradesOverviewTable) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.TradeTable) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.UncivSlider) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.UnitActionsTable) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.UnitOverviewTable) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.UnitTable) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.WonderOverviewTable) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + handlerContext(ContextId.WorldScreenTopBar) { + addInstantiationHandlers() + // addSingleParamHandlers( + // HandlerId.after_open, + // HandlerId.before_close, + // HandlerId.after_rebuild, + // ) + } + } -private inline fun HandlerDefinitionsBuilderScope.HandlerContextBuilderScope.addSingleParamHandlers(vararg handlerTypes: HandlerId, paramName: String? = null) { + +private inline fun HandlerDefinitionsBuilderScope.HandlerContextBuilderScope + .addSingleParamHandlers(vararg handlerTypes: HandlerId, paramName: String? = null) +{ for (handlerType in handlerTypes) { handler(handlerType) { param(paramName ?: V::class.simpleName!!.replaceFirstChar { it.lowercase()[0] }) @@ -54,7 +791,9 @@ private inline fun HandlerDefinitionsBuilderScope.HandlerContextBuil } } -private inline fun HandlerDefinitionsBuilderScope.HandlerContextBuilderScope.addInstantiationHandlers(paramName: String? = null) { +private inline fun HandlerDefinitionsBuilderScope.HandlerContextBuilderScope + .addInstantiationHandlers(paramName: String? = null) +{ addSingleParamHandlers(HandlerId.after_instantiate, HandlerId.before_discard, paramName = paramName) } @@ -63,42 +802,143 @@ val ALL_HANDLER_TYPES = HANDLER_DEFINITIONS.handlerContexts.values.asSequence() .flatten().toSet() +// TODO: Unit test to make sure all contexts IDs are used, and have at least one handler type. + // Enum of identifiers for a scripting handler context namespace. + +// Names map directly to and deserialize directly from JSON keys in mod files. enum class ContextId { // These are mostly so autocompletion, typodetection, usage search, etc will work. UncivGame, - MainMenuScreen, - PausePopup, GameInfo, - WorldScreen, + TileGroup, + + // Code generator: $ GetScreens | while read name; do echo "$name,"; done + AddMultiplayerGameScreen, + BaseScreen, + CityScreen, + CivilopediaScreen, + ConsoleScreen, DiplomacyScreen, + DiplomaticVotePickerScreen, + DiplomaticVoteResultScreen, + EditMultiplayerGameInfoScreen, + EmpireOverviewScreen, + GameParametersScreen, + GreatPersonPickerScreen, + ImprovementPickerScreen, + LanguagePickerScreen, + LoadGameScreen, + MainMenuScreen, + MapEditorScreen, + ModManagementScreen, + MultiplayerScreen, + NewGameScreen, + NewMapScreen, + PantheonPickerScreen, + PickerScreen, + PlayerReadyScreen, PolicyPickerScreen, - TradeScreen, - MapEditor, - ConsoleScreen, + PromotionPickerScreen, + ReligiousBeliefsPickerScreen, + SaveAndLoadMapScreen, + SaveGameScreen, + TechPickerScreen, + VictoryScreen, + WorldScreen, + + + // Code generator: $ GetPopups | while read name; do echo "$name,"; done + AlertPopup, + AskNumberPopup, + AskTextPopup, + ExitGamePopup, + MapEditorMainScreenPopup, + MapEditorMenuPopup, + MapEditorRulesetPopup, + NationPickerPopup, + OptionsPopup, + PassPopup, + Popup, + ToastPopup, + TradePopup, + TradeThanksPopup, + WorldScreenCommunityPopup, + WorldScreenMenuPopup, + YesNoPopup, + + // From "Type Hierarchy" browser for Table in Android Studio: + BattleTable, + CityButton, + CityInfoTable, + CityOverviewTable, + CityReligionInfoTable, + CityScreenCityPickerTable, + CityScreenTileTable, + CityStatsTable, + ConstructionInfoTable, + DiplomacyOverviewTable, + ExpanderTab, + GameOptionsTable, + IconTable, + IdleUnitButton, + InfluenceTable, + LanguageTable, + LeaderIntroTable, + MapEditorOptionsTable, + MapOptionsTable, + MapParametersTable, + Minimap, + MinimapHolder, + ModCheckboxTable, + NationTable, + OfferColumnsTable, + PlayerPickerTable, + ReligionOverviewTable, + ResourcesOverviewTable, + SpecialistAllocationTable, + StatsOverviewTable, + TabbedPager, + TechButton, + TileInfoTable, + TradesOverviewTable, + TradeTable, + UncivSlider, + UnitActionsTable, + UnitOverviewTable, + UnitTable, + WonderOverviewTable, + WorldScreenTopBar, } -// Enum of identifiers for names -@Suppress("EnumEntryName", "SpellCheckingInspection") +// Enum of identifiers for names of script handlers. + +// Each identifier can be used in more than one context namespace, but does not have to be implemented in all contexts, and may not be defined in a single context more than once. + +// Names map directly to and deserialize directly from JSON keys in mod files. +@Suppress("EnumEntryName", "SpellCheckingInspection") // Handler names are not Kotlin code, but more like JSON keys in mod configuration files. enum class HandlerId { - // After creation of a new object with the same name as the context. + // After creation and initialization of a new object with the same name as the context. after_instantiate, - // Before destruction of each object with the same name as the context. Not guaranteed to be run. + // Before destruction of each object with the same name as the context. Not guaranteed to be run, and not guaranteed to be run consistently even if run. before_discard, // After data represented by object is exposed as context to player, including if such happens multiple times. after_open, - // After data represented by object is hidden replaced as context to player, including if such happens multiple times. + // After data represented by object is replaced as working context GUI to player, including if such happens multiple times. before_close, + after_rebuild, before_gamesave, - after_turnautostart, + after_turnstart, after_unitmove, after_cityconstruction, after_cityfound, after_techfinished, after_policyadopted, before_turnend, - after_tileclicked, + after_click, after_modload, before_modunload, + + after_update, // E.G. WorldMapHolder, drawing cirles and arrows. } @@ -106,9 +946,20 @@ typealias Params = Map? typealias ParamGetter = (Any?) -> Params // Some handlerTypes may have parameters that should be set universally; Others may use or simply pass on parameters from where they're called. -interface HandlerDefinitions { +typealias HandlerContex = Map +typealias HandlerDefs = Map + +interface test { + var a: HandlerDefs +} + +fun test(a: test){ + a.a +} + +interface HandlerDefinitions: StatelessMap { val handlerContexts: Map - fun get(key: ContextId): HandlerContext { + override fun get(key: ContextId): HandlerContext { val handlerContext = handlerContexts[key] if (handlerContext == null) { val exc = NullPointerException("Unknown HandlerContext $key!") @@ -120,10 +971,10 @@ interface HandlerDefinitions { operator fun get(contextKey: ContextId, handlerKey: HandlerId) = get(contextKey).get(handlerKey) } -interface HandlerContext { +interface HandlerContext: StatelessMap { val name: ContextId val handlerTypes: Map - fun get(key: HandlerId): HandlerType { + override fun get(key: HandlerId): HandlerType { val handlerType = handlerTypes[key] if (handlerType == null) { val exc = NullPointerException("Unknown HandlerType $key for $name context!") @@ -162,11 +1013,32 @@ interface HandlerType { @DslMarker private annotation class HandlerBuilder +// Type-safe builder function for the root hierarchy of all handler context and handler type definitions. + +// @return A two-depth nested Map-like HandlerDefinitions object, which indexes handler types first by ContextId and then by HandlerId. +private fun handlerDefinitions(init: HandlerDefinitionsBuilderScope.() -> Unit): HandlerDefinitions { + val unconfigured = HandlerDefinitionsBuilderScope() + unconfigured.init() + return object: HandlerDefinitions { + override val handlerContexts = unconfigured.handlerContexts.toMap() + } +} + // Type-safe builder scope for the root hierarchy of all handler context and handler type definitions. @HandlerBuilder private class HandlerDefinitionsBuilderScope { - val handlerContexts = mutableMapOf() + // Type-safe builder method for a handler context namespace. + + // @param name The name of this context. + fun handlerContext(name: ContextId, init: HandlerContextBuilderScope.() -> Unit) { + val unconfigured = HandlerContextBuilderScope(name) + unconfigured.init() + handlerContexts[name] = object: HandlerContext { + override val name = unconfigured.name + override val handlerTypes = unconfigured.handlers.toMap() + } + } // Type-safe builder scope for a handler context namespace. @@ -174,7 +1046,21 @@ private class HandlerDefinitionsBuilderScope { @HandlerBuilder class HandlerContextBuilderScope(val name: ContextId) { - val handlers = mutableMapOf() + // Type-safe builder method for a handler type. + + // @param V The type that may be provided as an argument when running this handler type. + // @param name The name of this handler type. + fun handler(name: HandlerId, init: HandlerTypeBuilderScope.() -> Unit) { + val unconfigured = HandlerTypeBuilderScope(name) + unconfigured.init() + handlers[name] = object: HandlerType { + override val name = name + val context = this@HandlerContextBuilderScope + override val paramTypes = unconfigured.paramTypes.toMap() + override val paramGetter: ParamGetter = { given: Any? -> unconfigured.paramGetters.entries.associate { it.key to it.value(given as V) } } + override fun toString() = "HandlerType:${context.name}/${name}" + } + } // Type-safe builder scope for a handler type. @@ -183,9 +1069,6 @@ private class HandlerDefinitionsBuilderScope { @HandlerBuilder class HandlerTypeBuilderScope(val name: HandlerId) { - val paramTypes = mutableMapOf() - val paramGetters = mutableMapOf Any?>() - // Type-safe builder method for a parameter that a handler type accepts and then sets in a Map accessible by the scripting API while the handler type is running. // @param R The type that this value is to be set to in the script's execution context. @@ -197,93 +1080,16 @@ private class HandlerDefinitionsBuilderScope { paramGetters[name] = getter } - } - - // Type-safe builder method for a handler type. + val paramTypes = mutableMapOf() + val paramGetters = mutableMapOf Any?>() - // @param V The type that may be provided as an argument when running this handler type. - // @param name The name of this handler type. - fun handler(name: HandlerId, init: HandlerTypeBuilderScope.() -> Unit) { - val unconfigured = HandlerTypeBuilderScope(name) - unconfigured.init() - handlers[name] = object: HandlerType { - override val name = name - val context = this@HandlerContextBuilderScope - override val paramTypes = unconfigured.paramTypes.toMap() - override val paramGetter: ParamGetter = { given: Any? -> unconfigured.paramGetters.entries.associate { it.key to it.value(given as V) } } - override fun toString() = "HandlerType:${context.name}/${name}" - } } - } + val handlers = mutableMapOf() - // Type-safe builder method for a handler context namespace. - // @param name The name of this context. - fun handlerContext(name: ContextId, init: HandlerContextBuilderScope.() -> Unit) { - val unconfigured = HandlerContextBuilderScope(name) - unconfigured.init() - handlerContexts[name] = object: HandlerContext { - override val name = unconfigured.name - override val handlerTypes = unconfigured.handlers.toMap() - } } -} + val handlerContexts = mutableMapOf() -// Type-safe builder function for the root hierarchy of all handler context and handler type definitions. -private fun handlerDefinitions(init: HandlerDefinitionsBuilderScope.() -> Unit): HandlerDefinitions { - val unconfigured = HandlerDefinitionsBuilderScope() - unconfigured.init() - return object: HandlerDefinitions { - override val handlerContexts = unconfigured.handlerContexts.toMap() - } } - - -//object ModScripthingHandlerTypes2 { -//} - -// For defining the types and behaviours of available handlerTypes. -//object ModScriptingHandlerTypes { -// -// -// -//// private val defaultParamGetter: ParamGetter = { it } -// private val defaultParamGetter: ParamGetter = { mapOf() } -// -//// @Suppress("EnumEntryName") // Handler names are not Kotlin code, but more like JSON keys in mod configuration files. -// @Suppress("EnumEntryName") -// @OptIn(ExperimentalStdlibApi::class) // For typeOf(). Technically isn't needed for functional release builds (see HandlerType.checkParamsValid). -// object All { // Not a great name for imports. -// enum class UncivGame(override val paramGetter: ParamGetter = defaultParamGetter): HandlerType { -// after_instantiate() { -// override val paramTypes = mapOf( -// "testArg" to typeOf() -// ) -// } -// ; -// companion object: HandlerContext { -// override val name = "UncivGame" -// override val handlerTypes = values().toList() -// } -// } -// -// enum class GameInfo(override val paramGetter: ParamGetter = defaultParamGetter): HandlerType { -// ; -// companion object: HandlerContext { -// override val name = "GameInfo" -// override val handlerTypes = values().toList() -// } -// } -// } -// val all = listOf( // Explicitly type so anything with wrong or missing companion raises compile/IDE error. -// All.UncivGame, -// All.GameInfo -// ) -// val byContextAndName = all.associate { context -> -// context.name to context.handlerTypes.associateWith { handler -> -// handler.name -// } -// } -//} diff --git a/core/src/com/unciv/models/modscripting/ModScriptingRegistrationManager.kt b/core/src/com/unciv/models/modscripting/ModScriptingRegistrationManager.kt index cb3ea48b1dccd..8bdb7e453089c 100644 --- a/core/src/com/unciv/models/modscripting/ModScriptingRegistrationManager.kt +++ b/core/src/com/unciv/models/modscripting/ModScriptingRegistrationManager.kt @@ -8,17 +8,53 @@ object ModScriptingRegistrationManager { val activeMods = mutableSetOf() - init { + fun init() { +// registerMod( +// ScriptedModRules.fromJsonString( +// """ +// { +// "name": "Test Mod", +// "language": "pathcode", +// "handlers": { +// "ConsoleScreen": { +// "after_open": { +// "background": ["examples", "get apiExecutionContext.handlerParameters", "get apiExecutionContext.scriptingBackend", "faef"] +// } +// } +// } +// } +// """ +// ) +// ) +// registerMod( +// ScriptedModRules.fromJsonString( +// """ +// { +// "name": "Test Mod2", +// "language": "python", +// "handlers": { +// "ConsoleScreen": { +// "before_close": { +// "background": ["from unciv_scripting_examples.MapEditingMacros import *; (makeMandelbrot() if unciv.apiHelpers.isInGame else None)"], +// "foreground": ["from unciv_scripting_examples.EventPopup import *; (showEventPopup(**EVENT_POPUP_DEMOARGS()) if apiHelpers.isInGame else None)"] +// } +// } +// } +// } +// """ +// ) +// ) registerMod( ScriptedModRules.fromJsonString( """ { - "name": "Test Mod", - "language": "pathcode", + "name": "Test ModA", + "language": "python", "handlers": { "ConsoleScreen": { "after_open": { - "background": ["examples", "get apiExecutionContext.handlerParameters", "get apiExecutionContext.scriptingBackend", "faef"] + "background": ["import time; time.sleep(1); 'Ab'", "import time; time.sleep(2); 'Ab'"], + "foreground": ["import time; time.sleep(1); 'Af'", "import time; time.sleep(2); 'Af'"] } } } @@ -30,13 +66,13 @@ object ModScriptingRegistrationManager { ScriptedModRules.fromJsonString( """ { - "name": "Test Mod2", + "name": "Test ModB", "language": "python", "handlers": { "ConsoleScreen": { - "before_close": { - "background": ["from unciv_scripting_examples.MapEditingMacros import *; (makeMandelbrot() if unciv.apiHelpers.isInGame else None)"], - "foreground": ["from unciv_scripting_examples.EventPopup import *; (showEventPopup(**EVENT_POPUP_DEMOARGS()) if apiHelpers.isInGame else None)"] + "after_open": { + "background": ["import time; time.sleep(1); 'Bb'", "import time; time.sleep(2); 'Bb'"], + "foreground": ["import time; time.sleep(1); 'Bf'", "import time; time.sleep(2); 'Bf'"] } } } diff --git a/core/src/com/unciv/models/modscripting/ModScriptingRunManager.kt b/core/src/com/unciv/models/modscripting/ModScriptingRunManager.kt index 279d45d541ae9..4f71383fe6449 100644 --- a/core/src/com/unciv/models/modscripting/ModScriptingRunManager.kt +++ b/core/src/com/unciv/models/modscripting/ModScriptingRunManager.kt @@ -55,7 +55,8 @@ object ModScriptingRunManager { command = registeredHandler.code, asName = name, withParams = withParams, - withBackend = registeredHandler.backend + withBackend = registeredHandler.backend, + allowWait = true ) } catch(e: Throwable) { ScriptingErrorHandling.notifyPlayerScriptFailure(exception = e, asName = name) @@ -109,6 +110,9 @@ object ModScriptingRunManager { } } + fun queueRuns() { + } + private fun lockGame() { //isPlayersTurn. Gdx.input.inputProcessor = null diff --git a/core/src/com/unciv/models/modscripting/handlertypes/HandlerDefinitionsBuilders.kt b/core/src/com/unciv/models/modscripting/handlertypes/HandlerDefinitionsBuilders.kt new file mode 100644 index 0000000000000..73e24afd6203f --- /dev/null +++ b/core/src/com/unciv/models/modscripting/handlertypes/HandlerDefinitionsBuilders.kt @@ -0,0 +1,2 @@ +package com.unciv.models.modscripting.handlertypes + diff --git a/core/src/com/unciv/models/modscripting/handlertypes/HandlerIdentifiers.kt b/core/src/com/unciv/models/modscripting/handlertypes/HandlerIdentifiers.kt new file mode 100644 index 0000000000000..73e24afd6203f --- /dev/null +++ b/core/src/com/unciv/models/modscripting/handlertypes/HandlerIdentifiers.kt @@ -0,0 +1,2 @@ +package com.unciv.models.modscripting.handlertypes + diff --git a/core/src/com/unciv/models/modscripting/handlertypes/HandlerType.kt b/core/src/com/unciv/models/modscripting/handlertypes/HandlerType.kt new file mode 100644 index 0000000000000..73e24afd6203f --- /dev/null +++ b/core/src/com/unciv/models/modscripting/handlertypes/HandlerType.kt @@ -0,0 +1,2 @@ +package com.unciv.models.modscripting.handlertypes + diff --git a/core/src/com/unciv/scripting/ScriptingBackend.kt b/core/src/com/unciv/scripting/ScriptingBackend.kt index 60d5dd31587bb..27d71d2afba20 100644 --- a/core/src/com/unciv/scripting/ScriptingBackend.kt +++ b/core/src/com/unciv/scripting/ScriptingBackend.kt @@ -503,7 +503,8 @@ class ReflectiveScriptingBackend(): ScriptingBackend() { } // Uses SourceManager to copy library files for engine type into a temporary directory per instance. -// Deletes + +// Tries to deletes temporary directory on backend termination, and registers a handler with UncivGame to delete temporary directory on application end as well. abstract class EnvironmentedScriptingBackend(): ScriptingBackend() { companion object Metadata: EnvironmentedScriptBackend_metadata() { diff --git a/core/src/com/unciv/scripting/ScriptingConstants.kt b/core/src/com/unciv/scripting/ScriptingConstants.kt index 8bff9ee5a9584..30748fe0787b3 100644 --- a/core/src/com/unciv/scripting/ScriptingConstants.kt +++ b/core/src/com/unciv/scripting/ScriptingConstants.kt @@ -14,7 +14,7 @@ val ScriptingConstants: _ScriptingConstantsClasses.ScriptingConstantsClass = Jso * Class defining the structure of ScriptingConstants. */ object _ScriptingConstantsClasses{ - // Need to define classes to deserialize the JSONs into, but really this whole file should be one singleton. + // Need to define classes to deserialize the JSONs into, but really the whole file should be one singleton. // It would be slightly better with KotlinX, I think, since then I could at least use data classes and don't have to initialize all the properties with mutable values. LibGDX instantiates with no constructor arguments, and then assigns to properties/fields/whatever. /** diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index 909f98bce1a2c..648396ec120fa 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -4,11 +4,14 @@ import com.unciv.UncivGame // Only for blocking execution in multiplayer. import com.unciv.scripting.api.ScriptingScope import com.unciv.scripting.sync.ScriptingRunLock import com.unciv.scripting.sync.makeScriptingRunName +import com.unciv.scripting.utils.ScriptingDebugParameters import com.unciv.ui.utils.BaseScreen import com.unciv.ui.utils.ToastPopup import com.unciv.ui.utils.clipIndexToBounds import com.unciv.ui.utils.enforceValidIndex import com.unciv.ui.worldscreen.WorldScreen // Only for blocking execution in multiplayer. +import java.lang.IllegalStateException +import java.util.concurrent.Semaphore import kotlin.collections.ArrayList // TODO: Add .github/CODEOWNERS file for automatic PR notifications. @@ -126,18 +129,42 @@ object ScriptingState { } } + private val runLock = Semaphore(1, true) + var runningName: String? = null + private set + // @throws IllegalStateException On failure to acquire scripting lock. - @Synchronized fun exec(command: String, asName: String? = null, withParams: Map? = null): ExecResult { - if (UncivGame.Current.screen is WorldScreen && UncivGame.Current.isGameInfoInitialized() && UncivGame.Current.gameInfo.gameParameters.isOnlineMultiplayer) { // TODO: After leaving game? + fun exec( + command: String, + asName: String? = null, + withParams: Map? = null, + allowWait: Boolean = false + ): ExecResult { + // TODO: Synchronize here instead of in ScriptingRunLock and ScriptingRunThreader. + if (ScriptingDebugParameters.printCommandsForDebug) { + println("Running: $command") + } + if (UncivGame.Current.screen is WorldScreen + && UncivGame.Current.isGameInfoInitialized() + && UncivGame.Current.gameInfo.gameParameters.isOnlineMultiplayer + ) { // TODO: After leaving game? ToastPopup("Scripting not allowed in online multiplayer.", UncivGame.Current.screen as BaseScreen) // TODO: Translation. return ExecResult("", true) } val backend = activeBackend val name = asName ?: makeScriptingRunName(this::class.simpleName, backend) - val releaseKey = ScriptingRunLock.acquire(name) +// val releaseKey = ScriptingRunLock.acquire(name) // Lock acquisition failure gets propagated as thrown Exception, rather than as return. E.G.: Lets lambdas (from modApiHelpers) fail and trigger their own error handling (exposing misbehaving mods to the user via popup). // isException in ExecResult return value means exception in completely opaque scripting backend. Kotlin exception should still be thrown and propagated like normal. try { + if (allowWait) { + runLock.acquire() + } else { + if (!runLock.tryAcquire()) { + throw IllegalStateException() + } + } + runningName = name ScriptingScope.apiExecutionContext.apply { handlerParameters = withParams?.toMap() // Looking at the source code, some .to() extensions actually return mutable instances, and just type the return. @@ -154,7 +181,10 @@ object ScriptingState { } } activeCommandHistory = 0 - var out = if (hasBackend()) backend.exec(command) else ExecResult("") + var out = if (hasBackend()) + backend.exec(command) + else + ExecResult("${this::class.simpleName} has no backends.", true) echo(out.resultPrint) return out } finally { @@ -162,16 +192,25 @@ object ScriptingState { handlerParameters = null scriptingBackend = null } - ScriptingRunLock.release(releaseKey) + runningName = null + runLock.release() +// ScriptingRunLock.release(releaseKey) } } - fun exec(command: String, asName: String? = null, withParams: Map? = null, withBackend: ScriptingBackend): ExecResult { + fun exec( + command: String, + asName: String? = null, + withParams: Map? = null, + allowWait: Boolean = false, + withBackend: ScriptingBackend + ): ExecResult { switchToBackend(withBackend) return exec( command = command, asName = asName, - withParams = withParams + withParams = withParams, + allowWait = allowWait ) } } diff --git a/core/src/com/unciv/scripting/api/ScriptingApiAppHelpers.kt b/core/src/com/unciv/scripting/api/ScriptingApiAppHelpers.kt index f8f257ab32c68..d3d8f47dc1185 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiAppHelpers.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiAppHelpers.kt @@ -9,12 +9,15 @@ import java.io.ByteArrayOutputStream object ScriptingApiAppHelpers { //Debug/dev identity function for both Kotlin and scripts. Check if value survives serialization, force something to be added to ScriptingProtocol.instanceSaver, etc. + // @param path Path of an internal file as exposed in Gdx.files.internal. // @return The contents of the internal file read as a text string. fun assetFileString(path: String) = Gdx.files.internal(path).readString() + // @param path Path of an internal file as exposed in Gdx.files.internal. // @return The contents of the internal file encoded as a Base64 string. fun assetFileB64(path: String) = String(Base64Coder.encode(Gdx.files.internal(path).readBytes())) + // @param path Path of an internal image as exposed in ImageGetter as a TextureRegionDrawable from an atlas. // @return The image encoded as a PNG file encoded as a Base64 string. fun assetImageB64(path: String): String { @@ -30,4 +33,11 @@ object ScriptingApiAppHelpers { exporter.dispose() // This one should be called automatically by GC anyway. return String(Base64Coder.encode(fakepng.toByteArray())) } + + //val isMainThread get() = Thread().getCurrentThread() == UncivGame.mainThread + + //fun runInThread(func: () -> Unit) {} + + //fun runInMainLoop(func: () -> Unit) {} + } diff --git a/core/src/com/unciv/scripting/api/ScriptingApiExecutionContext.kt b/core/src/com/unciv/scripting/api/ScriptingApiExecutionContext.kt index 3bd86a7cc3b60..7696314aaecc1 100644 --- a/core/src/com/unciv/scripting/api/ScriptingApiExecutionContext.kt +++ b/core/src/com/unciv/scripting/api/ScriptingApiExecutionContext.kt @@ -3,7 +3,12 @@ package com.unciv.scripting.api import com.unciv.scripting.ScriptingBackend object ScriptingApiExecutionContext { + // Map that gets replaced with any contextual parameters when running script handlers. + + // E.G. The Unit that was moved, the city that was founded, the tech that was researched, the construction that was finished, the instance that was initialized. + + // To prevent scripts from interfering with each other by trying to mutate this, it is reset by ScriptingState before every script execution. As such, scripted handlers should re-access it when run to get valid values. var handlerParameters: Map? = null - // Collection that gets replaced with any contextual parameters when running script handlers. E.G. The Unit moved, the city founded, the tech researched, the construction finished. - var scriptingBackend: ScriptingBackend? = null + + var scriptingBackend: ScriptingBackend? = null // TODO: Actually just use ScriptingState.activeBackend? …Execution state shouldn't be kept completely under ScriptingScope at all now that it's all singletons. } diff --git a/core/src/com/unciv/scripting/api/ScriptingApiGlHelpers.kt b/core/src/com/unciv/scripting/api/ScriptingApiGlHelpers.kt new file mode 100644 index 0000000000000..e8493f797ea6c --- /dev/null +++ b/core/src/com/unciv/scripting/api/ScriptingApiGlHelpers.kt @@ -0,0 +1,6 @@ +package com.unciv.scripting.api + +object ScriptingApiGlHelpers { + // https://github.com/yairm210/Unciv/pull/2786 + // ShapeRenderer, Gdx.gl? +} diff --git a/core/src/com/unciv/scripting/sync/ScriptingRunThreader.kt b/core/src/com/unciv/scripting/sync/ScriptingRunThreader.kt index fb46fc678e9c7..1ed523bf6979b 100644 --- a/core/src/com/unciv/scripting/sync/ScriptingRunThreader.kt +++ b/core/src/com/unciv/scripting/sync/ScriptingRunThreader.kt @@ -8,9 +8,8 @@ import java.util.concurrent.Semaphore import kotlin.concurrent.thread // Utility to run multiple actions related to scripting in separate threads, but still sequentially. -object ScriptingRunThreader { +object ScriptingRunThreader { // TODO: Probably obsolete this whole class, right? private val runLock = Semaphore(1, true) - //// Should only ever be called from main thread actually, so fairness shouldn't matter. Nevermind. // Switched from AtomicBoolean to ReentrantLock, and now going to switch to a semaphore of some kind. private val runQueue = ConcurrentLinkedDeque<() -> Unit>() @Synchronized fun queueRun(toRun: () -> Unit) { if (ScriptingDebugParameters.printThreadingStatus) { @@ -56,7 +55,7 @@ object ScriptingRunThreader { } } -// When run the given function toRun, using the given concurrentRunner to run it, and block until it's done. +// Run the given function toRun, using the given concurrentRunner to run it, and block until it's done. // @throws IllegalStateException If already on the main thread. fun blockingConcurrentRun(concurrentRunner: (Runnable) -> Unit, toRun: () -> Unit) { diff --git a/core/src/com/unciv/scripting/utils/FakeMaps.kt b/core/src/com/unciv/scripting/utils/FakeMaps.kt index 30294cebd5374..6d87a15b4a7a2 100644 --- a/core/src/com/unciv/scripting/utils/FakeMaps.kt +++ b/core/src/com/unciv/scripting/utils/FakeMaps.kt @@ -5,8 +5,10 @@ package com.unciv.scripting.utils // Therefore, for certain models of scripting language bindings, it is easier to make guarantees about and expose flexible semantics for key indexing than for function calls. // The classes here help expose functions as Map-like instances, letting simple, side-effect-free functions with direct mappings from input to output be presented in scripting language bindings without having to worry about side effects from E.G. repeated or deferred calling. -abstract class StatelessMap: Map { - protected fun noStateError(): Nothing = throw(UnsupportedOperationException("Cannot access backing state of ${this::class.simpleName} by default.")) +interface StatelessMap: Map { // TODO: Move this. + companion object { + fun noStateError(): Nothing = throw(UnsupportedOperationException("Cannot access backing state of ${this::class.simpleName} by default.")) + } override val entries: MutableSet> get() = noStateError() override val keys: MutableSet get() = noStateError() override val values: MutableCollection get() = noStateError() @@ -29,7 +31,7 @@ interface InvokableMap: Map { // @property func The function that returns the value for a given key. // @property exposeState Whether to expose the content-specific members of the backing map. -class LazyMap(val func: (K) -> V, val exposeState: Boolean = false): StatelessMap(), InvokableMap { +class LazyMap(val func: (K) -> V, val exposeState: Boolean = false): StatelessMap, InvokableMap { // Benefit of a Map over a function is that because mapping access can be safely assumed by scripting language bindings to have no side effects, it's semantically easier for scripting language bindings to let the returned value be immediately called, autocompleted, indexed/attribute-read, etc. private val backingMap = hashMapOf() override fun get(key: K): V? { @@ -42,15 +44,15 @@ class LazyMap(val func: (K) -> V, val exposeState: Boolean = false): State } return result } - override val entries get() = if (exposeState) backingMap.entries else noStateError() - override val keys get() = if (exposeState) backingMap.keys else noStateError() - override val values get() = if (exposeState) backingMap.values else noStateError() - override val size get() = if (exposeState) backingMap.size else noStateError() - override fun containsKey(key: K) = if (exposeState) backingMap.containsKey(key) else noStateError() - override fun containsValue(value: V) = if (exposeState) backingMap.containsValue(value) else noStateError() - override fun isEmpty() = if (exposeState) backingMap.isEmpty() else noStateError() + override val entries get() = if (exposeState) backingMap.entries else StatelessMap.noStateError() + override val keys get() = if (exposeState) backingMap.keys else StatelessMap.noStateError() + override val values get() = if (exposeState) backingMap.values else StatelessMap.noStateError() + override val size get() = if (exposeState) backingMap.size else StatelessMap.noStateError() + override fun containsKey(key: K) = if (exposeState) backingMap.containsKey(key) else StatelessMap.noStateError() + override fun containsValue(value: V) = if (exposeState) backingMap.containsValue(value) else StatelessMap.noStateError() + override fun isEmpty() = if (exposeState) backingMap.isEmpty() else StatelessMap.noStateError() } -class FakeMap(private val getter: (K) -> V): StatelessMap(), InvokableMap { +class FakeMap(private val getter: (K) -> V): StatelessMap, InvokableMap { override fun get(key: K) = getter(key) } diff --git a/core/src/com/unciv/scripting/utils/ScriptingDebugParameters.kt b/core/src/com/unciv/scripting/utils/ScriptingDebugParameters.kt index 25514d69aa93a..17fb2dc9cc56f 100644 --- a/core/src/com/unciv/scripting/utils/ScriptingDebugParameters.kt +++ b/core/src/com/unciv/scripting/utils/ScriptingDebugParameters.kt @@ -1,10 +1,10 @@ package com.unciv.scripting.utils -object ScriptingDebugParameters { +object ScriptingDebugParameters { // TODO: See consoleLog. // Whether to print out all/most IPC packets for debug. var printPacketsForDebug = false // Whether to print out all executed script code strings for debug (more readable than printing all packets). - var printCommandsForDebug = false + var printCommandsForDebug = true // Whether to print out all/most IPC actions for debug (more readable than printing all packets). var printAccessForDebug = false // Whether to print out major token count changes and cleaning events in InstanceTokenizer for debug. @@ -16,7 +16,7 @@ object ScriptingDebugParameters { // Whether to print out when the lock for stopping simultaneous script run attempts is acquired and released. var printLockAcquisition = false // Whether to print out when the queue for running mod-controlled scripts in sequence is expanded or consumed. - var printThreadingStatus =false + var printThreadingStatus = false // TODO: Add to gameIsNotRunWithDebugModes unit tests. } diff --git a/core/src/com/unciv/scripting/utils/ScriptingErrorHandling.kt b/core/src/com/unciv/scripting/utils/ScriptingErrorHandling.kt index c418b653866b9..fcfa86e6a7206 100644 --- a/core/src/com/unciv/scripting/utils/ScriptingErrorHandling.kt +++ b/core/src/com/unciv/scripting/utils/ScriptingErrorHandling.kt @@ -12,7 +12,7 @@ import com.unciv.ui.utils.AutoScrollPane as ScrollPane object ScriptingErrorHandling { fun notifyPlayerScriptFailure(text: String, asName: String? = null, toConsole: Boolean = true) { Gdx.app.postRunnable { // This can potentially be run for scripts in worker threads, so in that case needs to go to the main thread to have OpenGL context and not crash. - val popup = Popup(UncivGame.Current.screen as BaseScreen) + val popup = Popup(UncivGame.Current.screen as BaseScreen) // TODO: Make this a class. val widthTarget = popup.screen.stage.width / 2 val msg1 = "{An error has occurred with the mod/script} \"${asName ?: ScriptingRunLock.runningName}\".\n\n{See system terminal output for details.}\n{Consider disabling mods if this keeps happening.}" // TODO: Translation. popup.add(msg1.toLabel().apply @@ -27,7 +27,7 @@ object ScriptingErrorHandling { wrap = true }).width(widthTarget).row() val scrollPane = ScrollPane(contentTable) - popup.add(scrollPane).row() + popup.add(scrollPane).row() // TODO: "Copy" button. popup.addOKButton{} popup.open(true) if (toConsole) diff --git a/core/src/com/unciv/scripting/utils/SourceManager.kt b/core/src/com/unciv/scripting/utils/SourceManager.kt index 2cc1a805f660c..9606919901b0d 100644 --- a/core/src/com/unciv/scripting/utils/SourceManager.kt +++ b/core/src/com/unciv/scripting/utils/SourceManager.kt @@ -9,7 +9,7 @@ import kotlin.concurrent.thread /** - * Object for managing, and using (copying/instantiating) internal assets associated with each script interpreter engine type. + * Functions for managing, and using (copying/instantiating) internal assets and modded files associated with each script interpreter engine type. */ object SourceManager { @@ -52,4 +52,12 @@ object SourceManager { } return outdir } + + fun applyModToInterpreterEnvironment( // It looks like mod loading is in RulesetCache? + modDir: FileHandle, + interpreterDir: FileHandle, + engine: String + ) { // https://github.com/yairm210/Unciv/pull/1825 + + } // TODO } diff --git a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt index ea54afd943e2a..c56668010f68c 100644 --- a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt @@ -193,11 +193,11 @@ class ConsoleScreen(var closeAction: () -> Unit): BaseScreen() { showWarningPopup() } ModScriptingRegistrationManager.activeMods - ModScriptingRunManager.runHandler(HANDLER_DEFINITIONS[ContextId.ConsoleScreen, HandlerId.after_open], this) +// ModScriptingRunManager.runHandler(HANDLER_DEFINITIONS[ContextId.ConsoleScreen, HandlerId.after_open], this) } fun closeConsole() { - ModScriptingRunManager.runHandler(HANDLER_DEFINITIONS[ContextId.ConsoleScreen, HandlerId.before_close], this) +// ModScriptingRunManager.runHandler(HANDLER_DEFINITIONS[ContextId.ConsoleScreen, HandlerId.before_close], this) closeAction() keyPressDispatcher.uninstall() this.isOpen = false @@ -220,9 +220,10 @@ class ConsoleScreen(var closeAction: () -> Unit): BaseScreen() { updateRunning() } var termbutton = ImageGetter.getImage("OtherIcons/Stop") - val terminable = backend.userTerminable + val terminable = backend.userTerminable // Grey out if not terminable. if (!terminable) { termbutton.setColor(0.7f, 0.7f, 0.7f, 0.3f) +// termbutton.color.a = 0.3f } termbutton.onClick { if (backend.userTerminable) { From e0f0fe34da7ebcf8271af99ef009349df8f45ee6 Mon Sep 17 00:00:00 2001 From: will-ca Date: Tue, 21 Dec 2021 19:57:50 +0000 Subject: [PATCH 92/93] Rewrite a lot of history. Purge the following files from branch divergence COMPLETELY: - `desktop/build.gradle.kts` - `android/assets/game.atlas` - `android/assets/game.png` - `docs/uniques.md` - `gradle.properties` `$ git filter-branch --force --tree-filter 'for f in desktop/build.gradle.kts android/assets/game.atlas android/assets/game.png docs/uniques.md gradle.properties; do git restore $f --source 48bc79c256645945922e865452d3e94a6ad06068; done; echo "$(git log HEAD^..HEAD)"' upstream/master..HEAD` From 241f89ea41c9158345a36bc8e758fa79d109a108 Mon Sep 17 00:00:00 2001 From: will-ca Date: Mon, 17 Jan 2022 22:33:17 +0000 Subject: [PATCH 93/93] Notes, comments. --- .../scripting/enginefiles/python/unciv_lib/wrapping.py | 2 ++ .../python/unciv_scripting_examples/EventPopup.py | 2 +- .../enginefiles/python/unciv_scripting_examples/Merfolk.py | 6 ++++++ core/src/com/unciv/models/modscripting/ScriptedModRules.kt | 5 ++++- core/src/com/unciv/scripting/ScriptingState.kt | 3 +++ core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt | 2 +- 6 files changed, 17 insertions(+), 3 deletions(-) diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py index 1cf6bd9de9bfc..e6a6148bf053a 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py @@ -5,6 +5,8 @@ # TODO: Definitely CProfile this. +# Random.sample, collection ABCs sequence? + class ForeignRequestMethod: """Decorator and descriptor protocol implementation for methods of ForeignObject subclasses that return values from foreign requests.""" def __init__(self, func): diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EventPopup.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EventPopup.py index ef1e4f6534be0..b32c333be0500 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EventPopup.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EventPopup.py @@ -117,7 +117,7 @@ def EVENT_POPUP_DEMOARGS(): ( (f"Let Chaos reign! (+{omniboost} {real(Fonts.gold.toString())}, {real(Fonts.culture.toString())}, {real(Fonts.science.toString())})", None), (f"(+{sum(omniproductionboosts)} {real(Fonts.production.toString())} spread across all your cities.)", None), - (f"All cities enter resistance for +{omniresistance} turns.", GdxColours['SCARLET']) + (f"All your cities enter resistance for +{omniresistance} turns.", GdxColours['SCARLET']) ): modApiHelpers.lambdifyCombine([ modApiHelpers.lambdifyReadPathcode(civInfo, f'.addGold({omniboost})'), diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Merfolk.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Merfolk.py index 52ccbd8310846..09be7ebd96c40 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Merfolk.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Merfolk.py @@ -29,6 +29,12 @@ #civInfo.addNotification("Test", civInfo.cities[0].location, apiHelpers.Jvm.arrayOfString(["StatIcons/Gold"])) +#import random; [random.sample([*gameInfo.civilizations], 1)[0].placeUnitNearTile(t.position, random.sample(gameInfo.ruleSet.units.keys(), 1)[0]) for t in gameInfo.tileMap.values] + +# import random; [civInfo.placeUnitNearTile(t.position, unitgetter()) for unitgetter in (lambda: random.sample(gameInfo.ruleSet.units.keys(), 1)[0], lambda: random.sample(('Missile Cruiser', 'Nuclear Submarine'), 1)[0], lambda: 'Worker', lambda: 'Guided Missile') for t in gameInfo.tileMap.values]; ([setattr(unit, 'action', random.sample(('Sleep', 'Automate', 'Fortify 0', 'Explore'), 1)[0]) for unit in apiHelpers.Jvm.toList(civInfo.getCivUnits())], civInfo.addGold(99999)) + +# [setattr(unit, 'action', 'Sleep') for unit in apiHelpers.Jvm.toList(civInfo.getIdleUnits())] + def moveCity(): tileInfo.owningCity = city #Seems to be used for rendering only. #city.tiles.add(tile) Causes exception in worker thread in next turn. diff --git a/core/src/com/unciv/models/modscripting/ScriptedModRules.kt b/core/src/com/unciv/models/modscripting/ScriptedModRules.kt index 440593aa23f04..3c72798593726 100644 --- a/core/src/com/unciv/models/modscripting/ScriptedModRules.kt +++ b/core/src/com/unciv/models/modscripting/ScriptedModRules.kt @@ -5,11 +5,14 @@ import com.badlogic.gdx.utils.JsonReader import com.badlogic.gdx.utils.JsonValue import com.unciv.scripting.ScriptingState + +// TODO: Make sure DropBox multiplayer doesn't provide quick and easy arbitrary code execution for would-be attackers? + class ScriptedModLoadable(val modRules: ScriptedModRules) { } -class ScriptedModRules {// See, try to follow conventions of, TilesetAndMod maybe? +class ScriptedModRules {// See, try to follow conventions of, TilesetAndMod and TilesetCache/ModOptions, maybe? // Getting this deserializing from JSON was a humongous, humongous pain. GDX seems to take only three levels of nested mappings before it stops knowing what to do with the arrays at the deepest level. I could have just used KotlinX instead as I'm bringing in the dependency anyway, but for some reason I seem to want to align the parts of the scripting API that interact with the existing codebase with the tools that are already used. diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index 648396ec120fa..6243b18d2d4de 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -26,6 +26,9 @@ import kotlin.collections.ArrayList // See https://github.com/yairm210/Unciv/pull/5592/commits/a1f51e08ab782ab46bda220e0c4aaae2e8ba21a4 for example of running locking operation in separate thread. + +// TODO: + /** * Self-contained instance of scripting API use. * diff --git a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt index c56668010f68c..07a123c48d16e 100644 --- a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt @@ -221,7 +221,7 @@ class ConsoleScreen(var closeAction: () -> Unit): BaseScreen() { } var termbutton = ImageGetter.getImage("OtherIcons/Stop") val terminable = backend.userTerminable // Grey out if not terminable. - if (!terminable) { + if (!terminable) { // TODO: There's a Button.disable() extension function. termbutton.setColor(0.7f, 0.7f, 0.7f, 0.3f) // termbutton.color.a = 0.3f }

;N~ri6+GHFLYC=A&c^ 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 z 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 diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py index bb0966e369d12..01d731052bed1 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py @@ -5,7 +5,7 @@ User modifiability, rapid development, idiomatic and expressive syntax, and dynamic access to system libraries. These example features below would be a nightmare to implement and maintain in the Kotlin code. -But in Python, they can be drafted up fairly quickly, and it doesn't even matter if they break, because they can't really interfere with anything else. +But in Python, they can be drafted up fairly quickly, and it's not a critical issue if they break, because they can't really interfere with anything else. And most importantly, they can be made and distributed by the user themselves as external files in their interpreter's library path. This lets very obscure and niche features be implemented and used by those who want them, without adding anything to the maintenance burden for the core Unciv codebase. """ @@ -328,17 +328,17 @@ def __call__(self, pixel): else: return self.get_terrainandcolour(pixel)[0] -def loadImageColours(tileMap=None, imagepath="EarthTerrainFantasyHex.png", terraincolours=None, maxdither=0, visualspace=True): +def loadImageColours(tileMap=None, imagepath="EarthTerrainFantasyHex.jpg", terraincolours=None, maxdither=0, visualspace=True): """ Set a given tileMap or the active tileMap's terrain based on an image file and a mapping of terrain strings to RGB tuples. - Recommended example values for imagepath: EarthTerrainFantasyHex.png, StarryNight.jpg, TurboRainbow.png (Try maxdither=0.5.) + Recommended example values for imagepath: EarthTerrainFantasyHex.png, StarryNight.jpg, TurboRainbow.png (Try maxdither=0.5.), WheatField.jpg """ #https://visibleearth.nasa.gov/images/73801/september-blue-marble-next-generation-w-topography-and-bathymetry #Generate TurboRainbow.png: from matplotlib import cm; from PIL import Image, ImageDraw; width=512; image = Image.new('RGB', (width,1), "white"); draw=ImageDraw.Draw(image); [draw.point((x,0), tuple(int(c*256) for c in cm.turbo(x/width)[:3])) for x in range(width)]; image.show("TurboRainbow.png"); image.close() + import PIL.Image assert visualspace tileMap = defaultArgTilemap(tileMap) - import PIL.Image if not os.path.exists(imagepath): _fallbackpath = os.path.join(os.path.dirname(__file__), imagepath) if os.path.exists(_fallbackpath): diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/PlayerMacros.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/PlayerMacros.py index 94c76a6dbd046..2c8dcb65dc907 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/PlayerMacros.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/PlayerMacros.py @@ -43,18 +43,27 @@ def addCitiesProduction(cities, queue=()): return cities def clearCitiesSpecialists(cities): + """Unassign all specialist jobs in given cities.""" for city in cities: city.population.specialistAllocations.clear() return cities def focusCitiesFood(cities): + """Assign all unassigned population in given cities to focus on food production.""" for city in cities: city.population.autoAssignPopulation(999) return cities -def buildCitiesQueue(cities, queue): +def buildCitiesQueue(cities, order): + """Assign all given cities to follow a given build order after their current queue.""" for city in cities: - apiHelpers.registeredObjects["x"] = city.cityConstructions.getBuildableBuildings() + with TokensAsWrappers(city.cityConstructions.getBuildableBuildings()) as queue: + pass + #apiHelpers.registeredObjects["x"] = city.cityConstructions.getBuildableBuildings() + +def rebaseUnitsEvenly(units=('Guided Missile',), ): + if isinstance(units, str): + units = (units,) #import os, sys; sys.path.append(os.path.join(os.getcwd(), "../..")); from democomms import * diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Utils.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Utils.py new file mode 100644 index 0000000000000..bcf48cd3e7994 --- /dev/null +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Utils.py @@ -0,0 +1,17 @@ +import atexit + +import unciv + + +RegistryKey = "package:unciv/unciv_scripting_examples.py" + +unciv.apiHelpers.instanceRegistry[RegistryKey] = {} + + +class TokensAsWrappers: + pass + + +@atexit.register +def on_exit(): + del unciv.apiHelpers.instanceRegistry[RegistryKey] diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/WheatField.jpg b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/WheatField.jpg new file mode 100644 index 0000000000000000000000000000000000000000..85c32e92a5985d00f33f9bd1d43dcd4b36b76e2e GIT binary patch literal 81048 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 = null) { @@ -376,7 +376,8 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = scope, TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) ) - data = TokenizingJson.getJsonElement(leaf is KCallable<*>) + throw Exception() +// data = TokenizingJson.getJsonElement(leaf is KCallable<*>) } catch (e: Exception) { data = JsonPrimitive(stringifyException(e)) flags.add(KnownFlag.Exception.value) @@ -388,7 +389,8 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = scope, TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) ) - data = TokenizingJson.getJsonElement((leaf as KCallable<*>).parameters.map { listOf(it.name.toString(), it.type.toString()) }) + throw Exception() +// data = TokenizingJson.getJsonElement((leaf as KCallable<*>).parameters.map { listOf(it.name.toString(), it.type.toString()) }) } catch (e: Exception) { data = JsonPrimitive(stringifyException(e)) flags.add(KnownFlag.Exception.value) diff --git a/core/src/com/unciv/scripting/reflection/Reflection.kt b/core/src/com/unciv/scripting/reflection/Reflection.kt index 0be626f5d782a..f7873512eef58 100644 --- a/core/src/com/unciv/scripting/reflection/Reflection.kt +++ b/core/src/com/unciv/scripting/reflection/Reflection.kt @@ -4,9 +4,14 @@ import com.unciv.scripting.utils.TokenizingJson import kotlin.collections.ArrayList import kotlin.reflect.KCallable import kotlin.reflect.KMutableProperty1 -//import kotlin.reflect.KParameter +import kotlin.reflect.KParameter import kotlin.reflect.KProperty1 //import kotlin.reflect.KType +//import kotlin.reflect.full.allSupertypes +//import kotlin.reflect.full.defaultType +//import kotlin.reflect.full.createType +import kotlin.reflect.full.isSuperclassOf +import kotlin.reflect.jvm.jvmErasure //import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import java.util.* @@ -22,31 +27,91 @@ object Reflection { .first { it.name == propertyName } as KProperty1 return property.get(instance) as R? } -/* + class InstanceMethodDispatcher(val instance: Any, val methodName: String) { + // This isn't just a nice-to-have feature. Before I implemented it, identical calls from demo scripts to methods with multiple dispatches (E.G. ArrayList().add()) would rarely but randomly fail because the member/signature that was found would change between runs or compilations. + // TODO: This is going to need unit tests. + // Could try to implement KCallable interface. But not sure it's be worth it or map closely enough— What do lambdas do? I guess isOpen, isAbstract, etc should just all be False? val methods: List> by lazy { instance::class.members.filter{ it.name == methodName }.map{ it as KCallable } } - fun call(arguments: Array): Any? { - val matches = methods.filter { - val params = it.parameters - params.size == arguments.size + 1 - // Check that the parameters size is same as arguments size plus one for the receiver. - && (arguments zip params.slice(1..arguments.size)).all{ - (arg, kparam) -> - // Check that every argument can be cast to the expected type. - if (arg == null) - kparam.type.isMarkedNullable - else - kparam.type in arg::class.supertypes - // Not totally sure if these KTypes are singleton-like. Hopefully equality-comparison works for containment here even if they aren't? - } + //Also filter down to is KCallable if name collisions with properties are a possible issue. + + fun debugprint(v: Any?): Unit { + if (false) { + println(v) + } + } + + // private fun ktypeQualified(ktype: KType): String { + // return ktype.toString()//.classifier.qualifiedName + // Identical to ktype.toString(), I believe. + // } + + // private fun checkKtypeGeneric(ktype: KType): Boolean { + // //I'm not seeing any way to check for this in the API... Maybe seeing if .jvmErasure or .classifier errors. + // //But the single capital letter convention seems pretty universal. And I think real classes will have a dot in their name. So this will hopefully catch all cases. + // debugprint("checkKtypeGeneric") + // debugprint(ktype::class) + // return false + // return ktype.classifier !is KClass<*> //Interfaces? + // + // } + + //@OptIn(kotlin.ExperimentalStdlibApi::class) //Required for typeOf<>() + private fun checkParameterMatches(kparam: KParameter, arg: Any?): Boolean { + // Check that every argument can be cast to the expected type. + debugprint("\n\ncheckParameterMatches()") + //if (arg != null) debugprint("kparam.type: ${kparam.type}\nkparam.type.classifier: ${kparam.type.classifier}\n\narg::class.createType(): ${arg::class.createType()}\n\narg::class.allSupertypes: ${arg::class.allSupertypes}") + if (arg == null) { + // Multiple dispatch of null between Any and Any? seems ambiguous in Kotlin even without reflection. + // Here, I'm resolving it myself, so it seems fine. + // However, with generics, even if I find the right KCallable, it seems that a nullable argument T? will usually (but not always, depending on when you compiled) be sent to the non-nullable T version of the function. + // KCallable.toString() shows the nullable signature, and KParam.name shows the argument name from the nullable version. But an exception is still thrown, and its text will use the argument name from the non-nullable version. + return kparam.type.isMarkedNullable + // } else if (checkKtypeGeneric(kparam.type)) { + // //Generics are erased at runtime. Type arguments aren't allowed for type parameters, so I can't try to do some kind of "inline fun typeofany(l: C) = T::class" either to get the instance's type arguments. So the best I'm going to try to do is to treat them like Any?. + // return true + } else { + return kparam.type.jvmErasure.isSuperclassOf(arg::class) + //Seems to also work for generics, I guess. + //|| kparam.type in arg::class.allSupertypes + // Not totally sure if these KTypes are singleton-like. Hopefully equality-comparison works for containment here even if they aren't? + } + } + + private fun checkCallableMatches(callable: KCallable, arguments: Array): Boolean { + val params = callable.parameters + debugprint("\n\ncheckCallableMatches()") + debugprint("params: ${params.map{ it.type }}") + return params.size == arguments.size + 1 // Check that the parameters size is same as arguments size plus one for the receiver. + && (params.slice(1..arguments.size) zip arguments).all { + (kparam, arg) -> checkParameterMatches(kparam, arg) } + } + + fun getMatchingCallables(arguments: Array): List> { + debugprint("\ngetMatchingCallables()") + debugprint("instance::class.typeParameters: ${instance::class.typeParameters}") + debugprint("\narguments: ${arguments.toList()}") + return methods.filter { checkCallableMatches(it, arguments) } + } + + fun call(arguments: Array): Any? { + // KCallable's .call() takes varargs instead of an array object. But spreads are expensive. + // To test from Python: + // gameInfo.civilizations.add(1, civInfo) + // gameInfo.civilizations.add(civInfo) + // Both need to work. + debugprint("call()") + val matches = getMatchingCallables(arguments) + //println("matches: $matches") + //println(arguments.toList())// if (matches.size < 1) { throw IllegalArgumentException("No matching signatures found for calling ${instance::class?.simpleName}.${methodName} with given arguments: (${arguments.map{if (it == null) "null" else it::class?.simpleName ?: "null"}.joinToString(", ")})") - //FIXME: A lot of non-null assertions and null checks can probably be replaced with safe calls. + //FIXME: A lot of non-null assertions and null checks (not here, but generally in the codebase) can probably be replaced with safe calls. } if (matches.size > 1) { //I guess could also allow this, producing ambiguous behaviour and leaving it up to the methods to avoid creating such scenarios. Actually no, that sounds like a terrible idea reading it back. - //TODO: Should try to choose most specific signatures based on inheritance hierarchy. + //Could try to choose most specific signatures based on inheritance hierarchy. Does Kotlin do that? Probably not. So shouldn't do it here, then, if that's the case. throw IllegalArgumentException("Multiple matching signatures found for calling ${methodName}:\n\t${matches.map{it.toString()}.joinToString("\n\t")}") } return matches[0]!!.call( @@ -54,14 +119,15 @@ object Reflection { *arguments ) } - }*/ - - fun readInstanceMethod(instance: Any, methodName: String): KCallable { - val method = instance::class.members - .first { it.name == methodName } as KCallable - return method } +// fun readInstanceMethod(instance: Any, methodName: String): KCallable { +// val method = instance::class.members +// .first { it.name == methodName } as KCallable +// return method +// } + fun readInstanceMethod(instance: Any, methodName: String) = InstanceMethodDispatcher(instance, methodName) + fun readInstanceItem(instance: Any, keyOrIndex: Any): Any? { if (keyOrIndex is Int) { return (instance as List)[keyOrIndex] @@ -110,7 +176,8 @@ object Reflection { */ val doEval: Boolean = false, val params: List<@Serializable(with=TokenizingJson.TokenizingSerializer::class) Any?> = listOf() -// val params: List<@Contextual Any?> = listOf() + //val namedParams + //Probably not worth it. But if you want to add support for named arguments in calls (which will also require changing InstanceMethodDispatcher's multiple dispatch resolution, and which respect default arguments), then it will probably have to be in a new field. ) @@ -222,6 +289,7 @@ object Reflection { fun resolveInstancePath(instance: Any, path: List): Any? { + //TODO: Allow passing an ((Any?)->Unit)? (or maybe Boolean) function as a parameter that gets called at every stage of resolution, to let exceptions be thrown if accessing something not whitelisted. var obj: Any? = instance var lastobj0: Any? = null var lastobj1: Any? = null // Keep the second last object traversed, for function calls to bind to. @@ -246,15 +314,16 @@ object Reflection { ) } PathElementType.Call -> { - obj = (obj as KCallable).call( - lastobj1!!, - *( - if (element.doEval) - splitToplevelExprs(element.name).map{ evalKotlinString(instance!!, it) } - else - element.params - ).toTypedArray() - ) +// obj = (obj as KCallable).call( +// lastobj1!!, +// *( +// if (element.doEval) +// splitToplevelExprs(element.name).map{ evalKotlinString(instance!!, it) } +// else +// element.params +// ).toTypedArray() +// ) + obj = (obj as InstanceMethodDispatcher).call(element.params.toTypedArray()) } else -> { throw UnsupportedOperationException("Unknown path element type: ${element.type}") diff --git a/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt b/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt index 5c806ca716f4d..1240c25efc984 100644 --- a/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt +++ b/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt @@ -11,9 +11,6 @@ import com.badlogic.gdx.utils.Json // Automatically running this should probably be a part of the build process. // Probably do whatever is done with TranslationFileWriter. -@Retention(AnnotationRetention.RUNTIME) -annotation class ExposeScriptingApi -// Eventually use this to whitelist safe API accessible members for security. data class ApiSpecDef( var path: String, @@ -104,7 +101,7 @@ class ApiSpecGenerator(val scriptingScope: ScriptingScope) { var c = 0 // Test count. Something like 5,400, IIRC. For now, it's easier to just dynamically generate the API using Python's magic methods and the reflective tools in ScriptingProtocol. JS has proxies too, but other languages may not be so dynamic. // TBF I think some of those might have been GDX/Kotlin/JVM classes, which I should filter oout by .qualifiedName. val output = mutableMapOf>( *classes.map{ - it.qualifiedName!! to it.members.map{ c += 1; makeMemberSpecDef(it) } + it.qualifiedName!! to it.members.map{ c += 1; makeMemberSpecDef(it) } //Reflective function reference instead of wrapping lambda? }.toTypedArray() ) println("\nGathered ${c} property specifications across ${classes.size} classes.\n") diff --git a/core/src/com/unciv/scripting/utils/ScriptingApiAccessible.kt b/core/src/com/unciv/scripting/utils/ScriptingApiAccessible.kt new file mode 100644 index 0000000000000..085f6151716ec --- /dev/null +++ b/core/src/com/unciv/scripting/utils/ScriptingApiAccessible.kt @@ -0,0 +1,17 @@ +package com.unciv.scripting.utils + + +enum class ScriptingApiExposure { + All, Player, Cheats, Mods +} + +@Retention(AnnotationRetention.RUNTIME) +annotation class ScriptingApiAccessible( + val readableBy: Array = arrayOf(ScriptingApiExposure.All), + val settableBy: Array = arrayOf(ScriptingApiExposure.All) +) + +// TODO (Later): Eventually use this to whitelist safe API accessible members for security/permission control. +// Probably keep a debug flag to expose everything. Or just reuse godmode flag? +// Anything that can be called directly by the GUI should probably have Player-level exposure. If it lets rules be broken, that should probably be fixed in Kotlin-side. +// Something will have to be done/decided about members of built-in types too. diff --git a/core/src/com/unciv/scripting/utils/ScriptingApiEnums.kt b/core/src/com/unciv/scripting/utils/ScriptingApiEnums.kt index a429c32c04048..185acd46f676f 100644 --- a/core/src/com/unciv/scripting/utils/ScriptingApiEnums.kt +++ b/core/src/com/unciv/scripting/utils/ScriptingApiEnums.kt @@ -8,6 +8,8 @@ inline fun > enumToMap() = enumValues().associateBy{ it.na /** * For use in ScriptingScope. Allows interpreted scripts to access Unciv Enum constants. + * + * Currently exposes enum values as maps. */ object ScriptingApiEnums { val Stat = enumToMap() diff --git a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt index 6e40184932a61..aed63c7f279ce 100644 --- a/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt +++ b/core/src/com/unciv/ui/consolescreen/ConsoleScreen.kt @@ -140,6 +140,7 @@ class ConsoleScreen(val scriptingState:ScriptingState, var closeAction: () -> Un fun openConsole() { game.setScreen(this) keyPressDispatcher.install(stage) + //TODO: See if can activate text input automatically. Already checked, but check again. this.isOpen = true } diff --git a/core/src/com/unciv/ui/utils/ExtensionFunctions.kt b/core/src/com/unciv/ui/utils/ExtensionFunctions.kt index 3deb2739e485e..153692eb5d507 100644 --- a/core/src/com/unciv/ui/utils/ExtensionFunctions.kt +++ b/core/src/com/unciv/ui/utils/ExtensionFunctions.kt @@ -310,7 +310,7 @@ fun TextureRegion.toPixmap(): Pixmap { textureData.format ) pixmap.drawPixmap( - textureData.consumePixmap(), // The other Pixmap + textureData.consumePixmap(), // The other Pixmap //FIXME: This may be a memory leak. 0, // The target x-coordinate (top left corner) 0, // The target y-coordinate (top left corner) this.regionX, // The source x-coordinate (top left corner) From b8474be540a181860b0f92bb3d2fdebbde406929 Mon Sep 17 00:00:00 2001 From: will-ca Date: Mon, 22 Nov 2021 05:30:15 +0000 Subject: [PATCH 48/93] Cleanup, docs. --- .../enginefiles/python/unciv_lib/api.py | 8 +- core/Module.md | 4 +- .../scripting/protocol/ScriptingProtocol.kt | 14 ++- .../unciv/scripting/reflection/Reflection.kt | 100 ++++++++---------- 4 files changed, 58 insertions(+), 68 deletions(-) diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/api.py b/android/assets/scripting/enginefiles/python/unciv_lib/api.py index c47e9d280e2eb..0a9d3ed4edeef 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/api.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/api.py @@ -40,9 +40,11 @@ def get_help(obj): """Get docstring of object. Fail silently if it has none, or get one through its IPC methods if it's a ForeignObject(). Used for PyAutocompleter.""" try: if isinstance(obj, wrapping.ForeignObject): - doc = f"\n\n{str(obj._docstring_() or wrapping.stringPathList(obj._getpath_()))}\n\nArguments:\n" - # TODO: This should proably be in ForeignObject.__getattr__/__getattribute__. - doc += "\n".join(f"\t{argname}: {argtype}" for argname, argtype in obj._args_()) + doc = f"\n\n{str(obj._docstring_() or wrapping.stringPathList(obj._getpath_()))}\n" + # TODO: Can this be in ForeignObject.__getattr__/__getattribute__? + for funcsig, funcargs in obj._args_().items(): + doc += f"\n{funcsig}\n" + doc += "\n".join(f"\t{argname}: {argtype}" for argname, argtype in funcargs) return doc else: with ipc.FakeStdout() as fakeout: diff --git a/core/Module.md b/core/Module.md index cc1dae0da165a..5b697a6174c86 100644 --- a/core/Module.md +++ b/core/Module.md @@ -294,8 +294,8 @@ Some action types, data formats, and expected response types and data formats fo ``` 'args': 'path': List<{'type':String, 'name':String, 'params':List}>} -> - 'args_response': List> or String - //Names and types of arguments accepted by a function. + 'args_response': Map>> or String + //Map of dispatchable signatures as strings to lists of pairs of names and types of arguments accepted by a function. //Response must be String if sent with Exception flag. //Currently just used by Python autocompleter to generate help text. //Could also be used to control functions in scripting environment. If so, then names of types should be standardized. diff --git a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt index 2740d556bbdeb..d0d636f7d63bd 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt @@ -5,7 +5,6 @@ import com.unciv.scripting.reflection.Reflection import com.unciv.scripting.utils.stringifyException import com.unciv.scripting.utils.TokenizingJson import kotlin.random.Random -import kotlin.reflect.KCallable import kotlinx.serialization.Serializable import kotlinx.serialization.encodeToString import kotlinx.serialization.decodeFromString @@ -376,8 +375,7 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = scope, TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) ) - throw Exception() -// data = TokenizingJson.getJsonElement(leaf is KCallable<*>) + data = TokenizingJson.getJsonElement(leaf is Reflection.InstanceMethodDispatcher) } catch (e: Exception) { data = JsonPrimitive(stringifyException(e)) flags.add(KnownFlag.Exception.value) @@ -389,8 +387,14 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = scope, TokenizingJson.json.decodeFromJsonElement>((packet.data as JsonObject)["path"]!!) ) - throw Exception() -// data = TokenizingJson.getJsonElement((leaf as KCallable<*>).parameters.map { listOf(it.name.toString(), it.type.toString()) }) + data = TokenizingJson.getJsonElement( + mapOf>>( + *((leaf as Reflection.InstanceMethodDispatcher).methods.map { + it.toString() to it.parameters.map{ listOf(it.name?.toString(), it.type.toString()) } + }).toTypedArray() + // The innermost listOf should semantically be a Pair as per the spec in Module.md, but a List seems safer to serialize. + ) + ) } catch (e: Exception) { data = JsonPrimitive(stringifyException(e)) flags.add(KnownFlag.Exception.value) diff --git a/core/src/com/unciv/scripting/reflection/Reflection.kt b/core/src/com/unciv/scripting/reflection/Reflection.kt index f7873512eef58..569828b92a170 100644 --- a/core/src/com/unciv/scripting/reflection/Reflection.kt +++ b/core/src/com/unciv/scripting/reflection/Reflection.kt @@ -6,19 +6,12 @@ import kotlin.reflect.KCallable import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KParameter import kotlin.reflect.KProperty1 -//import kotlin.reflect.KType -//import kotlin.reflect.full.allSupertypes -//import kotlin.reflect.full.defaultType -//import kotlin.reflect.full.createType import kotlin.reflect.full.isSuperclassOf import kotlin.reflect.jvm.jvmErasure -//import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import java.util.* -//TODO: Method dispatch to right methods? - object Reflection { @Suppress("UNCHECKED_CAST") fun readInstanceProperty(instance: Any, propertyName: String): R? { @@ -28,83 +21,81 @@ object Reflection { return property.get(instance) as R? } + /** + * Dynamic multiple dispatch for Any Kotlin instances by methodName. + * + * Uses reflection to first find all members matching the expected method name, and then to narrow them down to members that have the correct signature for a given array of arguments. + * + * @property instance The receiver on which to find and call a method. + * @property methodName The name of the method to resolve and call. + */ class InstanceMethodDispatcher(val instance: Any, val methodName: String) { // This isn't just a nice-to-have feature. Before I implemented it, identical calls from demo scripts to methods with multiple dispatches (E.G. ArrayList().add()) would rarely but randomly fail because the member/signature that was found would change between runs or compilations. // TODO: This is going to need unit tests. // Could try to implement KCallable interface. But not sure it's be worth it or map closely enough— What do lambdas do? I guess isOpen, isAbstract, etc should just all be False? + + /** + * Lazily evaluated list of KCallables for every method that matches the given name. + */ val methods: List> by lazy { instance::class.members.filter{ it.name == methodName }.map{ it as KCallable } } - //Also filter down to is KCallable if name collisions with properties are a possible issue. + //Filter down to is KCallable if name collisions with properties are a possible issue. - fun debugprint(v: Any?): Unit { - if (false) { - println(v) - } - } + /** + * @return Useful representative text. + */ + override fun toString() = """${this::class.simpleName}(instance=${this.instance::class.simpleName}(), methodName="${this.methodName}") with ${this.methods.size} dispatch candidates""" - // private fun ktypeQualified(ktype: KType): String { - // return ktype.toString()//.classifier.qualifiedName - // Identical to ktype.toString(), I believe. - // } - - // private fun checkKtypeGeneric(ktype: KType): Boolean { - // //I'm not seeing any way to check for this in the API... Maybe seeing if .jvmErasure or .classifier errors. - // //But the single capital letter convention seems pretty universal. And I think real classes will have a dot in their name. So this will hopefully catch all cases. - // debugprint("checkKtypeGeneric") - // debugprint(ktype::class) - // return false - // return ktype.classifier !is KClass<*> //Interfaces? - // - // } - - //@OptIn(kotlin.ExperimentalStdlibApi::class) //Required for typeOf<>() + /** + * @return Whether a given argument value can be cast to the type of a given KParameter. + */ private fun checkParameterMatches(kparam: KParameter, arg: Any?): Boolean { - // Check that every argument can be cast to the expected type. - debugprint("\n\ncheckParameterMatches()") - //if (arg != null) debugprint("kparam.type: ${kparam.type}\nkparam.type.classifier: ${kparam.type.classifier}\n\narg::class.createType(): ${arg::class.createType()}\n\narg::class.allSupertypes: ${arg::class.allSupertypes}") + // These could be static. if (arg == null) { // Multiple dispatch of null between Any and Any? seems ambiguous in Kotlin even without reflection. // Here, I'm resolving it myself, so it seems fine. - // However, with generics, even if I find the right KCallable, it seems that a nullable argument T? will usually (but not always, depending on when you compiled) be sent to the non-nullable T version of the function. - // KCallable.toString() shows the nullable signature, and KParam.name shows the argument name from the nullable version. But an exception is still thrown, and its text will use the argument name from the non-nullable version. + // However, with generics, even if I find the right KCallable, it seems that a nullable argument T? will usually (but not always, depending on when you compiled) be sent to the non-nullable T version of the function if one has been defined. + // KCallable.toString() shows the nullable signature, and KParam.name shows the argument name from the nullable version. But an exception is still thrown on .call(), and its text will use the argument name from the non-nullable version. + // I suppose it's not a problem as it seems broken in Kotlin generally. return kparam.type.isMarkedNullable - // } else if (checkKtypeGeneric(kparam.type)) { - // //Generics are erased at runtime. Type arguments aren't allowed for type parameters, so I can't try to do some kind of "inline fun typeofany(l: C) = T::class" either to get the instance's type arguments. So the best I'm going to try to do is to treat them like Any?. - // return true } else { return kparam.type.jvmErasure.isSuperclassOf(arg::class) - //Seems to also work for generics, I guess. - //|| kparam.type in arg::class.allSupertypes - // Not totally sure if these KTypes are singleton-like. Hopefully equality-comparison works for containment here even if they aren't? + //Seems to also work for generics, I guess. } } + /** + * @return Whether a given KCallable's signature might be compatible with a given Array of arguments. + */ private fun checkCallableMatches(callable: KCallable, arguments: Array): Boolean { + // I'm not aware of any situation where this function's behaviour will deviate from Kotlin, but that doesn't mean there aren't any. Wait, no. I do know that runtime checking and resolution of erased generics will probably be looser than at compile time. They seem to act like Any(?). val params = callable.parameters - debugprint("\n\ncheckCallableMatches()") - debugprint("params: ${params.map{ it.type }}") return params.size == arguments.size + 1 // Check that the parameters size is same as arguments size plus one for the receiver. - && (params.slice(1..arguments.size) zip arguments).all { + && (params.slice(1..arguments.size) zip arguments).all { // Check argument classes match parameter types, skipping the receiver. (kparam, arg) -> checkParameterMatches(kparam, arg) } } + /** + * @return A list containing a KCallable for every version of this dispatcher's method that has a signature which may be compatible with a given Array of arguments. + */ fun getMatchingCallables(arguments: Array): List> { - debugprint("\ngetMatchingCallables()") - debugprint("instance::class.typeParameters: ${instance::class.typeParameters}") - debugprint("\narguments: ${arguments.toList()}") return methods.filter { checkCallableMatches(it, arguments) } } + /** + * Call the correct version of the method for a given array of arguments. + * + * @param arguments The arguments with which to call the method. + * @return The result from dispatching the given arguments to the method definition with a compatible signature. + * @throws IllegalArgumentException If no compatible signature was found, or if more than one compatible signature was found. + */ fun call(arguments: Array): Any? { - // KCallable's .call() takes varargs instead of an array object. But spreads are expensive. + // KCallable's .call() takes varargs instead of an array object. But spreads are expensive, so I'm not doing that. // To test from Python: // gameInfo.civilizations.add(1, civInfo) // gameInfo.civilizations.add(civInfo) // Both need to work. - debugprint("call()") val matches = getMatchingCallables(arguments) - //println("matches: $matches") - //println(arguments.toList())// if (matches.size < 1) { throw IllegalArgumentException("No matching signatures found for calling ${instance::class?.simpleName}.${methodName} with given arguments: (${arguments.map{if (it == null) "null" else it::class?.simpleName ?: "null"}.joinToString(", ")})") //FIXME: A lot of non-null assertions and null checks (not here, but generally in the codebase) can probably be replaced with safe calls. @@ -121,13 +112,6 @@ object Reflection { } } -// fun readInstanceMethod(instance: Any, methodName: String): KCallable { -// val method = instance::class.members -// .first { it.name == methodName } as KCallable -// return method -// } - fun readInstanceMethod(instance: Any, methodName: String) = InstanceMethodDispatcher(instance, methodName) - fun readInstanceItem(instance: Any, keyOrIndex: Any): Any? { if (keyOrIndex is Int) { return (instance as List)[keyOrIndex] @@ -301,7 +285,7 @@ object Reflection { try { obj = readInstanceProperty(obj!!, element.name) } catch (e: ClassCastException) { - obj = readInstanceMethod(obj!!, element.name) + obj = InstanceMethodDispatcher(obj!!, element.name) } } PathElementType.Key -> { From 2ffd5ac215234dfd67a7a6ae9a5bf03f73b156fc Mon Sep 17 00:00:00 2001 From: will-ca Date: Mon, 22 Nov 2021 20:12:50 +0000 Subject: [PATCH 49/93] Fix pre-existing memory leak, string-parsing reflective call params. Docs, cleanup. --- .../enginefiles/python/unciv_lib/api.py | 4 +- .../enginefiles/python/unciv_lib/wrapping.py | 2 +- .../unciv_scripting_examples/EndTimes.py | 2 + .../MapEditingMacros.py | 1 + .../assets/scripting/enginefiles/qjs/main.js | 2 +- .../src/com/unciv/scripting/ScriptingScope.kt | 4 +- .../src/com/unciv/scripting/ScriptingState.kt | 3 ++ .../scripting/protocol/ScriptingProtocol.kt | 6 +-- .../protocol/ScriptingReplManager.kt | 2 +- .../unciv/scripting/reflection/Reflection.kt | 43 +++++++++---------- .../scripting/utils/InstanceFactories.kt | 1 + .../com/unciv/ui/utils/ExtensionFunctions.kt | 6 ++- .../ui/worldscreen/mainmenu/OptionsPopup.kt | 5 ++- 13 files changed, 44 insertions(+), 37 deletions(-) diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/api.py b/android/assets/scripting/enginefiles/python/unciv_lib/api.py index 0a9d3ed4edeef..fe284389704cb 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/api.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/api.py @@ -86,7 +86,7 @@ def isForeignToken(obj): class UncivReplTransceiver(ipc.ForeignActionReceiver, ipc.ForeignActionSender): - """Class that implements the Unciv IPC and scripting protocol by receiving and responding to its packets.""" + """Class that implements the Unciv IPC and scripting protocol by receiving and responding to its packets. See Module.md.""" def __init__(self, *args, apiscope=None, autocompleter=None, **kwargs): ipc.ForeignActionReceiver.__init__(self, *args, **kwargs) self.autocompleter = autocompleter @@ -100,7 +100,7 @@ def populateApiScope(self): self.scope.update({**self.apiscope, **self.scope}) # TODO: Replace this update with Kotlin-side init? def passMic(self): - """Send a 'PassMic' packet. See Module.md.""" + """Send a 'PassMic' packet.""" #TODO: This should use ForeignPacket(), no? self.SendForeignAction({'action':None, 'identifier': None, 'data':None, 'flags':('PassMic',)}) @ipc.receiverMethod('motd', 'motd_response') diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py index ae6ccb1ab3fec..4235bbe3f700f 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py @@ -182,7 +182,7 @@ def alreadyhas(name): @ResolveForOperators class ForeignObject: - """Wrapper for a foreign object Implements ScriptingProtocol.""" + """Wrapper for a foreign object. Implements the specifications on IPC packet action types and data structures in Module.md.""" def __init__(self, path, foreignrequester=dummyForeignRequester): object.__setattr__(self, '_path', (makePathElement(name=path),) if isinstance(path, str) else tuple(path)) object.__setattr__(self, '_foreignrequester', foreignrequester) diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EndTimes.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EndTimes.py index d2ea4c3d2f231..29fc9f53e928e 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EndTimes.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/EndTimes.py @@ -27,6 +27,8 @@ def scatterFallout(focus, improvementtype, maxdistance, tileselector=lambda t: T pass def spawnNewDisasters(naturalwonder, *falloutparams): + # Foreign calls are expensive. So instead of iterating over every tile on the map, we generate the coords in Python and then only work with the foreign object when we've already chosen the coordinates. + # Every attribute access in Python creates a new ForeignObject() wrapper, and every path element requires an extra reflective member resolution in Kotlin. So we reduce the overhead by reusing the same wrapper in Python, and by storing its target at a shorter path in the JVM. pass def spreadFalloutType(improvementtype, tilepermitter=lambda t: True): diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py index 01d731052bed1..656c7b9f2351a 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/MapEditingMacros.py @@ -228,6 +228,7 @@ def setMapFromImage(tileMap, image, pixelinterpreter=lambda pixel: "Ocean"): for tile in tileMap.values: # Since this just uses PIL images, we could also blur the image, or perhaps just jitter the sampled coordinates, by the projected tile radius here. Or could have earlier functions in the call stack wrap the image in a class that does that. + # See if reducing the Kotlin/JVM reflection depth by assigning tile to apiHelpers.registeredInstances reduces run time here. setTerrain( tile, pixelinterpreter(image.getpixel(( diff --git a/android/assets/scripting/enginefiles/qjs/main.js b/android/assets/scripting/enginefiles/qjs/main.js index ce6a06fbe324f..4a983f08d7197 100644 --- a/android/assets/scripting/enginefiles/qjs/main.js +++ b/android/assets/scripting/enginefiles/qjs/main.js @@ -12,7 +12,7 @@ With the lightweight nature and easy sandboxing of JS and Lua, plus the ready av Python should be disabled for downloaded mods, I think. CPython's big and porous, PyPy has a sandbox but it's complicated, and MicroPython's just a smaller and less compatible reimplementation of CPython— At which point, you as well just use JS/Lua. -Instead, CPython can be the favoured interpreter for developer tools and user script macros. Debug inspection, map editor tools, prototype features and research projects, player-written automation, etc. Because it wouldn't need to be sandboxed in these types of uses, this would let Python's massive standard library and high extensibility shine. Numpy, Cython modules, C extensions and CTypes, PIL, Tensorflow, etc would all be possible to use, as would the user's filesystem and their own modules. +Instead, CPython can be the favoured interpreter for developer tools and user script macros. Debug inspection, map editor tools, prototype features and research projects, player-written automation, etc. Because it wouldn't need to be sandboxed in these types of uses, this would let Python's massive library ecosystem and high extensibility shine. Numpy, Cython modules, C extensions and CTypes, PIL, Tensorflow, etc would all be possible to use, as would the user's filesystem and their own modules. So JS and Lua can be made highly portable/lightweight, and safely sandboxed to run mods. Meanwhile, CPython, if it's installed on the user's system, can be used as a richer scripting environment for developer/modder tools and user customization. ` diff --git a/core/src/com/unciv/scripting/ScriptingScope.kt b/core/src/com/unciv/scripting/ScriptingScope.kt index 063bfe5f60de1..fb480e7754806 100644 --- a/core/src/com/unciv/scripting/ScriptingScope.kt +++ b/core/src/com/unciv/scripting/ScriptingScope.kt @@ -78,10 +78,10 @@ class ScriptingScope( val fakepng = ByteArrayOutputStream() //Close this steam? Well, the docs say doing so "has no effect", and it should clearly get GC'd anyway. val pixmap = ImageGetter.getDrawable(path).getRegion().toPixmap() - val exporter = PixmapIO.PNG() + val exporter = PixmapIO.PNG() // Could be kept and "reused to encode multiple PNGs with minimal allocation", according to the docs. I don't see it as a sufficient bottleneck yet to necesarily justify the complexity and risk, though. exporter.setFlipY(false) exporter.write(fakepng, pixmap) - pixmap.dispose() // Doesn't seem to help with the memory leak. + pixmap.dispose() // In theory needed to avoid memory leak. Doesn't seem to actually have any impact, compared to the .dispose() inside .toPixmap(). Maybe the exporter's dispose also calls this? exporter.dispose() // This one should be called automatically by GC anyway. return String(Base64Coder.encode(fakepng.toByteArray())) } diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index eb00566d673ae..fe1209e0022af 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -13,6 +13,9 @@ fun ArrayList.clipIndexToBounds(index: Int, extendsize: Int = 0): Int { return max(0, min(this.size-1+extendsize, index)) } +// TODO: Check for places to use Sequences. +// Hm. It seems that Sequence performance isn't even a simple question of number of loops, and is also affected by boxed types and who know what else. +// Premature optimization and such. Clearly long chains of loops can be rewritten as sequences. // TODO: Replace Exception types with Throwable? Wait, no. Apparently that just includes "serious problems that a reasonable application should not try to catch." diff --git a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt index d0d636f7d63bd..a99399019f310 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingProtocol.kt @@ -158,7 +158,7 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = /** * Functions to generate requests to send to a script interpreter. * - * Implements the specifications on packet field and structure in Module.md. + * Implements the specifications on packet fields and structure in Module.md. * Function names and call arguments parallel ScriptingBackend. */ object makeActionRequests { @@ -189,7 +189,7 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = /** * Functions to parse a response packet received after a request packet sent to a scripting interpreter. * - * Implements the specifications on packet field and structure in Module.md. + * Implements the specifications on packet fields and structure in Module.md. * Function names and return types parallel ScriptingBackend. */ object parseActionResponses { @@ -420,5 +420,3 @@ class ScriptingProtocol(val scope: Any, val instanceSaver: MutableList? = } } - - diff --git a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt index 5d2230e3de585..76f89edc3012a 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt @@ -52,7 +52,7 @@ class ScriptingProtocolReplManager(scriptingScope: ScriptingScope, blackbox: Bla * ScriptingProtocol puts references to pre-tokenized returned objects in here. * Should be cleared here at the end of each REPL execution. * - * This makes sure a single script execution doesn't get its tokenized Kotlin/JVM objects garbage collected, and has a chance to save them elsewhere (E.G. ScriptingScope.apiHelpers) if it needs them later. + * This makes sure a single script execution doesn't get its tokenized Kotlin/JVM objects garbage collected, and has a chance to save them elsewhere (E.G. ScriptingScope.apiHelpers.registeredInstances) if it needs them later. * Should preserve each instance, not just each value, so should be List and not Set. * To test in Python console backend: x = apiHelpers.Factories.Vector2(1,2); civInfo.endTurn(); print(apiHelpers.toString(x)) */ diff --git a/core/src/com/unciv/scripting/reflection/Reflection.kt b/core/src/com/unciv/scripting/reflection/Reflection.kt index 569828b92a170..c40330f03f420 100644 --- a/core/src/com/unciv/scripting/reflection/Reflection.kt +++ b/core/src/com/unciv/scripting/reflection/Reflection.kt @@ -30,7 +30,7 @@ object Reflection { * @property methodName The name of the method to resolve and call. */ class InstanceMethodDispatcher(val instance: Any, val methodName: String) { - // This isn't just a nice-to-have feature. Before I implemented it, identical calls from demo scripts to methods with multiple dispatches (E.G. ArrayList().add()) would rarely but randomly fail because the member/signature that was found would change between runs or compilations. + // This isn't just a nice-to-have feature. Before I implemented it, identical calls from demo scripts to methods with multiple versions (E.G. ArrayList().add()) would rarely but randomly fail because the member/signature that was found would change between runs or compilations. // TODO: This is going to need unit tests. // Could try to implement KCallable interface. But not sure it's be worth it or map closely enough— What do lambdas do? I guess isOpen, isAbstract, etc should just all be False? @@ -50,10 +50,11 @@ object Reflection { */ private fun checkParameterMatches(kparam: KParameter, arg: Any?): Boolean { // These could be static. + // TODO: Inline these, actually. if (arg == null) { // Multiple dispatch of null between Any and Any? seems ambiguous in Kotlin even without reflection. // Here, I'm resolving it myself, so it seems fine. - // However, with generics, even if I find the right KCallable, it seems that a nullable argument T? will usually (but not always, depending on when you compiled) be sent to the non-nullable T version of the function if one has been defined. + // However, with generics, even if I find the right KCallable, it seems that a nullable argument T? will usually (but not always, depending on each time you compile) be sent to the non-nullable T version of the function if one has been defined. // KCallable.toString() shows the nullable signature, and KParam.name shows the argument name from the nullable version. But an exception is still thrown on .call(), and its text will use the argument name from the non-nullable version. // I suppose it's not a problem as it seems broken in Kotlin generally. return kparam.type.isMarkedNullable @@ -156,6 +157,8 @@ object Reflection { val name: String, /** * For key and index accesses, and function calls, whether to evaluate name instead of using params for arguments/key. + * This lets simple parsers be written and used, that can simply break up a common subset of many programming languages into string components without themselves having to analyze or understand any more complex semantics. + * * Default should be false, so deserialized JSON path lists are configured correctly in ScriptingProtocol.kt. */ val doEval: Boolean = false, @@ -245,15 +248,15 @@ object Reflection { } - fun stringifyKotlinPath() { - } +// fun stringifyKotlinPath() { +// } - private val closingbrackets = null +// private val closingbrackets = null - data class OpenBracket( - val char: Char, - var offset: Int - ) +// data class OpenBracket( +// val char: Char, +// var offset: Int +// ) //class OpenBracketIterator() { //} @@ -275,11 +278,7 @@ object Reflection { fun resolveInstancePath(instance: Any, path: List): Any? { //TODO: Allow passing an ((Any?)->Unit)? (or maybe Boolean) function as a parameter that gets called at every stage of resolution, to let exceptions be thrown if accessing something not whitelisted. var obj: Any? = instance - var lastobj0: Any? = null - var lastobj1: Any? = null // Keep the second last object traversed, for function calls to bind to. for (element in path) { - lastobj1 = lastobj0 - lastobj0 = obj when (element.type) { PathElementType.Property -> { try { @@ -298,16 +297,14 @@ object Reflection { ) } PathElementType.Call -> { -// obj = (obj as KCallable).call( -// lastobj1!!, -// *( -// if (element.doEval) -// splitToplevelExprs(element.name).map{ evalKotlinString(instance!!, it) } -// else -// element.params -// ).toTypedArray() -// ) - obj = (obj as InstanceMethodDispatcher).call(element.params.toTypedArray()) + obj = (obj as InstanceMethodDispatcher).call( + ( + if (element.doEval) + splitToplevelExprs(element.name).map{ evalKotlinString(instance!!, it) } + else + element.params + ).toTypedArray() + ) } else -> { throw UnsupportedOperationException("Unknown path element type: ${element.type}") diff --git a/core/src/com/unciv/scripting/utils/InstanceFactories.kt b/core/src/com/unciv/scripting/utils/InstanceFactories.kt index 70bdecd69132c..bcf2fb9275046 100644 --- a/core/src/com/unciv/scripting/utils/InstanceFactories.kt +++ b/core/src/com/unciv/scripting/utils/InstanceFactories.kt @@ -7,6 +7,7 @@ import com.badlogic.gdx.math.Vector2 * For use in ScriptingScope. Allows interpreted scripts to make new instances of Kotlin/JVM classes. */ object InstanceFactories { + fun Array() = "NotImplemented" fun Vector2(x: Float, y: Float) = com.badlogic.gdx.math.Vector2(x, y) fun MapUnit() = "NotImplemented" } diff --git a/core/src/com/unciv/ui/utils/ExtensionFunctions.kt b/core/src/com/unciv/ui/utils/ExtensionFunctions.kt index 153692eb5d507..53ca7763c7eea 100644 --- a/core/src/com/unciv/ui/utils/ExtensionFunctions.kt +++ b/core/src/com/unciv/ui/utils/ExtensionFunctions.kt @@ -296,6 +296,8 @@ fun List.randomWeighted(weights: List, random: Random = Random): T * Originally a function defined in the Fonts Object and used only in Fonts.kt. * Turned into an extension method to 1. Clean up the syntax and 2. Use it to expose internal, packed images to scripts in class ScriptingScope. * + * .dispose() must be called on the returned Pixmap when it is no longer needed, or else it will leave a memory leak behind. + * * @return New Pixmap with all the size and pixel data from this TextureRegion copied into it. */ // From https://stackoverflow.com/questions/29451787/libgdx-textureregion-to-pixmap @@ -309,8 +311,9 @@ fun TextureRegion.toPixmap(): Pixmap { this.regionHeight, textureData.format ) + val textureDataPixmap = textureData.consumePixmap() pixmap.drawPixmap( - textureData.consumePixmap(), // The other Pixmap //FIXME: This may be a memory leak. + textureDataPixmap, // The other Pixmap 0, // The target x-coordinate (top left corner) 0, // The target y-coordinate (top left corner) this.regionX, // The source x-coordinate (top left corner) @@ -318,6 +321,7 @@ fun TextureRegion.toPixmap(): Pixmap { this.regionWidth, // The width of the area from the other Pixmap in pixels this.regionHeight // The height of the area from the other Pixmap in pixels ) + textureDataPixmap.dispose() // Prevent memory leak. return pixmap } diff --git a/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt b/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt index f9e2e3d93baa1..1b0cb860380c0 100644 --- a/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt +++ b/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt @@ -250,8 +250,9 @@ class OptionsPopup(val previousScreen: BaseScreen) : Popup(previousScreen) { settings.enableScriptingConsole) { settings.enableScriptingConsole = it } - //TODO: Persist command history. - //TODO: Startup macros per backend type. + //TODO: Disable on Android. + //TODO (Later): Persist command history? + //TODO (Later): Startup macros per backend type. if (previousScreen.game.limitOrientationsHelper != null) { addYesNoRow("Enable portrait orientation", settings.allowAndroidPortrait) { From 4c976b8b22f3f4a981a58755fe2230a3ec5c0c9c Mon Sep 17 00:00:00 2001 From: will-ca Date: Tue, 23 Nov 2021 09:18:06 +0000 Subject: [PATCH 50/93] Docs, examples. --- .../enginefiles/python/PythonScripting.md | 116 +++++++++++++++++- .../enginefiles/python/unciv_lib/wrapping.py | 5 +- .../unciv_scripting_examples/Merfolk.py | 1 + .../unciv_scripting_examples/PlayerMacros.py | 1 + .../ProceduralTechtree.py | 91 ++++++++++++++ core/Module.md | 16 ++- .../src/com/unciv/scripting/ScriptingScope.kt | 10 +- .../src/com/unciv/scripting/ScriptingState.kt | 2 + .../protocol/ScriptingReplManager.kt | 2 + .../unciv/scripting/reflection/Reflection.kt | 5 +- .../unciv/scripting/utils/ApiSpecGenerator.kt | 2 + .../scripting/utils/InstanceFactories.kt | 12 ++ 12 files changed, 251 insertions(+), 12 deletions(-) create mode 100644 android/assets/scripting/enginefiles/python/unciv_scripting_examples/ProceduralTechtree.py diff --git a/android/assets/scripting/enginefiles/python/PythonScripting.md b/android/assets/scripting/enginefiles/python/PythonScripting.md index 9fffe96f8bb77..b6c06a89c6774 100644 --- a/android/assets/scripting/enginefiles/python/PythonScripting.md +++ b/android/assets/scripting/enginefiles/python/PythonScripting.md @@ -270,10 +270,9 @@ Further tools can be imported as `unciv_pyhelpers`. This is useful when writing modules that are meant to be imported from the main Unciv Python namespace. -In a file in your `PYTHONPATH`/`sys.path`: - ```python3 -#MyCoolModule.py +# MyCoolModule.py +# In PYTHONPATH/sys.path. import unciv import unciv_pyhelpers @@ -283,9 +282,9 @@ def printCivilizations(): print(f"{unciv_pyhelpers.real(civ.nation.name)}: {len(civ.cities)} cities") ``` -In Unciv: - ```python3 +# In Unciv. + >>> import MyCoolModule >>> MyCoolModule.printCivilizations() ``` @@ -296,6 +295,113 @@ In Unciv: --- +## Performance and Gotchas + +Initiating a foreign actions is likely to be expensive. They have to be encoded as request packets, serialized as JSON, sent to Kotlin/the JVM, decoded there, and evaluated in Kotlin/JVM using slow reflective mechanisms. The results then have to go through this entire process in reverse in order to return a value to Python. + +However, code running in Kotlin/the JVM is also likely to be much faster than code running in Python. The danger is in wasting lots of time bouncing back and forth just to exchange small amounts of data. + +Efficient scripts should try to do as much of their work in the same environment as possible. + +If something can be done with a single foreign action, then it probably should be, as that way the statically compiled and JIT-optimized JVM bytecode can do most of the heavy lifting. However, if a task can't be done in a single foreign action, then as much work should be done completely in Python as possible, in order to reduce the number of high-overhead IPC calls used. + +```python3 +def slow(): + for tile in gameInfo.tileMap.values: + # Iteration implicitly uses 1 IPC "length" action at the start. + tile.naturalWonder = "Krakatoa" if random.random() < 1/len(gameInfo.tileMap.values)*20 else tile.naturalWonder + # On every loop: + # +1 IPC "length" action in the "if". + # +1 IPC "read" action for the current .naturalWonder if reaching the "else" block. (Only gets resolved on serialization in the next step.) + # +1 IPC "assign" action to update .naturalWonder, even if it's not changing. + # This happens once for every tile— Hundreds or thousands of times in total. + +def faster(): + sizex = len(gameInfo.tileMap.tileMatrix) - 1 + sizey = len(gameInfo.tileMap.tileMatrix[0]) - 1 + # 2 IPC "read" actions for max map bounds at the start. + targetcount = random.randint(15, 25) + i = 0 + while i < targetcount: + x = random.randint(0, sizex) + y = random.randint(0, sizey) + if real(gameInfo.tileMap.tileMatrix[x][y]) is not None: + # +1 IPC "read" action. + # On hexagonal maps, check for validity. To be faster yet, could numerically do this in Python. + gameInfo.tileMap.tileMatrix[x][y] = "Krakatoa" + # +1 IPC "assign" action. + # Only done after already selecting coordinates and checking validity. + i += 1 + # Only iterate for as long as needed to make the wanted changes + +def fastest(): + apiHelpers.scatterRandomFeature("Krakatoa", random.randint(15, 25)) + # Only 1 IPC "assign" action. + # This function doesn't actually exist. But the point is that when available, a single IPC call that causes all of the work to then be done in the JVM is likely to be faster than a script-micromanaged solution. E.G., use one call to List<*>.addAll() instead of many calls to List<*>.add(). +``` + +Every time you access an attribute or item on a foreign wrapper in Python creates and initializes a new foreign wrapper object. So for code blocks that use a wrapper object at the same path multiple times, it may be worth saving a single wrapper at the start instead. + +```python3 +def slow(): + for i in len(civInfo.cities[0].cityStats.cityInfo.tilesInRange): + print(civInfo.cities[0].cityStats.cityInfo.tilesInRange[i]) + # Every loop starts out with civInfo, and then constructs a new wrapper object in Python for every attribute and item access. + +def faster(): + tilesInRange = civInfo.cities[0].cityStats.cityInfo.tilesInRange + for i in len(tilesInRange): + print(tilesInRange[i]) + # Saves 5 Python object instantiations with every loop! +``` + +Every element in the path sent by a wrapper object to Kotlin/the JVM also requires the Kotlin side to perform an additional reflective member resolution step. + +```python3 +def slow(): + for i in range(1000): + pass + + +def alsoSlow(): + uselesscache = + for i in range(1000): + uselesscache += + # Assigning the wrapper object to a name in Python saves on Python attribute time. But it doesn't actually shorten its Kotlin/JVM path, so the same number of steps still have to be taken when the Kotlin/JVM side processes the packet sent by Python. + +def fast(): + apiHelpers.registeredInstances["usefulcache"] = + usefulcache = apiHelpers.registeredInstances["usefulcache"] + for i in range(1000): + usefulcache. += + # Assigning the leaf wrapper to a single Python name reduces the number of new wrapper objects built in Python each loop to . + # Assigning the foreign object to + del apiHelpers.registeredInstances["usefulcache"] + +``` + +Because iteration over wrapper objects is currently implemented in Python by returning a new wrapper for every index within their length, foreign set-like containers without indices and generator-like iterables without fixed lengths cannot be idiomatically iterated over from Python. + +To get around this, you can simply resolve them into their serialized JSON forms. This turns them into JSON arrays and Python lists of primitive values and foreign token strings, on which regular Python iteration can take operate. + +```python3 +for e in civInfo.cities[0].cityStats.cityInfo.tiles: + print(e) + # Fails. CityInfo.tiles is a set-like instance that does not take indices. + +for i in range(len(civInfo.cities[0].cityStats.currentCityStats.values)): + print(i) + # Also fails. CityStats.currentCityStats.values is an iterator-like instance without a known length. + +for e in real(civInfo.cities[0].cityStats.currentCityStats.values): + print(e) + # Works. +``` + +Because the elements yielded this way do not have equivalent paths in the Kotlin/JVM namespace, and are not foreign object wrappers, any complex objects will have to be assigned as token strings to a concrete path in order to do anything with them. + +--- + ## Other Languages The Python-specific behaviour is not meant as a hard standard, in that it doesn't have to be copied exactly in any other languages. Some other design may be more suited for ECMAScript, Lua, and other possible backends. If implementing another language, I think some attempt should still be made to keep a similar API and feature equivalence, though. diff --git a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py index 4235bbe3f700f..8baf7ed37631a 100644 --- a/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py +++ b/android/assets/scripting/enginefiles/python/unciv_lib/wrapping.py @@ -197,18 +197,19 @@ def __getattr__(self, name): return self.__class__((*self._path, makePathElement(name=name)), self._foreignrequester) def __getattribute__(self, name): if name in ('values', 'keys', 'items'): - # Don't expose real .keys, .values, or .entries unless wrapping a foreign mapping. This prevents foreign members like TileMap.values from being blocked. + # Don't expose real .keys, .values, or .items unless wrapping a foreign mapping. This prevents foreign attributes like TileMap.values from being blocked. if not self._ismapping_(): return self.__getattr__(name) return object.__getattribute__(self, name) def __getitem__(self, key): return self.__class__((*self._path, makePathElement(ttype='Key', params=(key,))), self._foreignrequester) #TODO: Should negative indexing from end be supported? + #IIRC I decided "No". Not entirely sure why. Probably a mix of needing a __len__ IPC call for that, plus incongruency with Kotlin (and other languages') behaviour, and complexity here. def __iter__(self): try: return iter(self.keys()) except: - return (self[i] for i in range(0, len(self))) + return (self[i] for i in range(0, len(self))) #TODO: Obviously this won't work for sets. Practical example why that's a problem: CityInfo stores HashSet()s of tiles. Workaround: Call real() on the whole set, and use the resulting values or foreign tokens. Unindexability/potential unorderedness of sets means that iteration would have to be handled from the Kotlin side, which means, at minimum implementing the BeginIteration and EndIteration flags, plus an entire new type of PassMic loop. Even then, without indexes, you'd only get the raw value or foreign token anyway def __setattr__(self, name, value): return getattr(self, name)._setvalue_(value) def __setitem__(self, key, value): diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Merfolk.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Merfolk.py index aeb4c8f2f825f..cbb30b826e817 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Merfolk.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/Merfolk.py @@ -24,6 +24,7 @@ # Seed the oceans with basic and luxury resources. # If you really piss them off, flood/destroy your capital and displace its population. +#civInfo.cities[0].cityStats.cityInfo.resistanceCounter def onGameStart(): pass diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/PlayerMacros.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/PlayerMacros.py index 2c8dcb65dc907..c45841ab4e001 100644 --- a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/PlayerMacros.py +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/PlayerMacros.py @@ -60,6 +60,7 @@ def buildCitiesQueue(cities, order): with TokensAsWrappers(city.cityConstructions.getBuildableBuildings()) as queue: pass #apiHelpers.registeredObjects["x"] = city.cityConstructions.getBuildableBuildings() + #civInfo.cities[0].cityStats.cityInfo.cityConstructions.builtBuildings # HashSet(). Can do "in" via IPC magic, and made real(). But not iterable since __iter__ requires indexing. def rebaseUnitsEvenly(units=('Guided Missile',), ): if isinstance(units, str): diff --git a/android/assets/scripting/enginefiles/python/unciv_scripting_examples/ProceduralTechtree.py b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/ProceduralTechtree.py new file mode 100644 index 0000000000000..890256de71e4b --- /dev/null +++ b/android/assets/scripting/enginefiles/python/unciv_scripting_examples/ProceduralTechtree.py @@ -0,0 +1,91 @@ +""" +Proof-of-concept and developmental test case for potential future modding API. + +Adds functions to extend the tech tree indefinitely, with randomly generated new buildings, wonders, and units. + + +Call extendTechTree() at any time to add a new tier of technology. + +Call clearTechTree() and then extendTechTree(iterations=20) at any time to replace all undiscovered parts of the base tech tree with an entire new randomly generated one. + +Call scrambleTechTree() to keep all current technologies but randomize the order in which they are unlocked. +""" + + +import random + +# from unciv import * +# from unciv_pyhelpers import * + + +# techtree = gameInfo.ruleSet.technologies + +name_parts = { + "Building": ( + ("Old ", "Earth ", "Alien ", "Feedsite ", "Holo-", "Cel ", "Xeno ", "Xeno-" "Terra ", "Thorium ", "Xenofuel ", "Biofuel ", "Gaian ", "Field ", "Tidal ", "Cloning ", "Mass ", "Grow-", "Molecular ", "Nano-", "Civil ", "Cyto-", "Pharma-", "Gene ", "Bionics ", "Optical ", "Soma ", "Progenitor ", "Neuro-", "Organ ", "Hyper-", "Trade ", "Auto-", "Bio-", "Alloy ", "Dry-", "Repair ", "LEV ", "Microbial ", "Bore-", "Bioglass ", "Sky-", "Warp ", "Ultrasonic ", "Rocket ", "Defence ", "Surveillance ", "Command ", "Node ", "Mosaic ", "Sonar ", "Torpedo ", "Frontier ", "Drone ", "Launch ", "Mind ", "Neo-", "Voice ", "Pan-Spectral ", "Petrochemical ", "Thermohaline ", "Water ", "Xenomass "), + ("Relic", "Preserve", "Hub", "Suite", "Cradle", "Sanctuary", "Vault", "Reactor", "Plant", "Well", "Turbine", "Vivarium", "Digester", "Lab", "Forge", "Pasture", "Crèche", "Clinic", "Nursery", "Garden", "Smelter", "Surgery", "Distillery", "Laboratory", "Observatory", "Network", "Institute", "Printer", "Mantle", "Core", "Depot", "Recycler", "Factory", "Foundry", "Dock", "Facility", "Mine", "Hole", "Furnace", "Crane", "Spire", "Fence", "Battery", "Perimeter", "Web", "Center", "Bank", "Hull", "Net", "Stadium", "Augmentery", "Command", "Complex", "Stem", "Planetarium", "Archives", "Rudder", "Refinery", "House") + ), + "Unit": ( + ("Combat ", "Missile ", "Patrol ", "Gun-", "Tac-", "Xeno ", "Rock-", "Battle-", "LEV ", "Drone ", "Auto-", "Nano-", "Gelio-", "All-", "Laser-", "Phasal ", "Solar ", "Wolf ", "Raptor ", "Siege ", "Sea ", "Hydra-", "Tide-", "Under-", "Needle-", "Evolved ", "True ", "Prime ", "First ", "Master ", "Elder "), + ("Explorer", "Soldier", "Ranger", "Rover", "Boat", "Submarine", "Carrier", "Jet", "Swar", "Cavalry", "Octopus", "Titan", "Suit", "Aegis", "Tank", "Destroyer", "CNDR", "CARVR", "SABR", "ANGEL", "Immortal", "Architect", "Throne", "Cage", "Sled", "Golem", "Hive", "Pod", "Aquilon", "Seer", "Matrix", "Laser", "Carver", "Siren", "Batle", "Bug", "Worm", "Drones", "Manticore", "Dragon", "Kraken", "Coral", "Makara", "Ripper", "Scarab", "Marine", "Brawler", "Sentinel", "Disciple", "Maurauder", "Centurion", "Apostle", "Champion", "Eidolon", "Hellion", "Striker", "Guardian", "Overseer", "Shredder", "Warden", "Executor", "Kodiak", "Virtuoso", "Fury", "Armor", "Viper", "Lancer", "Prophet", "Cobra", "Dragoon", "Redeemer", "Gladiator", "Maestro", "Savage", "Artillery", "Centaur", "Punisher", "Educator", "Minotaur", "Devastator", "Ambassador", "Cutter", "Screamer", "Broadside", "Tenet", "Reaver", "Cannonade", "Edict", "Argo", "Baron", "Vortex", "Cruiser", "Triton", "Destroyer", "Arbiter", "Poseidon", "Dreadnought", "Vindicator", "Mako", "Countess", "Wrath", "Hunter", "Lurker", "Taker", "Whisper", "Leviathan", "Eradicator", "Shroud", "Hydra", "Bastion", "Shepherd", "Locust", "Raider", "Herald", "Shrike", "Predator", "Seraph") + ), + "Wonder": ( + ("Spy ", "Culper ", "Tessellation ", "Machine-Assisted ", "Dimensional ", "Folding ", "Dimensional Folding ", "Quantum ", "Temporal ", "Relativistic ", "Abyssal ", "Archimedes ", "Arma-", "Benthic ", "Byte-", "Daedaleus ", "Deep ", "Drone ", "Ecto-", "Genesis ", "Euphotic ", "Faraday ", "Gene ", "Guo Pu ", "Holon ", "Human ", "Markov ", "Mass ", "Master ", "Memet-", "Nano-", "New Terran ", "Pan-", "Precog ", "Promethean ", "Quantum ", "Resurrection ", "Stellar ", "Tectonic ", "The ", "Xeno-", "Emancipation ", "Exodus ", "Mind ", "Transcendental ", "Decode "), + ("Mirror", "Ansible", "Lever", "Sail", "Auger", "Geist", "Crawler", "Cynosure", "Ladder", "Memory", "Sphere", "Pod", "Genesis Pod", "Strand", "Gyre", "Vault", "Yaolan", "Chamber", "Hive", "Eclipse", "Driver", "Control", "Work", "Thermite", "Myth", "Opticon", "Project", "Promethean", "Computer", "Device", "Codex", "Anvil", "Akkorokamui", "Drome", "Malleum", "Nova", "Gate", "Flower", "Equation", "Signal", "Beacon") + ) +} + + +usedNames = set() + +def genRandomName(nametype): #Mix and match from BE. + prefixes, suffixes = name_parts[nametype] + prefix, suffix = random.sample(prefixes, 1)[0], random.sample(suffixes, 1)[0] + return prefix[:-1]+suffix[0].lower()+suffix[1:] if prefix[-1] == "-" else prefix+suffix + +def genRandomNameUnused(nametype): + name = None + while name is None or name in usedNames: + name = genRandomName(nametype) + return name + +# def genRandomEra(): + # pass + +def genRandomUnit(): + pass + +def genRandomBuildingUnique(): + # See replaceExamples in UniqueDocsWriter.kt. + pass + +def genRandomBuildingStats(name, totalstats, statsskew, numuniques): + assert 0 <= statsskew <= 1 + for x in x: + x *= 1+(random.random()*2-1)*statsskew + +def genRandomBuilding(): + pass + +def genRandomWonder(): + pass + + +def genRandomTech(column, row): + connections = random.sample((0, 0, -2, -1, 1, 2), random.randint(1,3)) + + +def _getInvalidTechs(): + pass + + +def extendTechTree(iterations=1): + pass + +def clearTechTree(*, safe=True): + for name in techtree.keys(): + if (not safe) or not any(name in civinfo.tech.techsResearched or name in civinfo.tech.techsInProgress or name == civinfo.tech.currentTechnologyName() for civinfo in gameInfo.civilizations): + del techtree[name] + +def scrambleTechTree(): + pass diff --git a/core/Module.md b/core/Module.md index 5b697a6174c86..69735f3b17fb9 100644 --- a/core/Module.md +++ b/core/Module.md @@ -11,6 +11,18 @@ # Package com.unciv.scripting +## Principles + +**The Kotlin/JVM code should neither know nor care about the language running on the other end of its scripting API.** If a behaviour is specific to a particular language, then it's also too messy and complex to try to take special account for from the other side of an IPC channel. Instead, the complexity of each specific scripting language should be handled entirely within that language itself, such that the only thing exposed to the Kotlin code is a common interface built around structures that exist in most computer programming languages (like command strings, attributes, keys, calls, assignments, collections, etc). This not only keeps the scripting protocols and interfaces compatible with multiple backends, it also serves as a test that helps keep their design relatively clean and maintainable by forcing messy or complicated behaviours to be implemented in more appropriate places. + +**Parts should be kept as modular and interchangeable as possible.** Each component type should have a somewhat well-defined job, and should not contain or be inseparably entwined with code that does things aside from that job. If a base class's primary role is to expose an REPL, for example, then extra features like undo history or implementation details like running a subprocess can be moved into either another class or a subclass. Again, IMO this both makes it easy to support versatile configurations and helps with keeping a reasonably neat codebase and architecture. + +**Different levels of execution/evaluation should not mix.** The IPC protocol defines the the packet structures, types, and communication order that are for *implementing* scripting language semantics for accessing Kotlin/JVM data. Therefore, the IPC protocol should not itself become a *part of* scripting language semantics; No user/mod script in any language should ever have to manually create and send or receive and parse IPC packets. Certain API functions have been defined to provide additional capabilities that are *accessible through* scripting language semantics (in class ApiHelpers). Therefore, those functions should never be used in *implementing* scripting language semantics; No overloaded operator presented to a user script as part of the core Unciv API should ever implicitly call such a method as part of its basic functionality. The entrypoints for the scripting system have the roles of taking code strings (from user input, from mods, etc) and returning a result string (to print out, log, etc) (and possibly an exception Boolean flag). Therefore, they should never have to understand, or even be able to use, any data aside from opaque strings (such as IPC packets or structured return results). + +**In an API meant for dynamic scripting languages, dynamic behaviours are better than static ones.** The Unciv Kotlin codebase was around 60k lines when I started on this scripting API. By using reflection in the JVM and operator overloading in scripting languages, nearly all of the classes and structures defined in there can be mirrored directly in the scripting environment without having to write or maintain a single line of hardcoded API. When the class structure in Kotlin changes, the attributes and methods available from all scripting backends also immediately match the new Kotlin code. + +**In the IPC mechanisms, the specification and architecture comes before implementation.** IPC actions aren't statically checked like the Kotlin code is. They aren't even syntax-checked like Python code. I do have them spitting out exceptions showing the offending packets in both Kotlin and Python in cases of obvious desync, but even that's breakable. The only thing really keeping them working is simultaneous adherence on both ends to a common protocol, which is easier when the protocol is fairly simple. If implementing a particular syntax for a scripting language would require adding a new packet type or changing the REPL loop flow, consider whether the use case for it would be better served by adding an API-level function instead. + ## Class Overview The major classes involved in the scripting API are structured as follows. `UpperCamelCase()` and parentheses means a new instantiation of a class. `lowerCamelCase` means a reference to an already-existing instance. An asterisk at the start of an item means zero or multiple instances of that class may be held. A question mark at the start of an item means that it may not exist in all implementations of the parent base class/interface. A question mark at the end of an item means that it is nullable, or otherwise may not be available in all states. @@ -330,8 +342,10 @@ Flags are string values for communicating extra information that doesn't need a ``` //'BeginIteration' - //'StopIteration' + //'EndIteration' //Not implemented. Probably needed if iteration over non-sized objects is needed. Probably not worth the trouble. + //Deprecated without ever being implemented. Lack of indices for such objects means you wouldn't be able to directly do anything with their iterated results anyway in the same way that you can for "lists" "iterated" by appending indices to their paths, as you wouldn't have a path by which to refer back to them. + //Alternate solutions: Sets are already serialized as JSON arrays, which can be resolved in running scripts. Script-accessible Kotlin-side helper functions can be defined to convert other containers to lists, or if needed, to yield their values per call. Instance tokens arising from these operations can be assigned by running scripts to a name on the Kotlin side, creating a concrete path by which to reflectively access their own members. ``` --- diff --git a/core/src/com/unciv/scripting/ScriptingScope.kt b/core/src/com/unciv/scripting/ScriptingScope.kt index fb480e7754806..96c7f62f3f0e4 100644 --- a/core/src/com/unciv/scripting/ScriptingScope.kt +++ b/core/src/com/unciv/scripting/ScriptingScope.kt @@ -17,6 +17,8 @@ import com.unciv.ui.worldscreen.WorldScreen import java.io.ByteArrayOutputStream +// TODO: Move this, and all nested objects and classes, to api/? + /** * Holds references to all internal game data that scripting backends have access to. * @@ -26,8 +28,9 @@ import java.io.ByteArrayOutputStream * * WorldScreen gives access to UnitTable.selectedUnit, MapHolder.selectedTile, etc. Useful for contextual operations. * - * The members of this class and its nested classes should be designed for use by running scripts, not for in implementing the protocol or API of scripting backends. - * E.G.: If you need access to a file to build the scripting environment, then add it to ScriptingEngineConstants.json instead of using apiHelpers.assetFileB64. If you need access to some new type of property, then geneneralize it as much as possible and add an IPC request type for it in ScriptingProtocol.kt. + * The members of this class and its nested classes should be designed for use by running scripts, not for implementing the protocol or API of scripting backends. + * E.G.: If you need access to a file to build the scripting environment, then add it to ScriptingEngineConstants.json instead of using apiHelpers.assetFileB64. If you need access to some new type of property, then geneneralize it as much as possible and add an IPC request type for it in ScriptingProtocol.kt or add support for it in Reflection.kt. + * In Python terms, that means that magic methods all directly send and parse IPC packets, while running scripts transparently use those magic methods to access the functions here. * API calls are for running scripts, and may be less stable. Building the scripting environment itself should be done directly using the IPC protocol and other lower-level constructs. * * To reduce the chance of E.G. name collisions in .apiHelpers.registeredInstances, or one misbehaving mod breaking everything by unassigning .gameInfo, different ScriptingState()s should each have their own ScriptingScope(). @@ -46,6 +49,7 @@ class ScriptingScope( class ApiHelpers(val scriptingScope: ScriptingScope) { // This could probably eventually include ways for scripts to create and inject their own UI elements too. Create, populate, show even popups for mods, inject buttons that execute script strings for macros. + // TODO: The vast majority of these don't need scriptingScope access, and thus can be put on singletons. val isInGame: Boolean get() = (scriptingScope.civInfo != null && scriptingScope.gameInfo != null && scriptingScope.uncivGame != null) val Factories = InstanceFactories @@ -85,6 +89,8 @@ class ScriptingScope( exporter.dispose() // This one should be called automatically by GC anyway. return String(Base64Coder.encode(fakepng.toByteArray())) } +// fun applyProperties(instance: Any, properties: Map) { +// } } } diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index fe1209e0022af..fb40c9ec6b576 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -19,6 +19,8 @@ fun ArrayList.clipIndexToBounds(index: Int, extendsize: Int = 0): Int { // TODO: Replace Exception types with Throwable? Wait, no. Apparently that just includes "serious problems that a reasonable application should not try to catch." +// See https://github.com/yairm210/Unciv/pull/5592/commits/a1f51e08ab782ab46bda220e0c4aaae2e8ba21a4 for example of running locking operation in separate thread. + /** * Make sure an index is valid for this array. * diff --git a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt index 76f89edc3012a..c42b0b877d06e 100644 --- a/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt +++ b/core/src/com/unciv/scripting/protocol/ScriptingReplManager.kt @@ -48,6 +48,8 @@ class ScriptingRawReplManager(scriptingScope: ScriptingScope, blackbox: Blackbox */ class ScriptingProtocolReplManager(scriptingScope: ScriptingScope, blackbox: Blackbox): ScriptingReplManager(scriptingScope, blackbox) { +// TODO: scriptingScope can be an Any. + /** * ScriptingProtocol puts references to pre-tokenized returned objects in here. * Should be cleared here at the end of each REPL execution. diff --git a/core/src/com/unciv/scripting/reflection/Reflection.kt b/core/src/com/unciv/scripting/reflection/Reflection.kt index c40330f03f420..dca10ec29dcde 100644 --- a/core/src/com/unciv/scripting/reflection/Reflection.kt +++ b/core/src/com/unciv/scripting/reflection/Reflection.kt @@ -18,6 +18,7 @@ object Reflection { // From https://stackoverflow.com/a/35539628/12260302 val property = instance::class.members .first { it.name == propertyName } as KProperty1 + // If scripting member access performance becomes an issue, memoizing this could be a potential first step. return property.get(instance) as R? } @@ -55,8 +56,8 @@ object Reflection { // Multiple dispatch of null between Any and Any? seems ambiguous in Kotlin even without reflection. // Here, I'm resolving it myself, so it seems fine. // However, with generics, even if I find the right KCallable, it seems that a nullable argument T? will usually (but not always, depending on each time you compile) be sent to the non-nullable T version of the function if one has been defined. - // KCallable.toString() shows the nullable signature, and KParam.name shows the argument name from the nullable version. But an exception is still thrown on .call(), and its text will use the argument name from the non-nullable version. - // I suppose it's not a problem as it seems broken in Kotlin generally. + // KCallable.toString() shows the nullable signature, and KParam.name shows the argument name from the nullable version. But an exception is still thrown on .call() with null, and its text will use the argument name from the non-nullable version. + // I suppose it's not a problem here as it seems broken in Kotlin generally. return kparam.type.isMarkedNullable } else { return kparam.type.jvmErasure.isSuperclassOf(arg::class) diff --git a/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt b/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt index 1240c25efc984..a8fd237b2fc9f 100644 --- a/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt +++ b/core/src/com/unciv/scripting/utils/ApiSpecGenerator.kt @@ -11,6 +11,8 @@ import com.badlogic.gdx.utils.Json // Automatically running this should probably be a part of the build process. // Probably do whatever is done with TranslationFileWriter. +// TODO: See UniqueDocsWriter.kt. https://github.com/yairm210/Unciv/commit/4617bc21a70f4f4bd29dc80ba7d648349c9fc3f8 + data class ApiSpecDef( var path: String, diff --git a/core/src/com/unciv/scripting/utils/InstanceFactories.kt b/core/src/com/unciv/scripting/utils/InstanceFactories.kt index bcf2fb9275046..f6ccb14c914a3 100644 --- a/core/src/com/unciv/scripting/utils/InstanceFactories.kt +++ b/core/src/com/unciv/scripting/utils/InstanceFactories.kt @@ -3,12 +3,24 @@ package com.unciv.scripting.utils import com.badlogic.gdx.math.Vector2 +//TODO: Rename to ScriptingApiFactories? Move to separate package with other API-focused things? + /** * For use in ScriptingScope. Allows interpreted scripts to make new instances of Kotlin/JVM classes. */ object InstanceFactories { + //This, and possible ApiHelpers itself, need better nested namespaces. + object Math { + } + object Rulesets { + } + object Kotlin { + } + object GUI { + } fun Array() = "NotImplemented" fun Vector2(x: Float, y: Float) = com.badlogic.gdx.math.Vector2(x, y) fun MapUnit() = "NotImplemented" + fun Technology() = "NotImplemented" } From 4f74dd1d37ff9f4373dbc26d30f00e08568f6d59 Mon Sep 17 00:00:00 2001 From: will-ca Date: Tue, 23 Nov 2021 17:20:16 +0000 Subject: [PATCH 51/93] Docs. --- .../enginefiles/python/PythonScripting.md | 8 +- core/Module.md | 12 +- .../src/com/unciv/scripting/ScriptingScope.kt | 1 + .../src/com/unciv/scripting/ScriptingState.kt | 13 +- extraImages/ScriptingCallTrace.odg | Bin 0 -> 20198 bytes extraImages/ScriptingCallTrace.png | Bin 0 -> 303058 bytes extraImages/ScriptingCallTraceExported.pdf | Bin 0 -> 43337 bytes extraImages/ScriptingCallTraceFiltered.svg | 2095 +++++++++++++++++ 8 files changed, 2119 insertions(+), 10 deletions(-) create mode 100644 extraImages/ScriptingCallTrace.odg create mode 100644 extraImages/ScriptingCallTrace.png create mode 100644 extraImages/ScriptingCallTraceExported.pdf create mode 100644 extraImages/ScriptingCallTraceFiltered.svg diff --git a/android/assets/scripting/enginefiles/python/PythonScripting.md b/android/assets/scripting/enginefiles/python/PythonScripting.md index b6c06a89c6774..eee7045bbe762 100644 --- a/android/assets/scripting/enginefiles/python/PythonScripting.md +++ b/android/assets/scripting/enginefiles/python/PythonScripting.md @@ -331,8 +331,8 @@ def faster(): gameInfo.tileMap.tileMatrix[x][y] = "Krakatoa" # +1 IPC "assign" action. # Only done after already selecting coordinates and checking validity. - i += 1 - # Only iterate for as long as needed to make the wanted changes + i += 1 + # Only iterate for as long as needed to make the wanted changes def fastest(): apiHelpers.scatterRandomFeature("Krakatoa", random.randint(15, 25)) @@ -345,7 +345,7 @@ Every time you access an attribute or item on a foreign wrapper in Python create ```python3 def slow(): for i in len(civInfo.cities[0].cityStats.cityInfo.tilesInRange): - print(civInfo.cities[0].cityStats.cityInfo.tilesInRange[i]) + print(civInfo.cities[0].cityStats.cityInfo.tilesInRange[i]) # FIXME: This doesn't actually take indices, does it? # Every loop starts out with civInfo, and then constructs a new wrapper object in Python for every attribute and item access. def faster(): @@ -360,7 +360,7 @@ Every element in the path sent by a wrapper object to Kotlin/the JVM also requir ```python3 def slow(): for i in range(1000): - pass + pass # TODO def alsoSlow(): diff --git a/core/Module.md b/core/Module.md index 69735f3b17fb9..fed90fe7b8f36 100644 --- a/core/Module.md +++ b/core/Module.md @@ -11,17 +11,17 @@ # Package com.unciv.scripting -## Principles +## Design rinciples **The Kotlin/JVM code should neither know nor care about the language running on the other end of its scripting API.** If a behaviour is specific to a particular language, then it's also too messy and complex to try to take special account for from the other side of an IPC channel. Instead, the complexity of each specific scripting language should be handled entirely within that language itself, such that the only thing exposed to the Kotlin code is a common interface built around structures that exist in most computer programming languages (like command strings, attributes, keys, calls, assignments, collections, etc). This not only keeps the scripting protocols and interfaces compatible with multiple backends, it also serves as a test that helps keep their design relatively clean and maintainable by forcing messy or complicated behaviours to be implemented in more appropriate places. -**Parts should be kept as modular and interchangeable as possible.** Each component type should have a somewhat well-defined job, and should not contain or be inseparably entwined with code that does things aside from that job. If a base class's primary role is to expose an REPL, for example, then extra features like undo history or implementation details like running a subprocess can be moved into either another class or a subclass. Again, IMO this both makes it easy to support versatile configurations and helps with keeping a reasonably neat codebase and architecture. +**Parts should be kept as modular and interchangeable as possible.** Each component type should have a somewhat well-defined job, and should not contain or be inseparably entwined with code that does things aside from that job. If a base class's primary role is to expose an REPL, for example, then extra features like command history or implementation details like running a subprocess can be moved into either another class or a subclass. Again, IMO this both makes it easy to support versatile configurations and helps with keeping a reasonably neat codebase and architecture. -**Different levels of execution/evaluation should not mix.** The IPC protocol defines the the packet structures, types, and communication order that are for *implementing* scripting language semantics for accessing Kotlin/JVM data. Therefore, the IPC protocol should not itself become a *part of* scripting language semantics; No user/mod script in any language should ever have to manually create and send or receive and parse IPC packets. Certain API functions have been defined to provide additional capabilities that are *accessible through* scripting language semantics (in class ApiHelpers). Therefore, those functions should never be used in *implementing* scripting language semantics; No overloaded operator presented to a user script as part of the core Unciv API should ever implicitly call such a method as part of its basic functionality. The entrypoints for the scripting system have the roles of taking code strings (from user input, from mods, etc) and returning a result string (to print out, log, etc) (and possibly an exception Boolean flag). Therefore, they should never have to understand, or even be able to use, any data aside from opaque strings (such as IPC packets or structured return results). +**Different levels of execution/evaluation should not mix.** The IPC protocol defines the packet structures, types, and communication order that are for *implementing* scripting language semantics for accessing Kotlin/JVM data. Therefore, the IPC protocol should not itself become a *part of* scripting language semantics; No user/mod script in any language should ever have to manually create and send or receive and parse IPC packets. Certain API functions have been defined to provide additional capabilities that are *accessible through* scripting language semantics (in class ApiHelpers). Therefore, those functions should never be used in *implementing* scripting language semantics; No overloaded operator presented to a user script as part of the core Unciv API should ever implicitly call such a method as part of its basic functionality. The entrypoints for the scripting system have the roles of taking code strings (from user input, from mods, etc) and returning a result string (to print out, log, etc) (and possibly an exception Boolean flag). Therefore, they should never have to understand, or even be able to use, any data aside from opaque strings (such as IPC packets or structured return results). -**In an API meant for dynamic scripting languages, dynamic behaviours are better than static ones.** The Unciv Kotlin codebase was around 60k lines when I started on this scripting API. By using reflection in the JVM and operator overloading in scripting languages, nearly all of the classes and structures defined in there can be mirrored directly in the scripting environment without having to write or maintain a single line of hardcoded API. When the class structure in Kotlin changes, the attributes and methods available from all scripting backends also immediately match the new Kotlin code. +**In an API meant for dynamic scripting languages, dynamic behaviours are better than static ones.** The Unciv Kotlin codebase was around 60k lines when I started on this scripting API. By using reflection in the JVM and operator overloading in scripting languages, nearly all of the classes and structures defined in there can be mirrored directly in the scripting environment without having to write or maintain a single line of hardcoded API. Because API endpoints exposed in scripting languages are already all dynamically generated at runtime, when the class structure in Kotlin changes, the attributes and methods available from all scripting backends also immediately match the new Kotlin code. -**In the IPC mechanisms, the specification and architecture comes before implementation.** IPC actions aren't statically checked like the Kotlin code is. They aren't even syntax-checked like Python code. I do have them spitting out exceptions showing the offending packets in both Kotlin and Python in cases of obvious desync, but even that's breakable. The only thing really keeping them working is simultaneous adherence on both ends to a common protocol, which is easier when the protocol is fairly simple. If implementing a particular syntax for a scripting language would require adding a new packet type or changing the REPL loop flow, consider whether the use case for it would be better served by adding an API-level function instead. +**In the IPC mechanisms, the specification and architecture come before implementation.** IPC actions aren't statically checked like the Kotlin code is. They aren't even syntax-checked like Python code. I do have them spitting out exceptions showing the offending packets in both Kotlin and Python in cases of obvious desync, but even that's breakable. The only thing really keeping them working is simultaneous adherence on both ends to a common protocol, which is easier when the protocol is fairly simple. If implementing a particular syntax for a scripting language would require adding a new packet type or changing the REPL loop's control flow, consider whether the use case for it would be better served by adding an API-level helper function instead. ## Class Overview @@ -96,6 +96,8 @@ You would presumably have to interrupt the main Kotlin-side thread anyway in ord Plus, letting the script interpreter run completely in parallel would probably introduce potential for all sorts of issues with non-deterministic synchronicity, and performance issues Calling the script interpreter from the Kotlin side means that the state of the Kotlin side is more predictable at the moment of script execution. +![This simple, thirty-step process is all it takes to execute a single scripted command…](/extraImages/ScriptingCallTrace.png) + ## IPC Protocol *Implemented by `ScriptingProtocol.kt`, `ipc.py`, and `wrapping.py`.* diff --git a/core/src/com/unciv/scripting/ScriptingScope.kt b/core/src/com/unciv/scripting/ScriptingScope.kt index 96c7f62f3f0e4..486cead7c63da 100644 --- a/core/src/com/unciv/scripting/ScriptingScope.kt +++ b/core/src/com/unciv/scripting/ScriptingScope.kt @@ -91,6 +91,7 @@ class ScriptingScope( } // fun applyProperties(instance: Any, properties: Map) { // } + //setTimeout? } } diff --git a/core/src/com/unciv/scripting/ScriptingState.kt b/core/src/com/unciv/scripting/ScriptingState.kt index fb40c9ec6b576..f739e2786c8ef 100644 --- a/core/src/com/unciv/scripting/ScriptingState.kt +++ b/core/src/com/unciv/scripting/ScriptingState.kt @@ -149,7 +149,7 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr while (commandHistory.size > maxCommandHistory) { commandHistory.removeAt(0) // No need to restrict activeCommandHistory to valid indices here because it gets set to zero anyway. - // Also O(n). + // Also probably O(n) to remove from start.. } } activeCommandHistory = 0 @@ -162,4 +162,15 @@ class ScriptingState(val scriptingScope: ScriptingScope, initialBackendType: Scr echo(out) return out } + +// fun acquireUiLock() { +// scriptingScope.worldScreen?.isPlayersTurn = false + //TODO + //Not perfect. I think scriptingScope also exposes mutating the GUI itself, and many things that aren't protected by this? Then again, a script that *wants* to cause a crash/ANR will always be able to do so by just assigning an invalid value or deleting a required node somewhere. Could make mod handlers outside of worldScreen blocking, with written stipulations on (dis)recommended size, and then +// } + +// fun releaseUiLock() { +// scriptingScope.worldScreen?.isPlayersTurn = true + //Hm. Should return to original value, not necessarily true. That means keeping a property, which means I'd rather put this in its own class. +// } } diff --git a/extraImages/ScriptingCallTrace.odg b/extraImages/ScriptingCallTrace.odg new file mode 100644 index 0000000000000000000000000000000000000000..6696ae0c95df3b3526d482d7a7e958b9e3d34c25 GIT binary patch literal 20198 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