Track lua event listener count, and let the C++ host know.

This allows completely avoiding the call overhead if there
are none. The downside is that the event object now has to
be a userdata with lots of metamethods.
develop
Alexander Gavrilov 2012-08-23 19:27:12 +04:00
parent 7046a6abbc
commit c6c5ad56c9
7 changed files with 268 additions and 62 deletions

@ -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...)``

@ -1596,8 +1596,8 @@ Using <tt class="docutils literal">timeout_active(id,nil)</tt> cancels the timer
</ul>
<div class="section" id="event-type">
<h3><a class="toc-backref" href="#id29">Event type</a></h3>
<p>An event is just a lua table with a predefined metatable that
contains a __call metamethod. When it is invoked, it loops
<p>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.</p>
<p>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.</p>
<p>Creates a new instance of an event.</p>
</li>
<li><p class="first"><tt class="docutils literal">event[key] = function</tt></p>
<p>Sets the function as one of the listeners.</p>
<p>Sets the function as one of the listeners. Assign <em>nil</em> to remove it.</p>
<p><strong>NOTE</strong>: The <tt class="docutils literal">df.NULL</tt> key is reserved for the use by
the C++ owner of the event, and has some special semantics.</p>
the C++ owner of the event; it is an error to try setting it.</p>
</li>
<li><p class="first"><tt class="docutils literal">#event</tt></p>
<p>Returns the number of non-nil listeners.</p>
</li>
<li><p class="first"><tt class="docutils literal">pairs(event)</tt></p>
<p>Iterates over all listeners in the table.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">event(args...)</span></tt></p>
<p>Invokes all listeners contained in the event in an arbitrary

@ -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, "<event: %d listeners>", 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);

@ -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());
}

@ -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); \

@ -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<std::string, LuaCommand*> 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<std::string, LuaFunction*> 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<std::string, LuaEvent*> lua_events;
void index_lua(DFLibrary *lib);

@ -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 "<event>"
end
function dfhack.persistent:__tostring()
return "<persistent "..self.entry_id..":"..self.key.."=\""
..self.value.."\":"..table.concat(self.ints,",")..">"