mirror of
https://github.com/BeamMP/BeamMP-Server.git
synced 2025-08-17 16:57:05 +00:00
Lua: Add variadic print, LuaAPI
This commit is contained in:
parent
5978665ad6
commit
9b9c18a4c1
@ -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")
|
||||
|
@ -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
13
include/LuaAPI.h
Normal 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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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
22
src/LuaAPI.cpp
Normal 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);
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user