mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-07 18:31:26 -10:00
w_bank: add a move constructor. object_copy_including_array_member returns by value, and without a move constructor the return fell back to the shallow copy constructor whenever NRVO was not applied (e.g. MSVC debug builds), after which the local's destructor deleted the freshly allocated arrays -- leaving the stored bank dangling and double-deleting at model unload. Also add noexcept to the move operations, replace the destructor call in move-assignment with explicit delete[]s (using an object after its lifetime has ended is undefined behavior), guard against self-move, and document that the defaulted copy constructor is intentionally shallow for object_copy_including_array_member's copy-then-replace pattern. LuaThread: declare the defaulted move operations noexcept. This exposed that LuaTable was not movable at all (its user-declared destructor suppressed the implicit moves), so LuaThread's implicit move spec was formally throwing -- a hard error on gcc 9, which still requires the spec on a defaulted redeclaration to match the implicit one. Restore LuaTable's rule of five and declare the LuaValue and LuaFunction copy operations noexcept (they only copy a shared_ptr, a raw pointer, and an enum), making the noexcept claims hold throughout the hierarchy. Co-Authored-By: Claude Fable 5 <[email protected]>
163 lines
5.7 KiB
C++
163 lines
5.7 KiB
C++
#include "LuaThread.h"
|
|
|
|
#include "LuaException.h"
|
|
|
|
#include <utility>
|
|
|
|
namespace luacpp {
|
|
LuaThread LuaThread::create(lua_State* L, const LuaFunction& func)
|
|
{
|
|
auto luaThread = lua_newthread(L);
|
|
|
|
LuaThread thread(L, luaThread);
|
|
thread.setReference(UniqueLuaReference::create(L));
|
|
|
|
lua_pop(L, 1);
|
|
|
|
//Make sure that the C++-side reference of the LuaThread (and everything it holds) is cleared when its parents references are auto-garbage collected by lua for whatever reason (usually due to the parent thread dying).
|
|
//To do this, register an object with a __gc method in the thread (usually we'd want to directly attach it to the thread, but only userdata will have __gc methods called).
|
|
//Usually we'd want to do this when the childs references are GC'd, but creating tables on child threads from C causes tests to fail for some reason.
|
|
auto threadRef = std::weak_ptr<UniqueLuaReference>(thread.getReference());
|
|
//These must be pointers to weak pointers, as we cannot know the weak pointers before creating the lambda.
|
|
auto delFuncRef = std::make_shared<std::weak_ptr<UniqueLuaReference>>();
|
|
auto delTabRef = std::make_shared<std::weak_ptr<UniqueLuaReference>>();
|
|
auto delUserdataRef = std::make_shared<std::weak_ptr<UniqueLuaReference>>();
|
|
|
|
int stack = lua_gettop(L);
|
|
|
|
//Make sure to create the function that clears the LuaThread reference BEFORE creating the userdata value, otherwise the function will be garbage-collected itself before it's called.
|
|
thread.deleterFunc = LuaFunction::createFromStdFunction(L, [threadRef, delFuncRef, delTabRef, delUserdataRef](lua_State*, const LuaValueList&) -> LuaValueList {
|
|
if (!threadRef.expired())
|
|
threadRef.lock()->removeReference();
|
|
if (!delFuncRef->expired())
|
|
delFuncRef->lock()->removeReference();
|
|
if (!delTabRef->expired())
|
|
delTabRef->lock()->removeReference();
|
|
if (!delUserdataRef->expired())
|
|
delUserdataRef->lock()->removeReference();
|
|
return {};
|
|
});
|
|
|
|
//NOW, create the dummy userdata, and its metatable
|
|
lua_newuserdata(L, sizeof(bool));
|
|
thread.deleterUserdata = UniqueLuaReference::create(L);
|
|
thread.deleterTable = LuaTable::create(L);
|
|
|
|
*delFuncRef = std::weak_ptr<UniqueLuaReference>(thread.deleterFunc.getReference());
|
|
*delTabRef = std::weak_ptr<UniqueLuaReference>(thread.deleterTable.getReference());
|
|
*delUserdataRef = std::weak_ptr<UniqueLuaReference>(thread.deleterUserdata);
|
|
|
|
//Since we hold references to all we need, tidy up the stack.
|
|
lua_settop(L, stack);
|
|
|
|
//Push the values in the correct order for assembly. Userdata, then table, then func
|
|
thread.deleterUserdata->pushValue(L);
|
|
thread.deleterTable.pushValue(L);
|
|
thread.deleterFunc.pushValue(L);
|
|
lua_setfield(L, -2, "__gc"); //Takes the top value (func) and assigns it to the second to last one (table) as __gc, and pops the top value
|
|
lua_setmetatable(L, -2); //Takes the top value (table) and assigns it as the metadata table of the second to last one (userdata), and pops the last value
|
|
lua_pop(L, 1); //Pop the userdata
|
|
|
|
func.pushValue(L);
|
|
// Move the main function to the thread (I have no idea what this actually does but the Lua code does the same...)
|
|
lua_xmove(L, luaThread, 1);
|
|
|
|
return thread;
|
|
}
|
|
|
|
LuaThread::LuaThread() = default;
|
|
LuaThread::LuaThread(lua_State* luaState, lua_State* thread) : LuaValue(luaState), _thread(thread) {}
|
|
|
|
LuaThread::LuaThread(LuaThread&&) noexcept = default;
|
|
LuaThread& LuaThread::operator=(LuaThread&&) noexcept = default;
|
|
|
|
LuaThread::~LuaThread() = default;
|
|
|
|
void LuaThread::setReference(const LuaReference& ref)
|
|
{
|
|
lua_State* L = ref->getState();
|
|
|
|
ref->pushValue(L);
|
|
|
|
if (lua_type(L, -1) != LUA_TTHREAD) {
|
|
lua_pop(L, 1);
|
|
throw LuaException("Reference does not refer to a thread!");
|
|
} else {
|
|
lua_pop(L, 1);
|
|
LuaValue::setReference(ref);
|
|
}
|
|
}
|
|
LuaThread::ResumeState LuaThread::resume(const LuaValueList& params) const
|
|
{
|
|
int nargs = static_cast<int>(params.size());
|
|
for (const auto& val : params) {
|
|
val.pushValue(_luaState);
|
|
}
|
|
|
|
// Move parameters to the thread
|
|
lua_xmove(_luaState, _thread, nargs);
|
|
|
|
// now resume
|
|
const auto result = lua_resume(_thread, nargs);
|
|
|
|
if (result != 0 && result != LUA_YIELD) {
|
|
if (_errorCallback) {
|
|
if (_errorCallback(_luaState, _thread)) {
|
|
// If the error was handled just pretend that we resumed successfully
|
|
ResumeState resume;
|
|
resume.completed = true;
|
|
return resume;
|
|
}
|
|
}
|
|
|
|
throw LuaException("Lua coroutine failed to resume!");
|
|
}
|
|
|
|
LuaValueList returnVals;
|
|
auto numRet = lua_gettop(_thread);
|
|
returnVals.reserve(numRet);
|
|
|
|
// Move the values back to the main state so that it uses the right state
|
|
auto previousStack = lua_gettop(_luaState); // Keep this for cleaning up later
|
|
lua_xmove(_thread, _luaState, numRet);
|
|
|
|
auto retValsStart = previousStack + 1; // We need to start at + 1 since that is where the first return val would be
|
|
for (int i = retValsStart; i <= previousStack + numRet; ++i) {
|
|
LuaValue val;
|
|
convert::popValue(_luaState, val, i, false);
|
|
returnVals.push_back(std::move(val));
|
|
}
|
|
|
|
// Cleanup the stack
|
|
lua_settop(_luaState, previousStack);
|
|
|
|
ResumeState state;
|
|
state.completed = result != LUA_YIELD;
|
|
state.returnVals = std::move(returnVals);
|
|
return state;
|
|
}
|
|
void LuaThread::setErrorCallback(LuaThread::ErrorCallback errorCallback) { _errorCallback = std::move(errorCallback); }
|
|
|
|
lua_State* LuaThread::getThreadHandle() const { return _thread; }
|
|
|
|
bool convert::popValue(lua_State* luaState, LuaThread& target, int stackposition, bool remove)
|
|
{
|
|
if (!internal::isValidIndex(luaState, stackposition)) {
|
|
return false;
|
|
}
|
|
|
|
if (!lua_isthread(luaState, stackposition)) {
|
|
return false;
|
|
} else {
|
|
target.setReference(UniqueLuaReference::create(luaState, stackposition));
|
|
|
|
if (remove) {
|
|
lua_remove(luaState, stackposition);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
} // namespace luacpp
|