mirror of
https://github.com/BeamMP/BeamMP-Server.git
synced 2026-07-12 17:54:04 +00:00
refactor Lua result handling for safety
this massively improves thread safety and cleanly serializes accesses into the lua engine's result objects where accesses before were extremely unsafe and could access a corrupt/invalid stack. this fixes various obscure crashes related to accessing results, without changing any observable behavior.
This commit is contained in:
@@ -54,6 +54,7 @@ set(PRJ_HEADERS
|
||||
include/Profiling.h
|
||||
include/ChronoWrapper.h
|
||||
include/TConnectionLimiter.h
|
||||
include/TLuaResult.h
|
||||
)
|
||||
# add all source files (.cpp) to this, except the one with main()
|
||||
set(PRJ_SOURCES
|
||||
@@ -82,6 +83,7 @@ set(PRJ_SOURCES
|
||||
src/Profiling.cpp
|
||||
src/ChronoWrapper.cpp
|
||||
src/TConnectionLimiter.cpp
|
||||
src/TLuaResult.cpp
|
||||
)
|
||||
|
||||
find_package(Lua REQUIRED)
|
||||
|
||||
+2
-32
@@ -19,13 +19,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "Profiling.h"
|
||||
#include "TLuaResult.h"
|
||||
#include "TNetwork.h"
|
||||
#include "TServer.h"
|
||||
#include <any>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <filesystem>
|
||||
#include <initializer_list>
|
||||
#include <list>
|
||||
#include <lua.hpp>
|
||||
#include <memory>
|
||||
@@ -72,35 +71,6 @@ enum TLuaType {
|
||||
|
||||
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<std::mutex> ReadyMutex {
|
||||
std::make_shared<std::mutex>()
|
||||
};
|
||||
std::shared_ptr<std::condition_variable> ReadyCondition {
|
||||
std::make_shared<std::condition_variable>()
|
||||
};
|
||||
|
||||
void SetErrorMessageFromResult(sol::protected_function_result& Res) {
|
||||
if (Res.valid()) {
|
||||
beammp_lua_errorf("Error was not an error");
|
||||
}
|
||||
if (Res.get_type() == sol::type::string) {
|
||||
ErrorMessage = Res.get<sol::error>().what();
|
||||
} else {
|
||||
ErrorMessage = "(unknown error; error object is not inspectable)";
|
||||
}
|
||||
}
|
||||
|
||||
void MarkAsReady();
|
||||
void WaitUntilReady();
|
||||
};
|
||||
|
||||
struct TLuaPluginConfig {
|
||||
static inline const std::string FileName = "PluginConfig.toml";
|
||||
TLuaStateId StateId;
|
||||
@@ -242,7 +212,7 @@ public:
|
||||
std::unordered_map<std::string /*event name */, std::vector<std::string> /* handlers */> Debug_GetEventsForState(TLuaStateId StateId);
|
||||
std::queue<std::pair<TLuaChunk, std::shared_ptr<TLuaResult>>> Debug_GetStateExecuteQueueForState(TLuaStateId StateId);
|
||||
std::vector<QueuedFunction> Debug_GetStateFunctionQueueForState(TLuaStateId StateId);
|
||||
std::vector<TLuaResult> Debug_GetResultsToCheckForState(TLuaStateId StateId);
|
||||
std::vector<TLuaResult::DetachedSnapshot> Debug_GetResultsToCheckForState(TLuaStateId StateId);
|
||||
|
||||
private:
|
||||
void CollectAndInitPlugins();
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
#pragma once
|
||||
|
||||
#include "Common.h"
|
||||
#include <condition_variable>
|
||||
#include <string>
|
||||
#include <sol/sol.hpp>
|
||||
|
||||
using TLuaStateId = std::string;
|
||||
|
||||
struct TDetachedLuaValue {
|
||||
using Array = std::vector<TDetachedLuaValue>;
|
||||
using Object = std::unordered_map<std::string, TDetachedLuaValue>;
|
||||
std::variant<std::monostate, bool, double, int, std::string, Array, Object> V;
|
||||
};
|
||||
std::ostream& operator<<(std::ostream& os, const TDetachedLuaValue& value);
|
||||
|
||||
struct TLuaResult {
|
||||
struct Snapshot {
|
||||
bool Error;
|
||||
bool Ready;
|
||||
std::string ErrorMessage;
|
||||
/// CAUTION: Accessing this object causes the Lua stack of the owning state
|
||||
/// to be accessed. Only call this from the owning Lua state. Otherwise,
|
||||
/// use `DetachedSnapshot`.
|
||||
sol::object Result;
|
||||
TLuaStateId StateId;
|
||||
std::string Function;
|
||||
};
|
||||
|
||||
struct DetachedSnapshot {
|
||||
bool Error;
|
||||
bool Ready;
|
||||
std::string ErrorMessage;
|
||||
/// Serialized Lua result. Has no reference to the Lua state, and is safe to use
|
||||
/// from any thread that acquires it.
|
||||
TDetachedLuaValue Result;
|
||||
TLuaStateId StateId;
|
||||
std::string Function;
|
||||
};
|
||||
|
||||
|
||||
TLuaResult(TLuaStateId StateId, std::string FunctionName) : mStateId(StateId), mFunction(std::move(FunctionName)) {}
|
||||
|
||||
/// Marks this result as success & sets it as ready, waking all threads waiting on it.
|
||||
void MarkReadySuccess(sol::object Res);
|
||||
/// Marks this result as erroneous & sets it as ready, waking all threads waiting on it.
|
||||
void MarkReadyError(sol::protected_function_result Res);
|
||||
/// Marks this result as erroneous & sets it as ready, waking all threads waiting on it.
|
||||
void MarkReadyError(std::string Res);
|
||||
/// Wait (suspend) until this result is ready. Use `GetSnapshot` to get the results.
|
||||
void WaitUntilReady();
|
||||
bool IsReady() const;
|
||||
bool IsError() const;
|
||||
TLuaStateId OwnerState() const;
|
||||
void SetOwnerState(TLuaStateId StateId);
|
||||
|
||||
/// To get the result or error, while in the owning state, use this function.
|
||||
/// Pass your lua state ID so that the function can verify the safety of the access.
|
||||
/// If you have no state ID, use `GetDetachedSnapshot`.
|
||||
/// Accesses the Lua state directly when using the result.
|
||||
Snapshot GetSnapshot(TLuaStateId CallingState) const;
|
||||
/// Returns a snapshot with, if not an error, a serialized version of the result
|
||||
/// object. In contrast to `GetSnapshot`, this does not access the Lua stack when
|
||||
/// accessing `.Result`.
|
||||
DetachedSnapshot GetDetachedSnapshot() const;
|
||||
|
||||
private:
|
||||
mutable std::mutex mMutex {};
|
||||
bool mReady { false };
|
||||
bool mError;
|
||||
std::string mErrorMessage;
|
||||
sol::object mResult { sol::lua_nil };
|
||||
TDetachedLuaValue mDetachedResult;
|
||||
TLuaStateId mStateId;
|
||||
std::string mFunction;
|
||||
std::condition_variable mReadyCondition {};
|
||||
|
||||
TDetachedLuaValue Freeze(const sol::object& o, int depth = 0);
|
||||
|
||||
void SetErrorMessageFromResult(sol::protected_function_result& Res) {
|
||||
if (Res.valid()) {
|
||||
beammp_lua_errorf("Error was not an error");
|
||||
}
|
||||
if (Res.get_type() == sol::type::string) {
|
||||
mErrorMessage = Res.get<sol::error>().what();
|
||||
} else {
|
||||
mErrorMessage = "(unknown error; error object is not inspectable)";
|
||||
}
|
||||
}
|
||||
|
||||
void MarkAsReady();
|
||||
};
|
||||
@@ -31,6 +31,7 @@
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
|
||||
#include "TLuaResult.h"
|
||||
#include "Compat.h"
|
||||
#include "CustomAssert.h"
|
||||
#include "Http.h"
|
||||
@@ -373,6 +374,44 @@ std::string GetPlatformAgnosticErrorString() {
|
||||
return "(no human-readable errors on this platform)";
|
||||
#endif
|
||||
}
|
||||
std::ostream& operator<<(std::ostream& os, const TDetachedLuaValue& value) {
|
||||
|
||||
std::visit([&os](auto&& arg) {
|
||||
using T = std::decay_t<decltype(arg)>;
|
||||
if constexpr (std::is_same_v<T, std::vector<TDetachedLuaValue>>) {
|
||||
size_t i = 0;
|
||||
for (auto val : arg) {
|
||||
if (i > 0) {
|
||||
os << ", ";
|
||||
}
|
||||
os << val;
|
||||
}
|
||||
} else if constexpr (std::is_same_v<T, std::unordered_map<std::string, TDetachedLuaValue>>) {
|
||||
size_t i = 0;
|
||||
for (auto [key, val] : arg) {
|
||||
if (i > 0) {
|
||||
os << ", ";
|
||||
}
|
||||
os << key << "=" << val;
|
||||
}
|
||||
} else if constexpr (std::is_same_v<T, bool>)
|
||||
os << (arg ? "true" : "false");
|
||||
else if constexpr (std::is_same_v<T, double>)
|
||||
os << arg;
|
||||
else if constexpr (std::is_same_v<T, int>)
|
||||
os << arg;
|
||||
else if constexpr (std::is_same_v<T, std::string>)
|
||||
os << arg;
|
||||
else if constexpr (std::is_same_v<T, std::monostate>)
|
||||
// monostate means no result value
|
||||
os << "";
|
||||
else
|
||||
static_assert(false, "non-exhaustive visitor!");
|
||||
},
|
||||
value.V);
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
// TODO: add unit tests to SplitString
|
||||
void SplitString(const std::string& str, const char delim, std::vector<std::string>& out) {
|
||||
|
||||
+17
-10
@@ -22,9 +22,9 @@
|
||||
|
||||
#include "Client.h"
|
||||
#include "CustomAssert.h"
|
||||
#include "Http.h"
|
||||
#include "LuaAPI.h"
|
||||
#include "TLuaEngine.h"
|
||||
#include "Http.h"
|
||||
|
||||
#include <ctime>
|
||||
#include <lua.hpp>
|
||||
@@ -689,9 +689,13 @@ void TConsole::Command_Status(const std::string&, const std::vector<std::string>
|
||||
void TConsole::RunAsCommand(const std::string& cmd, bool IgnoreNotACommand) {
|
||||
auto FutureIsNonNil =
|
||||
[](const std::shared_ptr<TLuaResult>& Future) {
|
||||
if (!Future->Error && Future->Result.valid()) {
|
||||
auto Type = Future->Result.get_type();
|
||||
return Type != sol::type::lua_nil && Type != sol::type::none;
|
||||
if (!Future->IsError()) {
|
||||
auto Snapshot = Future->GetDetachedSnapshot();
|
||||
if (Snapshot.Result.V.valueless_by_exception() || std::get_if<std::monostate>(&Snapshot.Result.V) != nullptr) {
|
||||
// no value contained
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
@@ -701,7 +705,7 @@ void TConsole::RunAsCommand(const std::string& cmd, bool IgnoreNotACommand) {
|
||||
TLuaEngine::WaitForAll(Futures, std::chrono::seconds(5));
|
||||
size_t Count = 0;
|
||||
for (auto& Future : Futures) {
|
||||
if (!Future->Error) {
|
||||
if (!Future->IsError()) {
|
||||
++Count;
|
||||
}
|
||||
}
|
||||
@@ -719,14 +723,16 @@ void TConsole::RunAsCommand(const std::string& cmd, bool IgnoreNotACommand) {
|
||||
std::stringstream Reply;
|
||||
if (NonNilFutures.size() > 1) {
|
||||
for (size_t i = 0; i < NonNilFutures.size(); ++i) {
|
||||
Reply << NonNilFutures[i]->StateId << ": \n"
|
||||
<< LuaAPI::LuaToString(NonNilFutures[i]->Result);
|
||||
auto Snapshot = NonNilFutures[i]->GetDetachedSnapshot();
|
||||
Reply << Snapshot.StateId << ": \n"
|
||||
<< Snapshot.Result;
|
||||
if (i < NonNilFutures.size() - 1) {
|
||||
Reply << "\n";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Reply << LuaAPI::LuaToString(NonNilFutures[0]->Result);
|
||||
auto Snapshot = NonNilFutures[0]->GetDetachedSnapshot();
|
||||
Reply << Snapshot.Result;
|
||||
}
|
||||
Application::Console().WriteRaw(Reply.str());
|
||||
}
|
||||
@@ -807,8 +813,9 @@ void TConsole::InitializeCommandline() {
|
||||
} else {
|
||||
auto Future = mLuaEngine->EnqueueScript(mStateId, { std::make_shared<std::string>(TrimmedCmd), "", "" });
|
||||
Future->WaitUntilReady();
|
||||
if (Future->Error) {
|
||||
beammp_lua_error("error in " + mStateId + ": " + Future->ErrorMessage);
|
||||
if (Future->IsError()) {
|
||||
auto Snapshot = Future->GetDetachedSnapshot();
|
||||
beammp_lua_error("error in " + mStateId + ": " + Snapshot.ErrorMessage);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
+84
-72
@@ -20,20 +20,25 @@
|
||||
#include "Client.h"
|
||||
#include "Common.h"
|
||||
#include "CustomAssert.h"
|
||||
#include "Env.h"
|
||||
#include "Http.h"
|
||||
#include "LuaAPI.h"
|
||||
#include "Env.h"
|
||||
#include "Profiling.h"
|
||||
#include "TLuaPlugin.h"
|
||||
#include "TLuaResult.h"
|
||||
#include "sol/object.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <fmt/core.h>
|
||||
#include <mutex>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <random>
|
||||
#include <sol/types.hpp>
|
||||
#include <thread>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <variant>
|
||||
|
||||
TLuaEngine* LuaAPI::MP::Engine;
|
||||
|
||||
@@ -74,8 +79,9 @@ void TLuaEngine::operator()() {
|
||||
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 Snapshot = Future->GetDetachedSnapshot();
|
||||
if (Snapshot.Error && Snapshot.ErrorMessage != BeamMPFnNotFoundError) {
|
||||
beammp_lua_error("Calling \"onInit\" on \"" + Snapshot.StateId + "\" failed: " + Snapshot.ErrorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,10 +91,11 @@ void TLuaEngine::operator()() {
|
||||
std::unique_lock Lock(mResultsToCheckMutex);
|
||||
if (!mResultsToCheck.empty()) {
|
||||
mResultsToCheck.remove_if([](const std::shared_ptr<TLuaResult>& Ptr) -> bool {
|
||||
if (Ptr->Ready) {
|
||||
if (Ptr->Error) {
|
||||
if (Ptr->ErrorMessage != BeamMPFnNotFoundError) {
|
||||
beammp_lua_error(Ptr->Function + ": " + Ptr->ErrorMessage);
|
||||
if (Ptr->IsReady()) {
|
||||
auto Snapshot = Ptr->GetDetachedSnapshot();
|
||||
if (Snapshot.Error) {
|
||||
if (Snapshot.ErrorMessage != BeamMPFnNotFoundError) {
|
||||
beammp_lua_error(Snapshot.Function + ": " + Snapshot.ErrorMessage);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -128,6 +135,7 @@ void TLuaEngine::operator()() {
|
||||
}
|
||||
}
|
||||
}
|
||||
std::unique_lock Lock(mLuaStatesMutex);
|
||||
if (mLuaStates.empty()) {
|
||||
beammp_trace("No Lua states, event loop running extremely sparsely");
|
||||
Application::SleepSafeSeconds(10);
|
||||
@@ -215,16 +223,16 @@ std::vector<TLuaEngine::QueuedFunction> TLuaEngine::Debug_GetStateFunctionQueueF
|
||||
return Result;
|
||||
}
|
||||
|
||||
std::vector<TLuaResult> TLuaEngine::Debug_GetResultsToCheckForState(TLuaStateId StateId) {
|
||||
std::vector<TLuaResult::DetachedSnapshot> TLuaEngine::Debug_GetResultsToCheckForState(TLuaStateId StateId) {
|
||||
std::unique_lock Lock(mResultsToCheckMutex);
|
||||
auto ResultsToCheckCopy = mResultsToCheck;
|
||||
Lock.unlock();
|
||||
std::vector<TLuaResult> Result;
|
||||
std::vector<TLuaResult::DetachedSnapshot> Result;
|
||||
while (!ResultsToCheckCopy.empty()) {
|
||||
auto ResultToCheck = std::move(ResultsToCheckCopy.front());
|
||||
ResultsToCheckCopy.pop_front();
|
||||
if (ResultToCheck->StateId == StateId) {
|
||||
Result.push_back(*ResultToCheck);
|
||||
if (ResultToCheck->OwnerState() == StateId) {
|
||||
Result.push_back(ResultToCheck->GetDetachedSnapshot());
|
||||
}
|
||||
}
|
||||
return Result;
|
||||
@@ -311,27 +319,30 @@ void TLuaEngine::WaitForAll(std::vector<std::shared_ptr<TLuaResult>>& Results, c
|
||||
size_t ms = 0;
|
||||
std::set<std::string> WarnedResults;
|
||||
|
||||
while (!Result->Ready && !Cancelled) {
|
||||
while (!Result->IsReady() && !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).");
|
||||
auto Snapshot = Result->GetDetachedSnapshot();
|
||||
beammp_trace("'" + Snapshot.Function + "' in '" + Snapshot.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;
|
||||
auto Snapshot = Result->GetDetachedSnapshot();
|
||||
auto ResultId = Snapshot.StateId + "_" + Snapshot.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.");
|
||||
beammp_lua_warn("'" + Snapshot.Function + "' in '" + Snapshot.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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Snapshot = Result->GetDetachedSnapshot();
|
||||
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.");
|
||||
beammp_lua_warn("'" + Snapshot.Function + "' in '" + Snapshot.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);
|
||||
} else if (Snapshot.Error) {
|
||||
if (Snapshot.ErrorMessage != BeamMPFnNotFoundError) {
|
||||
beammp_lua_error(Snapshot.Function + ": " + Snapshot.ErrorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -431,10 +442,11 @@ void TLuaEngine::EnsureStateExists(TLuaStateId StateId, const std::string& Name,
|
||||
mLuaStates[StateId] = std::move(DataPtr);
|
||||
RegisterEvent("onInit", StateId, "onInit");
|
||||
if (!DontCallOnInit) {
|
||||
auto Res = EnqueueFunctionCall(StateId, "onInit", {}, "onInit");
|
||||
auto Res = EnqueueFunctionCall(StateId, "onInit", { }, "onInit");
|
||||
Res->WaitUntilReady();
|
||||
if (Res->Error && Res->ErrorMessage != TLuaEngine::BeamMPFnNotFoundError) {
|
||||
beammp_lua_error("Calling \"onInit\" on \"" + StateId + "\" failed: " + Res->ErrorMessage);
|
||||
auto Snapshot = Res->GetSnapshot(StateId);
|
||||
if (Snapshot.Error && Snapshot.ErrorMessage != TLuaEngine::BeamMPFnNotFoundError) {
|
||||
beammp_lua_error("Calling \"onInit\" on \"" + StateId + "\" failed: " + Snapshot.ErrorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -446,6 +458,7 @@ void TLuaEngine::RegisterEvent(const std::string& EventName, TLuaStateId StateId
|
||||
}
|
||||
|
||||
std::set<std::string> TLuaEngine::GetEventHandlersForState(const std::string& EventName, TLuaStateId StateId) {
|
||||
std::unique_lock Lock(mLuaEventsMutex);
|
||||
return mLuaEvents[EventName][StateId];
|
||||
}
|
||||
|
||||
@@ -495,15 +508,12 @@ sol::table TLuaEngine::StateThreadData::Lua_TriggerGlobalEvent(const std::string
|
||||
auto Fn = mStateView[Handler];
|
||||
if (Fn.valid()) {
|
||||
auto LuaResult = Fn(LocalArgs);
|
||||
auto Result = std::make_shared<TLuaResult>();
|
||||
auto Result = std::make_shared<TLuaResult>(mStateId, Handler);
|
||||
if (LuaResult.valid()) {
|
||||
Result->Error = false;
|
||||
Result->Result = LuaResult;
|
||||
Result->MarkReadySuccess(LuaResult);
|
||||
} else {
|
||||
Result->Error = true;
|
||||
Result->ErrorMessage = "Function result in TriggerGlobalEvent was invalid";
|
||||
Result->MarkReadyError("Function result in TriggerGlobalEvent was invalid");
|
||||
}
|
||||
Result->MarkAsReady();
|
||||
Return.push_back(Result);
|
||||
}
|
||||
}
|
||||
@@ -511,26 +521,55 @@ sol::table TLuaEngine::StateThreadData::Lua_TriggerGlobalEvent(const std::string
|
||||
sol::table AsyncEventReturn = StateView.create_table();
|
||||
AsyncEventReturn["ReturnValueImpl"] = Return;
|
||||
AsyncEventReturn.set_function("IsDone",
|
||||
[&](const sol::table& Self) -> bool {
|
||||
[](const sol::table& Self) -> bool {
|
||||
auto Vector = Self.get<std::vector<std::shared_ptr<TLuaResult>>>("ReturnValueImpl");
|
||||
for (const auto& Value : Vector) {
|
||||
if (!Value->Ready) {
|
||||
if (!Value->IsReady()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
TLuaStateId StateId = mStateId;
|
||||
AsyncEventReturn.set_function("GetResults",
|
||||
[&](const sol::table& Self) -> sol::table {
|
||||
sol::state_view StateView(mState);
|
||||
[StateId](const sol::table& Self, sol::this_state State) -> sol::table {
|
||||
sol::state_view StateView(State);
|
||||
sol::table Result = StateView.create_table();
|
||||
auto Vector = Self.get<std::vector<std::shared_ptr<TLuaResult>>>("ReturnValueImpl");
|
||||
int i = 1;
|
||||
for (const auto& Value : Vector) {
|
||||
if (!Value->Ready) {
|
||||
if (!Value->IsReady()) {
|
||||
return sol::lua_nil;
|
||||
}
|
||||
Result.set(i, Value->Result);
|
||||
// event results from this state are valid unserialized
|
||||
if (Value->OwnerState() == StateId) {
|
||||
auto Snapshot = Value->GetSnapshot(StateId);
|
||||
Result.set(i, Snapshot.Result);
|
||||
} else {
|
||||
// event result from another state, goes through serialization boundary
|
||||
auto Snapshot = Value->GetDetachedSnapshot();
|
||||
std::visit([i, &Result](auto&& arg) {
|
||||
using T = std::decay_t<decltype(arg)>;
|
||||
if constexpr (std::is_same_v<T, std::vector<TDetachedLuaValue>>)
|
||||
Result.set(i, arg);
|
||||
else if constexpr (std::is_same_v<T, std::unordered_map<std::string, TDetachedLuaValue>>)
|
||||
Result.set(i, arg);
|
||||
else if constexpr (std::is_same_v<T, bool>)
|
||||
Result.set(i, arg);
|
||||
else if constexpr (std::is_same_v<T, double>)
|
||||
Result.set(i, arg);
|
||||
else if constexpr (std::is_same_v<T, int>)
|
||||
Result.set(i, arg);
|
||||
else if constexpr (std::is_same_v<T, std::string>)
|
||||
Result.set(i, arg);
|
||||
else if constexpr (std::is_same_v<T, std::monostate>)
|
||||
// monostate means no result value
|
||||
Result.set(i, sol::lua_nil_t());
|
||||
else
|
||||
static_assert(false, "non-exhaustive visitor!");
|
||||
}, Snapshot.Result.V);
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
return Result;
|
||||
@@ -589,7 +628,6 @@ std::variant<std::string, sol::nil_t> TLuaEngine::StateThreadData::Lua_GetPlayer
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sol::table TLuaEngine::StateThreadData::Lua_GetPlayers() {
|
||||
sol::table Result = mStateView.create_table();
|
||||
mEngine->Server().ForEachClient([&](std::weak_ptr<TClient> Client) -> bool {
|
||||
@@ -1080,13 +1118,15 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateI
|
||||
|
||||
std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueScript(const TLuaChunk& Script) {
|
||||
std::unique_lock Lock(mStateExecuteQueueMutex);
|
||||
auto Result = std::make_shared<TLuaResult>();
|
||||
// explicitly passing empty string as there's no single function being called here
|
||||
auto Result = std::make_shared<TLuaResult>(mStateId, std::string());
|
||||
mStateExecuteQueue.push({ Script, Result });
|
||||
return Result;
|
||||
}
|
||||
|
||||
std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueFunctionCallFromCustomEvent(const std::string& FunctionName, const std::vector<TLuaValue>& Args, const std::string& EventName, CallStrategy Strategy) {
|
||||
// TODO: Document all this
|
||||
std::unique_lock Lock(mStateFunctionQueueMutex);
|
||||
decltype(mStateFunctionQueue)::iterator Iter = mStateFunctionQueue.end();
|
||||
if (Strategy == CallStrategy::BestEffort) {
|
||||
Iter = std::find_if(mStateFunctionQueue.begin(), mStateFunctionQueue.end(),
|
||||
@@ -1095,10 +1135,7 @@ std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueFunctionCallFrom
|
||||
});
|
||||
}
|
||||
if (Iter == mStateFunctionQueue.end()) {
|
||||
auto Result = std::make_shared<TLuaResult>();
|
||||
Result->StateId = mStateId;
|
||||
Result->Function = FunctionName;
|
||||
std::unique_lock Lock(mStateFunctionQueueMutex);
|
||||
auto Result = std::make_shared<TLuaResult>(mStateId, FunctionName);
|
||||
mStateFunctionQueue.push_back({ FunctionName, Result, Args, EventName });
|
||||
mStateFunctionQueueCond.notify_all();
|
||||
return Result;
|
||||
@@ -1108,9 +1145,7 @@ std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueFunctionCallFrom
|
||||
}
|
||||
|
||||
std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueFunctionCall(const std::string& FunctionName, const std::vector<TLuaValue>& Args, const std::string& EventName) {
|
||||
auto Result = std::make_shared<TLuaResult>();
|
||||
Result->StateId = mStateId;
|
||||
Result->Function = FunctionName;
|
||||
auto Result = std::make_shared<TLuaResult>(mStateId, FunctionName);
|
||||
std::unique_lock Lock(mStateFunctionQueueMutex);
|
||||
mStateFunctionQueue.push_back({ FunctionName, Result, Args, EventName });
|
||||
mStateFunctionQueueCond.notify_all();
|
||||
@@ -1159,13 +1194,10 @@ void TLuaEngine::StateThreadData::operator()() {
|
||||
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);
|
||||
S.second->MarkReadySuccess(std::move(Res));
|
||||
} else {
|
||||
S.second->Error = true;
|
||||
S.second->SetErrorMessageFromResult(Res);
|
||||
S.second->MarkReadyError(std::move(Res));
|
||||
}
|
||||
S.second->MarkAsReady();
|
||||
}
|
||||
}
|
||||
{ // StateFunctionQueue Scope
|
||||
@@ -1182,7 +1214,7 @@ void TLuaEngine::StateThreadData::operator()() {
|
||||
auto& Result = TheQueuedFunction.Result;
|
||||
auto Args = TheQueuedFunction.Args;
|
||||
// TODO: Use TheQueuedFunction.EventName for errors, warnings, etc
|
||||
Result->StateId = mStateId;
|
||||
Result->SetOwnerState(mStateId);
|
||||
sol::state_view StateView(mState);
|
||||
auto Fn = StateView[FnName];
|
||||
if (Fn.valid() && Fn.get_type() == sol::type::function) {
|
||||
@@ -1222,17 +1254,12 @@ void TLuaEngine::StateThreadData::operator()() {
|
||||
}
|
||||
auto Res = Fn(sol::as_args(LuaArgs));
|
||||
if (Res.valid()) {
|
||||
Result->Error = false;
|
||||
Result->Result = std::move(Res);
|
||||
Result->MarkReadySuccess(std::move(Res));
|
||||
} else {
|
||||
Result->Error = true;
|
||||
Result->SetErrorMessageFromResult(Res);
|
||||
Result->MarkReadyError(std::move(Res));
|
||||
}
|
||||
Result->MarkAsReady();
|
||||
} else {
|
||||
Result->Error = true;
|
||||
Result->ErrorMessage = BeamMPFnNotFoundError; // special error kind that we can ignore later
|
||||
Result->MarkAsReady();
|
||||
Result->MarkReadyError(BeamMPFnNotFoundError);
|
||||
}
|
||||
auto ProfEnd = prof::now();
|
||||
auto ProfDuration = prof::duration(ProfStart, ProfEnd);
|
||||
@@ -1285,21 +1312,6 @@ void TLuaEngine::StateThreadData::AddPath(const fs::path& Path) {
|
||||
mPaths.push(Path);
|
||||
}
|
||||
|
||||
void TLuaResult::MarkAsReady() {
|
||||
{
|
||||
std::lock_guard<std::mutex> 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<std::string> Content, std::string FileName, std::string PluginPath)
|
||||
: Content(Content)
|
||||
, FileName(FileName)
|
||||
|
||||
+3
-2
@@ -63,8 +63,9 @@ TLuaPlugin::TLuaPlugin(TLuaEngine& Engine, const TLuaPluginConfig& Config, const
|
||||
}
|
||||
for (auto& Result : ResultsToCheck) {
|
||||
Result.second->WaitUntilReady();
|
||||
if (Result.second->Error) {
|
||||
beammp_lua_error("Failed: \"" + Result.first.string() + "\": " + Result.second->ErrorMessage);
|
||||
auto Snapshot = Result.second->GetDetachedSnapshot();
|
||||
if (Snapshot.Error) {
|
||||
beammp_lua_error("Failed: \"" + Result.first.string() + "\": " + Snapshot.ErrorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
#include "TLuaResult.h"
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
|
||||
void TLuaResult::MarkReadySuccess(sol::object Res) {
|
||||
std::unique_lock Lock(mMutex);
|
||||
mError = false;
|
||||
mResult = Res;
|
||||
mDetachedResult = Freeze(Res);
|
||||
|
||||
MarkAsReady();
|
||||
}
|
||||
|
||||
void TLuaResult::MarkReadyError(sol::protected_function_result Res) {
|
||||
std::unique_lock Lock(mMutex);
|
||||
SetErrorMessageFromResult(Res);
|
||||
|
||||
MarkAsReady();
|
||||
}
|
||||
|
||||
void TLuaResult::MarkReadyError(std::string Res) {
|
||||
std::unique_lock Lock(mMutex);
|
||||
mError = true;
|
||||
mErrorMessage = std::move(Res);
|
||||
|
||||
MarkAsReady();
|
||||
}
|
||||
|
||||
bool TLuaResult::IsReady() const {
|
||||
std::unique_lock Lock(mMutex);
|
||||
return mReady;
|
||||
}
|
||||
bool TLuaResult::IsError() const {
|
||||
std::unique_lock Lock(mMutex);
|
||||
return mError;
|
||||
}
|
||||
|
||||
TLuaStateId TLuaResult::OwnerState() const {
|
||||
std::unique_lock Lock(mMutex);
|
||||
// copy
|
||||
return mStateId;
|
||||
}
|
||||
void TLuaResult::SetOwnerState(TLuaStateId StateId) {
|
||||
std::unique_lock Lock(mMutex);
|
||||
mStateId = std::move(StateId);
|
||||
}
|
||||
|
||||
TLuaResult::Snapshot TLuaResult::GetSnapshot(TLuaStateId CallingState) const {
|
||||
std::unique_lock Lock(mMutex);
|
||||
if (CallingState != mStateId) {
|
||||
throw std::logic_error("Tried to get snapshot from non-owning state (use a detached snapshot instead)");
|
||||
}
|
||||
Snapshot snapshot {
|
||||
.Error = mError,
|
||||
.Ready = mReady,
|
||||
.ErrorMessage = mErrorMessage,
|
||||
.Result = mResult,
|
||||
.StateId = mStateId,
|
||||
.Function = mFunction,
|
||||
};
|
||||
return snapshot;
|
||||
}
|
||||
TLuaResult::DetachedSnapshot TLuaResult::GetDetachedSnapshot() const {
|
||||
std::unique_lock Lock(mMutex);
|
||||
DetachedSnapshot snapshot {
|
||||
.Error = mError,
|
||||
.Ready = mReady,
|
||||
.ErrorMessage = mErrorMessage,
|
||||
.Result = mDetachedResult,
|
||||
.StateId = mStateId,
|
||||
.Function = mFunction,
|
||||
};
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
TDetachedLuaValue TLuaResult::Freeze(const sol::object& o, int depth) {
|
||||
if (depth > 64)
|
||||
throw std::runtime_error("max depth (64) reached");
|
||||
switch (o.get_type()) {
|
||||
case sol::type::lua_nil:
|
||||
return { { std::monostate { } } };
|
||||
case sol::type::boolean:
|
||||
return { { o.as<bool>() } };
|
||||
case sol::type::number: {
|
||||
if (o.is<int>()) {
|
||||
return { { o.as<int>() } };
|
||||
} else {
|
||||
return { { o.as<double>() } };
|
||||
}
|
||||
}
|
||||
case sol::type::string:
|
||||
return { { o.as<std::string>() } };
|
||||
case sol::type::table: {
|
||||
TDetachedLuaValue::Object out;
|
||||
for (auto&& [k, v] : o.as<sol::table>()) {
|
||||
if (!k.is<std::string>())
|
||||
continue; // no numeric-key handling, don't need it
|
||||
out.emplace(k.as<std::string>(), Freeze(v, depth + 1));
|
||||
}
|
||||
return { { std::move(out) } };
|
||||
}
|
||||
default:
|
||||
throw std::runtime_error("unsupported Lua type for cross-thread snapshot");
|
||||
}
|
||||
}
|
||||
void TLuaResult::MarkAsReady() {
|
||||
mReady = true;
|
||||
mReadyCondition.notify_all();
|
||||
}
|
||||
|
||||
void TLuaResult::WaitUntilReady() {
|
||||
std::unique_lock Lock(mMutex);
|
||||
while (!mReady)
|
||||
mReadyCondition.wait_for(Lock, std::chrono::milliseconds(50),
|
||||
[this] {
|
||||
return mReady;
|
||||
});
|
||||
}
|
||||
|
||||
TEST_CASE("TLuaResult MarkReadyError(string) marks ready and wakes waiters") {
|
||||
TLuaResult result("state_a", "fn_a");
|
||||
std::atomic<bool> waiterDone { false };
|
||||
|
||||
auto waiter = std::thread([&] {
|
||||
result.WaitUntilReady();
|
||||
waiterDone.store(true, std::memory_order_release);
|
||||
});
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
CHECK_FALSE(waiterDone.load(std::memory_order_acquire));
|
||||
|
||||
result.MarkReadyError(std::string("boom"));
|
||||
waiter.join();
|
||||
|
||||
CHECK(result.IsReady());
|
||||
const auto snapshot = result.GetDetachedSnapshot();
|
||||
CHECK(snapshot.Ready);
|
||||
CHECK(snapshot.Error);
|
||||
CHECK(snapshot.ErrorMessage == "boom");
|
||||
CHECK(snapshot.StateId == "state_a");
|
||||
CHECK(snapshot.Function == "fn_a");
|
||||
}
|
||||
|
||||
TEST_CASE("TLuaResult GetSnapshot enforces owner state id") {
|
||||
TLuaResult result("owner_state", "fn_owner");
|
||||
sol::state lua;
|
||||
lua.open_libraries(sol::lib::base);
|
||||
result.MarkReadySuccess(sol::make_object(lua.lua_state(), std::string("ok")));
|
||||
|
||||
CHECK_NOTHROW(result.GetSnapshot("owner_state"));
|
||||
CHECK_THROWS_AS(result.GetSnapshot("different_state"), std::logic_error);
|
||||
}
|
||||
|
||||
TEST_CASE("TLuaResult detached snapshot freezes nested string-keyed tables") {
|
||||
TLuaResult result("state_table", "fn_table");
|
||||
sol::state lua;
|
||||
lua.open_libraries(sol::lib::base);
|
||||
|
||||
auto outer = lua.create_table();
|
||||
auto inner = lua.create_table();
|
||||
inner["k"] = std::string("v");
|
||||
|
||||
outer["flag"] = true;
|
||||
outer["msg"] = std::string("hello");
|
||||
outer["inner"] = inner;
|
||||
outer[1] = std::string("ignored_numeric_key");
|
||||
|
||||
result.MarkReadySuccess(sol::make_object(lua.lua_state(), outer));
|
||||
const auto detached = result.GetDetachedSnapshot();
|
||||
|
||||
CHECK(detached.Ready);
|
||||
CHECK_FALSE(detached.Error);
|
||||
const auto* object = std::get_if<TDetachedLuaValue::Object>(&detached.Result.V);
|
||||
REQUIRE(object != nullptr);
|
||||
|
||||
CHECK(object->contains("flag"));
|
||||
CHECK(object->contains("msg"));
|
||||
CHECK(object->contains("inner"));
|
||||
CHECK_FALSE(object->contains("1"));
|
||||
|
||||
const auto* flag = std::get_if<bool>(&object->at("flag").V);
|
||||
REQUIRE(flag != nullptr);
|
||||
CHECK(*flag);
|
||||
|
||||
const auto* msg = std::get_if<std::string>(&object->at("msg").V);
|
||||
REQUIRE(msg != nullptr);
|
||||
CHECK(*msg == "hello");
|
||||
|
||||
const auto* innerObj = std::get_if<TDetachedLuaValue::Object>(&object->at("inner").V);
|
||||
REQUIRE(innerObj != nullptr);
|
||||
REQUIRE(innerObj->contains("k"));
|
||||
const auto* innerValue = std::get_if<std::string>(&innerObj->at("k").V);
|
||||
REQUIRE(innerValue != nullptr);
|
||||
CHECK(*innerValue == "v");
|
||||
}
|
||||
|
||||
TEST_CASE("TLuaResult MarkReadySuccess throws on unsupported Lua function value") {
|
||||
TLuaResult result("state_fn", "fn_fn");
|
||||
sol::state lua;
|
||||
lua.open_libraries(sol::lib::base);
|
||||
lua["f"] = [] { return 1; };
|
||||
const sol::table globals = lua.globals();
|
||||
const sol::protected_function fn = globals.get<sol::protected_function>("f");
|
||||
const sol::object fnObj = sol::make_object(lua.lua_state(), fn);
|
||||
|
||||
CHECK_THROWS_AS(result.MarkReadySuccess(fnObj), std::runtime_error);
|
||||
CHECK_FALSE(result.IsReady());
|
||||
}
|
||||
+19
-10
@@ -40,6 +40,7 @@
|
||||
#include <memory>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <variant>
|
||||
#include <zlib.h>
|
||||
|
||||
typedef boost::asio::detail::socket_option::integer<SOL_SOCKET, SO_RCVTIMEO> rcv_timeout_option;
|
||||
@@ -507,23 +508,31 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
|
||||
bool BypassLimit = false;
|
||||
|
||||
for (const auto& Result : Futures) {
|
||||
if (!Result->Error && Result->Result.is<int>()) {
|
||||
auto Res = Result->Result.as<int>();
|
||||
auto Snapshot = Result->GetDetachedSnapshot();
|
||||
if (!Snapshot.Error) {
|
||||
const int* MaybeInt = std::get_if<int>(&Snapshot.Result.V);
|
||||
if (MaybeInt != nullptr) {
|
||||
auto Res = *MaybeInt;
|
||||
|
||||
if (Res == 1) {
|
||||
NotAllowed = true;
|
||||
break;
|
||||
} else if (Res == 2) {
|
||||
BypassLimit = true;
|
||||
if (Res == 1) {
|
||||
NotAllowed = true;
|
||||
break;
|
||||
} else if (Res == 2) {
|
||||
BypassLimit = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
std::string Reason;
|
||||
bool NotAllowedWithReason = std::any_of(Futures.begin(), Futures.end(),
|
||||
[&Reason](const std::shared_ptr<TLuaResult>& Result) -> bool {
|
||||
if (!Result->Error && Result->Result.is<std::string>()) {
|
||||
Reason = Result->Result.as<std::string>();
|
||||
return true;
|
||||
auto Snapshot = Result->GetDetachedSnapshot();
|
||||
if (!Snapshot.Error) {
|
||||
const std::string* MaybeStr = std::get_if<std::string>(&Snapshot.Result.V);
|
||||
if (MaybeStr != nullptr) {
|
||||
Reason = *MaybeStr;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
@@ -69,8 +69,9 @@ void TPluginMonitor::operator()() {
|
||||
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);
|
||||
if (Res->IsError()) {
|
||||
auto Snapshot = Res->GetDetachedSnapshot();
|
||||
beammp_lua_errorf("Error while hot-reloading \"{}\": {}", Pair.first, Snapshot.ErrorMessage);
|
||||
} else {
|
||||
mEngine->ReportErrors(mEngine->TriggerLocalEvent(StateID, "onInit"));
|
||||
mEngine->ReportErrors(mEngine->TriggerEvent("onFileChanged", "", Pair.first));
|
||||
|
||||
+27
-5
@@ -265,9 +265,15 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uin
|
||||
LogChatMessage(LockedClient->GetName(), LockedClient->GetID(), PacketAsString.substr(PacketAsString.find(':', 3) + 1));
|
||||
bool Rejected = std::any_of(Futures.begin(), Futures.end(),
|
||||
[](const std::shared_ptr<TLuaResult>& Elem) {
|
||||
return !Elem->Error
|
||||
&& Elem->Result.is<int>()
|
||||
&& bool(Elem->Result.as<int>());
|
||||
auto Snapshot = Elem->GetDetachedSnapshot();
|
||||
if (Snapshot.Error) {
|
||||
return false;
|
||||
}
|
||||
const int* MaybeInt = std::get_if<int>(&Snapshot.Result.V);
|
||||
if (MaybeInt == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return bool(*MaybeInt);
|
||||
});
|
||||
if (!Rejected) {
|
||||
std::string SanitizedPacket = fmt::format("C:{}: {}", LockedClient->GetName(), Message);
|
||||
@@ -380,7 +386,15 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
|
||||
TLuaEngine::WaitForAll(Futures);
|
||||
bool ShouldntSpawn = std::any_of(Futures.begin(), Futures.end(),
|
||||
[](const std::shared_ptr<TLuaResult>& Result) {
|
||||
return !Result->Error && Result->Result.is<int>() && Result->Result.as<int>() != 0;
|
||||
auto Snapshot = Result->GetDetachedSnapshot();
|
||||
if (Snapshot.Error) {
|
||||
return false;
|
||||
}
|
||||
const int* MaybeInt = std::get_if<int>(&Snapshot.Result.V);
|
||||
if (MaybeInt == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return *MaybeInt != 0;
|
||||
});
|
||||
|
||||
bool SpawnConfirmed = false;
|
||||
@@ -417,7 +431,15 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
|
||||
TLuaEngine::WaitForAll(Futures);
|
||||
bool ShouldntAllow = std::any_of(Futures.begin(), Futures.end(),
|
||||
[](const std::shared_ptr<TLuaResult>& Result) {
|
||||
return !Result->Error && Result->Result.is<int>() && Result->Result.as<int>() != 0;
|
||||
auto Snapshot = Result->GetDetachedSnapshot();
|
||||
if (Snapshot.Error) {
|
||||
return false;
|
||||
}
|
||||
const int* MaybeInt = std::get_if<int>(&Snapshot.Result.V);
|
||||
if (MaybeInt == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return *MaybeInt != 0;
|
||||
});
|
||||
|
||||
auto FoundPos = Packet.find('{');
|
||||
|
||||
Reference in New Issue
Block a user