diff --git a/NEWS b/NEWS index c7a259674..9dc77de01 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,7 @@ DFHack Future Scripts can be enabled with the built-in enable/disable commands A new function, reqscript(), is available as a safer alternative to script_environment() New internal commands + kill-lua: Interrupt running Lua scripts New plugins New scripts burial: sets all unowned coffins to allow burial ("-pets" to allow pets too) diff --git a/Readme.rst b/Readme.rst index 4c88affd1..00502edde 100644 --- a/Readme.rst +++ b/Readme.rst @@ -2095,6 +2095,12 @@ Tools: * ``sand``: Displays an indicator when sand is present in the currently-selected area, similar to the default clay/stone indicators. * ``sticky``: Maintains the selected local area while navigating the world map +kill-lua +-------- +Interrupts any currently-running Lua scripts. By default, scripts can only be +interrupted every 256 instructions. Use ``kill-lua force`` to interrupt +the next instruction. + petcapRemover ------------- This plugin allows you to remove or raise the pet population cap. In vanilla diff --git a/library/Core.cpp b/library/Core.cpp index 2696e9526..f99c12039 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -753,6 +753,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v " cls - Clear the console.\n" " fpause - Force DF to pause.\n" " die - Force DF to close immediately\n" + " kill-lua - Stop an active Lua script\n" " keybinding - Modify bindings of commands to keys\n" " script FILENAME - Run the commands specified in a file.\n" " sc-script - Automatically run specified scripts on state change events\n" @@ -870,6 +871,17 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v { _exit(666); } + else if(first == "kill-lua") + { + bool force = false; + for (auto it = parts.begin(); it != parts.end(); ++it) + { + if (*it == "force") + force = true; + } + if (!Lua::Interrupt(force)) + con.printerr("Failed to register hook - use 'kill-lua force' to force\n"); + } else if(first == "script") { if(parts.size() == 1) diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 94c67828b..8c0e5ef1a 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -24,6 +24,7 @@ distribution. #include "Internal.h" +#include #include #include #include @@ -354,6 +355,35 @@ static int dfhack_lineedit(lua_State *S) * Exception handling */ +volatile std::sig_atomic_t lstop = 0; + +static void interrupt_hook (lua_State *L, lua_Debug *ar); +static void interrupt_init (lua_State *L) +{ + lua_sethook(L, interrupt_hook, LUA_MASKCOUNT, 256); +} + +static void interrupt_hook (lua_State *L, lua_Debug *ar) +{ + if (lstop) + { + lstop = 0; + interrupt_init(L); // Restore default settings if necessary + luaL_error(L, "interrupted!"); + } +} + +bool DFHack::Lua::Interrupt (bool force) +{ + lua_State *L = Lua::Core::State; + if (L->hook != interrupt_hook && !force) + return false; + if (force) + lua_sethook(L, interrupt_hook, LUA_MASKCALL | LUA_MASKRET | LUA_MASKLINE | LUA_MASKCOUNT, 1); + lstop = 1; + return true; +} + static int DFHACK_EXCEPTION_META_TOKEN = 0; static void error_tostring(lua_State *L, bool keep_old = false) @@ -1561,6 +1591,8 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) if (!state) state = luaL_newstate(); + interrupt_init(state); + luaL_openlibs(state); AttachDFGlobals(state); diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index 042870273..9121660ec 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -263,6 +263,17 @@ namespace DFHack {namespace Lua { bool (*init)(color_ostream&, lua_State*, void*), void *arg); + /** + * Attempt to interrupt the currently-executing lua function by raising a lua error + * from a lua debug hook, similar to how SIGINT is handled in the lua interpreter (depends/lua/src/lua.c). + * The flag set here will only be checked every 256 instructions by default. + * Returns false if another debug hook is set and 'force' is false. + * + * force: Overwrite any existing debug hooks and interrupt the next instruction + */ + + DFHACK_EXPORT bool Interrupt (bool force=false); + /** * Push utility functions */