diff --git a/LUA_API.rst b/LUA_API.rst index 5631e93a2..76f6454f4 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -1448,8 +1448,8 @@ Core context specific functions: Event type ---------- -An event is just a lua table with a predefined metatable that -contains a __call metamethod. When it is invoked, it loops +An event is a native object transparently wrapping a lua table, +and implementing 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. @@ -1464,10 +1464,18 @@ Features: * ``event[key] = function`` - Sets the function as one of the listeners. + Sets the function as one of the listeners. Assign *nil* to remove it. **NOTE**: The ``df.NULL`` key is reserved for the use by - the C++ owner of the event, and has some special semantics. + the C++ owner of the event; it is an error to try setting it. + +* ``#event`` + + Returns the number of non-nil listeners. + +* ``pairs(event)`` + + Iterates over all listeners in the table. * ``event(args...)`` diff --git a/Lua API.html b/Lua API.html index 0f1ecc3b1..c4ab9c8c4 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1596,8 +1596,8 @@ Using timeout_active(id,nil) cancels the timer

Event type

-

An event is just a lua table with a predefined metatable that -contains a __call metamethod. When it is invoked, it loops +

An event is a native object transparently wrapping a lua table, +and implementing 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 @@ -1608,9 +1608,15 @@ core context has the actual events defined by C++ code.

Creates a new instance of an event.

  • event[key] = function

    -

    Sets the function as one of the listeners.

    +

    Sets the function as one of the listeners. Assign nil to remove it.

    NOTE: The df.NULL key is reserved for the use by -the C++ owner of the event, and has some special semantics.

    +the C++ owner of the event; it is an error to try setting it.

    +
  • +
  • #event

    +

    Returns the number of non-nil listeners.

    +
  • +
  • pairs(event)

    +

    Iterates over all listeners in the table.

  • event(args...)

    Invokes all listeners contained in the event in an arbitrary diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 75dfe23f2..7c2c8f8d6 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -68,6 +68,11 @@ lua_State *DFHack::Lua::Core::State = NULL; void dfhack_printerr(lua_State *S, const std::string &str); +inline bool is_null_userdata(lua_State *L, int idx) +{ + return lua_islightuserdata(L, idx) && !lua_touserdata(L, idx); +} + inline void AssertCoreSuspend(lua_State *state) { assert(!Lua::IsCoreContext(state) || DFHack::Core::getInstance().isSuspended()); @@ -1244,14 +1249,123 @@ static const luaL_Reg dfhack_coro_funcs[] = { static int DFHACK_EVENT_META_TOKEN = 0; -int DFHack::Lua::NewEvent(lua_State *state) +namespace { + struct EventObject { + int item_count; + Lua::Event::Owner *owner; + }; +} + +void DFHack::Lua::Event::New(lua_State *state, Owner *owner) { - lua_newtable(state); + auto obj = (EventObject *)lua_newuserdata(state, sizeof(EventObject)); + obj->item_count = 0; + obj->owner = owner; + lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_EVENT_META_TOKEN); lua_setmetatable(state, -2); + lua_newtable(state); + lua_setuservalue(state, -2); +} + +void DFHack::Lua::Event::SetPrivateCallback(lua_State *L, int event) +{ + lua_getuservalue(L, event); + lua_swap(L); + lua_rawsetp(L, -2, NULL); + lua_pop(L, 1); +} + +static int dfhack_event_new(lua_State *L) +{ + Lua::Event::New(L); return 1; } +static int dfhack_event_len(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TUSERDATA); + auto obj = (EventObject *)lua_touserdata(L, 1); + lua_pushinteger(L, obj->item_count); + return 1; +} + +static int dfhack_event_tostring(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TUSERDATA); + auto obj = (EventObject *)lua_touserdata(L, 1); + lua_pushfstring(L, "", obj->item_count); + return 1; +} + +static int dfhack_event_index(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TUSERDATA); + lua_getuservalue(L, 1); + lua_pushvalue(L, 2); + lua_rawget(L, -2); + return 1; +} + +static int dfhack_event_next(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TUSERDATA); + lua_getuservalue(L, 1); + lua_pushvalue(L, 2); + while (lua_next(L, -2)) + { + if (is_null_userdata(L, -2)) + lua_pop(L, 1); + else + return 2; + } + lua_pushnil(L); + return 1; +} + +static int dfhack_event_pairs(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TUSERDATA); + lua_pushcfunction(L, dfhack_event_next); + lua_pushvalue(L, 1); + lua_pushnil(L); + return 3; +} + +static int dfhack_event_newindex(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TUSERDATA); + if (is_null_userdata(L, 2)) + luaL_argerror(L, 2, "Key NULL is reserved in events."); + + lua_settop(L, 3); + lua_getuservalue(L, 1); + bool new_nil = lua_isnil(L, 3); + + lua_pushvalue(L, 2); + lua_rawget(L, 4); + bool old_nil = lua_isnil(L, -1); + lua_settop(L, 4); + + lua_pushvalue(L, 2); + lua_pushvalue(L, 3); + lua_rawset(L, 4); + + int delta = 0; + if (old_nil && !new_nil) delta = 1; + else if (new_nil && !old_nil) delta = -1; + + if (delta != 0) + { + auto obj = (EventObject *)lua_touserdata(L, 1); + obj->item_count += delta; + if (obj->owner) + obj->owner->on_count_changed(obj->item_count, delta); + } + + return 0; +} + static void do_invoke_event(lua_State *L, int argbase, int num_args, int errorfun) { for (int i = 0; i < num_args; i++) @@ -1292,7 +1406,7 @@ static void dfhack_event_invoke(lua_State *L, int base, bool from_c) while (lua_next(L, event)) { // Skip the NULL key in the main loop - if (lua_islightuserdata(L, -2) && !lua_touserdata(L, -2)) + if (is_null_userdata(L, -2)) lua_pop(L, 1); else do_invoke_event(L, argbase, num_args, errorfun); @@ -1303,14 +1417,20 @@ static void dfhack_event_invoke(lua_State *L, int base, bool from_c) static int dfhack_event_call(lua_State *state) { - luaL_checktype(state, 1, LUA_TTABLE); + luaL_checktype(state, 1, LUA_TUSERDATA); luaL_checkstack(state, lua_gettop(state)+2, "stack overflow in event dispatch"); + auto obj = (EventObject *)lua_touserdata(state, 1); + if (obj->owner) + obj->owner->on_invoked(state, lua_gettop(state)-1, false); + + lua_getuservalue(state, 1); + lua_replace(state, 1); dfhack_event_invoke(state, 0, false); return 0; } -void DFHack::Lua::InvokeEvent(color_ostream &out, lua_State *state, void *key, int num_args) +void DFHack::Lua::Event::Invoke(color_ostream &out, lua_State *state, void *key, int num_args) { AssertCoreSuspend(state); @@ -1325,7 +1445,7 @@ void DFHack::Lua::InvokeEvent(color_ostream &out, lua_State *state, void *key, i lua_rawgetp(state, LUA_REGISTRYINDEX, key); - if (!lua_istable(state, -1)) + if (!lua_isuserdata(state, -1)) { if (!lua_isnil(state, -1)) out.printerr("Invalid event object in Lua::InvokeEvent"); @@ -1333,22 +1453,29 @@ void DFHack::Lua::InvokeEvent(color_ostream &out, lua_State *state, void *key, i return; } + auto obj = (EventObject *)lua_touserdata(state, -1); lua_insert(state, base+1); + if (obj->owner) + obj->owner->on_invoked(state, num_args, true); + + lua_getuservalue(state, base+1); + lua_replace(state, base+1); + color_ostream *cur_out = Lua::GetOutput(state); set_dfhack_output(state, &out); dfhack_event_invoke(state, base, true); set_dfhack_output(state, cur_out); } -void DFHack::Lua::MakeEvent(lua_State *state, void *key) +void DFHack::Lua::Event::Make(lua_State *state, void *key, Owner *owner) { lua_rawgetp(state, LUA_REGISTRYINDEX, key); if (lua_isnil(state, -1)) { lua_pop(state, 1); - NewEvent(state); + New(state, owner); } lua_dup(state); @@ -1358,7 +1485,7 @@ void DFHack::Lua::MakeEvent(lua_State *state, void *key) void DFHack::Lua::Notification::invoke(color_ostream &out, int nargs) { assert(state); - InvokeEvent(out, state, key, nargs); + Event::Invoke(out, state, key, nargs); } void DFHack::Lua::Notification::bind(lua_State *state, void *key) @@ -1369,12 +1496,12 @@ void DFHack::Lua::Notification::bind(lua_State *state, void *key) void DFHack::Lua::Notification::bind(lua_State *state, const char *name) { - MakeEvent(state, this); + Event::Make(state, this); if (handler) { PushFunctionWrapper(state, 0, name, handler); - lua_rawsetp(state, -2, NULL); + Event::SetPrivateCallback(state, -2); } this->state = state; @@ -1435,11 +1562,26 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) lua_newtable(state); lua_pushcfunction(state, dfhack_event_call); lua_setfield(state, -2, "__call"); - lua_pushcfunction(state, Lua::NewEvent); - lua_setfield(state, -2, "new"); + lua_pushcfunction(state, dfhack_event_len); + lua_setfield(state, -2, "__len"); + lua_pushcfunction(state, dfhack_event_tostring); + lua_setfield(state, -2, "__tostring"); + lua_pushcfunction(state, dfhack_event_index); + lua_setfield(state, -2, "__index"); + lua_pushcfunction(state, dfhack_event_newindex); + lua_setfield(state, -2, "__newindex"); + lua_pushcfunction(state, dfhack_event_pairs); + lua_setfield(state, -2, "__pairs"); lua_dup(state); lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_EVENT_META_TOKEN); - lua_setfield(state, -2, "event"); + + lua_newtable(state); + lua_pushcfunction(state, dfhack_event_new); + lua_setfield(state, -2, "new"); + lua_dup(state); + lua_setfield(state, -3, "__metatable"); + lua_setfield(state, -3, "event"); + lua_pop(state, 1); // Initialize the dfhack global luaL_setfuncs(state, dfhack_funcs, 0); @@ -1599,7 +1741,7 @@ void DFHack::Lua::Core::onStateChange(color_ostream &out, int code) { } Lua::Push(State, code); - Lua::InvokeEvent(out, State, (void*)onStateChange, 1); + Lua::Event::Invoke(out, State, (void*)onStateChange, 1); } static void run_timers(color_ostream &out, lua_State *L, @@ -1653,7 +1795,7 @@ void DFHack::Lua::Core::Init(color_ostream &out) // Register events lua_rawgetp(State, LUA_REGISTRYINDEX, &DFHACK_DFHACK_TOKEN); - MakeEvent(State, (void*)onStateChange); + Event::Make(State, (void*)onStateChange); lua_setfield(State, -2, "onStateChange"); lua_pushcfunction(State, dfhack_timeout); diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index 668b2aec6..3c015b2a7 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -108,6 +108,50 @@ struct Plugin::RefAutoinc ~RefAutoinc(){ lock->lock_sub(); }; }; +struct Plugin::LuaCommand { + Plugin *owner; + std::string name; + int (*command)(lua_State *state); + + LuaCommand(Plugin *owner, std::string name) + : owner(owner), name(name), command(NULL) {} +}; + +struct Plugin::LuaFunction { + Plugin *owner; + std::string name; + function_identity_base *identity; + bool silent; + + LuaFunction(Plugin *owner, std::string name) + : owner(owner), name(name), identity(NULL), silent(false) {} +}; + +struct Plugin::LuaEvent : public Lua::Event::Owner { + LuaFunction handler; + Lua::Notification *event; + bool active; + int count; + + LuaEvent(Plugin *owner, std::string name) + : handler(owner,name), event(NULL), active(false), count(0) + { + handler.silent = true; + } + + void on_count_changed(int new_cnt, int delta) { + RefAutoinc lock(handler.owner->access); + count = new_cnt; + if (event) + event->on_count_changed(new_cnt, delta); + } + void on_invoked(lua_State *state, int nargs, bool from_c) { + RefAutoinc lock(handler.owner->access); + if (event) + event->on_invoked(state, nargs, from_c); + } +}; + Plugin::Plugin(Core * core, const std::string & filepath, const std::string & _filename, PluginManager * pm) { filename = filepath; @@ -439,7 +483,11 @@ void Plugin::index_lua(DFLibrary *lib) cmd->handler.identity = evlist->event->get_handler(); cmd->event = evlist->event; if (cmd->active) + { cmd->event->bind(Lua::Core::State, cmd); + if (cmd->count > 0) + cmd->event->on_count_changed(cmd->count, 0); + } } } } @@ -477,8 +525,13 @@ int Plugin::lua_fun_wrapper(lua_State *state) RefAutoinc lock(cmd->owner->access); if (!cmd->identity) + { + if (cmd->silent) + return 0; + luaL_error(state, "plugin function %s() has been unloaded", (cmd->owner->name+"."+cmd->name).c_str()); + } return LuaWrapper::method_wrapper_core(state, cmd->identity); } @@ -506,14 +559,14 @@ void Plugin::open_lua(lua_State *state, int table) { for (auto it = lua_events.begin(); it != lua_events.end(); ++it) { - Lua::MakeEvent(state, it->second); + Lua::Event::Make(state, it->second, it->second); push_function(state, &it->second->handler); - lua_rawsetp(state, -2, NULL); + Lua::Event::SetPrivateCallback(state, -2); it->second->active = true; if (it->second->event) - it->second->event->bind(state, it->second); + it->second->event->bind(Lua::Core::State, it->second); lua_setfield(state, table, it->first.c_str()); } diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index 897e7032e..6b1afb88b 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -310,9 +310,18 @@ namespace DFHack {namespace Lua { DFHACK_EXPORT bool IsCoreContext(lua_State *state); - DFHACK_EXPORT int NewEvent(lua_State *state); - DFHACK_EXPORT void MakeEvent(lua_State *state, void *key); - DFHACK_EXPORT void InvokeEvent(color_ostream &out, lua_State *state, void *key, int num_args); + namespace Event { + struct DFHACK_EXPORT Owner { + virtual ~Owner() {} + virtual void on_count_changed(int new_cnt, int delta) {} + virtual void on_invoked(lua_State *state, int nargs, bool from_c) {} + }; + + DFHACK_EXPORT void New(lua_State *state, Owner *owner = NULL); + DFHACK_EXPORT void Make(lua_State *state, void *key, Owner *owner = NULL); + DFHACK_EXPORT void SetPrivateCallback(lua_State *state, int ev_idx); + DFHACK_EXPORT void Invoke(color_ostream &out, lua_State *state, void *key, int num_args); + } class StackUnwinder { lua_State *state; @@ -365,18 +374,24 @@ namespace DFHack {namespace Lua { } } - class DFHACK_EXPORT Notification { + class DFHACK_EXPORT Notification : public Event::Owner { lua_State *state; void *key; function_identity_base *handler; + int count; public: Notification(function_identity_base *handler = NULL) - : state(NULL), key(NULL), handler(handler) {} + : state(NULL), key(NULL), handler(handler), count(0) {} + int get_listener_count() { return count; } lua_State *get_state() { return state; } function_identity_base *get_handler() { return handler; } + lua_State *state_if_count() { return (count > 0) ? state : NULL; } + + void on_count_changed(int new_cnt, int) { count = new_cnt; } + void invoke(color_ostream &out, int nargs); void bind(lua_State *state, const char *name); @@ -388,7 +403,7 @@ namespace DFHack {namespace Lua { static DFHack::Lua::Notification name##_event(df::wrap_function(handler, true)); \ void name(color_ostream &out) { \ handler(out); \ - if (name##_event.get_state()) { \ + if (name##_event.state_if_count()) { \ name##_event.invoke(out, 0); \ } \ } @@ -397,7 +412,7 @@ namespace DFHack {namespace Lua { static DFHack::Lua::Notification name##_event(df::wrap_function(handler, true)); \ void name(color_ostream &out, arg_type1 arg1) { \ handler(out, arg1); \ - if (auto state = name##_event.get_state()) { \ + if (auto state = name##_event.state_if_count()) { \ DFHack::Lua::Push(state, arg1); \ name##_event.invoke(out, 1); \ } \ @@ -407,7 +422,7 @@ namespace DFHack {namespace Lua { static DFHack::Lua::Notification name##_event(df::wrap_function(handler, true)); \ void name(color_ostream &out, arg_type1 arg1, arg_type2 arg2) { \ handler(out, arg1, arg2); \ - if (auto state = name##_event.get_state()) { \ + if (auto state = name##_event.state_if_count()) { \ DFHack::Lua::Push(state, arg1); \ DFHack::Lua::Push(state, arg2); \ name##_event.invoke(out, 2); \ @@ -418,7 +433,7 @@ namespace DFHack {namespace Lua { static DFHack::Lua::Notification name##_event(df::wrap_function(handler, true)); \ void name(color_ostream &out, arg_type1 arg1, arg_type2 arg2, arg_type3 arg3) { \ handler(out, arg1, arg2, arg3); \ - if (auto state = name##_event.get_state()) { \ + if (auto state = name##_event.state_if_count()) { \ DFHack::Lua::Push(state, arg1); \ DFHack::Lua::Push(state, arg2); \ DFHack::Lua::Push(state, arg3); \ @@ -430,7 +445,7 @@ namespace DFHack {namespace Lua { static DFHack::Lua::Notification name##_event(df::wrap_function(handler, true)); \ void name(color_ostream &out, arg_type1 arg1, arg_type2 arg2, arg_type3 arg3, arg_type4 arg4) { \ handler(out, arg1, arg2, arg3, arg4); \ - if (auto state = name##_event.get_state()) { \ + if (auto state = name##_event.state_if_count()) { \ DFHack::Lua::Push(state, arg1); \ DFHack::Lua::Push(state, arg2); \ DFHack::Lua::Push(state, arg3); \ @@ -443,7 +458,7 @@ namespace DFHack {namespace Lua { static DFHack::Lua::Notification name##_event(df::wrap_function(handler, true)); \ void name(color_ostream &out, arg_type1 arg1, arg_type2 arg2, arg_type3 arg3, arg_type4 arg4, arg_type5 arg5) { \ handler(out, arg1, arg2, arg3, arg4, arg5); \ - if (auto state = name##_event.get_state()) { \ + if (auto state = name##_event.state_if_count()) { \ DFHack::Lua::Push(state, arg1); \ DFHack::Lua::Push(state, arg2); \ DFHack::Lua::Push(state, arg3); \ diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index 22171a15c..25b05ad40 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -173,31 +173,16 @@ namespace DFHack PluginManager * parent; plugin_state state; - struct LuaCommand { - Plugin *owner; - std::string name; - int (*command)(lua_State *state); - LuaCommand(Plugin *owner, std::string name) : owner(owner), name(name) {} - }; + struct LuaCommand; std::map lua_commands; static int lua_cmd_wrapper(lua_State *state); - struct LuaFunction { - Plugin *owner; - std::string name; - function_identity_base *identity; - LuaFunction(Plugin *owner, std::string name) : owner(owner), name(name) {} - }; + struct LuaFunction; std::map lua_functions; static int lua_fun_wrapper(lua_State *state); void push_function(lua_State *state, LuaFunction *fn); - struct LuaEvent { - LuaFunction handler; - Lua::Notification *event; - bool active; - LuaEvent(Plugin *owner, std::string name) : handler(owner,name), active(false) {} - }; + struct LuaEvent; std::map lua_events; void index_lua(DFLibrary *lib); diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 5ebc9809c..5699e8a20 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -122,8 +122,9 @@ end -- Misc functions function printall(table) - if type(table) == 'table' or df.isvalid(table) == 'ref' then - for k,v in pairs(table) do + local ok,f,t,k = pcall(pairs,table) + if ok then + for k,v in f,t,k do print(string.format("%-23s\t = %s",tostring(k),tostring(v))) end end @@ -177,10 +178,6 @@ end -- String conversions -function dfhack.event:__tostring() - return "" -end - function dfhack.persistent:__tostring() return ""