From 9b9c18a4c1b9c41cd73dc1324d96f49bf6c7fbef Mon Sep 17 00:00:00 2001 From: Lion Kortlepel Date: Thu, 16 Sep 2021 11:54:52 +0200 Subject: [PATCH] Lua: Add variadic print, LuaAPI --- CMakeLists.txt | 3 +- include/Common.h | 1 + include/LuaAPI.h | 13 ++++ include/TLuaEngine.h | 18 +++++- include/TLuaPlugin.h | 5 +- src/LuaAPI.cpp | 22 +++++++ src/TLuaEngine.cpp | 138 ++++++++++++++++++++++++++++++++----------- src/TLuaPlugin.cpp | 53 ++++++++++++++++- 8 files changed, 213 insertions(+), 40 deletions(-) create mode 100644 include/LuaAPI.h create mode 100644 src/LuaAPI.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8523a78..1e7f1c4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,7 +43,8 @@ add_executable(BeamMP-Server include/THeartbeatThread.h src/THeartbeatThread.cpp include/Http.h src/Http.cpp include/TPPSMonitor.h src/TPPSMonitor.cpp - include/TNetwork.h src/TNetwork.cpp) + include/TNetwork.h src/TNetwork.cpp + include/LuaAPI.h src/LuaAPI.cpp) target_include_directories(BeamMP-Server PRIVATE "${PROJECT_SOURCE_DIR}/deps/asio/asio/include") target_include_directories(BeamMP-Server PRIVATE "${PROJECT_SOURCE_DIR}/deps/rapidjson/include") diff --git a/include/Common.h b/include/Common.h index 52577f9..c74a16e 100644 --- a/include/Common.h +++ b/include/Common.h @@ -125,6 +125,7 @@ void RegisterThread(const std::string str); #define beammp_warn(x) Application::Console().Write(_this_location + std::string("[WARN] ") + (x)) #define beammp_info(x) Application::Console().Write(_this_location + std::string("[INFO] ") + (x)) #define beammp_error(x) Application::Console().Write(_this_location + std::string("[ERROR] ") + (x)) +#define beammp_lua_error(x) Application::Console().Write(_this_location + std::string("[LUA ERROR] ") + (x)) #define luaprint(x) Application::Console().Write(_this_location + std::string("[LUA] ") + (x)) #define beammp_debug(x) \ do { \ diff --git a/include/LuaAPI.h b/include/LuaAPI.h new file mode 100644 index 0000000..43a1722 --- /dev/null +++ b/include/LuaAPI.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include "TLuaEngine.h" + +namespace LuaAPI { +void Print(sol::variadic_args); +namespace MP { + static inline TLuaEngine* Engine { nullptr }; + void GetOSName(); + std::tuple GetServerVersion(); +} +} diff --git a/include/TLuaEngine.h b/include/TLuaEngine.h index 4899817..b80dfc3 100644 --- a/include/TLuaEngine.h +++ b/include/TLuaEngine.h @@ -12,6 +12,9 @@ #include #include +#define SOL_ALL_SAFETIES_ON 1 +#include + using TLuaStateId = std::string; namespace fs = std::filesystem; @@ -21,7 +24,9 @@ struct TLuaResult { std::atomic_bool Ready; std::atomic_bool Error; std::string ErrorMessage; + sol::protected_function_result Result; // TODO: Add condition_variable + void WaitUntilReady(); }; struct TLuaPluginConfig { @@ -37,27 +42,34 @@ public: void operator()() override; [[nodiscard]] std::shared_ptr EnqueueScript(TLuaStateId StateID, const std::shared_ptr& Script); - void EnsureStateExists(TLuaStateId StateId, const std::string& Name); + [[nodiscard]] std::shared_ptr EnqueueFunctionCall(TLuaStateId StateID, const std::string& FunctionName); + void EnsureStateExists(TLuaStateId StateId, const std::string& Name, bool DontCallOnInit = false); + + static constexpr const char* BeamMPFnNotFoundError = "BEAMMP_FN_NOT_FOUND"; private: - void CollectPlugins(); + void CollectAndInitPlugins(); void InitializePlugin(const fs::path& Folder, const TLuaPluginConfig& Config); void FindAndParseConfig(const fs::path& Folder, TLuaPluginConfig& Config); class StateThreadData : IThreaded { public: - StateThreadData(const std::string& Name, std::atomic_bool& Shutdown); + StateThreadData(const std::string& Name, std::atomic_bool& Shutdown, TLuaStateId StateId); StateThreadData(const StateThreadData&) = delete; [[nodiscard]] std::shared_ptr EnqueueScript(const std::shared_ptr& Script); + [[nodiscard]] std::shared_ptr EnqueueFunctionCall(const std::string& FunctionName); void operator()() override; private: std::string mName; std::atomic_bool& mShutdown; + TLuaStateId mStateId; lua_State* mState; std::thread mThread; std::queue, std::shared_ptr>> mStateExecuteQueue; std::mutex mStateExecuteQueueMutex; + std::queue>> mStateFunctionQueue; + std::mutex mStateFunctionQueueMutex; }; TNetwork& mNetwork; diff --git a/include/TLuaPlugin.h b/include/TLuaPlugin.h index c3ffc2f..a4cb5bd 100644 --- a/include/TLuaPlugin.h +++ b/include/TLuaPlugin.h @@ -2,7 +2,7 @@ class TLuaPlugin { public: - TLuaPlugin(TLuaEngine& Engine, const TLuaPluginConfig& Config); + TLuaPlugin(TLuaEngine& Engine, const TLuaPluginConfig& Config, const fs::path& MainFolder); TLuaPlugin(const TLuaPlugin&) = delete; TLuaPlugin& operator=(const TLuaPlugin&) = delete; ~TLuaPlugin() noexcept = default; @@ -12,4 +12,7 @@ public: private: TLuaPluginConfig mConfig; TLuaEngine& mEngine; + fs::path mFolder; + std::string mPluginName; + std::unordered_map> mFileContents; }; diff --git a/src/LuaAPI.cpp b/src/LuaAPI.cpp new file mode 100644 index 0000000..f50a04a --- /dev/null +++ b/src/LuaAPI.cpp @@ -0,0 +1,22 @@ +#include "LuaAPI.h" +#include "TLuaEngine.h" + +void LuaAPI::MP::GetOSName() { +} + +std::tuple LuaAPI::MP::GetServerVersion() { + return { Application::ServerVersion().major, Application::ServerVersion().minor, Application::ServerVersion().patch }; +} + +void LuaAPI::Print(sol::variadic_args Args) { + std::string ToPrint = ""; + for (const auto& Arg : Args) { + if (Arg.get_type() == sol::type::string) { + ToPrint += Arg.as(); + } else { + ToPrint += "((unprintable type))"; + } + ToPrint += " "; + } + luaprint(ToPrint); +} diff --git a/src/TLuaEngine.cpp b/src/TLuaEngine.cpp index 3b0fa1e..94fe614 100644 --- a/src/TLuaEngine.cpp +++ b/src/TLuaEngine.cpp @@ -2,12 +2,11 @@ #include "CustomAssert.h" #include "TLuaPlugin.h" +#include "LuaAPI.h" + #include #include -#define SOL_ALL_SAFETIES_ON 1 -#include - static std::mt19937_64 MTGen64; static TLuaStateId GenerateUniqueStateId() { @@ -18,6 +17,7 @@ static TLuaStateId GenerateUniqueStateId() { TLuaEngine::TLuaEngine(TServer& Server, TNetwork& Network) : mNetwork(Network) , mServer(Server) { + LuaAPI::MP::Engine = this; if (!fs::exists(Application::Settings.Resource)) { fs::create_directory(Application::Settings.Resource); } @@ -38,7 +38,16 @@ TLuaEngine::TLuaEngine(TServer& Server, TNetwork& Network) void TLuaEngine::operator()() { RegisterThread("LuaEngine"); // lua engine main thread - CollectPlugins(); + CollectAndInitPlugins(); + // now call all onInit's + for (const auto& Pair : mLuaStates) { + auto Res = EnqueueFunctionCall(Pair.first, "onInit"); + Res->WaitUntilReady(); + if (Res->Error && Res->ErrorMessage != TLuaEngine::BeamMPFnNotFoundError) { + beammp_lua_error("Calling \"onInit\" on \"" + Pair.first + "\" failed: " + Res->ErrorMessage); + } + } + // this thread handles timers while (!mShutdown) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); } @@ -46,12 +55,17 @@ void TLuaEngine::operator()() { std::shared_ptr TLuaEngine::EnqueueScript(TLuaStateId StateID, const std::shared_ptr& Script) { std::unique_lock Lock(mLuaStatesMutex); - TLuaResult Result; beammp_debug("enqueuing script into \"" + StateID + "\""); return mLuaStates.at(StateID)->EnqueueScript(Script); } -void TLuaEngine::CollectPlugins() { +std::shared_ptr TLuaEngine::EnqueueFunctionCall(TLuaStateId StateID, const std::string& FunctionName) { + std::unique_lock Lock(mLuaStatesMutex); + beammp_debug("calling \"" + FunctionName + "\" in \"" + StateID + "\""); + return mLuaStates.at(StateID)->EnqueueFunctionCall(FunctionName); +} + +void TLuaEngine::CollectAndInitPlugins() { for (const auto& Dir : fs::directory_iterator(mResourceServerPath)) { auto Path = Dir.path(); Path = fs::relative(Path); @@ -69,9 +83,10 @@ void TLuaEngine::CollectPlugins() { void TLuaEngine::InitializePlugin(const fs::path& Folder, const TLuaPluginConfig& Config) { beammp_assert(fs::exists(Folder)); beammp_assert(fs::is_directory(Folder)); - TLuaPlugin Plugin(*this, Config); std::unique_lock Lock(mLuaStatesMutex); - EnsureStateExists(Config.StateId, Folder.stem().string()); + EnsureStateExists(Config.StateId, Folder.stem().string(), true); + Lock.unlock(); + TLuaPlugin Plugin(*this, Config, Folder); } void TLuaEngine::FindAndParseConfig(const fs::path& Folder, TLuaPluginConfig& Config) { @@ -96,56 +111,113 @@ void TLuaEngine::FindAndParseConfig(const fs::path& Folder, TLuaPluginConfig& Co } } -void TLuaEngine::EnsureStateExists(TLuaStateId StateId, const std::string& Name) { +void TLuaEngine::EnsureStateExists(TLuaStateId StateId, const std::string& Name, bool DontCallOnInit) { if (mLuaStates.find(StateId) == mLuaStates.end()) { beammp_debug("Creating lua state for state id \"" + StateId + "\""); - auto DataPtr = std::make_unique(Name, mShutdown); + auto DataPtr = std::make_unique(Name, mShutdown, StateId); mLuaStates[StateId] = std::move(DataPtr); + if (!DontCallOnInit) { + auto Res = EnqueueFunctionCall(StateId, "onInit"); + Res->WaitUntilReady(); + if (Res->Error && Res->ErrorMessage != TLuaEngine::BeamMPFnNotFoundError) { + beammp_lua_error("Calling \"onInit\" on \"" + StateId + "\" failed: " + Res->ErrorMessage); + } + } } } -TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, std::atomic_bool& Shutdown) +TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, std::atomic_bool& Shutdown, TLuaStateId StateId) : mName(Name) - , mShutdown(Shutdown) { + , mShutdown(Shutdown) + , mStateId(StateId) { mState = luaL_newstate(); luaL_openlibs(mState); sol::state_view StateView(mState); - auto LuaPrint = [](const std::string& Msg) { luaprint(Msg); }; - StateView.set_function("print", LuaPrint); + StateView.set_function("print", &LuaAPI::Print); + auto Table = StateView.create_named_table("MP"); + Table.set_function("GetOSName", &LuaAPI::MP::GetOSName); + Table.set_function("GetServerVersion", &LuaAPI::MP::GetServerVersion); Start(); } std::shared_ptr TLuaEngine::StateThreadData::EnqueueScript(const std::shared_ptr& Script) { - beammp_debug("enqueuing script into \"" + mName + "\""); + beammp_debug("enqueuing script into \"" + mStateId + "\""); std::unique_lock Lock(mStateExecuteQueueMutex); auto Result = std::make_shared(); mStateExecuteQueue.push({ Script, Result }); return Result; } +std::shared_ptr TLuaEngine::StateThreadData::EnqueueFunctionCall(const std::string& FunctionName) { + beammp_debug("calling \"" + FunctionName + "\" in \"" + mName + "\""); + std::unique_lock Lock(mStateFunctionQueueMutex); + auto Result = std::make_shared(); + mStateFunctionQueue.push({ FunctionName, Result }); + return Result; +} + void TLuaEngine::StateThreadData::operator()() { - RegisterThread(mName); + RegisterThread("Lua:" + mStateId); while (!mShutdown) { - std::unique_lock Lock(mStateExecuteQueueMutex); - if (mStateExecuteQueue.empty()) { - Lock.unlock(); - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } else { - auto S = mStateExecuteQueue.front(); - mStateExecuteQueue.pop(); - Lock.unlock(); - beammp_debug("Running script"); - sol::state_view StateView(mState); - auto Res = StateView.safe_script(*S.first, sol::script_pass_on_error); - if (Res.valid()) { - S.second->Error = false; + { // StateExecuteQueue Scope + std::unique_lock Lock(mStateExecuteQueueMutex); + if (mStateExecuteQueue.empty()) { + Lock.unlock(); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); } else { - S.second->Error = true; - sol::error Err = Res; - S.second->ErrorMessage = Err.what(); + auto S = mStateExecuteQueue.front(); + mStateExecuteQueue.pop(); + Lock.unlock(); + beammp_debug("Running script"); + sol::state_view StateView(mState); + auto Res = StateView.safe_script(*S.first, sol::script_pass_on_error); + S.second->Ready = true; + if (Res.valid()) { + S.second->Error = false; + S.second->Result = std::move(Res); + } else { + S.second->Error = true; + sol::error Err = Res; + S.second->ErrorMessage = Err.what(); + } } - S.second->Ready = true; } + { // StateFunctionQueue Scope + std::unique_lock Lock(mStateFunctionQueueMutex); + if (mStateFunctionQueue.empty()) { + Lock.unlock(); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } else { + auto FnNameResultPair = mStateFunctionQueue.front(); + mStateFunctionQueue.pop(); + Lock.unlock(); + beammp_debug("Running function"); + sol::state_view StateView(mState); + auto Fn = StateView[FnNameResultPair.first]; + if (Fn.valid() && Fn.get_type() == sol::type::function) { + auto Res = Fn(); + FnNameResultPair.second->Ready = true; + if (Res.valid()) { + FnNameResultPair.second->Error = false; + FnNameResultPair.second->Result = std::move(Res); + } else { + FnNameResultPair.second->Error = true; + sol::error Err = Res; + FnNameResultPair.second->ErrorMessage = Err.what(); + } + } else { + FnNameResultPair.second->Ready = true; + FnNameResultPair.second->Error = true; + FnNameResultPair.second->ErrorMessage = BeamMPFnNotFoundError; // special error kind that we can ignore later + } + } + } + } +} + +void TLuaResult::WaitUntilReady() { + while (!Ready) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); } } diff --git a/src/TLuaPlugin.cpp b/src/TLuaPlugin.cpp index 5567335..48e7d8a 100644 --- a/src/TLuaPlugin.cpp +++ b/src/TLuaPlugin.cpp @@ -4,7 +4,56 @@ #include #include -TLuaPlugin::TLuaPlugin(TLuaEngine& Engine, const TLuaPluginConfig& Config) +TLuaPlugin::TLuaPlugin(TLuaEngine& Engine, const TLuaPluginConfig& Config, const fs::path& MainFolder) : mConfig(Config) - , mEngine(Engine) { + , mEngine(Engine) + , mFolder(MainFolder) + , mPluginName(MainFolder.stem().string()) + , mFileContents(0) { + beammp_debug("Lua plugin \"" + mPluginName + "\" starting in \"" + mFolder.string() + "\""); + std::vector Entries; + for (const auto& Entry : fs::directory_iterator(mFolder)) { + if (Entry.is_regular_file() && Entry.path().extension() == ".lua") { + beammp_debug("Found script \"" + Entry.path().string() + "\" in \"" + mFolder.string() + "\""); + Entries.push_back(Entry); + } + } + // sort alphabetically (not needed if config is used to determine call order) + // TODO: Use config to figure out what to run in which order + std::sort(Entries.begin(), Entries.end(), [](const fs::path& first, const fs::path& second) { + auto firstStr = first.string(); + auto secondStr = second.string(); + std::transform(firstStr.begin(), firstStr.end(), firstStr.begin(), ::tolower); + std::transform(secondStr.begin(), secondStr.end(), secondStr.begin(), ::tolower); + return firstStr < secondStr; + }); + std::vector>> ResultsToCheck; + for (const auto& Entry : Entries) { + // read in entire file + std::FILE* File = std::fopen(Entry.c_str(), "r"); + if (File) { + auto Size = std::filesystem::file_size(Entry); + auto Contents = std::make_shared(); + Contents->resize(Size); + auto NRead = std::fread(Contents->data(), 1, Contents->size(), File); + if (NRead == Contents->size()) { + beammp_debug("Successfully read \"" + Entry.string() + "\" (" + std::to_string(NRead) + " Bytes)"); + mFileContents[fs::relative(Entry)] = Contents; + // Execute first time + auto Result = mEngine.EnqueueScript(mConfig.StateId, Contents); + ResultsToCheck.emplace_back(Entry, std::move(Result)); + } else { + beammp_error("Error while reading script file \"" + Entry.string() + "\". Did the file change while reading?"); + } + std::fclose(File); + } else { + beammp_error("Could not read script file \"" + Entry.string() + "\": " + std::strerror(errno)); + } + } + for (auto& Result : ResultsToCheck) { + Result.second->WaitUntilReady(); + if (Result.second->Error) { + beammp_lua_error("Failed: \"" + Result.first.string() + "\": " + Result.second->ErrorMessage); + } + } }