diff --git a/LUA_API.rst b/LUA_API.rst index e68a87a66..1a38c4edc 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -428,6 +428,11 @@ Currently it defines the following features: If the thread owns the interactive console, shows a prompt and returns the entered string. Otherwise returns *nil, error*. + Depending on the context, this function may actually yield the + running coroutine and let the C++ code release the core suspend + lock. Using an explicit ``dfhack.with_suspend`` will prevent + this, forcing the function to block on input with lock held. + * ``dfhack.interpreter([prompt[,env[,history_filename]]])`` Starts an interactive lua interpreter, using the specified prompt @@ -449,6 +454,10 @@ Currently it defines the following features: Just like pcall, but also prints the error using printerr before returning. Intended as a convenience function. +* ``dfhack.saferesume(coroutine[,args...])`` + + Compares to coroutine.resume like dfhack.safecall vs pcall. + * ``dfhack.with_suspend(f[,args...])`` Calls ``f`` with arguments after grabbing the DF core suspend lock. @@ -813,3 +822,37 @@ Core context specific functions: * ``dfhack.is_core_context`` Boolean value; *true* in the core context. + +* ``dfhack.onStateChange.foo = function(code)`` + + Event. Receives the same codes as plugin_onstatechange in C++. + + +Event type +---------- + +An event is just a lua table with a predefined metatable that +contains a __call metamethod. When it is invoked, it loops +through the table with next and calls all contained values. +This is intended as an extensible way to add listeners. + +This type itself is available in any context, but only the +core context has the actual events defined by C++ code. + +Features: + +* ``dfhack.event.new()`` + + Creates a new instance of an event. + +* ``event[key] = function`` + + Sets the function as one of the listeners. + + **NOTE**: The ``df.NULL`` key is reserved for the use by + the C++ owner of the event, and has some special semantics. + +* ``event(args...)`` + + Invokes all listeners contained in the event in an arbitrary + order using ``dfhack.safecall``. diff --git a/Lua API.html b/Lua API.html index 38a375d86..d5d849d91 100644 --- a/Lua API.html +++ b/Lua API.html @@ -344,7 +344,10 @@ ul.auto-toc {
  • Maps module
  • -
  • Core interpreter context
  • +
  • Core interpreter context +
  • @@ -705,6 +708,10 @@ works with DFHack output infrastructure.

  • dfhack.lineedit([prompt[,history_filename]])

    If the thread owns the interactive console, shows a prompt and returns the entered string. Otherwise returns nil, error.

    +

    Depending on the context, this function may actually yield the +running coroutine and let the C++ code release the core suspend +lock. Using an explicit dfhack.with_suspend will prevent +this, forcing the function to block on input with lock held.

  • dfhack.interpreter([prompt[,env[,history_filename]]])

    Starts an interactive lua interpreter, using the specified prompt @@ -722,6 +729,9 @@ in C++, and dfhack.safecall.

    Just like pcall, but also prints the error using printerr before returning. Intended as a convenience function.

  • +
  • dfhack.saferesume(coroutine[,args...])

    +

    Compares to coroutine.resume like dfhack.safecall vs pcall.

    +
  • dfhack.with_suspend(f[,args...])

    Calls f with arguments after grabbing the DF core suspend lock. Suspending is necessary for accessing a consistent state of DF memory.

    @@ -1021,9 +1031,36 @@ only context that can receive events from DF and plugins.

  • dfhack.is_core_context

    Boolean value; true in the core context.

  • +
  • dfhack.onStateChange.foo = function(code)

    +

    Event. Receives the same codes as plugin_onstatechange in C++.

    +
  • + +
    +

    Event type

    +

    An event is just a lua table with a predefined metatable that +contains a __call metamethod. When it is invoked, it loops +through the table with next and calls all contained values. +This is intended as an extensible way to add listeners.

    +

    This type itself is available in any context, but only the +core context has the actual events defined by C++ code.

    +

    Features:

    +
    + diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index be4fa864e..469caa65f 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -1098,6 +1098,18 @@ int DFHack::Lua::NewEvent(lua_State *state) return 1; } +static void do_invoke_event(lua_State *L, int argbase, int num_args, int errorfun) +{ + for (int i = 0; i < num_args; i++) + lua_pushvalue(L, argbase+i); + + if (lua_pcall(L, num_args, 0, errorfun) != LUA_OK) + { + report_error(L); + lua_pop(L, 1); + } +} + static void dfhack_event_invoke(lua_State *L, int base, bool from_c) { int event = base+1; @@ -1108,23 +1120,31 @@ static void dfhack_event_invoke(lua_State *L, int base, bool from_c) lua_insert(L, errorfun); int argbase = base+3; - lua_pushnil(L); - // stack: |base| event errorfun (args) key cb (args) + // stack: |base| event errorfun (args) - while (lua_next(L, event)) + if (!from_c) { - if (from_c && lua_islightuserdata(L, -1) && !lua_touserdata(L, -1)) - continue; + // Invoke the NULL key first + lua_rawgetp(L, event, NULL); - for (int i = 0; i < num_args; i++) - lua_pushvalue(L, argbase+i); + if (lua_isnil(L, -1)) + lua_pop(L, 1); + else + do_invoke_event(L, argbase, num_args, errorfun); + } - if (lua_pcall(L, num_args, 0, errorfun) != LUA_OK) - { - report_error(L); + lua_pushnil(L); + + // stack: |base| event errorfun (args) key || cb (args) + + while (lua_next(L, event)) + { + // Skip the NULL key in the main loop + if (lua_islightuserdata(L, -2) && !lua_touserdata(L, -2)) lua_pop(L, 1); - } + else + do_invoke_event(L, argbase, num_args, errorfun); } lua_settop(L, base); @@ -1285,13 +1305,29 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) return state; } +void DFHack::Lua::Core::onStateChange(color_ostream &out, int code) { + if (!State) return; + + Lua::Push(State, code); + Lua::InvokeEvent(out, State, (void*)onStateChange, 1); +} + void DFHack::Lua::Core::Init(color_ostream &out) { if (State) return; State = luaL_newstate(); + Lua::Open(out, State); + + // Register events + lua_getglobal(State, "dfhack"); + + MakeEvent(State, (void*)onStateChange); + lua_setfield(State, -2, "onStateChange"); + + lua_pop(State, 1); } void DFHack::Lua::Core::Reset(color_ostream &out, const char *where) diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index 1821e18d7..837c3e2d5 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -641,6 +641,8 @@ void PluginManager::OnStateChange(color_ostream &out, state_change_event event) { all_plugins[i]->on_state_change(out, event); } + + Lua::Core::onStateChange(out, event); } // FIXME: doesn't check name collisions! diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index 54aa6dcc7..b2d440a6b 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -278,6 +278,9 @@ namespace DFHack {namespace Lua { void Init(color_ostream &out); void Reset(color_ostream &out, const char *where); + // Events signalled by the core + void onStateChange(color_ostream &out, int code); + template inline void Push(T &arg) { Lua::Push(State, arg); } template inline void Push(const T &arg) { Lua::Push(State, arg); } template inline void PushVector(const T &arg) { Lua::PushVector(State, arg); } diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index a51d90747..7f98bf422 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -67,13 +67,13 @@ namespace DFHack enum state_change_event { - SC_WORLD_LOADED, - SC_WORLD_UNLOADED, - SC_MAP_LOADED, - SC_MAP_UNLOADED, - SC_VIEWSCREEN_CHANGED, - SC_CORE_INITIALIZED, - SC_BEGIN_UNLOAD + SC_WORLD_LOADED = 0, + SC_WORLD_UNLOADED = 1, + SC_MAP_LOADED = 2, + SC_MAP_UNLOADED = 3, + SC_VIEWSCREEN_CHANGED = 4, + SC_CORE_INITIALIZED = 5, + SC_BEGIN_UNLOAD = 6 }; struct DFHACK_EXPORT CommandReg { const char *name; diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 4be58661b..c7e2669c5 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -21,6 +21,17 @@ COLOR_LIGHTMAGENTA = 13 COLOR_YELLOW = 14 COLOR_WHITE = 15 +-- Events + +if dfhack.is_core_context then + SC_WORLD_LOADED = 0 + SC_WORLD_UNLOADED = 1 + SC_MAP_LOADED = 2 + SC_MAP_UNLOADED = 3 + SC_VIEWSCREEN_CHANGED = 4 + SC_CORE_INITIALIZED = 5 +end + -- Error handling safecall = dfhack.safecall