1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
|
#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 = make_shared<std::weak_ptr<UniqueLuaReference>>();
auto delTabRef = make_shared<std::weak_ptr<UniqueLuaReference>>();
auto delUserdataRef = 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&&) = default;
LuaThread& LuaThread::operator=(LuaThread&&) = 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
|