fix lua result serialization causing various issues

for example, loading a library which returns a table of functions would
fail with an inexplicable (to the user) error about serialization. This
is now fixed. We no longer use a single result type, instead there are
void results and normal results, and the former simply never has to
worry about the result.

This was likely causing even more issues; if the `sol::object` was
populated before, it hitting the dtor in another path would, again, like
we've seen before, corrupt the lua stack.
This commit is contained in:
Lion Kortlepel
2026-04-29 18:29:02 +00:00
parent 57fe7cb055
commit 4104dc1f18
7 changed files with 319 additions and 100 deletions
+47 -16
View File
@@ -37,19 +37,54 @@ struct TDetachedLuaValue {
};
std::ostream& operator<<(std::ostream& os, const TDetachedLuaValue& value);
struct TLuaResult {
/// Result for operations that stay within one Lua state (e.g. script loads).
/// This type intentionally does not store any Lua references or serialized Lua values.
struct TLuaVoidResult {
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;
};
explicit TLuaVoidResult(TLuaStateId StateId)
: mStateId(std::move(StateId)) {}
/// Marks this result as success & sets it as ready, waking all waiting threads.
void MarkReadySuccess();
/// Marks this result as erroneous & sets it as ready, waking all waiting threads.
void MarkReadyError(sol::protected_function_result Res);
/// Marks this result as erroneous & sets it as ready, waking all waiting threads.
void MarkReadyError(std::string Res);
/// Wait (suspend) until this result is ready.
void WaitUntilReady();
bool IsReady() const;
bool IsError() const;
Snapshot GetSnapshot() const;
private:
mutable std::mutex mMutex {};
bool mReady { false };
bool mError { false };
std::string mErrorMessage;
TLuaStateId mStateId;
std::condition_variable mReadyCondition {};
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();
};
struct TLuaResult {
struct DetachedSnapshot {
bool Error;
bool Ready;
@@ -66,33 +101,29 @@ struct TLuaResult {
/// Marks this result as success & sets it as ready, waking all threads waiting on it.
void MarkReadySuccess(sol::object Res);
/// Marks this result as successful and ready without serializing a Lua value.
void MarkReadySuccessNoResult();
/// 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.
/// Wait (suspend) until this result is ready. Use `GetDetachedSnapshot` to get the result.
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`.
/// object. This never stores or returns raw Lua references and is safe to use
/// across threads.
DetachedSnapshot GetDetachedSnapshot() const;
private:
mutable std::mutex mMutex {};
bool mReady { false };
bool mError;
bool mError { false };
std::string mErrorMessage;
sol::object mResult { sol::lua_nil };
TDetachedLuaValue mDetachedResult;
TLuaStateId mStateId;
std::string mFunction;