diff --git a/platform/xcode/liblove.xcodeproj/project.pbxproj b/platform/xcode/liblove.xcodeproj/project.pbxproj index d588d34b8..4f1e5fef5 100644 --- a/platform/xcode/liblove.xcodeproj/project.pbxproj +++ b/platform/xcode/liblove.xcodeproj/project.pbxproj @@ -920,6 +920,7 @@ /* Begin PBXFileReference section */ 503971A86B7167A91B670FBA /* boot.lua.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = boot.lua.h; sourceTree = ""; }; + FA024C1C1C0D81CD00567EB0 /* wrap_Event.lua */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = wrap_Event.lua; sourceTree = ""; }; FA08F5AE16C7525600F007B5 /* liblove-macosx.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "liblove-macosx.plist"; path = "macosx/liblove-macosx.plist"; sourceTree = ""; }; FA0B78DD1A958B90000E1D17 /* liblove.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = liblove.a; sourceTree = BUILT_PRODUCTS_DIR; }; FA0B78F71A958E3B000E1D17 /* b64.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = b64.cpp; sourceTree = ""; }; @@ -2170,6 +2171,7 @@ FA0B7B551A95902C000E1D17 /* sdl */, FA8951A01AA2EDF300EC385A /* wrap_Event.cpp */, FA8951A11AA2EDF300EC385A /* wrap_Event.h */, + FA024C1C1C0D81CD00567EB0 /* wrap_Event.lua */, ); path = event; sourceTree = ""; diff --git a/src/common/runtime.cpp b/src/common/runtime.cpp index dca9bb2df..dd01e65ec 100644 --- a/src/common/runtime.cpp +++ b/src/common/runtime.cpp @@ -243,7 +243,17 @@ int luax_register_module(lua_State *L, const WrappedModule &m) // Register all the functions. if (m.functions != nullptr) - luax_setfuncs(L, m.functions); + { + const luaL_Reg *l = m.functions; + + for (; l->name != nullptr; l++) + { + // Set the module's table as an upvalue for the module's functions. + lua_pushvalue(L, -1); + lua_pushcclosure(L, l->func, 1); + lua_setfield(L, -2, l->name); + } + } // Register types. if (m.types != nullptr) diff --git a/src/modules/event/Event.h b/src/modules/event/Event.h index 35073d4ba..de0c5612d 100644 --- a/src/modules/event/Event.h +++ b/src/modules/event/Event.h @@ -49,6 +49,8 @@ public: int toLua(lua_State *L); static Message *fromLua(lua_State *L, int n); + const std::string &getName() const { return name; } + private: std::string name; diff --git a/src/modules/event/wrap_Event.cpp b/src/modules/event/wrap_Event.cpp index 8f9af72c1..d730ca01a 100644 --- a/src/modules/event/wrap_Event.cpp +++ b/src/modules/event/wrap_Event.cpp @@ -25,6 +25,11 @@ #include "sdl/Event.h" +// Put the Lua code directly into a raw string literal. +static const char event_lua[] = +#include "wrap_Event.lua" +; + namespace love { namespace event @@ -32,55 +37,82 @@ namespace event #define instance() (Module::getInstance(Module::M_EVENT)) -static int poll_i(lua_State *L) -{ - Message *m; +static const char *listenersKey = "_listeners"; - while (instance()->poll(m)) +static int processMessage(lua_State *L, Message *m) +{ + if (m == nullptr) + return 0; + + int args = m->toLua(L); + + // Push the love.event[listenersKey][eventname] table onto the stack. + lua_getfield(L, lua_upvalueindex(1), listenersKey); + lua_getfield(L, -1, m->getName().c_str()); + + if (lua_istable(L, -1)) { - int args = m->toLua(L); - m->release(); - return args; + int len = (int) luax_objlen(L, -1); + + // Each array entry in the table is a listener function we should call. + for (int i = 1; i <= len; i++) + { + lua_rawgeti(L, -1, i); + + // Push all message args (including the event name), in order. + for (int j = 1; j <= args; j++) + lua_pushvalue(L, -(args + 3)); + + lua_call(L, args, 0); + } } + lua_pop(L, 2); + + m->release(); + + // Leave the message's args on the stack. + return args; +} + +static int w_poll_i(lua_State *L) +{ + Message *m = nullptr; + + if (instance()->poll(m)) + return processMessage(L, m); + // No pending events. return 0; } +int w_poll(lua_State *L) +{ + // w_poll_i needs access to the love.event table via an upvalue. + lua_pushvalue(L, lua_upvalueindex(1)); + lua_pushcclosure(L, w_poll_i, 1); + return 1; +} + int w_pump(lua_State *) { instance()->pump(); return 0; } -int w_poll(lua_State *L) -{ - lua_pushcclosure(L, &poll_i, 0); - return 1; -} - int w_wait(lua_State *L) { - Message *m; - - if ((m = instance()->wait())) - { - int args = m->toLua(L); - m->release(); - return args; - } - - return 0; + Message *m = instance()->wait(); + return processMessage(L, m); } int w_push(lua_State *L) { - Message *m; + Message *m = Message::fromLua(L, 1); - bool success = (m = Message::fromLua(L, 1)) != NULL; - luax_pushboolean(L, success); + luax_pushboolean(L, m != nullptr); - if (!success) + if (m == nullptr) return 1; instance()->push(m); @@ -117,6 +149,7 @@ int w_quit(lua_State *L) // List of functions to wrap. static const luaL_Reg functions[] = { + // addListener and removeListener are defined in wrap_Event.lua { "pump", w_pump }, { "poll", w_poll }, { "wait", w_wait }, @@ -143,7 +176,19 @@ extern "C" int luaopen_love_event(lua_State *L) w.functions = functions; w.types = nullptr; - return luax_register_module(L, w); + int ret = luax_register_module(L, w); + + // love.event[listenersKey] = {} + lua_newtable(L); + lua_setfield(L, -2, listenersKey); + + // Execute wrap_Event.lua, sending the event and listeners tables as args. + luaL_loadbuffer(L, event_lua, sizeof(event_lua), "wrap_Event.lua"); + lua_pushvalue(L, -2); + lua_getfield(L, -1, listenersKey); + lua_call(L, 2, 0); + + return ret; } } // event diff --git a/src/modules/event/wrap_Event.lua b/src/modules/event/wrap_Event.lua new file mode 100644 index 000000000..22828407a --- /dev/null +++ b/src/modules/event/wrap_Event.lua @@ -0,0 +1,97 @@ +R"luastring"--( +-- DO NOT REMOVE THE ABOVE LINE. It is used to load this file as a C++ string. +-- There is a matching delimiter at the bottom of the file. + +--[[ +Copyright (c) 2006-2015 LOVE Development Team + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not +claim that you wrote the original software. If you use this software +in a product, an acknowledgment in the product documentation would be +appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be +misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +--]] + +local event, listeners = ... + +local type, select, error = type, select, error +local pairs, ipairs = pairs, ipairs +local table_insert, table_remove = table.insert, table.remove +local max = math.max + +local function addListenerForEvent(event, func) + listeners[event] = listeners[event] or {} + + -- Don't add a function to the event's array more than once. + for i,v in ipairs(listeners[event]) do + if v == func then + return + end + end + + table_insert(listeners[event], func) +end + +function event.addListener(func, ...) + if type(func) ~= "function" then + error("Invalid argument #1 to addListener (expected function)", 2) + end + + local t = (...) + if type(t) == "table" then + for i, name in ipairs(t) do + if type(name) ~= "string" then + error("Invalid table element #"..i.." in argument #2 to love.event.addListener (expected string)", 2) + end + + addListenerForEvent(name, func) + end + else + local n = select("#", ...) + for i = 1, max(n, 1) do + local name = select(i, ...) + + if type(name) ~= "string" then + error("Invalid argument #"..(i+1).." to addListener (expected string)", 2) + end + + addListenerForEvent(name, func) + end + end + + return func +end + +function event.removeListener(func) + if type(func) ~= "function" then + error("Invalid argument #1 to removeListener (expected function)", 2) + end + + local success = false + + -- Remove the listener function from all event names it was subscribed to. + for eventname, eventlisteners in pairs(listeners) do + for i=#eventlisteners, 1, -1 do + if eventlisteners[i] == func then + table_remove(eventlisteners, i) + success = true + break + end + end + end + + return success +end + +-- DO NOT REMOVE THE NEXT LINE. It is used to load this file as a C++ string. +--)luastring"--"