Lua: Add variadic print, LuaAPI

This commit is contained in:
Lion Kortlepel 2021-09-16 11:54:52 +02:00
parent 5978665ad6
commit 9b9c18a4c1
No known key found for this signature in database
GPG Key ID: 4322FF2B4C71259B
8 changed files with 213 additions and 40 deletions

View File

@ -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")

View File

@ -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 { \

13
include/LuaAPI.h Normal file
View File

@ -0,0 +1,13 @@
#pragma once
#include <tuple>
#include "TLuaEngine.h"
namespace LuaAPI {
void Print(sol::variadic_args);
namespace MP {
static inline TLuaEngine* Engine { nullptr };
void GetOSName();
std::tuple<int, int, int> GetServerVersion();
}
}

View File

@ -12,6 +12,9 @@
#include <unordered_map>
#include <vector>
#define SOL_ALL_SAFETIES_ON 1
#include <sol/sol.hpp>
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<TLuaResult> EnqueueScript(TLuaStateId StateID, const std::shared_ptr<std::string>& Script);
void EnsureStateExists(TLuaStateId StateId, const std::string& Name);
[[nodiscard]] std::shared_ptr<TLuaResult> 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<TLuaResult> EnqueueScript(const std::shared_ptr<std::string>& Script);
[[nodiscard]] std::shared_ptr<TLuaResult> 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::pair<std::shared_ptr<std::string>, std::shared_ptr<TLuaResult>>> mStateExecuteQueue;
std::mutex mStateExecuteQueueMutex;
std::queue<std::pair<std::string, std::shared_ptr<TLuaResult>>> mStateFunctionQueue;
std::mutex mStateFunctionQueueMutex;
};
TNetwork& mNetwork;

View File

@ -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<std::string, std::shared_ptr<std::string>> mFileContents;
};

22
src/LuaAPI.cpp Normal file
View File

@ -0,0 +1,22 @@
#include "LuaAPI.h"
#include "TLuaEngine.h"
void LuaAPI::MP::GetOSName() {
}
std::tuple<int, int, int> 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<std::string>();
} else {
ToPrint += "((unprintable type))";
}
ToPrint += " ";
}
luaprint(ToPrint);
}

View File

@ -2,12 +2,11 @@
#include "CustomAssert.h"
#include "TLuaPlugin.h"
#include "LuaAPI.h"
#include <chrono>
#include <random>
#define SOL_ALL_SAFETIES_ON 1
#include <sol/sol.hpp>
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<TLuaResult> TLuaEngine::EnqueueScript(TLuaStateId StateID, const std::shared_ptr<std::string>& 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<TLuaResult> 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<StateThreadData>(Name, mShutdown);
auto DataPtr = std::make_unique<StateThreadData>(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<TLuaResult> TLuaEngine::StateThreadData::EnqueueScript(const std::shared_ptr<std::string>& Script) {
beammp_debug("enqueuing script into \"" + mName + "\"");
beammp_debug("enqueuing script into \"" + mStateId + "\"");
std::unique_lock Lock(mStateExecuteQueueMutex);
auto Result = std::make_shared<TLuaResult>();
mStateExecuteQueue.push({ Script, Result });
return Result;
}
std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueFunctionCall(const std::string& FunctionName) {
beammp_debug("calling \"" + FunctionName + "\" in \"" + mName + "\"");
std::unique_lock Lock(mStateFunctionQueueMutex);
auto Result = std::make_shared<TLuaResult>();
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));
}
}

View File

@ -4,7 +4,56 @@
#include <random>
#include <utility>
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<fs::path> 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<std::pair<fs::path, std::shared_ptr<TLuaResult>>> 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<std::string>();
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);
}
}
}