Added Lua Events

- LuaWrapper creates a simple event layer for lua states. Allowing events to have registered callbacks & be waited on until fired
- EventWrapper exposes this api to scripts
This commit is contained in:
CPunch 2021-04-10 19:01:20 -05:00 committed by gsemaj
parent 43aa4eaeb8
commit 2aa23a83de
5 changed files with 426 additions and 4 deletions

View File

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

190
src/lua/EventWrapper.cpp Normal file
View File

@ -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 <unordered_set>
#define LIBNAME "event"
#define LISTNR "listener"
typedef lEvent* eventData;
std::unordered_set<lEvent*> 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<lEvent*>();
}
// just a wrapper for pushEvent()
void LuaManager::Event::push(lua_State *state, lEvent *event) {
pushEvent(state, event);
}

10
src/lua/EventWrapper.hpp Normal file
View File

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

View File

@ -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<lua_State*, Script*>();
REGISTER_SHARD_TIMER(luaScheduler, 200);

217
src/lua/LuaWrapper.hpp Normal file
View File

@ -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 <string>
#include <map>
#include <unordered_set>
#include <cassert>
#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<typename T, class... Rest>
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<T, int>::value || std::is_same<T, double>::value) {
lua_pushnumber(state, (lua_Number)arg);
} else if constexpr(std::is_same<T, char*>::value || std::is_same<T, const char*>::value) {
lua_pushstring(state, (const char*)arg);
} else if constexpr(std::is_same<T, lRegistry>::value) {
// grab the value from the registry
lua_rawgeti(state, LUA_REGISTRYINDEX, (int)arg);
} else if constexpr(std::is_same<T, bool>::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<lEvent*> activeEvents;
class lEvent {
private:
struct rawEvent {
lua_State *state;
eventType type;
lRegistry ref;
bool disabled;
};
uint32_t nextId; // next ID
std::map<uint32_t, rawEvent> 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<uint32_t, rawEvent>();
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<class... Args> 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;
}
}
}
};