mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2024-11-05 06:50:04 +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/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
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/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
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