diff --git a/Makefile b/Makefile index b91b0a3..83d9603 100644 --- a/Makefile +++ b/Makefile @@ -44,6 +44,7 @@ CXXSRC=\ src/servers/CNShardServer.cpp\ src/servers/Monitor.cpp\ src/lua/LuaManager.cpp\ + src/lua/EventWrapper.cpp\ src/db/init.cpp\ src/db/login.cpp\ src/db/shard.cpp\ @@ -88,6 +89,8 @@ CXXHDR=\ src/servers/CNShardServer.hpp\ src/servers/Monitor.hpp\ src/lua/LuaManager.hpp\ + src/lua/LuaWrapper.hpp\ + src/lua/EventWrapper.hpp\ src/db/Database.hpp\ src/db/internal.hpp\ vendor/bcrypt/BCrypt.hpp\ diff --git a/src/lua/EventWrapper.cpp b/src/lua/EventWrapper.cpp new file mode 100644 index 0000000..9a22230 --- /dev/null +++ b/src/lua/EventWrapper.cpp @@ -0,0 +1,190 @@ +/* + This loads the Event library, basically a wrapper for lEvents, allows the lua script to register callbacks or wait for an event to be fired +*/ + +#include "lua/EventWrapper.hpp" +#include "lua/LuaWrapper.hpp" + +#include + +#define LIBNAME "event" +#define LISTNR "listener" + +typedef lEvent* eventData; + +std::unordered_set activeEvents; + +struct lstnrData { + lEvent *event; + uint32_t rawListener; +}; + +static void pushListener(lua_State *state, lEvent *event, uint32_t listener) { + lstnrData *lstnr = (lstnrData*)lua_newuserdata(state, sizeof(lstnrData)); + lstnr->event = event; + lstnr->rawListener = listener; + + // attaches our metatable from the registry to the udata + luaL_getmetatable(state, LISTNR); + lua_setmetatable(state, -2); +} + +static void pushEvent(lua_State *state, lEvent *event) { + eventData *eData = (eventData*)lua_newuserdata(state, sizeof(eventData)); + *eData = event; + + // attaches our metatable from the registry to the udata + luaL_getmetatable(state, LIBNAME); + lua_setmetatable(state, -2); +} + +static lEvent *grabEvent(lua_State *state, int indx) { + // first, make sure its a userdata + luaL_checktype(state, indx, LUA_TUSERDATA); + + // now, check and make sure its our libraries metatable attached to this userdata + eventData *event = (eventData*)luaL_checkudata(state, indx, LIBNAME); + if (event == NULL) { + luaL_typerror(state, indx, LIBNAME); + return NULL; + } + + // if the event is not active return NULL and don't throw an error (this is an easily forgivable user-error) + if (activeEvents.find(*event) == activeEvents.end()) + return NULL; + + // return the pointer to the lEvent + return *event; +} + +static lstnrData *grabListener(lua_State *state, int indx) { + // first, make sure its a userdata + luaL_checktype(state, indx, LUA_TUSERDATA); + + // now, check and make sure its our libraries metatable attached to this userdata + lstnrData *lstnr = (lstnrData*)luaL_checkudata(state, indx, LISTNR); + if (lstnr == NULL) { + luaL_typerror(state, indx, LISTNR); + return NULL; + } + + // if the event is not active return NULL and don't throw an error (this is an easily forgivable user-error) + if (activeEvents.find(lstnr->event) == activeEvents.end()) + return NULL; + + // return the pointer to the lEvent + return lstnr; +} + +// connects a callback to an event +static int evnt_listen(lua_State *state) { + int nargs = lua_gettop(state); + lEvent *event = grabEvent(state, 1); + + // sanity check + if (event == NULL) + return 0; + + // for each argument passed, check that it's a function and add it to the event + for (int i = 2; i <= nargs; i++) { + luaL_checktype(state, i, LUA_TFUNCTION); + lua_pushvalue(state, i); + pushListener(state, event, event->addCallback(state, luaL_ref(state, LUA_REGISTRYINDEX))); + } + + // we return this many listeners + return nargs - 1; +} + +// yields the thread until the event is triggered +static int evnt_wait(lua_State *state) { + lEvent *event = grabEvent(state, 1); + + // sanity check + if (event == NULL) + return 0; + + event->addWait(state); + return lua_yield(state, 0); +} + +static int lstnr_disconnect(lua_State *state) { + lstnrData *lstnr = grabListener(state, 1); + + // sanity check + if (lstnr == NULL) + return 0; + + // disconnect the event + lstnr->event->disconnectEvent(lstnr->rawListener); + return 0; +} + +static int lstnr_reconnect(lua_State *state) { + lstnrData *lstnr = grabListener(state, 1); + + // sanity check + if (lstnr == NULL) + return 0; + + // disconnect the event + lstnr->event->reconnectEvent(lstnr->rawListener); + return 0; +} + +static int lstnr_gc(lua_State *state) { + lstnrData *lstnr = grabListener(state, 1); + + // sanity check + if (lstnr == NULL) + return 0; + + // if the listener is disabled, clear it + if (lstnr->event->isDisabled(lstnr->rawListener)) + lstnr->event->clear(lstnr->rawListener); + + return 0; +} + +static luaL_Reg evnt_methods[] = { + {"listen", evnt_listen}, + {"wait", evnt_wait}, + {0, 0} +}; + +static luaL_Reg lstnr_methods[] = { + {"disconnect", lstnr_disconnect}, + {"reconnect", lstnr_reconnect}, + {0, 0} +}; + +void LuaManager::Event::init(lua_State *state) { + // register the library & pop it + luaL_register(state, LIBNAME, evnt_methods); + + // create the event metatable + luaL_newmetatable(state, LIBNAME); + lua_pushstring(state, "__index"); + lua_pushvalue(state, -3); + lua_rawset(state, -3); + + // create the listener metatable + luaL_newmetatable(state, LISTNR); + lua_pushstring(state, "__index"); + lua_newtable(state); + luaL_register(state, NULL, lstnr_methods); + lua_rawset(state, -3); + lua_pushstring(state, "__gc"); + lua_pushcfunction(state, lstnr_gc); + lua_rawset(state, -3); + + // pop the tables off the stack + lua_pop(state, 3); + + activeEvents = std::unordered_set(); +} + +// just a wrapper for pushEvent() +void LuaManager::Event::push(lua_State *state, lEvent *event) { + pushEvent(state, event); +} \ No newline at end of file diff --git a/src/lua/EventWrapper.hpp b/src/lua/EventWrapper.hpp new file mode 100644 index 0000000..b27a907 --- /dev/null +++ b/src/lua/EventWrapper.hpp @@ -0,0 +1,10 @@ +#include "lua/LuaManager.hpp" +#include "lua/LuaWrapper.hpp" + +namespace LuaManager { + namespace Event { + void init(lua_State *state); + + void push(lua_State *state, lEvent *event); + } +} \ No newline at end of file diff --git a/src/lua/LuaManager.cpp b/src/lua/LuaManager.cpp index da16f3b..cab6ee7 100644 --- a/src/lua/LuaManager.cpp +++ b/src/lua/LuaManager.cpp @@ -1,4 +1,6 @@ #include "lua/LuaManager.hpp" +#include "lua/LuaWrapper.hpp" +#include "lua/EventWrapper.hpp" #include "servers/CNShardServer.hpp" #include "settings.hpp" @@ -85,10 +87,7 @@ void luaScheduler(CNServer *serv, time_t currtime) { // resume the state, (wait() returns the delta time since call) lua_pushnumber(thread, ((double)currtime - event)/10); - - int err = lua_resume(thread, 1); - if (err != 0 && err != LUA_YIELD) // if it returned LUA_YIELD, wait() was just called again, not an error! - LuaManager::printError(lua_tostring(thread, -1)); + yieldCall(thread, 1); } else // go to the next iteration ++iter; } @@ -112,6 +111,9 @@ void LuaManager::init() { // add wait() lua_register(global, "wait", OF_wait); + // register our libraries + Event::init(global); + activeScripts = std::map(); REGISTER_SHARD_TIMER(luaScheduler, 200); diff --git a/src/lua/LuaWrapper.hpp b/src/lua/LuaWrapper.hpp new file mode 100644 index 0000000..a05c68e --- /dev/null +++ b/src/lua/LuaWrapper.hpp @@ -0,0 +1,217 @@ +#pragma once +/* + This is a very simple header that adds a small "event" layer, where scripts can register callbacks & "waits" on events. The actual + Event metatables & library is in EventWrapper.[hc]pp +*/ + +#include +#include +#include +#include + +#include "lua/LuaManager.hpp" + +#define yieldCall(state, nargs) \ + int _retCode = lua_resume(state, nargs); \ + if (_retCode != 0 && _retCode != LUA_YIELD) \ + LuaManager::printError(lua_tostring(state, -1)); \ + +inline static int lua_autoPush(lua_State* state, int nargs) { + // return the number of pushed arguments :) + return nargs; +} + +/* + This function will automatically push all of the passed arguments onto the stack and returns the # of pushed values + + Supported datatypes are: + double or int : LUA_TNUMBER + char* or const char* : LUA_TSTRING + bool : LUA_TBOOLEAN + lRegistry : grabs the object from the lua registry and pushes it onto the stack +*/ +template +inline static int lua_autoPush(lua_State* state, int nargs, T arg, Rest... rest) { + // pick which branch to compile based on the type of arg + if constexpr(std::is_same::value || std::is_same::value) { + lua_pushnumber(state, (lua_Number)arg); + } else if constexpr(std::is_same::value || std::is_same::value) { + lua_pushstring(state, (const char*)arg); + } else if constexpr(std::is_same::value) { + // grab the value from the registry + lua_rawgeti(state, LUA_REGISTRYINDEX, (int)arg); + } else if constexpr(std::is_same::value) { + lua_pushboolean(state, arg); + } + + // recursively call, expanding rest and pushing the left-most rvalue into arg + return lua_autoPush(state, ++nargs, rest...); +} + +enum eventType { + EVENT_CALLBACK, // standard callback + EVENT_WAIT // state needs to be resumed with the arguments +}; + +class lEvent; + +extern std::unordered_set activeEvents; + +class lEvent { +private: + struct rawEvent { + lua_State *state; + eventType type; + lRegistry ref; + bool disabled; + }; + + uint32_t nextId; // next ID + std::map events; + + uint32_t registerEvent(lua_State *state, lRegistry ref, eventType type) { + uint32_t id = nextId++; + + assert(nextId < UINT32_MAX); + events[id] = {state, type, ref, false}; + return id; + } + + void freeEvent(rawEvent *event) { + // the registry of all states is the same! + luaL_unref(event->state, LUA_REGISTRYINDEX, event->ref); + } + +public: + lEvent() { + events = std::map(); + nextId = 0; // start at 0 + activeEvents.insert(this); + } + + ~lEvent() { + // remove from the active set and disable all existing callbacks + activeEvents.erase(this); + clear(); + } + + uint32_t addCallback(lua_State *state, lRegistry ref) { + return registerEvent(state, ref, EVENT_CALLBACK); + } + + // yields the thread until the event is called + uint32_t addWait(lua_State *state) { + // push the thread onto the stack and register it in the global registry + lua_pushthread(state); + lRegistry ref = luaL_ref(state, LUA_REGISTRYINDEX); + return registerEvent(state, ref, EVENT_WAIT); + } + + // walks through the events and unregister them from the state + void clear() { + for (auto &pair : events) { + freeEvent(&pair.second); + } + + events.clear(); + } + + // free the event based on id + void clear(uint32_t id) { + auto iter = events.find(id); + + // sanity check + if (iter == events.end()) + return; + + // free the event + freeEvent(&(*iter).second); + events.erase(iter); + } + + // frees all events to this state + void clear(lua_State *state) { + for (auto iter = events.begin(); iter != events.end();) { + rawEvent *event = &(*iter).second; + + // if this is our state, free this event and erase it from the map + if (event->state == state) { + freeEvent(event); + events.erase(iter++); + } else + ++iter; + } + } + + void disconnectEvent(uint32_t id) { + auto iter = events.find(id); + + // sanity check + if (iter == events.end()) + return; + + (*iter).second.disabled = true; + } + + void reconnectEvent(uint32_t id) { + auto iter = events.find(id); + + // sanity check + if (iter == events.end()) + return; + + (*iter).second.disabled = false; + } + + bool isDisabled(uint32_t id) { + auto iter = events.find(id); + + // sanity check + if (iter == events.end()) + return true; + + return (*iter).second.disabled; + } + + template inline void call(Args... args) { + auto eventsClone = events; // so if a callback is added, we don't iterate into undefined behavior + for (auto &pair : eventsClone) { + rawEvent *event = &pair.second; + + // if the event is disabled, skip it + if (event->disabled) + continue; + + switch (event->type) { + case EVENT_CALLBACK: { + // make thread for this callback + lua_State *nThread = lua_newthread(event->state); + + // push the callable first, the push all the arguments + lua_rawgeti(nThread, LUA_REGISTRYINDEX, (int)event->ref); + int nargs = lua_autoPush(nThread, 0, args...); + + // then call it :) + yieldCall(nThread, nargs); + + // we can safely pop the thread off the stack now + lua_pop(event->state, 1); + break; + } + case EVENT_WAIT: { + // erase this event + freeEvent(&pair.second); + events.erase(pair.first); + + // the :wait() will return the passed arguments + int nargs = lua_autoPush(event->state, 0, args...); + yieldCall(event->state, nargs); + break; + } + default: + std::cout << "[WARN] INVALID EVENT TYPE : " << event->type << std::endl; + break; + } + } + } +}; \ No newline at end of file