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); -}