Enabled state tracking and restoration across all tools
As part of the usability overhaul (#2225), we want to be able to make the enabled state of DFHack tools persistent so players can enable a tool once and it will stay that way across reloads until they turn it off. This will reduce the need to edit init scripts to manually maintain the desired state for DFHack tools. The DFHack control panel (#2213) depends on this kind of persistent behavior across DFHack tools.
Goals
- The player should be able to configure DFHack tools in-game and to have that configuration persisted across program reloads (for globally scoped tools) or savegame reloads (for save-scoped tools). No init file editing should be required (though the player can choose to replace/override the system proposed in this document with init script configuration).
- The player should not have to care whether a tool is a plugin or a Lua script. The player interface should be the same.
Challenges
- Whereas plugins have the plugin_init() hook that they could use to restore state, scripts have no such hook. We need to design a mechanism that will allow scripts to bootstrap themselves.
- In order to know when a tool should be re-enabled, we need to know whether the tool is global or per-save. Right now there is no way to programmatically determine that.
- Plugins export whether they are enabled but scripts do not. We need to design a way for scripts to report their enabled status.
most class (2) tools (UI enhancements) can be ported to the overlay framework and have their state managed by overlay. we still have to have a solution for class (3) (per-save, persistent config) scripts.
seedwatch immediately comes to mind (from a recent playthrough) as a plugin that does not properly save state, will open an issue
Here's the solution I'm leaning towards:
- all scripts can declare a global function named
onStateChange(sc)that will receive state change events (similar to what scripts in init.d can already do) - all scripts are
reqscript()ed (similar to how the overlay framework scans scripts forOVERLAY_WIDGETS) and thoseonStateChange()functions are discovered and hooked up todfhack.onStateChange - scripts load their state and re-enable themselves just as plugins do in the
onStateChangecallback
the code that scans scripts and connects their onStateChange methods can be a plugin to separate the functionality from core. script-manager. Scanning will be done in the plugin's own plugin_onStateChange() function for SC_CORE_INITIALIZED. Scripts can still react to SC_CORE_INITIALIZED since onStateChange hits plugins before scripts.
For scripts added/changed during gameplay, we can detect a script being (re)loaded in dfhack.lua and call out to require('plugins.script_manager').refresh(script_name).
in addition, scripts can declare a global function named isEnabled which will return the enabled status for the script. when the enable command is run, scripts are scanned for this global function and it is called to get the current status. Other scripts/plugins can also query the enabled status of the script via this function.