From daa674f448715e12ce9e59a5073bbbd0f0b56ec2 Mon Sep 17 00:00:00 2001 From: Lion Kortlepel Date: Thu, 10 Mar 2022 12:31:02 +0100 Subject: [PATCH 1/8] add infinite snowmen bug to changelog --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index 5bb611a..982c893 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,7 @@ - ADDED Backup URLs to UpdateCheck (will fail less often now) - ADDED console cursor left and right movement (with arrow keys) and working HOME and END key (via github.com/lionkor/commandline) +- FIXED infinite snowmen / infinite unicycle spawning bug - FIXED a bug where, when run with --working-directory, the Server.log would still be in the original directory - FIXED a bug which could cause the plugin reload thread to spin at 100% if the reloaded plugin's didn't terminate - FIXED an issue which would cause servers to crash on mod download via SIGPIPE on POSIX From 71c2d4b859464b1ae0e3816276fa85cf4df4d52e Mon Sep 17 00:00:00 2001 From: Lion Kortlepel Date: Fri, 25 Mar 2022 12:55:35 +0100 Subject: [PATCH 2/8] Simplify "Backend heartbeat response" error (closes #97) --- deps/commandline | 2 +- include/Common.h | 1 + src/THeartbeatThread.cpp | 8 ++++++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/deps/commandline b/deps/commandline index 71240f6..01434c1 160000 --- a/deps/commandline +++ b/deps/commandline @@ -1 +1 @@ -Subproject commit 71240f634b211d830679e7d2841b897c7c30dad9 +Subproject commit 01434c11aaf82d37a126dc70f5aa02cc523dbbb4 diff --git a/include/Common.h b/include/Common.h index 2cf15b9..4698f6e 100644 --- a/include/Common.h +++ b/include/Common.h @@ -12,6 +12,7 @@ extern TSentry Sentry; #include #include #include +#include #include "Compat.h" diff --git a/src/THeartbeatThread.cpp b/src/THeartbeatThread.cpp index 6efdd74..42e7b36 100644 --- a/src/THeartbeatThread.cpp +++ b/src/THeartbeatThread.cpp @@ -62,8 +62,8 @@ void THeartbeatThread::operator()() { beammp_trace(T); Doc.Parse(T.data(), T.size()); if (Doc.HasParseError() || !Doc.IsObject()) { - beammp_error("Backend response failed to parse as valid json"); - beammp_debug("Response was: `" + T + "`"); + beammp_debug("Failed to contact backend at " + Url + " (this is not an error)."); + beammp_trace("Response was: " + T); Sentry.SetContext("JSON Response", { { "reponse", T } }); SentryReportError(Url + Target, ResponseCode); } else if (ResponseCode != 200) { @@ -105,6 +105,10 @@ void THeartbeatThread::operator()() { beammp_error("Missing/invalid json members in backend response"); Sentry.LogError("Missing/invalid json members in backend response", __FILE__, std::to_string(__LINE__)); } + } else { + if (!Application::Settings.Private) { + beammp_warn("Backend failed to respond to a heartbeat. Your server may temporarily disappear from the server list. This is not an error, and will likely resolve itself soon. Direct connect will still work."); + } } if (Ok && !isAuth) { From f8c58f363aab2e7a6d4d2cd9edf0373e422b241a Mon Sep 17 00:00:00 2001 From: Lion Kortlepel Date: Fri, 25 Mar 2022 13:32:41 +0100 Subject: [PATCH 3/8] Change default MaxPlayers to 8 --- include/Common.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/Common.h b/include/Common.h index 4698f6e..88e2732 100644 --- a/include/Common.h +++ b/include/Common.h @@ -44,7 +44,7 @@ public: std::string SSLKeyPath { "./.ssl/HttpServer/key.pem" }; std::string SSLCertPath { "./.ssl/HttpServer/cert.pem" }; bool HTTPServerEnabled { false }; - int MaxPlayers { 10 }; + int MaxPlayers { 8 }; bool Private { true }; int MaxCars { 1 }; bool DebugModeEnabled { false }; From de82caef335403767ffd50cac7f69e6ff2131c75 Mon Sep 17 00:00:00 2001 From: Lion Kortlepel Date: Fri, 25 Mar 2022 13:33:03 +0100 Subject: [PATCH 4/8] Add HideUpdateMessages setting ("ImScaredOfUpdates") and periodic update reminders (every 5th heartbeat) --- include/Common.h | 1 + src/Common.cpp | 2 +- src/TConfig.cpp | 28 ++++++++++++++++++---------- src/THeartbeatThread.cpp | 5 +++++ 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/include/Common.h b/include/Common.h index 88e2732..5d224fb 100644 --- a/include/Common.h +++ b/include/Common.h @@ -54,6 +54,7 @@ public: bool SendErrorsMessageEnabled { true }; int HTTPServerPort { 8080 }; bool HTTPServerUseSSL { true }; + bool HideUpdateMessages { false }; [[nodiscard]] bool HasCustomIP() const { return !CustomIP.empty(); } }; diff --git a/src/Common.cpp b/src/Common.cpp index a113f74..99d0009 100644 --- a/src/Common.cpp +++ b/src/Common.cpp @@ -107,7 +107,7 @@ void Application::CheckForUpdates() { auto RemoteVersion = Version(VersionStrToInts(Response)); if (IsOutdated(MyVersion, RemoteVersion)) { std::string RealVersionString = RemoteVersion.AsString(); - beammp_warn(std::string(ANSI_YELLOW_BOLD) + "NEW VERSION OUT! There's a new version (v" + RealVersionString + ") of the BeamMP-Server available! For more info visit https://wiki.beammp.com/en/home/server-maintenance#updating-the-server." + std::string(ANSI_RESET)); + beammp_warn(std::string(ANSI_YELLOW_BOLD) + "NEW VERSION IS OUT! Please update to the new version (v" + RealVersionString + ") of the BeamMP-Server! Download it here: https://beammp.com/! For a guide on how to update, visit: https://wiki.beammp.com/en/home/server-maintenance#updating-the-server" + std::string(ANSI_RESET)); } else { beammp_info("Server up-to-date!"); } diff --git a/src/TConfig.cpp b/src/TConfig.cpp index f1c3126..1260cb7 100644 --- a/src/TConfig.cpp +++ b/src/TConfig.cpp @@ -17,12 +17,15 @@ static constexpr std::string_view StrName = "Name"; static constexpr std::string_view StrDescription = "Description"; static constexpr std::string_view StrResourceFolder = "ResourceFolder"; static constexpr std::string_view StrAuthKey = "AuthKey"; + +// Misc static constexpr std::string_view StrSendErrors = "SendErrors"; static constexpr std::string_view StrSendErrorsMessageEnabled = "SendErrorsShowMessage"; -static constexpr std::string_view StrHTTPServerEnabled = "HTTPServerEnabled"; -static constexpr std::string_view StrHTTPServerUseSSL = "UseSSL"; +static constexpr std::string_view StrHideUpdateMessages = "ImScaredOfUpdates"; // HTTP +static constexpr std::string_view StrHTTPServerEnabled = "HTTPServerEnabled"; +static constexpr std::string_view StrHTTPServerUseSSL = "UseSSL"; static constexpr std::string_view StrSSLKeyPath = "SSLKeyPath"; static constexpr std::string_view StrSSLCertPath = "SSLCertPath"; static constexpr std::string_view StrHTTPServerPort = "HTTPServerPort"; @@ -59,7 +62,6 @@ void SetComment(CommentsT& Comments, const std::string& Comment) { */ void TConfig::FlushToFile() { auto data = toml::parse(mConfigFileName); - data["General"] = toml::table(); data["General"][StrAuthKey.data()] = Application::Settings.Key; SetComment(data["General"][StrAuthKey.data()].comments(), " AuthKey has to be filled out in order to run the server"); data["General"][StrDebug.data()] = Application::Settings.DebugModeEnabled; @@ -71,10 +73,14 @@ void TConfig::FlushToFile() { data["General"][StrMap.data()] = Application::Settings.MapName; data["General"][StrDescription.data()] = Application::Settings.ServerDesc; data["General"][StrResourceFolder.data()] = Application::Settings.Resource; - data["General"][StrSendErrors.data()] = Application::Settings.SendErrors; - SetComment(data["General"][StrSendErrors.data()].comments(), " You can turn on/off the SendErrors message you get on startup here"); - data["General"][StrSendErrorsMessageEnabled.data()] = Application::Settings.SendErrorsMessageEnabled; - SetComment(data["General"][StrSendErrorsMessageEnabled.data()].comments(), " If SendErrors is `true`, the server will send helpful info about crashes and other issues back to the BeamMP developers. This info may include your config, who is on your server at the time of the error, and similar general information. This kind of data is vital in helping us diagnose and fix issues faster. This has no impact on server performance. You can opt-out of this system by setting this to `false`"); + // Misc + data["Misc"][StrHideUpdateMessages.data()] = Application::Settings.HideUpdateMessages; + SetComment(data["Misc"][StrHideUpdateMessages.data()].comments(), " Hides the periodic update message which notifies you of a new server version. You should really keep this on and always update as soon as possible. For more information visit https://wiki.beammp.com/en/home/server-maintenance#updating-the-server. An update message will always appear at startup regardless."); + data["Misc"][StrSendErrors.data()] = Application::Settings.SendErrors; + SetComment(data["Misc"][StrSendErrors.data()].comments(), " You can turn on/off the SendErrors message you get on startup here"); + data["Misc"][StrSendErrorsMessageEnabled.data()] = Application::Settings.SendErrorsMessageEnabled; + SetComment(data["Misc"][StrSendErrorsMessageEnabled.data()].comments(), " If SendErrors is `true`, the server will send helpful info about crashes and other issues back to the BeamMP developers. This info may include your config, who is on your server at the time of the error, and similar general information. This kind of data is vital in helping us diagnose and fix issues faster. This has no impact on server performance. You can opt-out of this system by setting this to `false`"); + // HTTP data["HTTP"][StrSSLKeyPath.data()] = Application::Settings.SSLKeyPath; data["HTTP"][StrSSLCertPath.data()] = Application::Settings.SSLCertPath; data["HTTP"][StrHTTPServerPort.data()] = Application::Settings.HTTPServerPort; @@ -82,7 +88,7 @@ void TConfig::FlushToFile() { SetComment(data["HTTP"][StrHTTPServerUseSSL.data()].comments(), " Recommended to keep enabled. With SSL the server will serve https and requires valid key and cert files"); data["HTTP"][StrHTTPServerEnabled.data()] = Application::Settings.HTTPServerEnabled; SetComment(data["HTTP"][StrHTTPServerEnabled.data()].comments(), " Enables the internal HTTP server"); - std::ofstream Stream(mConfigFileName); + std::ofstream Stream(mConfigFileName, std::ios::trunc | std::ios::out); Stream << data << std::flush; } @@ -158,8 +164,10 @@ void TConfig::ParseFromFile(std::string_view name) { TryReadValue(data, "General", StrDescription, Application::Settings.ServerDesc); TryReadValue(data, "General", StrResourceFolder, Application::Settings.Resource); TryReadValue(data, "General", StrAuthKey, Application::Settings.Key); - TryReadValue(data, "General", StrSendErrors, Application::Settings.SendErrors); - TryReadValue(data, "General", StrSendErrorsMessageEnabled, Application::Settings.SendErrorsMessageEnabled); + // Misc + TryReadValue(data, "Misc", StrSendErrors, Application::Settings.SendErrors); + TryReadValue(data, "Misc", StrHideUpdateMessages, Application::Settings.HideUpdateMessages); + TryReadValue(data, "Misc", StrSendErrorsMessageEnabled, Application::Settings.SendErrorsMessageEnabled); // HTTP TryReadValue(data, "HTTP", StrSSLKeyPath, Application::Settings.SSLKeyPath); TryReadValue(data, "HTTP", StrSSLCertPath, Application::Settings.SSLCertPath); diff --git a/src/THeartbeatThread.cpp b/src/THeartbeatThread.cpp index 42e7b36..d65380e 100644 --- a/src/THeartbeatThread.cpp +++ b/src/THeartbeatThread.cpp @@ -19,7 +19,9 @@ void THeartbeatThread::operator()() { static std::chrono::high_resolution_clock::time_point LastNormalUpdateTime = std::chrono::high_resolution_clock::now(); bool isAuth = false; + size_t UpdateReminderCounter = 0; while (!mShutdown) { + ++UpdateReminderCounter; Body = GenerateCall(); // a hot-change occurs when a setting has changed, to update the backend of that change. auto Now = std::chrono::high_resolution_clock::now(); @@ -128,6 +130,9 @@ void THeartbeatThread::operator()() { if (isAuth) { Application::SetSubsystemStatus("Heartbeat", Application::Status::Good); } + if (!Application::Settings.HideUpdateMessages && UpdateReminderCounter % 5) { + Application::CheckForUpdates(); + } } } From b97397132d7f603636e8cfcf21c573a6edf0bf39 Mon Sep 17 00:00:00 2001 From: Lion Kortlepel Date: Thu, 31 Mar 2022 15:59:31 +0200 Subject: [PATCH 5/8] TLuaEngine: improve result queue handling --- include/TLuaEngine.h | 6 ++++-- src/TLuaEngine.cpp | 42 ++++++++++++++++++++++-------------------- src/main.cpp | 2 +- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/include/TLuaEngine.h b/include/TLuaEngine.h index 9b0a884..9d3abd3 100644 --- a/include/TLuaEngine.h +++ b/include/TLuaEngine.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -218,8 +219,9 @@ private: std::recursive_mutex mLuaEventsMutex; std::vector mTimedEvents; std::recursive_mutex mTimedEventsMutex; - std::queue> mResultsToCheck; - std::recursive_mutex mResultsToCheckMutex; + std::list> mResultsToCheck; + std::mutex mResultsToCheckMutex; + std::condition_variable mResultsToCheckCond; }; // std::any TriggerLuaEvent(const std::string& Event, bool local, TLuaPlugin* Caller, std::shared_ptr arg, bool Wait); diff --git a/src/TLuaEngine.cpp b/src/TLuaEngine.cpp index 41ffb96..78724c9 100644 --- a/src/TLuaEngine.cpp +++ b/src/TLuaEngine.cpp @@ -44,7 +44,7 @@ void TLuaEngine::operator()() { CollectAndInitPlugins(); // now call all onInit's auto Futures = TriggerEvent("onInit", ""); - WaitForAll(Futures); + 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); @@ -54,25 +54,21 @@ void TLuaEngine::operator()() { auto ResultCheckThread = std::thread([&] { RegisterThread("ResultCheckThread"); while (!mShutdown) { - std::this_thread::sleep_for(std::chrono::milliseconds(10)); std::unique_lock Lock(mResultsToCheckMutex); + mResultsToCheckCond.wait_for(Lock, std::chrono::milliseconds(20)); if (!mResultsToCheck.empty()) { - auto Res = mResultsToCheck.front(); - mResultsToCheck.pop(); - Lock.unlock(); - - if (!Res->Ready) { - Lock.lock(); - mResultsToCheck.push(Res); - Lock.unlock(); - } - if (Res->Error) { - if (Res->ErrorMessage != BeamMPFnNotFoundError) { - beammp_lua_error(Res->Function + ": " + Res->ErrorMessage); + mResultsToCheck.remove_if([](const std::shared_ptr& Ptr) -> bool { + if (Ptr->Ready) { + return true; + } else if (Ptr->Error) { + if (Ptr->ErrorMessage != BeamMPFnNotFoundError) { + beammp_lua_error(Ptr->Function + ": " + Ptr->ErrorMessage); + } + return true; } - } + return false; + }); } - std::this_thread::yield(); } }); // event loop @@ -91,7 +87,8 @@ void TLuaEngine::operator()() { std::unique_lock Lock2(mResultsToCheckMutex); for (auto& Handler : Handlers) { auto Res = mLuaStates[Timer.StateId]->EnqueueFunctionCall(Handler, {}); - mResultsToCheck.push(Res); + mResultsToCheck.push_back(Res); + mResultsToCheckCond.notify_one(); } } } @@ -144,7 +141,8 @@ TLuaStateId TLuaEngine::GetStateIDForPlugin(const fs::path& PluginPath) { void TLuaEngine::AddResultToCheck(const std::shared_ptr& Result) { std::unique_lock Lock(mResultsToCheckMutex); - mResultsToCheck.push(Result); + mResultsToCheck.push_back(Result); + mResultsToCheckCond.notify_one(); } void TLuaEngine::WaitForAll(std::vector>& Results, const std::optional& Max) { @@ -155,9 +153,12 @@ void TLuaEngine::WaitForAll(std::vector>& Results, c 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)"); + 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) { + 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."); @@ -174,7 +175,8 @@ void TLuaEngine::WaitForAll(std::vector>& Results, c void TLuaEngine::ReportErrors(const std::vector>& Results) { std::unique_lock Lock2(mResultsToCheckMutex); for (const auto& Result : Results) { - mResultsToCheck.push(Result); + mResultsToCheck.push_back(Result); + mResultsToCheckCond.notify_one(); } } diff --git a/src/main.cpp b/src/main.cpp index 8b2f3c5..d076cc1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -133,7 +133,7 @@ int BeamMPServerMain(MainArguments Arguments) { }); Application::RegisterShutdownHandler([] { auto Futures = LuaAPI::MP::Engine->TriggerEvent("onShutdown", ""); - TLuaEngine::WaitForAll(Futures); + TLuaEngine::WaitForAll(Futures, std::chrono::seconds(5)); }); TServer Server(Arguments.List); From 81dbf747d524a5ce5deaff08de3aaf40ede5e7ba Mon Sep 17 00:00:00 2001 From: Lion Kortlepel Date: Thu, 31 Mar 2022 16:50:00 +0200 Subject: [PATCH 6/8] Kick client if we fail to send them a client event --- src/LuaAPI.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/LuaAPI.cpp b/src/LuaAPI.cpp index 14cd8f6..4a76cdc 100644 --- a/src/LuaAPI.cpp +++ b/src/LuaAPI.cpp @@ -112,7 +112,8 @@ bool LuaAPI::MP::TriggerClientEvent(int PlayerID, const std::string& EventName, } auto c = MaybeClient.value().lock(); if (!Engine->Network().Respond(*c, Packet, true)) { - beammp_lua_error("Respond failed"); + beammp_lua_error("Respond failed, dropping client " + std::to_string(PlayerID)); + Engine->Network().ClientKick(*c, "Disconnected after failing to receive packets"); return false; } } From d4b30a258375f01f50f5c9276322cfa880bb3540 Mon Sep 17 00:00:00 2001 From: Lion Kortlepel Date: Thu, 31 Mar 2022 20:13:59 +0200 Subject: [PATCH 7/8] CreateEventTimer: Implement CallStrategy There are two CallStrategies: - BestEffort (default): Will try to get your event to trigger at the specified interval, but will refuse to queue handlers if a handler takes too long. - Precise: Will enqueue event handlers at the exact interval specified. Can lead to the queue filling up if the handler takes longer than the interval. --- include/TLuaEngine.h | 20 ++++++++-- src/TLuaEngine.cpp | 92 ++++++++++++++++++++++++++++++++++---------- src/TNetwork.cpp | 1 + 3 files changed, 90 insertions(+), 23 deletions(-) diff --git a/include/TLuaEngine.h b/include/TLuaEngine.h index 9d3abd3..5ca2222 100644 --- a/include/TLuaEngine.h +++ b/include/TLuaEngine.h @@ -5,8 +5,8 @@ #include #include #include -#include #include +#include #include #include #include @@ -73,6 +73,18 @@ private: class TLuaEngine : IThreaded { 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(); ~TLuaEngine() noexcept { beammp_debug("Lua Engine terminated"); @@ -146,7 +158,7 @@ public: return Results; // } std::set GetEventHandlersForState(const std::string& EventName, TLuaStateId StateId); - void CreateEventTimer(const std::string& EventName, TLuaStateId StateId, size_t IntervalMS); + 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); @@ -167,6 +179,7 @@ private: ~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 operator()() override; @@ -189,7 +202,7 @@ private: std::thread mThread; std::queue>> mStateExecuteQueue; std::recursive_mutex mStateExecuteQueueMutex; - std::queue, std::vector>> mStateFunctionQueue; + std::vector mStateFunctionQueue; std::mutex mStateFunctionQueueMutex; std::condition_variable mStateFunctionQueueCond; TLuaEngine* mEngine; @@ -203,6 +216,7 @@ private: std::chrono::high_resolution_clock::time_point LastCompletion {}; std::string EventName; TLuaStateId StateId; + CallStrategy Strategy; bool Expired(); void Reset(); }; diff --git a/src/TLuaEngine.cpp b/src/TLuaEngine.cpp index 78724c9..1d684d8 100644 --- a/src/TLuaEngine.cpp +++ b/src/TLuaEngine.cpp @@ -81,22 +81,31 @@ void TLuaEngine::operator()() { 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]->EnqueueFunctionCall(Handler, {}); - mResultsToCheck.push_back(Res); - mResultsToCheckCond.notify_one(); + 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; + } } } } } - std::chrono::high_resolution_clock::duration Diff; - if ((Diff = std::chrono::high_resolution_clock::now() - Before) - < std::chrono::milliseconds(10)) { - std::this_thread::sleep_for(Diff); + const auto Expected = std::chrono::milliseconds(10); + if (auto Diff = std::chrono::high_resolution_clock::now() - Before; + Diff < Expected) { + std::this_thread::sleep_for(Expected - Diff); } else { beammp_trace("Event loop cannot keep up! Running " + std::to_string(Diff.count()) + "s behind"); } @@ -155,9 +164,8 @@ void TLuaEngine::WaitForAll(std::vector>& Results, c 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) { - 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."); + } else if (ms > 1000 * 60) { + 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) { @@ -504,11 +512,27 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, std::atomi return Lua_GetPlayerIdentifiers(ID); }); MPTable.set_function("Sleep", &LuaAPI::MP::Sleep); - MPTable.set_function("CreateEventTimer", [&](const std::string& EventName, size_t IntervalMS) { + // 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); + mEngine->CreateEventTimer(EventName, mStateId, IntervalMS, Strategy); }); MPTable.set_function("CancelEventTimer", [&](const std::string& EventName) { mEngine->CancelEventTimers(EventName, mStateId); @@ -528,6 +552,10 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, std::atomi "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); @@ -550,12 +578,34 @@ std::shared_ptr TLuaEngine::StateThreadData::EnqueueScript(const TLu 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({ FunctionName, Result, Args }); + mStateFunctionQueue.push_back({ FunctionName, Result, Args, "" }); mStateFunctionQueueCond.notify_all(); return Result; } @@ -618,12 +668,13 @@ void TLuaEngine::StateThreadData::operator()() { std::chrono::milliseconds(500), [&]() -> bool { return !mStateFunctionQueue.empty(); }); if (NotExpired) { - auto FnNameResultPair = std::move(mStateFunctionQueue.front()); - mStateFunctionQueue.pop(); + auto TheQueuedFunction = std::move(mStateFunctionQueue.front()); + mStateFunctionQueue.erase(mStateFunctionQueue.begin()); Lock.unlock(); - auto& FnName = std::get<0>(FnNameResultPair); - auto& Result = std::get<1>(FnNameResultPair); - auto Args = std::get<2>(FnNameResultPair); + 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]; @@ -671,13 +722,14 @@ void TLuaEngine::StateThreadData::operator()() { } } -void TLuaEngine::CreateEventTimer(const std::string& EventName, TLuaStateId StateId, size_t IntervalMS) { +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 + StateId, + Strategy }; mTimedEvents.push_back(std::move(Event)); beammp_trace("created event timer for \"" + EventName + "\" on \"" + StateId + "\" with " + std::to_string(IntervalMS) + "ms interval"); diff --git a/src/TNetwork.cpp b/src/TNetwork.cpp index 1e0e5be..ef2064e 100644 --- a/src/TNetwork.cpp +++ b/src/TNetwork.cpp @@ -514,6 +514,7 @@ void TNetwork::Looper(const std::weak_ptr& c) { } } } + void TNetwork::TCPClient(const std::weak_ptr& c) { // TODO: the c.expired() might cause issues here, remove if you end up here with your debugger if (c.expired() || c.lock()->GetTCPSock() == -1) { From d01d79a49a6d01a5f1cc76abc55af47bdb705815 Mon Sep 17 00:00:00 2001 From: Lion Kortlepel Date: Thu, 31 Mar 2022 20:27:08 +0200 Subject: [PATCH 8/8] update changelog --- Changelog.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Changelog.md b/Changelog.md index 982c893..ac45ff8 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,11 @@ +# v3.0.2 + +- ADDED Periodic update message if a new server is released +- CHANGED Default MaxPlayers to 8 +- FIXED `MP.CreateEventTimer` filling up the queue (see https://wiki.beammp.com/en/Scripting/new-lua-scripting#mpcreateeventtimerevent_name-string-interval_ms-number-strategy-number-since-v302) +- FIXED `MP.TriggerClientEvent` not kicking the client if it failed +- FIXED Lua result queue handling not checking all results + # v3.0.1 - ADDED Backup URLs to UpdateCheck (will fail less often now)