diff --git a/include/TLuaEngine.h b/include/TLuaEngine.h
deleted file mode 100644
index 91480dc..0000000
--- a/include/TLuaEngine.h
+++ /dev/null
@@ -1,299 +0,0 @@
-// BeamMP, the BeamNG.drive multiplayer mod.
-// Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
-//
-// BeamMP Ltd. can be contacted by electronic mail via contact@beammp.com.
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published
-// by the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see .
-
-#pragma once
-
-#include "Common.h"
-#include "IThreaded.h"
-#include "Network.h"
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#define SOL_ALL_SAFETIES_ON 1
-#include
-
-using TLuaStateId = std::string;
-namespace fs = std::filesystem;
-/**
- * std::variant means, that TLuaArgTypes may be one of the Types listed as template args
- */
-using TLuaArgTypes = std::variant>;
-static constexpr size_t TLuaArgTypes_String = 0;
-static constexpr size_t TLuaArgTypes_Int = 1;
-static constexpr size_t TLuaArgTypes_VariadicArgs = 2;
-static constexpr size_t TLuaArgTypes_Bool = 3;
-static constexpr size_t TLuaArgTypes_StringStringMap = 4;
-
-class TLuaPlugin;
-
-struct TLuaResult {
- bool Ready;
- bool Error;
- std::string ErrorMessage;
- sol::object Result { sol::lua_nil };
- TLuaStateId StateId;
- std::string Function;
- std::shared_ptr ReadyMutex {
- std::make_shared()
- };
- std::shared_ptr ReadyCondition {
- std::make_shared()
- };
-
- void MarkAsReady();
- void WaitUntilReady();
-};
-
-struct TLuaPluginConfig {
- static inline const std::string FileName = "PluginConfig.toml";
- TLuaStateId StateId;
- // TODO: Add execute list
- // TODO: Build a better toml serializer, or some way to do this in an easier way
-};
-
-struct TLuaChunk {
- TLuaChunk(std::shared_ptr Content,
- std::string FileName,
- std::string PluginPath);
- std::shared_ptr Content;
- std::string FileName;
- std::string PluginPath;
-};
-
-class TLuaEngine : public std::enable_shared_from_this {
-public:
- enum CallStrategy : int {
- BestEffort,
- Precise,
- };
-
- struct QueuedFunction {
- std::string FunctionName;
- std::shared_ptr Result;
- std::vector Args;
- std::string EventName; // optional, may be empty
- };
-
- TLuaEngine();
- virtual ~TLuaEngine() noexcept {
- beammp_debug("Lua Engine terminated");
- }
-
- void Start();
-
- std::shared_ptr<::Network> Network() { return mNetwork; }
-
- void SetNetwork(const std::shared_ptr<::Network>& network) { mNetwork = network; }
-
- size_t GetResultsToCheckSize() {
- std::unique_lock Lock(mResultsToCheckMutex);
- return mResultsToCheck.size();
- }
-
- size_t GetLuaStateCount() {
- std::unique_lock Lock(mLuaStatesMutex);
- return mLuaStates.size();
- }
- std::vector GetLuaStateNames() {
- std::vector names {};
- for (auto const& [stateId, _] : mLuaStates) {
- names.push_back(stateId);
- }
- return names;
- }
- size_t GetTimedEventsCount() {
- std::unique_lock Lock(mTimedEventsMutex);
- return mTimedEvents.size();
- }
- size_t GetRegisteredEventHandlerCount() {
- std::unique_lock Lock(mLuaEventsMutex);
- size_t LuaEventsCount = 0;
- for (const auto& State : mLuaEvents) {
- for (const auto& Events : State.second) {
- LuaEventsCount += Events.second.size();
- }
- }
- return LuaEventsCount - GetLuaStateCount();
- }
-
- static void WaitForAll(std::vector>& Results,
- const std::optional& Max = std::nullopt);
- void ReportErrors(const std::vector>& Results);
- bool HasState(TLuaStateId StateId);
- [[nodiscard]] std::shared_ptr EnqueueScript(TLuaStateId StateID, const TLuaChunk& Script);
- [[nodiscard]] std::shared_ptr EnqueueFunctionCall(TLuaStateId StateID, const std::string& FunctionName, const std::vector& Args);
- void EnsureStateExists(TLuaStateId StateId, const std::string& Name, bool DontCallOnInit = false);
- void RegisterEvent(const std::string& EventName, TLuaStateId StateId, const std::string& FunctionName);
- /**
- *
- * @tparam ArgsT Template Arguments for the event (Metadata) todo: figure out what this means
- * @param EventName Name of the event
- * @param IgnoreId
- * @param Args
- * @return
- */
- template
- [[nodiscard]] std::vector> TriggerEvent(const std::string& EventName, TLuaStateId IgnoreId, ArgsT&&... Args) {
- std::unique_lock Lock(mLuaEventsMutex);
- beammp_event(EventName);
- if (mLuaEvents.find(EventName) == mLuaEvents.end()) { // if no event handler is defined for 'EventName', return immediately
- return {};
- }
-
- std::vector> Results;
- std::vector Arguments { TLuaArgTypes { std::forward(Args) }... };
-
- for (const auto& Event : mLuaEvents.at(EventName)) {
- for (const auto& Function : Event.second) {
- if (Event.first != IgnoreId) {
- Results.push_back(EnqueueFunctionCall(Event.first, Function, Arguments));
- }
- }
- }
- return Results; //
- }
- template
- [[nodiscard]] std::vector> TriggerLocalEvent(const TLuaStateId& StateId, const std::string& EventName, ArgsT&&... Args) {
- std::unique_lock Lock(mLuaEventsMutex);
- beammp_event(EventName + " in '" + StateId + "'");
- if (mLuaEvents.find(EventName) == mLuaEvents.end()) { // if no event handler is defined for 'EventName', return immediately
- return {};
- }
- std::vector> Results;
- std::vector Arguments { TLuaArgTypes { std::forward(Args) }... };
- const auto Handlers = GetEventHandlersForState(EventName, StateId);
- for (const auto& Handler : Handlers) {
- Results.push_back(EnqueueFunctionCall(StateId, Handler, Arguments));
- }
- return Results;
- }
- std::set GetEventHandlersForState(const std::string& EventName, TLuaStateId StateId);
- void CreateEventTimer(const std::string& EventName, TLuaStateId StateId, size_t IntervalMS, CallStrategy Strategy);
- void CancelEventTimers(const std::string& EventName, TLuaStateId StateId);
- sol::state_view GetStateForPlugin(const fs::path& PluginPath);
- TLuaStateId GetStateIDForPlugin(const fs::path& PluginPath);
- void AddResultToCheck(const std::shared_ptr& Result);
-
- static constexpr const char* BeamMPFnNotFoundError = "BEAMMP_FN_NOT_FOUND";
-
- std::vector GetStateGlobalKeysForState(TLuaStateId StateId);
- std::vector GetStateTableKeysForState(TLuaStateId StateId, std::vector keys);
-
- // Debugging functions (slow)
- std::unordered_map /* handlers */> Debug_GetEventsForState(TLuaStateId StateId);
- std::queue>> Debug_GetStateExecuteQueueForState(TLuaStateId StateId);
- std::vector Debug_GetStateFunctionQueueForState(TLuaStateId StateId);
- std::vector Debug_GetResultsToCheckForState(TLuaStateId StateId);
-
-private:
- void CollectAndInitPlugins();
- void InitializePlugin(const fs::path& Folder, const TLuaPluginConfig& Config);
- void FindAndParseConfig(const fs::path& Folder, TLuaPluginConfig& Config);
- size_t CalculateMemoryUsage();
-
- class StateThreadData {
- public:
- StateThreadData(const std::string& Name, TLuaStateId StateId, TLuaEngine& Engine);
- StateThreadData(const StateThreadData&) = delete;
- virtual ~StateThreadData() noexcept { beammp_debug("\"" + mStateId + "\" destroyed"); }
- [[nodiscard]] std::shared_ptr EnqueueScript(const TLuaChunk& Script);
- [[nodiscard]] std::shared_ptr EnqueueFunctionCall(const std::string& FunctionName, const std::vector& Args);
- [[nodiscard]] std::shared_ptr EnqueueFunctionCallFromCustomEvent(const std::string& FunctionName, const std::vector& Args, const std::string& EventName, CallStrategy Strategy);
- void RegisterEvent(const std::string& EventName, const std::string& FunctionName);
- void AddPath(const fs::path& Path); // to be added to path and cpath
- void Start();
- sol::state_view State() { return sol::state_view(mState); }
-
- std::vector GetStateGlobalKeys();
- std::vector GetStateTableKeys(const std::vector& keys);
-
- // Debug functions, slow
- std::queue>> Debug_GetStateExecuteQueue();
- std::vector Debug_GetStateFunctionQueue();
-
- sol::table Lua_JsonDecode(const std::string& str);
-
- private:
- sol::table Lua_TriggerGlobalEvent(const std::string& EventName, sol::variadic_args EventArgs);
- sol::table Lua_TriggerLocalEvent(const std::string& EventName, sol::variadic_args EventArgs);
- sol::table Lua_GetPlayerIdentifiers(int ID);
- sol::table Lua_GetPlayers();
- std::string Lua_GetPlayerName(int ID);
- sol::table Lua_GetPlayerVehicles(int ID);
- std::pair Lua_GetVehicleStatus(int VID);
- sol::table Lua_HttpCreateConnection(const std::string& host, uint16_t port);
- int Lua_GetPlayerIDByName(const std::string& Name);
- sol::table Lua_FS_ListFiles(const std::string& Path);
- sol::table Lua_FS_ListDirectories(const std::string& Path);
-
- std::string mName;
- TLuaStateId mStateId;
- lua_State* mState;
- std::queue>> mStateExecuteQueue;
- std::recursive_mutex mStateExecuteQueueMutex;
- std::vector mStateFunctionQueue;
- std::mutex mStateFunctionQueueMutex;
- std::condition_variable mStateFunctionQueueCond;
- TLuaEngine* mEngine;
- sol::state_view mStateView { mState };
- std::queue mPaths;
- std::recursive_mutex mPathsMutex;
- std::mt19937 mMersenneTwister;
- std::uniform_real_distribution mUniformRealDistribution01;
- boost::scoped_thread<> mThread;
- };
-
- struct TimedEvent {
- std::chrono::high_resolution_clock::duration Duration {};
- std::chrono::high_resolution_clock::time_point LastCompletion {};
- std::string EventName;
- TLuaStateId StateId;
- CallStrategy Strategy;
- bool Expired();
- void Reset();
- };
-
- std::shared_ptr<::Network> mNetwork;
- const fs::path mResourceServerPath;
- std::vector> mLuaPlugins;
- std::unordered_map> mLuaStates;
- std::recursive_mutex mLuaStatesMutex;
- std::unordered_map>> mLuaEvents;
- std::recursive_mutex mLuaEventsMutex;
- std::vector mTimedEvents;
- std::recursive_mutex mTimedEventsMutex;
- std::list> mResultsToCheck;
- std::mutex mResultsToCheckMutex;
- std::condition_variable mResultsToCheckCond;
- boost::scoped_thread<> mThread;
-};
-
-// std::any TriggerLuaEvent(const std::string& Event, bool local, TLuaPlugin* Caller, std::shared_ptr arg, bool Wait);
diff --git a/include/TLuaPlugin.h b/include/TLuaPlugin.h
deleted file mode 100644
index 7ea871c..0000000
--- a/include/TLuaPlugin.h
+++ /dev/null
@@ -1,37 +0,0 @@
-// BeamMP, the BeamNG.drive multiplayer mod.
-// Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
-//
-// BeamMP Ltd. can be contacted by electronic mail via contact@beammp.com.
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published
-// by the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see .
-
-#include "TLuaEngine.h"
-
-class TLuaPlugin {
-public:
- TLuaPlugin(TLuaEngine& Engine, const TLuaPluginConfig& Config, const fs::path& MainFolder);
- TLuaPlugin(const TLuaPlugin&) = delete;
- TLuaPlugin& operator=(const TLuaPlugin&) = delete;
- ~TLuaPlugin() noexcept = default;
-
- const TLuaPluginConfig& GetConfig() const { return mConfig; }
- fs::path GetFolder() const { return mFolder; }
-
-private:
- TLuaPluginConfig mConfig;
- TLuaEngine& mEngine;
- fs::path mFolder;
- std::string mPluginName;
- std::unordered_map> mFileContents;
-};
diff --git a/include/TPluginMonitor.h b/include/TPluginMonitor.h
deleted file mode 100644
index e0e4e0f..0000000
--- a/include/TPluginMonitor.h
+++ /dev/null
@@ -1,40 +0,0 @@
-// BeamMP, the BeamNG.drive multiplayer mod.
-// Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
-//
-// BeamMP Ltd. can be contacted by electronic mail via contact@beammp.com.
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published
-// by the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see .
-
-#pragma once
-
-#include "Common.h"
-#include "IThreaded.h"
-
-#include
-#include
-#include
-
-class TLuaEngine;
-
-class TPluginMonitor : IThreaded, public std::enable_shared_from_this {
-public:
- TPluginMonitor(const fs::path& Path, std::shared_ptr Engine);
-
- void operator()();
-
-private:
- std::shared_ptr mEngine;
- fs::path mPath;
- std::unordered_map mFileTimes;
-};
diff --git a/src/TLuaEngine.cpp b/src/TLuaEngine.cpp
deleted file mode 100644
index 13a7392..0000000
--- a/src/TLuaEngine.cpp
+++ /dev/null
@@ -1,1103 +0,0 @@
-// BeamMP, the BeamNG.drive multiplayer mod.
-// Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
-//
-// BeamMP Ltd. can be contacted by electronic mail via contact@beammp.com.
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published
-// by the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see .
-
-#include "TLuaEngine.h"
-#include "CustomAssert.h"
-#include "Http.h"
-#include "LuaAPI.h"
-#include "TLuaPlugin.h"
-#include "sol/object.hpp"
-
-#include
-#include
-#include
-#include
-#include
-#include
-
-TLuaEngine* LuaAPI::MP::Engine;
-
-TLuaEngine::TLuaEngine()
- : mResourceServerPath(fs::path(Application::Settings.Resource) / "Server") {
- Application::SetSubsystemStatus("LuaEngine", Application::Status::Starting);
- LuaAPI::MP::Engine = this;
- if (!fs::exists(Application::Settings.Resource)) {
- fs::create_directory(Application::Settings.Resource);
- }
- if (!fs::exists(mResourceServerPath)) {
- fs::create_directory(mResourceServerPath);
- }
- Application::RegisterShutdownHandler([&] {
- Application::SetSubsystemStatus("LuaEngine", Application::Status::ShuttingDown);
- if (mThread.joinable()) {
- mThread.join();
- }
- Application::SetSubsystemStatus("LuaEngine", Application::Status::Shutdown);
- });
- mThread = boost::scoped_thread<>(&TLuaEngine::Start, this);
-}
-
-TEST_CASE("TLuaEngine ctor & dtor") {
- Application::Settings.Resource = "beammp_server_test_resources";
- TLuaEngine engine;
- Application::GracefullyShutdown();
-}
-
-void TLuaEngine::Start() {
- RegisterThread("LuaEngine");
- Application::SetSubsystemStatus("LuaEngine", Application::Status::Good);
- // lua engine main thread
- CollectAndInitPlugins();
- // now call all onInit's
- auto Futures = TriggerEvent("onInit", "");
- WaitForAll(Futures, std::chrono::seconds(5));
- for (const auto& Future : Futures) {
- if (Future->Error && Future->ErrorMessage != BeamMPFnNotFoundError) {
- beammp_lua_error("Calling \"onInit\" on \"" + Future->StateId + "\" failed: " + Future->ErrorMessage);
- }
- }
-
- auto ResultCheckThread = std::thread([&] {
- RegisterThread("ResultCheckThread");
- while (!Application::IsShuttingDown()) {
- std::unique_lock Lock(mResultsToCheckMutex);
- if (!mResultsToCheck.empty()) {
- mResultsToCheck.remove_if([](const std::shared_ptr& Ptr) -> bool {
- if (Ptr->Ready) {
- if (Ptr->Error) {
- if (Ptr->ErrorMessage != BeamMPFnNotFoundError) {
- beammp_lua_error(Ptr->Function + ": " + Ptr->ErrorMessage);
- }
- }
- return true;
- }
- return false;
- });
- } else {
- mResultsToCheckCond.wait_for(Lock, std::chrono::milliseconds(20));
- }
- }
- });
- // event loop
- auto Before = std::chrono::high_resolution_clock::now();
- while (!Application::IsShuttingDown()) {
- { // Timed Events Scope
- std::unique_lock Lock(mTimedEventsMutex);
- for (auto& Timer : mTimedEvents) {
- if (Timer.Expired()) {
- auto LastCompletionBeforeReset = Timer.LastCompletion;
- Timer.Reset();
- auto Handlers = GetEventHandlersForState(Timer.EventName, Timer.StateId);
- std::unique_lock StateLock(mLuaStatesMutex);
- std::unique_lock Lock2(mResultsToCheckMutex);
- for (auto& Handler : Handlers) {
- auto Res = mLuaStates[Timer.StateId]->EnqueueFunctionCallFromCustomEvent(Handler, {}, Timer.EventName, Timer.Strategy);
- if (Res) {
- mResultsToCheck.push_back(Res);
- mResultsToCheckCond.notify_one();
- } else {
- // "revert" reset
- Timer.LastCompletion = LastCompletionBeforeReset;
- // beammp_trace("Reverted reset of \"" + Timer.EventName + "\" timer");
- // no need to try to enqueue more handlers for this event (they will all fail)
- break;
- }
- }
- }
- }
- }
- if (mLuaStates.size() == 0) {
- beammp_trace("No Lua states, event loop running extremely sparsely");
- Application::SleepSafeSeconds(10);
- } else {
- constexpr double NsFactor = 1000000.0;
- constexpr double Expected = 10.0; // ms
- const auto Diff = (std::chrono::high_resolution_clock::now() - Before).count() / NsFactor;
- if (Diff < Expected) {
- std::this_thread::sleep_for(std::chrono::nanoseconds(size_t((Expected - Diff) * NsFactor)));
- } else {
- beammp_tracef("Event loop cannot keep up! Running {}ms behind", Diff);
- }
- }
- Before = std::chrono::high_resolution_clock::now();
- }
-
- if (ResultCheckThread.joinable()) {
- ResultCheckThread.join();
- }
-}
-
-size_t TLuaEngine::CalculateMemoryUsage() {
- size_t Usage = 0;
- std::unique_lock Lock(mLuaStatesMutex);
- for (auto& State : mLuaStates) {
- Usage += State.second->State().memory_used();
- }
- return Usage;
-}
-
-sol::state_view TLuaEngine::GetStateForPlugin(const fs::path& PluginPath) {
- for (const auto& Plugin : mLuaPlugins) {
- if (fs::equivalent(Plugin->GetFolder(), PluginPath)) {
- std::unique_lock Lock(mLuaStatesMutex);
- return mLuaStates.at(Plugin->GetConfig().StateId)->State();
- }
- }
- beammp_assert_not_reachable();
- return mLuaStates.begin()->second->State();
-}
-
-TLuaStateId TLuaEngine::GetStateIDForPlugin(const fs::path& PluginPath) {
- for (const auto& Plugin : mLuaPlugins) {
- if (fs::equivalent(Plugin->GetFolder(), PluginPath)) {
- std::unique_lock Lock(mLuaStatesMutex);
- return Plugin->GetConfig().StateId;
- }
- }
- beammp_assert_not_reachable();
- return "";
-}
-
-void TLuaEngine::AddResultToCheck(const std::shared_ptr& Result) {
- std::unique_lock Lock(mResultsToCheckMutex);
- mResultsToCheck.push_back(Result);
- mResultsToCheckCond.notify_one();
-}
-
-std::unordered_map /* handlers */> TLuaEngine::Debug_GetEventsForState(TLuaStateId StateId) {
- std::unordered_map> Result;
- std::unique_lock Lock(mLuaEventsMutex);
- for (const auto& EventNameToEventMap : mLuaEvents) {
- for (const auto& IdSetOfHandlersPair : EventNameToEventMap.second) {
- if (IdSetOfHandlersPair.first == StateId) {
- for (const auto& Handler : IdSetOfHandlersPair.second) {
- Result[EventNameToEventMap.first].push_back(Handler);
- }
- }
- }
- }
- return Result;
-}
-
-std::queue>> TLuaEngine::Debug_GetStateExecuteQueueForState(TLuaStateId StateId) {
- std::queue>> Result;
- std::unique_lock Lock(mLuaStatesMutex);
- Result = mLuaStates.at(StateId)->Debug_GetStateExecuteQueue();
- return Result;
-}
-
-std::vector TLuaEngine::Debug_GetStateFunctionQueueForState(TLuaStateId StateId) {
- std::vector Result;
- std::unique_lock Lock(mLuaStatesMutex);
- Result = mLuaStates.at(StateId)->Debug_GetStateFunctionQueue();
- return Result;
-}
-
-std::vector TLuaEngine::Debug_GetResultsToCheckForState(TLuaStateId StateId) {
- std::unique_lock Lock(mResultsToCheckMutex);
- auto ResultsToCheckCopy = mResultsToCheck;
- Lock.unlock();
- std::vector Result;
- while (!ResultsToCheckCopy.empty()) {
- auto ResultToCheck = std::move(ResultsToCheckCopy.front());
- ResultsToCheckCopy.pop_front();
- if (ResultToCheck->StateId == StateId) {
- Result.push_back(*ResultToCheck);
- }
- }
- return Result;
-}
-
-std::vector TLuaEngine::GetStateGlobalKeysForState(TLuaStateId StateId) {
- std::unique_lock Lock(mLuaStatesMutex);
- auto Result = mLuaStates.at(StateId)->GetStateGlobalKeys();
- return Result;
-}
-
-std::vector TLuaEngine::StateThreadData::GetStateGlobalKeys() {
- auto globals = mStateView.globals();
- std::vector Result;
- for (const auto& [key, value] : globals) {
- Result.push_back(key.as());
- }
- return Result;
-}
-
-std::vector TLuaEngine::GetStateTableKeysForState(TLuaStateId StateId, std::vector keys) {
- std::unique_lock Lock(mLuaStatesMutex);
- auto Result = mLuaStates.at(StateId)->GetStateTableKeys(keys);
- return Result;
-}
-
-std::vector TLuaEngine::StateThreadData::GetStateTableKeys(const std::vector& keys) {
- auto globals = mStateView.globals();
-
- sol::table current = globals;
- std::vector Result {};
-
- for (const auto& [key, value] : current) {
- std::string s = key.as();
- if (value.get_type() == sol::type::function) {
- s += "(";
- }
- Result.push_back(s);
- }
-
- if (!keys.empty()) {
- Result.clear();
- }
-
- for (size_t i = 0; i < keys.size(); ++i) {
- auto obj = current.get(keys.at(i));
- if (obj.get_type() == sol::type::nil) {
- // error
- break;
- } else if (i == keys.size() - 1) {
- if (obj.get_type() == sol::type::table) {
- for (const auto& [key, value] : obj.as()) {
- std::string s = key.as();
- if (value.get_type() == sol::type::function) {
- s += "(";
- }
- Result.push_back(s);
- }
- } else {
- Result = { obj.as() };
- }
- break;
- }
- if (obj.get_type() == sol::type::table) {
- current = obj;
- } else {
- // error
- break;
- }
- }
-
- return Result;
-}
-
-/*
-
- _G.a.b.c.d.
-
-*/
-
-void TLuaEngine::WaitForAll(std::vector>& Results, const std::optional& Max) {
- for (const auto& Result : Results) {
- bool Cancelled = false;
- size_t ms = 0;
- std::set WarnedResults;
-
- while (!Result->Ready && !Cancelled) {
- std::this_thread::sleep_for(std::chrono::milliseconds(10));
- ms += 10;
- if (Max.has_value() && std::chrono::milliseconds(ms) > Max.value()) {
- beammp_trace("'" + Result->Function + "' in '" + Result->StateId + "' did not finish executing in time (took: " + std::to_string(ms) + "ms).");
- Cancelled = true;
- } else if (ms > 1000 * 60) {
- auto ResultId = Result->StateId + "_" + Result->Function;
- if (WarnedResults.count(ResultId) == 0) {
- WarnedResults.insert(ResultId);
- beammp_lua_warn("'" + Result->Function + "' in '" + Result->StateId + "' is taking very long. The event it's handling is too important to discard the result of this handler, but may block this event and possibly the whole lua state.");
- }
- }
- }
-
- if (Cancelled) {
- beammp_lua_warn("'" + Result->Function + "' in '" + Result->StateId + "' failed to execute in time and was not waited for. It may still finish executing at a later time.");
- LuaAPI::MP::Engine->ReportErrors({ Result });
- } else if (Result->Error) {
- if (Result->ErrorMessage != BeamMPFnNotFoundError) {
- beammp_lua_error(Result->Function + ": " + Result->ErrorMessage);
- }
- }
- }
-}
-
-// run this on the error checking thread
-void TLuaEngine::ReportErrors(const std::vector>& Results) {
- std::unique_lock Lock2(mResultsToCheckMutex);
- for (const auto& Result : Results) {
- mResultsToCheck.push_back(Result);
- mResultsToCheckCond.notify_one();
- }
-}
-
-bool TLuaEngine::HasState(TLuaStateId StateId) {
- std::unique_lock Lock(mLuaStatesMutex);
- return mLuaStates.find(StateId) != mLuaStates.end();
-}
-
-std::shared_ptr TLuaEngine::EnqueueScript(TLuaStateId StateID, const TLuaChunk& Script) {
- std::unique_lock Lock(mLuaStatesMutex);
- return mLuaStates.at(StateID)->EnqueueScript(Script);
-}
-
-std::shared_ptr TLuaEngine::EnqueueFunctionCall(TLuaStateId StateID, const std::string& FunctionName, const std::vector& Args) {
- std::unique_lock Lock(mLuaStatesMutex);
- return mLuaStates.at(StateID)->EnqueueFunctionCall(FunctionName, Args);
-}
-
-void TLuaEngine::CollectAndInitPlugins() {
- if (!fs::exists(mResourceServerPath)) {
- fs::create_directories(mResourceServerPath);
- }
- for (const auto& Dir : fs::directory_iterator(mResourceServerPath)) {
- auto Path = Dir.path();
- Path = fs::relative(Path);
- if (!Dir.is_directory()) {
- beammp_error("\"" + Dir.path().string() + "\" is not a directory, skipping");
- } else {
- TLuaPluginConfig Config { Path.stem().string() };
- FindAndParseConfig(Path, Config);
- InitializePlugin(Path, Config);
- }
- }
-}
-
-void TLuaEngine::InitializePlugin(const fs::path& Folder, const TLuaPluginConfig& Config) {
- beammp_assert(fs::exists(Folder));
- beammp_assert(fs::is_directory(Folder));
- std::unique_lock Lock(mLuaStatesMutex);
- EnsureStateExists(Config.StateId, Folder.stem().string(), true);
- mLuaStates[Config.StateId]->AddPath(Folder); // add to cpath + path
- Lock.unlock();
- auto Plugin = std::make_shared(*this, Config, Folder);
- mLuaPlugins.emplace_back(std::move(Plugin));
-}
-
-void TLuaEngine::FindAndParseConfig(const fs::path& Folder, TLuaPluginConfig& Config) {
- auto ConfigFile = Folder / TLuaPluginConfig::FileName;
- if (fs::exists(ConfigFile) && fs::is_regular_file(ConfigFile)) {
- try {
- auto Data = toml::parse(ConfigFile);
- if (Data.contains("LuaStateID")) {
- auto ID = toml::find(Data, "LuaStateID");
- if (!ID.empty()) {
- beammp_debug("Plugin \"" + Folder.string() + "\" specified it wants LuaStateID \"" + ID + "\"");
- Config.StateId = ID;
- } else {
- beammp_debug("LuaStateID empty, using plugin name");
- }
- }
- } catch (const std::exception& e) {
- beammp_error(Folder.string() + ": " + e.what());
- }
- }
-}
-
-void TLuaEngine::EnsureStateExists(TLuaStateId StateId, const std::string& Name, bool DontCallOnInit) {
- beammp_assert(!StateId.empty());
- std::unique_lock Lock(mLuaStatesMutex);
- if (mLuaStates.find(StateId) == mLuaStates.end()) {
- beammp_debug("Creating lua state for state id \"" + StateId + "\"");
- auto DataPtr = std::make_unique(Name, StateId, *this);
- mLuaStates[StateId] = std::move(DataPtr);
- RegisterEvent("onInit", StateId, "onInit");
- 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);
- }
- }
- }
-}
-
-void TLuaEngine::RegisterEvent(const std::string& EventName, TLuaStateId StateId, const std::string& FunctionName) {
- std::unique_lock Lock(mLuaEventsMutex);
- mLuaEvents[EventName][StateId].insert(FunctionName);
-}
-
-std::set TLuaEngine::GetEventHandlersForState(const std::string& EventName, TLuaStateId StateId) {
- return mLuaEvents[EventName][StateId];
-}
-
-sol::table TLuaEngine::StateThreadData::Lua_TriggerGlobalEvent(const std::string& EventName, sol::variadic_args EventArgs) {
- auto Return = mEngine->TriggerEvent(EventName, mStateId, EventArgs);
- auto MyHandlers = mEngine->GetEventHandlersForState(EventName, mStateId);
- for (const auto& Handler : MyHandlers) {
- auto Fn = mStateView[Handler];
- if (Fn.valid()) {
- auto LuaResult = Fn(EventArgs);
- auto Result = std::make_shared();
- if (LuaResult.valid()) {
- Result->Error = false;
- Result->Result = LuaResult;
- } else {
- Result->Error = true;
- Result->ErrorMessage = "Function result in TriggerGlobalEvent was invalid";
- }
- Result->MarkAsReady();
- Return.push_back(Result);
- }
- }
- sol::state_view StateView(mState);
- sol::table AsyncEventReturn = StateView.create_table();
- AsyncEventReturn["ReturnValueImpl"] = Return;
- AsyncEventReturn.set_function("IsDone",
- [&](const sol::table& Self) -> bool {
- auto Vector = Self.get>>("ReturnValueImpl");
- for (const auto& Value : Vector) {
- if (!Value->Ready) {
- return false;
- }
- }
- return true;
- });
- AsyncEventReturn.set_function("GetResults",
- [&](const sol::table& Self) -> sol::table {
- sol::state_view StateView(mState);
- sol::table Result = StateView.create_table();
- auto Vector = Self.get>>("ReturnValueImpl");
- for (const auto& Value : Vector) {
- if (!Value->Ready) {
- return sol::lua_nil;
- }
- Result.add(Value->Result);
- }
- return Result;
- });
- return AsyncEventReturn;
-}
-
-sol::table TLuaEngine::StateThreadData::Lua_TriggerLocalEvent(const std::string& EventName, sol::variadic_args EventArgs) {
- // TODO: make asynchronous?
- sol::table Result = mStateView.create_table();
- for (const auto& Handler : mEngine->GetEventHandlersForState(EventName, mStateId)) {
- auto Fn = mStateView[Handler];
- if (Fn.valid() && Fn.get_type() == sol::type::function) {
- auto FnRet = Fn(EventArgs);
- if (FnRet.valid()) {
- Result.add(FnRet);
- } else {
- sol::error Err = FnRet;
- beammp_lua_error(std::string("TriggerLocalEvent: ") + Err.what());
- }
- }
- }
- return Result;
-}
-
-sol::table TLuaEngine::StateThreadData::Lua_GetPlayerIdentifiers(int ID) {
- auto MaybeClient = mEngine->Network()->get_client(ClientID(ID), bmp::State::Authentication);
- if (MaybeClient) {
- auto IDs = MaybeClient.value()->identifiers.synchronize();
- if (IDs->empty()) {
- return sol::lua_nil;
- }
- sol::table Result = mStateView.create_table();
- for (const auto& Pair : *IDs) {
- Result[Pair.first] = Pair.second;
- }
- return Result;
- } else {
- return sol::lua_nil;
- }
-}
-
-sol::table TLuaEngine::StateThreadData::Lua_GetPlayers() {
- sol::table result = mStateView.create_table();
- auto clients = mEngine->Network()->authenticated_clients();
- for (const auto& [id, client] : clients) {
- result[client->id] = client->name.get();
- }
- return result;
-}
-
-int TLuaEngine::StateThreadData::Lua_GetPlayerIDByName(const std::string& Name) {
- auto clients = mEngine->Network()->authenticated_clients();
- for (const auto& [id, client] : clients) {
- if (client->name.get() == Name) {
- return int(client->id);
- }
- }
- return -1;
-}
-
-sol::table TLuaEngine::StateThreadData::Lua_FS_ListFiles(const std::string& Path) {
- if (!std::filesystem::exists(Path)) {
- return sol::lua_nil;
- }
- auto table = mStateView.create_table();
- for (const auto& entry : std::filesystem::directory_iterator(Path)) {
- if (entry.is_regular_file() || entry.is_symlink()) {
- table[table.size() + 1] = entry.path().lexically_relative(Path).string();
- }
- }
- return table;
-}
-
-sol::table TLuaEngine::StateThreadData::Lua_FS_ListDirectories(const std::string& Path) {
- if (!std::filesystem::exists(Path)) {
- return sol::lua_nil;
- }
- auto table = mStateView.create_table();
- for (const auto& entry : std::filesystem::directory_iterator(Path)) {
- if (entry.is_directory()) {
- table[table.size() + 1] = entry.path().lexically_relative(Path).string();
- }
- }
- return table;
-}
-
-std::string TLuaEngine::StateThreadData::Lua_GetPlayerName(int ID) {
- auto maybe_client = mEngine->Network()->get_client(ClientID(ID), bmp::State::Authentication);
- if (maybe_client) {
- return maybe_client.value()->name.get();
- } else {
- return "";
- }
-}
-
-sol::table TLuaEngine::StateThreadData::Lua_GetPlayerVehicles(int ID) {
- auto vehicles = mEngine->Network()->get_vehicles_owned_by(ClientID(ID));
- if (vehicles.empty()) {
- return sol::lua_nil;
- }
- sol::state_view state_view(mState);
- sol::table result = state_view.create_table();
- for (const auto& [vid, vehicle] : vehicles) {
- result[vid] = vehicle->data.get();
- }
- return result;
-}
-
-std::pair TLuaEngine::StateThreadData::Lua_GetVehicleStatus(int VID) {
- auto maybe_vehicle = mEngine->Network()->get_vehicle(VehicleID(VID));
- if (maybe_vehicle) {
- sol::state_view state_view(mState);
- sol::table result = state_view.create_table();
- auto veh = maybe_vehicle.value();
- auto status = veh->get_status();
- result["pos"]["x"] = status.pos.x;
- result["pos"]["y"] = status.pos.y;
- result["pos"]["z"] = status.pos.z;
- result["vel"]["x"] = status.vel.x;
- result["vel"]["y"] = status.vel.y;
- result["vel"]["z"] = status.vel.z;
- result["rvel"]["x"] = status.rvel.x;
- result["rvel"]["y"] = status.rvel.y;
- result["rvel"]["z"] = status.rvel.z;
- result["rot"]["x"] = status.rot.x;
- result["rot"]["y"] = status.rot.y;
- result["rot"]["z"] = status.rot.z;
- result["rot"]["w"] = status.rot.w;
- result["time"] = status.time;
- return { result, "" };
- } else {
- return { sol::lua_nil, fmt::format("Vehicle {} not found", VID) };
- }
-}
-
-sol::table TLuaEngine::StateThreadData::Lua_HttpCreateConnection(const std::string& host, uint16_t port) {
- auto table = mStateView.create_table();
- constexpr const char* InternalClient = "__InternalClient";
- table["host"] = host;
- table["port"] = port;
- auto client = std::make_shared(host, port);
- table[InternalClient] = client;
- table.set_function("Get", [&InternalClient](const sol::table& table, const std::string& path, const sol::table& headers) {
- httplib::Headers GetHeaders;
- for (const auto& pair : headers) {
- if (pair.first.is() && pair.second.is()) {
- GetHeaders.insert(std::pair(pair.first.as(), pair.second.as()));
- } else {
- beammp_lua_error("Http:Get: Expected string-string pairs for headers, got something else, ignoring that header");
- }
- }
- auto client = table[InternalClient].get>();
- client->Get(path.c_str(), GetHeaders);
- });
- return table;
-}
-
-template
-static void AddToTable(sol::table& table, const std::string& left, const T& value) {
- if (left.empty()) {
- table[table.size() + 1] = value;
- } else {
- table[left] = value;
- }
-}
-
-static void JsonDecodeRecursive(sol::state_view& StateView, sol::table& table, const std::string& left, const nlohmann::json& right) {
- switch (right.type()) {
- case nlohmann::detail::value_t::null:
- return;
- case nlohmann::detail::value_t::object: {
- auto value = table.create();
- value.clear();
- for (const auto& entry : right.items()) {
- JsonDecodeRecursive(StateView, value, entry.key(), entry.value());
- }
- AddToTable(table, left, value);
- break;
- }
- case nlohmann::detail::value_t::array: {
- auto value = table.create();
- value.clear();
- for (const auto& entry : right.items()) {
- JsonDecodeRecursive(StateView, value, "", entry.value());
- }
- AddToTable(table, left, value);
- break;
- }
- case nlohmann::detail::value_t::string:
- AddToTable(table, left, right.get());
- break;
- case nlohmann::detail::value_t::boolean:
- AddToTable(table, left, right.get());
- break;
- case nlohmann::detail::value_t::number_integer:
- AddToTable(table, left, right.get());
- break;
- case nlohmann::detail::value_t::number_unsigned:
- AddToTable(table, left, right.get());
- break;
- case nlohmann::detail::value_t::number_float:
- AddToTable(table, left, right.get());
- break;
- case nlohmann::detail::value_t::binary:
- beammp_lua_error("JsonDecode can't handle binary blob in json, ignoring");
- return;
- case nlohmann::detail::value_t::discarded:
- return;
- default:
- beammp_assert_not_reachable();
- }
-}
-
-sol::table TLuaEngine::StateThreadData::Lua_JsonDecode(const std::string& str) {
- sol::state_view StateView(mState);
- auto table = StateView.create_table();
- if (!nlohmann::json::accept(str)) {
- beammp_lua_error("string given to JsonDecode is not valid json: `" + str + "`");
- return sol::lua_nil;
- }
- nlohmann::json json = nlohmann::json::parse(str);
- if (json.is_object()) {
- for (const auto& entry : json.items()) {
- JsonDecodeRecursive(StateView, table, entry.key(), entry.value());
- }
- } else if (json.is_array()) {
- for (const auto& entry : json) {
- JsonDecodeRecursive(StateView, table, "", entry);
- }
- } else {
- beammp_lua_error("JsonDecode expected array or object json, instead got " + std::string(json.type_name()));
- return sol::lua_nil;
- }
- return table;
-}
-
-TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateId StateId, TLuaEngine& Engine)
- : mName(Name)
- , mStateId(StateId)
- , mState(luaL_newstate())
- , mEngine(&Engine) {
- if (!mState) {
- beammp_error("failed to create lua state for \"" + StateId + "\"");
- return;
- }
- luaL_openlibs(mState);
- sol::state_view StateView(mState);
- lua_atpanic(mState, LuaAPI::PanicHandler);
- // StateView.globals()["package"].get()
- StateView.set_function("print", &LuaAPI::Print);
- StateView.set_function("printRaw", &LuaAPI::MP::PrintRaw);
- StateView.set_function("exit", &Application::GracefullyShutdown);
-
- auto MPTable = StateView.create_named_table("MP");
- MPTable.set_function("CreateTimer", [&]() -> sol::table {
- sol::state_view StateView(mState);
- sol::table Result = StateView.create_table();
- Result["__StartTime"] = std::chrono::high_resolution_clock::now();
- Result.set_function("GetCurrent", [&](const sol::table& Table) -> float {
- auto End = std::chrono::high_resolution_clock::now();
- auto Start = Table.get("__StartTime");
- return std::chrono::duration_cast(End - Start).count() / 1000000.0f;
- });
- Result.set_function("Start", [&](sol::table Table) {
- Table["__StartTime"] = std::chrono::high_resolution_clock::now();
- });
- return Result;
- });
- MPTable.set_function("GetOSName", &LuaAPI::MP::GetOSName);
- MPTable.set_function("GetServerVersion", &LuaAPI::MP::GetServerVersion);
- MPTable.set_function("RegisterEvent", [this](const std::string& EventName, const std::string& FunctionName) {
- RegisterEvent(EventName, FunctionName);
- });
- MPTable.set_function("TriggerGlobalEvent", [&](const std::string& EventName, sol::variadic_args EventArgs) -> sol::table {
- return Lua_TriggerGlobalEvent(EventName, EventArgs);
- });
- MPTable.set_function("TriggerLocalEvent", [&](const std::string& EventName, sol::variadic_args EventArgs) -> sol::table {
- return Lua_TriggerLocalEvent(EventName, EventArgs);
- });
- MPTable.set_function("TriggerClientEvent", &LuaAPI::MP::TriggerClientEvent);
- MPTable.set_function("TriggerClientEventJson", &LuaAPI::MP::TriggerClientEventJson);
- MPTable.set_function("GetPlayerCount", &LuaAPI::MP::GetPlayerCount);
- MPTable.set_function("IsPlayerConnected", &LuaAPI::MP::IsPlayerConnected);
- MPTable.set_function("GetPlayerIDByName", [&](const std::string& Name) -> int {
- return Lua_GetPlayerIDByName(Name);
- });
- MPTable.set_function("GetPlayerName", [&](int ID) -> std::string {
- return Lua_GetPlayerName(ID);
- });
- MPTable.set_function("RemoveVehicle", &LuaAPI::MP::RemoveVehicle);
- MPTable.set_function("GetPlayerVehicles", [&](int ID) -> sol::table {
- return Lua_GetPlayerVehicles(ID);
- });
- MPTable.set_function("GetVehicleStatus", [&](int VID) -> std::pair {
- return Lua_GetVehicleStatus(VID);
- });
- MPTable.set_function("SendChatMessage", &LuaAPI::MP::SendChatMessage);
- MPTable.set_function("GetPlayers", [&]() -> sol::table {
- return Lua_GetPlayers();
- });
- MPTable.set_function("IsPlayerGuest", &LuaAPI::MP::IsPlayerGuest);
- MPTable.set_function("DropPlayer", &LuaAPI::MP::DropPlayer);
- MPTable.set_function("GetStateMemoryUsage", [&]() -> size_t {
- return mStateView.memory_used();
- });
- MPTable.set_function("GetLuaMemoryUsage", [&]() -> size_t {
- return mEngine->CalculateMemoryUsage();
- });
- MPTable.set_function("GetPlayerIdentifiers", [&](int ID) -> sol::table {
- return Lua_GetPlayerIdentifiers(ID);
- });
- MPTable.set_function("Sleep", &LuaAPI::MP::Sleep);
- // const std::string& EventName, size_t IntervalMS, int strategy
- MPTable.set_function("CreateEventTimer", [&](sol::variadic_args Args) {
- if (Args.size() < 2 || Args.size() > 3) {
- beammp_lua_error("CreateEventTimer expects 2 or 3 arguments.");
- }
- if (Args.get_type(0) != sol::type::string) {
- beammp_lua_error("CreateEventTimer expects 1st argument to be a string");
- }
- if (Args.get_type(1) != sol::type::number) {
- beammp_lua_error("CreateEventTimer expects 2nd argument to be a number");
- }
- if (Args.size() == 3 && Args.get_type(2) != sol::type::number) {
- beammp_lua_error("CreateEventTimer expects 3rd argument to be a number (MP.CallStrategy)");
- }
- auto EventName = Args.get(0);
- auto IntervalMS = Args.get(1);
- CallStrategy Strategy = Args.size() > 2 ? Args.get(2) : CallStrategy::BestEffort;
- if (IntervalMS < 25) {
- beammp_warn("Timer for \"" + EventName + "\" on \"" + mStateId + "\" is set to trigger at <25ms, which is likely too fast and won't cancel properly.");
- }
- mEngine->CreateEventTimer(EventName, mStateId, IntervalMS, Strategy);
- });
- MPTable.set_function("CancelEventTimer", [&](const std::string& EventName) {
- mEngine->CancelEventTimers(EventName, mStateId);
- });
- MPTable.set_function("Set", &LuaAPI::MP::Set);
-
- auto UtilTable = StateView.create_named_table("Util");
- UtilTable.set_function("JsonEncode", &LuaAPI::MP::JsonEncode);
- UtilTable.set_function("JsonDecode", [this](const std::string& str) {
- return Lua_JsonDecode(str);
- });
- UtilTable.set_function("JsonDiff", &LuaAPI::MP::JsonDiff);
- UtilTable.set_function("JsonFlatten", &LuaAPI::MP::JsonFlatten);
- UtilTable.set_function("JsonUnflatten", &LuaAPI::MP::JsonUnflatten);
- UtilTable.set_function("JsonPrettify", &LuaAPI::MP::JsonPrettify);
- UtilTable.set_function("JsonMinify", &LuaAPI::MP::JsonMinify);
- UtilTable.set_function("Random", [this] {
- return mUniformRealDistribution01(mMersenneTwister);
- });
- UtilTable.set_function("RandomRange", [this](double min, double max) -> double {
- return std::uniform_real_distribution(min, max)(mMersenneTwister);
- });
- UtilTable.set_function("RandomIntRange", [this](int64_t min, int64_t max) -> int64_t {
- return std::uniform_int_distribution(min, max)(mMersenneTwister);
- });
-
- auto HttpTable = StateView.create_named_table("Http");
- HttpTable.set_function("CreateConnection", [this](const std::string& host, uint16_t port) {
- return Lua_HttpCreateConnection(host, port);
- });
-
- MPTable.create_named("Settings",
- "Debug", 0,
- "Private", 1,
- "MaxCars", 2,
- "MaxPlayers", 3,
- "Map", 4,
- "Name", 5,
- "Description", 6);
-
- MPTable.create_named("CallStrategy",
- "BestEffort", CallStrategy::BestEffort,
- "Precise", CallStrategy::Precise);
-
- auto FSTable = StateView.create_named_table("FS");
- FSTable.set_function("CreateDirectory", &LuaAPI::FS::CreateDirectory);
- FSTable.set_function("Exists", &LuaAPI::FS::Exists);
- FSTable.set_function("Remove", &LuaAPI::FS::Remove);
- FSTable.set_function("Rename", &LuaAPI::FS::Rename);
- FSTable.set_function("Copy", &LuaAPI::FS::Copy);
- FSTable.set_function("GetFilename", &LuaAPI::FS::GetFilename);
- FSTable.set_function("GetExtension", &LuaAPI::FS::GetExtension);
- FSTable.set_function("GetParentFolder", &LuaAPI::FS::GetParentFolder);
- FSTable.set_function("IsDirectory", &LuaAPI::FS::IsDirectory);
- FSTable.set_function("IsFile", &LuaAPI::FS::IsFile);
- FSTable.set_function("ConcatPaths", &LuaAPI::FS::ConcatPaths);
- FSTable.set_function("ListFiles", [this](const std::string& Path) {
- return Lua_FS_ListFiles(Path);
- });
- FSTable.set_function("ListDirectories", [this](const std::string& Path) {
- return Lua_FS_ListDirectories(Path);
- });
- mThread = boost::scoped_thread<>(&StateThreadData::Start, this);
-}
-
-std::shared_ptr TLuaEngine::StateThreadData::EnqueueScript(const TLuaChunk& Script) {
- std::unique_lock Lock(mStateExecuteQueueMutex);
- auto Result = std::make_shared();
- mStateExecuteQueue.push({ Script, Result });
- return Result;
-}
-
-std::shared_ptr TLuaEngine::StateThreadData::EnqueueFunctionCallFromCustomEvent(const std::string& FunctionName, const std::vector& Args, const std::string& EventName, CallStrategy Strategy) {
- // TODO: Document all this
- decltype(mStateFunctionQueue)::iterator Iter = mStateFunctionQueue.end();
- if (Strategy == CallStrategy::BestEffort) {
- Iter = std::find_if(mStateFunctionQueue.begin(), mStateFunctionQueue.end(),
- [&EventName](const QueuedFunction& Element) {
- return Element.EventName == EventName;
- });
- }
- if (Iter == mStateFunctionQueue.end()) {
- auto Result = std::make_shared();
- Result->StateId = mStateId;
- Result->Function = FunctionName;
- std::unique_lock Lock(mStateFunctionQueueMutex);
- mStateFunctionQueue.push_back({ FunctionName, Result, Args, EventName });
- mStateFunctionQueueCond.notify_all();
- return Result;
- } else {
- return nullptr;
- }
-}
-
-std::shared_ptr TLuaEngine::StateThreadData::EnqueueFunctionCall(const std::string& FunctionName, const std::vector& Args) {
- auto Result = std::make_shared();
- Result->StateId = mStateId;
- Result->Function = FunctionName;
- std::unique_lock Lock(mStateFunctionQueueMutex);
- mStateFunctionQueue.push_back({ FunctionName, Result, Args, "" });
- mStateFunctionQueueCond.notify_all();
- return Result;
-}
-
-void TLuaEngine::StateThreadData::RegisterEvent(const std::string& EventName, const std::string& FunctionName) {
- mEngine->RegisterEvent(EventName, mStateId, FunctionName);
-}
-
-void TLuaEngine::StateThreadData::Start() {
- RegisterThread("Lua:" + mStateId);
- while (!Application::IsShuttingDown()) {
- { // StateExecuteQueue Scope
- std::unique_lock Lock(mStateExecuteQueueMutex);
- if (!mStateExecuteQueue.empty()) {
- auto S = mStateExecuteQueue.front();
- mStateExecuteQueue.pop();
- Lock.unlock();
-
- { // Paths Scope
- std::unique_lock Lock(mPathsMutex);
- if (!mPaths.empty()) {
- std::stringstream PathAdditions;
- std::stringstream CPathAdditions;
- while (!mPaths.empty()) {
- auto Path = mPaths.front();
- mPaths.pop();
- PathAdditions << ";" << (Path / "?.lua").string();
- PathAdditions << ";" << (Path / "lua/?.lua").string();
-#if WIN32
- CPathAdditions << ";" << (Path / "?.dll").string();
- CPathAdditions << ";" << (Path / "lib/?.dll").string();
-#else // unix
- CPathAdditions << ";" << (Path / "?.so").string();
- CPathAdditions << ";" << (Path / "lib/?.so").string();
-#endif
- }
- sol::state_view StateView(mState);
- auto PackageTable = StateView.globals().get("package");
- PackageTable["path"] = PackageTable.get("path") + PathAdditions.str();
- PackageTable["cpath"] = PackageTable.get("cpath") + CPathAdditions.str();
- StateView.globals()["package"] = PackageTable;
- }
- }
- sol::state_view StateView(mState);
- auto Res = StateView.safe_script(*S.first.Content, sol::script_pass_on_error, S.first.FileName);
- 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->MarkAsReady();
- }
- }
- { // StateFunctionQueue Scope
- std::unique_lock Lock(mStateFunctionQueueMutex);
- auto NotExpired = mStateFunctionQueueCond.wait_for(Lock,
- std::chrono::milliseconds(500),
- [&]() -> bool { return !mStateFunctionQueue.empty(); });
- if (NotExpired) {
- auto TheQueuedFunction = std::move(mStateFunctionQueue.front());
- mStateFunctionQueue.erase(mStateFunctionQueue.begin());
- Lock.unlock();
- auto& FnName = TheQueuedFunction.FunctionName;
- auto& Result = TheQueuedFunction.Result;
- auto Args = TheQueuedFunction.Args;
- // TODO: Use TheQueuedFunction.EventName for errors, warnings, etc
- Result->StateId = mStateId;
- sol::state_view StateView(mState);
- auto Fn = StateView[FnName];
- if (Fn.valid() && Fn.get_type() == sol::type::function) {
- std::vector LuaArgs;
- for (const auto& Arg : Args) {
- if (Arg.valueless_by_exception()) {
- continue;
- }
- switch (Arg.index()) {
- case TLuaArgTypes_String:
- LuaArgs.push_back(sol::make_object(StateView, std::get(Arg)));
- break;
- case TLuaArgTypes_Int:
- LuaArgs.push_back(sol::make_object(StateView, std::get(Arg)));
- break;
- case TLuaArgTypes_VariadicArgs:
- LuaArgs.push_back(sol::make_object(StateView, std::get(Arg)));
- break;
- case TLuaArgTypes_Bool:
- LuaArgs.push_back(sol::make_object(StateView, std::get(Arg)));
- break;
- case TLuaArgTypes_StringStringMap: {
- auto Map = std::get>(Arg);
- auto Table = StateView.create_table();
- for (const auto& [k, v] : Map) {
- Table[k] = v;
- }
- LuaArgs.push_back(sol::make_object(StateView, Table));
- break;
- }
- default:
- beammp_error("Unknown argument type, passed as nil");
- break;
- }
- }
- auto Res = Fn(sol::as_args(LuaArgs));
- if (Res.valid()) {
- Result->Error = false;
- Result->Result = std::move(Res);
- } else {
- Result->Error = true;
- sol::error Err = Res;
- Result->ErrorMessage = Err.what();
- }
- Result->MarkAsReady();
- } else {
- Result->Error = true;
- Result->ErrorMessage = BeamMPFnNotFoundError; // special error kind that we can ignore later
- Result->MarkAsReady();
- }
- }
- }
- }
-}
-
-std::queue>> TLuaEngine::StateThreadData::Debug_GetStateExecuteQueue() {
- std::unique_lock Lock(mStateExecuteQueueMutex);
- return mStateExecuteQueue;
-}
-
-std::vector TLuaEngine::StateThreadData::Debug_GetStateFunctionQueue() {
- std::unique_lock Lock(mStateFunctionQueueMutex);
- return mStateFunctionQueue;
-}
-
-void TLuaEngine::CreateEventTimer(const std::string& EventName, TLuaStateId StateId, size_t IntervalMS, CallStrategy Strategy) {
- std::unique_lock Lock(mTimedEventsMutex);
- TimedEvent Event {
- std::chrono::high_resolution_clock::duration { std::chrono::milliseconds(IntervalMS) },
- std::chrono::high_resolution_clock::now(),
- EventName,
- StateId,
- Strategy
- };
- mTimedEvents.push_back(std::move(Event));
- beammp_trace("created event timer for \"" + EventName + "\" on \"" + StateId + "\" with " + std::to_string(IntervalMS) + "ms interval");
-}
-
-void TLuaEngine::CancelEventTimers(const std::string& EventName, TLuaStateId StateId) {
- std::unique_lock Lock(mTimedEventsMutex);
- beammp_trace("cancelling event timer for \"" + EventName + "\" on \"" + StateId + "\"");
- for (;;) {
- auto Iter = std::find_if(mTimedEvents.begin(), mTimedEvents.end(), [&](const TimedEvent& Event) -> bool {
- return Event.EventName == EventName && Event.StateId == StateId;
- });
- if (Iter != mTimedEvents.end()) {
- mTimedEvents.erase(Iter);
- } else {
- break;
- }
- }
-}
-
-void TLuaEngine::StateThreadData::AddPath(const fs::path& Path) {
- std::unique_lock Lock(mPathsMutex);
- mPaths.push(Path);
-}
-
-void TLuaResult::MarkAsReady() {
- {
- std::lock_guard readyLock(*this->ReadyMutex);
- this->Ready = true;
- }
- this->ReadyCondition->notify_all();
-}
-
-void TLuaResult::WaitUntilReady() {
- std::unique_lock readyLock(*this->ReadyMutex);
- // wait if not ready yet
- if (!this->Ready)
- this->ReadyCondition->wait(readyLock);
-}
-
-TLuaChunk::TLuaChunk(std::shared_ptr Content, std::string FileName, std::string PluginPath)
- : Content(Content)
- , FileName(FileName)
- , PluginPath(PluginPath) {
-}
-
-bool TLuaEngine::TimedEvent::Expired() {
- auto Waited = (std::chrono::high_resolution_clock::now() - LastCompletion);
- return Waited >= Duration;
-}
-
-void TLuaEngine::TimedEvent::Reset() {
- LastCompletion = std::chrono::high_resolution_clock::now();
-}
diff --git a/src/TLuaPlugin.cpp b/src/TLuaPlugin.cpp
deleted file mode 100644
index 50620be..0000000
--- a/src/TLuaPlugin.cpp
+++ /dev/null
@@ -1,70 +0,0 @@
-// BeamMP, the BeamNG.drive multiplayer mod.
-// Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
-//
-// BeamMP Ltd. can be contacted by electronic mail via contact@beammp.com.
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published
-// by the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see .
-
-#include "TLuaPlugin.h"
-#include
-#include
-#include
-#include
-
-TLuaPlugin::TLuaPlugin(TLuaEngine& Engine, const TLuaPluginConfig& Config, const fs::path& MainFolder)
- : mConfig(Config)
- , 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") {
- 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
- try {
- std::ifstream FileStream(Entry.string(), std::ios::in | std::ios::binary);
- auto Size = std::filesystem::file_size(Entry);
- auto Contents = std::make_shared();
- Contents->resize(Size);
- FileStream.read(Contents->data(), Contents->size());
- mFileContents[fs::relative(Entry).string()] = Contents;
- // Execute first time
- auto Result = mEngine.EnqueueScript(mConfig.StateId, TLuaChunk(Contents, Entry.string(), MainFolder.string()));
- ResultsToCheck.emplace_back(Entry.string(), std::move(Result));
- } catch (const std::exception& e) {
- beammp_error("Error loading file \"" + Entry.string() + "\": " + e.what());
- }
- }
- for (auto& Result : ResultsToCheck) {
- Result.second->WaitUntilReady();
- if (Result.second->Error) {
- beammp_lua_error("Failed: \"" + Result.first.string() + "\": " + Result.second->ErrorMessage);
- }
- }
-}
diff --git a/src/TPluginMonitor.cpp b/src/TPluginMonitor.cpp
deleted file mode 100644
index 5710999..0000000
--- a/src/TPluginMonitor.cpp
+++ /dev/null
@@ -1,94 +0,0 @@
-// BeamMP, the BeamNG.drive multiplayer mod.
-// Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
-//
-// BeamMP Ltd. can be contacted by electronic mail via contact@beammp.com.
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published
-// by the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see .
-
-#include "TPluginMonitor.h"
-
-#include "TLuaEngine.h"
-#include
-
-TPluginMonitor::TPluginMonitor(const fs::path& Path, std::shared_ptr Engine)
- : mEngine(Engine)
- , mPath(Path) {
- Application::SetSubsystemStatus("PluginMonitor", Application::Status::Starting);
- if (!fs::exists(mPath)) {
- fs::create_directories(mPath);
- }
- for (const auto& Entry : fs::recursive_directory_iterator(mPath, fs::directory_options::follow_directory_symlink)) {
- // TODO: trigger an event when a subfolder file changes
- if (Entry.is_regular_file()) {
- mFileTimes[Entry.path().string()] = fs::last_write_time(Entry.path());
- }
- }
-
- Application::RegisterShutdownHandler([this] {
- if (mThread.joinable()) {
- mThread.join();
- }
- });
-
- Start();
-}
-
-void TPluginMonitor::operator()() {
- RegisterThread("PluginMonitor");
- beammp_info("PluginMonitor started");
- Application::SetSubsystemStatus("PluginMonitor", Application::Status::Good);
- while (!Application::IsShuttingDown()) {
- std::vector ToRemove;
- for (const auto& Pair : mFileTimes) {
- try {
- auto CurrentTime = fs::last_write_time(Pair.first);
- if (CurrentTime > Pair.second) {
- mFileTimes[Pair.first] = CurrentTime;
- // grandparent of the path should be Resources/Server
- if (fs::equivalent(fs::path(Pair.first).parent_path().parent_path(), mPath)) {
- beammp_infof("File \"{}\" changed, reloading", Pair.first);
- // is in root folder, so reload
- std::ifstream FileStream(Pair.first, std::ios::in | std::ios::binary);
- auto Size = std::filesystem::file_size(Pair.first);
- auto Contents = std::make_shared();
- Contents->resize(Size);
- FileStream.read(Contents->data(), Contents->size());
- TLuaChunk Chunk(Contents, Pair.first, fs::path(Pair.first).parent_path().string());
- auto StateID = mEngine->GetStateIDForPlugin(fs::path(Pair.first).parent_path());
- auto Res = mEngine->EnqueueScript(StateID, Chunk);
- Res->WaitUntilReady();
- if (Res->Error) {
- beammp_lua_errorf("Error while hot-reloading \"{}\": {}", Pair.first, Res->ErrorMessage);
- } else {
- mEngine->ReportErrors(mEngine->TriggerLocalEvent(StateID, "onInit"));
- mEngine->ReportErrors(mEngine->TriggerEvent("onFileChanged", "", Pair.first));
- }
- } else {
- // is in subfolder, dont reload, just trigger an event
- beammp_debugf("File \"{}\" changed, not reloading because it's in a subdirectory. Triggering 'onFileChanged' event instead", Pair.first);
- mEngine->ReportErrors(mEngine->TriggerEvent("onFileChanged", "", Pair.first));
- }
- }
- } catch (const std::exception& e) {
- ToRemove.push_back(Pair.first);
- }
- }
- Application::SleepSafeSeconds(3);
- for (const auto& File : ToRemove) {
- mFileTimes.erase(File);
- beammp_warnf("File \"{}\" couldn't be accessed, so it was removed from plugin hot reload monitor (probably got deleted)", File);
- }
- }
- Application::SetSubsystemStatus("PluginMonitor", Application::Status::Shutdown);
-}