Added base lua manager

- CMakeLists.txt, Makefile, and appveyor now use luajit
- lua/LuaManager.[ch]pp has been added which includes a basic script loader & thread scheduler
- SCRIPTSDIR has been added to settings.[ch]pp, this specifies what directory lua scripts should be loaded from
- Makefile has been updated to include lua/LuaManager.[ch]pp
This commit is contained in:
2021-04-10 18:41:55 -05:00
committed by gsemaj
parent 6f59001be1
commit 43aa4eaeb8
9 changed files with 197 additions and 2 deletions

156
src/lua/LuaManager.cpp Normal file
View File

@@ -0,0 +1,156 @@
#include "lua/LuaManager.hpp"
#include "servers/CNShardServer.hpp"
#include "settings.hpp"
#include <filesystem>
#include <vector>
time_t getTime();
class Script;
// our "main" state, holds our environment
lua_State *LuaManager::global;
std::map<lua_State*, Script*> activeScripts;
/*
Basically each script is treated as a coroutine, when wait() is called it gets yielded and is pushed onto the scheduler queue to be resumed.
*/
class Script {
private:
lua_State *thread;
lRegistry threadRef; // we'll need to unref this when closing this state
public:
Script(std::string source) {
// make the thread & register it in the registry
thread = lua_newthread(LuaManager::global);
threadRef = luaL_ref(LuaManager::global, LUA_REGISTRYINDEX);
// add this script to the map
activeScripts[thread] = this;
// compile & run the script, if it error'd, print the error
int _retCode;
if (luaL_loadfile(thread, source.c_str()) || ((_retCode = lua_resume(thread, 0)) != 0 && (_retCode != LUA_YIELD))) {
std::cout << "[LUA ERROR]: " << lua_tostring(thread, -1) << std::endl;
}
}
// unregister all of our events from the wrappers
~Script() {
LuaManager::clearState(thread);
// remove it from the global registry
luaL_unref(LuaManager::global, LUA_REGISTRYINDEX, threadRef);
}
// c++ moment....
lua_State* getState() {
return thread;
}
};
struct scheduledThread {
lRegistry ref; // ref that should be unref'd before resuming
time_t time;
};
std::map<lua_State*, scheduledThread> scheduleQueue;
// pauses the script for x seconds
int OF_wait(lua_State *state) {
double seconds = luaL_checknumber(state, 1);
// register the thread in the global registry so we don't get GC'd
lua_pushthread(state);
lRegistry ref = luaL_ref(state, LUA_REGISTRYINDEX); // threads decentant of a global state all share the global registry
// yield the state and push the state onto our scheduler queue
scheduleQueue[state] = {ref, (int)(seconds*1000) + getTime()};
return lua_yield(state, 0);
}
void luaScheduler(CNServer *serv, time_t currtime) {
for (auto iter = scheduleQueue.begin(); iter != scheduleQueue.end();) {
time_t event = (*iter).second.time;
lRegistry ref = (*iter).second.ref;
lua_State *thread = (*iter).first;
// is it time to run the event?
if (event <= currtime) {
// remove from the scheduler queue
scheduleQueue.erase(iter++);
// unregister the thread
luaL_unref(thread, LUA_REGISTRYINDEX, ref);
// 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));
} else // go to the next iteration
++iter;
}
}
void LuaManager::printError(std::string err) {
std::cerr << "[LUA ERR]: " << err << std::endl;
}
void LuaManager::init() {
// allocate our state
global = luaL_newstate();
// open lua's base libraries (excluding the IO for now)
luaopen_base(global);
luaopen_table(global);
luaopen_string(global);
luaopen_math(global);
luaopen_debug(global);
// add wait()
lua_register(global, "wait", OF_wait);
activeScripts = std::map<lua_State*, Script*>();
REGISTER_SHARD_TIMER(luaScheduler, 200);
// load our scripts
loadScripts();
}
void LuaManager::runScript(std::string filename) {
new Script(filename);
}
void LuaManager::stopScripts() {
// clear the scheduler queue
scheduleQueue.clear();
// free all the scripts, they'll take care of everything for us :)
for (auto as : activeScripts) {
delete as.second;
}
// finally clear the map
activeScripts.clear();
}
void LuaManager::loadScripts() {
if (!std::filesystem::exists(settings::SCRIPTSDIR)) {
std::cout << "[WARN] scripts directory \"" << settings::SCRIPTSDIR << "\" doesn't exist!" << std::endl;
return;
}
// for each file in the scripts director, load the script
std::filesystem::path dir(settings::SCRIPTSDIR);
for (auto &d : std::filesystem::directory_iterator(dir)) {
if (d.path().extension().u8string() == ".lua")
runScript(d.path().u8string());
}
}
void LuaManager::clearState(lua_State *state) {
// TODO
}

26
src/lua/LuaManager.hpp Normal file
View File

@@ -0,0 +1,26 @@
#pragma once
#include "core/CNProtocol.hpp"
#include <string>
#ifdef _WIN32
#include <luajit/lua.hpp>
#else
#include <luajit-2.1/lua.hpp>
#endif
typedef int lRegistry;
namespace LuaManager {
extern lua_State *global;
void init();
void printError(std::string err);
// runs the script in the passed file
void runScript(std::string filename);
void stopScripts();
void loadScripts();
// unregisters the events tied to this state with all wrappers
void clearState(lua_State *state);
}

View File

@@ -1,5 +1,6 @@
#include "servers/CNLoginServer.hpp"
#include "servers/CNShardServer.hpp"
#include "lua/LuaManager.hpp"
#include "PlayerManager.hpp"
#include "PlayerMovement.hpp"
#include "BuiltinCommands.hpp"
@@ -120,6 +121,7 @@ int main() {
Racing::init();
Database::open();
Trading::init();
LuaManager::init();
switch (settings::EVENTMODE) {
case 0: break; // no event

View File

@@ -35,6 +35,7 @@ int settings::SPAWN_ANGLE = 130;
std::string settings::DBPATH = "database.db";
std::string settings::TDATADIR = "tdata/";
std::string settings::PATCHDIR = "tdata/patch/";
std::string settings::SCRIPTSDIR = "scripts";
std::string settings::NPCJSON = "NPCs.json";
std::string settings::MOBJSON = "mobs.json";

View File

@@ -27,6 +27,7 @@ namespace settings {
extern std::string PATCHDIR;
extern std::string ENABLEDPATCHES;
extern std::string TDATADIR;
extern std::string SCRIPTSDIR;
extern int EVENTMODE;
extern bool MONITORENABLED;
extern int MONITORPORT;