#include "lua/LuaManager.hpp" #include "lua/LuaWrapper.hpp" #include "lua/EventWrapper.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); yieldCall(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); // register our libraries Event::init(global); 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 }