From 3beb2ebf25b94736a3823c317f603a8fc8bd0f53 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 17 Apr 2012 11:45:09 +0400 Subject: [PATCH 1/2] Export the onStateChange event to core lua context & add some docs. --- LUA_API.rst | 43 ++++++++++++++++++++++++ Lua API.html | 39 +++++++++++++++++++++- library/LuaTools.cpp | 58 ++++++++++++++++++++++++++------- library/PluginManager.cpp | 2 ++ library/include/LuaTools.h | 3 ++ library/include/PluginManager.h | 14 ++++---- library/lua/dfhack.lua | 11 +++++++ 7 files changed, 151 insertions(+), 19 deletions(-) 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 From 378a1fb914655569c8dfa4dffcda9acb6f3931bf Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 17 Apr 2012 12:15:45 +0400 Subject: [PATCH 2/2] Support the '#' string as index for wrapper vector insert at end. --- LUA_API.rst | 2 +- Lua API.html | 2 +- library/LuaTypes.cpp | 15 ++++++++++++--- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index 1a38c4edc..050714c72 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -209,7 +209,7 @@ Implemented features: * ``ref:insert(index,item)`` Inserts a new item at the specified index. To add at the end, - use ``#ref`` as index. + use ``#ref``, or just ``'#'`` as index. * ``ref:erase(index)`` diff --git a/Lua API.html b/Lua API.html index d5d849d91..cbf35f0eb 100644 --- a/Lua API.html +++ b/Lua API.html @@ -516,7 +516,7 @@ possible.

  • ref:insert(index,item)

    Inserts a new item at the specified index. To add at the end, -use #ref as index.

    +use #ref, or just '#' as index.

  • ref:erase(index)

    Removes the element at the given valid index.

    diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index c5ffe0f6b..2cfe8a84e 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -733,8 +733,18 @@ static int lookup_container_field(lua_State *state, int field, const char *mode * Index verification: number and in range. */ static int check_container_index(lua_State *state, int len, - int fidx, int iidx, const char *mode) + int fidx, int iidx, const char *mode, + bool is_insert = false) { + if (is_insert && len >= 0) + { + if (lua_type(state, iidx) == LUA_TSTRING + && strcmp(lua_tostring(state, iidx), "#") == 0) + return len; + + len++; + } + if (!lua_isnumber(state, iidx)) field_error(state, fidx, "invalid index", mode); @@ -867,8 +877,7 @@ static int method_container_insert(lua_State *state) auto id = (container_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID); int len = id->lua_item_count(state, ptr, container_identity::COUNT_LEN); - if (len >= 0) len++; - int idx = check_container_index(state, len, UPVAL_METHOD_NAME, 2, "call"); + int idx = check_container_index(state, len, UPVAL_METHOD_NAME, 2, "call", true); if (!id->lua_insert(state, UPVAL_METHOD_NAME, ptr, idx, 3)) field_error(state, UPVAL_METHOD_NAME, "not supported", "call");