mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2024-11-05 15:00:06 +00:00
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:
parent
43aa4eaeb8
commit
2aa23a83de
3
Makefile
3
Makefile
@ -44,6 +44,7 @@ CXXSRC=\
|
|||||||
src/servers/CNShardServer.cpp\
|
src/servers/CNShardServer.cpp\
|
||||||
src/servers/Monitor.cpp\
|
src/servers/Monitor.cpp\
|
||||||
src/lua/LuaManager.cpp\
|
src/lua/LuaManager.cpp\
|
||||||
|
src/lua/EventWrapper.cpp\
|
||||||
src/db/init.cpp\
|
src/db/init.cpp\
|
||||||
src/db/login.cpp\
|
src/db/login.cpp\
|
||||||
src/db/shard.cpp\
|
src/db/shard.cpp\
|
||||||
@ -88,6 +89,8 @@ CXXHDR=\
|
|||||||
src/servers/CNShardServer.hpp\
|
src/servers/CNShardServer.hpp\
|
||||||
src/servers/Monitor.hpp\
|
src/servers/Monitor.hpp\
|
||||||
src/lua/LuaManager.hpp\
|
src/lua/LuaManager.hpp\
|
||||||
|
src/lua/LuaWrapper.hpp\
|
||||||
|
src/lua/EventWrapper.hpp\
|
||||||
src/db/Database.hpp\
|
src/db/Database.hpp\
|
||||||
src/db/internal.hpp\
|
src/db/internal.hpp\
|
||||||
vendor/bcrypt/BCrypt.hpp\
|
vendor/bcrypt/BCrypt.hpp\
|
||||||
|
190
src/lua/EventWrapper.cpp
Normal file
190
src/lua/EventWrapper.cpp
Normal 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
10
src/lua/EventWrapper.hpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,6 @@
|
|||||||
#include "lua/LuaManager.hpp"
|
#include "lua/LuaManager.hpp"
|
||||||
|
#include "lua/LuaWrapper.hpp"
|
||||||
|
#include "lua/EventWrapper.hpp"
|
||||||
|
|
||||||
#include "servers/CNShardServer.hpp"
|
#include "servers/CNShardServer.hpp"
|
||||||
#include "settings.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)
|
// resume the state, (wait() returns the delta time since call)
|
||||||
lua_pushnumber(thread, ((double)currtime - event)/10);
|
lua_pushnumber(thread, ((double)currtime - event)/10);
|
||||||
|
yieldCall(thread, 1);
|
||||||
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));
|
|
||||||
} else // go to the next iteration
|
} else // go to the next iteration
|
||||||
++iter;
|
++iter;
|
||||||
}
|
}
|
||||||
@ -112,6 +111,9 @@ void LuaManager::init() {
|
|||||||
// add wait()
|
// add wait()
|
||||||
lua_register(global, "wait", OF_wait);
|
lua_register(global, "wait", OF_wait);
|
||||||
|
|
||||||
|
// register our libraries
|
||||||
|
Event::init(global);
|
||||||
|
|
||||||
activeScripts = std::map<lua_State*, Script*>();
|
activeScripts = std::map<lua_State*, Script*>();
|
||||||
|
|
||||||
REGISTER_SHARD_TIMER(luaScheduler, 200);
|
REGISTER_SHARD_TIMER(luaScheduler, 200);
|
||||||
|
217
src/lua/LuaWrapper.hpp
Normal file
217
src/lua/LuaWrapper.hpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user