summaryrefslogtreecommitdiff
path: root/indra/llcommon/lua_function.h
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llcommon/lua_function.h')
-rw-r--r--indra/llcommon/lua_function.h101
1 files changed, 74 insertions, 27 deletions
diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h
index 8b93053a46..7a3d9e7dd7 100644
--- a/indra/llcommon/lua_function.h
+++ b/indra/llcommon/lua_function.h
@@ -213,19 +213,21 @@ std::string lua_emplace_metaname(const std::string& Tname = LLError::Log::classn
* On the stack belonging to the passed lua_State, push a Lua userdata object
* with a newly-constructed C++ object std::optional<T>(args...). The new
* userdata has a metadata table with a __gc() function to ensure that when
- * the userdata instance is garbage-collected, ~T() is called.
+ * the userdata instance is garbage-collected, ~T() is called. Also call
+ * LL.atexit(lua_emplace_call_gc<T>(object)) to make ~LuaState() call ~T().
*
* We wrap the userdata object as std::optional<T> so we can explicitly
* destroy the contained T, and detect that we've done so.
*
* Usage:
* lua_emplace<T>(L, T constructor args...);
+ * // L's Lua stack top is now a userdata containing T
*/
template <class T, typename... ARGS>
void lua_emplace(lua_State* L, ARGS&&... args)
{
using optT = std::optional<T>;
- luaL_checkstack(L, 3, nullptr);
+ luaL_checkstack(L, 5, nullptr);
auto ptr = lua_newuserdata(L, sizeof(optT));
// stack is uninitialized userdata
// For now, assume (but verify) that lua_newuserdata() returns a
@@ -254,38 +256,94 @@ void lua_emplace(lua_State* L, ARGS&&... args)
lua_setmetatable(L, -2);
// Stack is now userdata, initialized with T(args),
// with metatable.__gc pointing to lua_emplace_gc<T>.
+
// But wait, there's more! Use our atexit() function to ensure that this
- // C++ object is eventually cleaned up even if the garbage collector never
+ // C++ object is eventually destroyed even if the garbage collector never
// gets around to it.
lua_getglobal(L, "LL");
// stack contains userdata, LL
lua_getfield(L, -1, "atexit");
// stack contains userdata, LL, LL.atexit
+ // ditch LL
+ lua_replace(L, -2);
+ // stack contains userdata, LL.atexit
+
+ // We have a bit of a problem here. We want to allow the garbage collector
+ // to collect the userdata if it must; but we also want to register a
+ // cleanup function to destroy the value if (usual case) it has NOT been
+ // garbage-collected. The problem is that if we bind into atexit()'s queue
+ // a strong reference to the userdata, we ensure that the garbage
+ // collector cannot collect it, making our metatable with __gc function
+ // completely moot. And we must assume that lua_pushcclosure() binds a
+ // strong reference to each value passed as a closure.
+
+ // The solution is to use one more indirection: create a weak table whose
+ // sole entry is the userdata. If all other references to the new userdata
+ // are forgotten, so the only remaining reference is the weak table, the
+ // userdata can be collected. Then we can bind that weak table as the
+ // closure value for our cleanup function.
+ // The new weak table will have at most 1 array value, 0 other keys.
+ lua_createtable(L, 1, 0);
+ // stack contains userdata, LL.atexit, weak_table
+ if (luaL_newmetatable(L, "weak_values"))
+ {
+ // stack contains userdata, LL.atexit, weak_table, weak_values
+ // just created "weak_values" metatable: populate it
+ // Registry.weak_values = {__mode="v"}
+ lua_pushliteral(L, "v");
+ // stack contains userdata, LL.atexit, weak_table, weak_values, "v"
+ lua_setfield(L, -2, "__mode");
+ }
+ // stack contains userdata, LL.atexit, weak_table, weak_values
+ // setmetatable(weak_table, weak_values)
+ lua_setmetatable(L, -2);
+ // stack contains userdata, LL.atexit, weak_table
+ lua_pushinteger(L, 1);
+ // stack contains userdata, LL.atexit, weak_table, 1
// duplicate userdata
- lua_pushvalue(L, -3);
- // stack contains userdata, LL, LL.atexit, userdata
- // push a closure binding (lua_emplace_call_gc<T>, userdata)
+ lua_pushvalue(L, -4);
+ // stack contains userdata, LL.atexit, weak_table, 1, userdata
+ // weak_table[1] = userdata
+ lua_settable(L, -3);
+ // stack contains userdata, LL.atexit, weak_table
+
+ // push a closure binding (lua_emplace_call_gc<T>, weak_table)
auto callgcname{ stringize("lua_emplace_call_gc<", Tname, ">") };
lua_pushcclosure(L, lua_emplace_call_gc<T>, callgcname.c_str(), 1);
- // stack contains userdata, LL, LL.atexit, closure
+ // stack contains userdata, LL.atexit, closure
// Call LL.atexit(closure)
lua_call(L, 1, 0);
- // stack contains userdata, LL
- lua_pop(L, 1);
// stack contains userdata -- return that
}
namespace {
-// passed to LL.atexit(closure(lua_emplace_call_gc<T>, userdata));
+// passed to LL.atexit(closure(lua_emplace_call_gc<T>, weak_table{userdata}));
// retrieves bound userdata to pass to lua_emplace_gc<T>()
template <class T>
int lua_emplace_call_gc(lua_State* L)
{
- luaL_checkstack(L, 1, nullptr);
- // retrieve the first (only) bound upvalue and push to stack top as the
- // argument for lua_emplace_gc<T>()
+ luaL_checkstack(L, 2, nullptr);
+ // retrieve the first (only) bound upvalue and push to stack top
lua_pushvalue(L, lua_upvalueindex(1));
+ // This is the weak_table bound by lua_emplace<T>(). Its one and only
+ // entry should be the lua_emplace<T>() userdata -- unless userdata has
+ // been garbage collected. Retrieve weak_table[1].
+ lua_pushinteger(L, 1);
+ // stack contains weak_table, 1
+ lua_gettable(L, -2);
+ // stack contains weak_table, weak_table[1]
+ // If our userdata was garbage-collected, there is no weak_table[1],
+ // and we just retrieved nil.
+ if (lua_isnil(L, -1))
+ {
+ lua_pop(L, 2);
+ return 0;
+ }
+ // stack contains weak_table, userdata
+ // ditch weak_table
+ lua_replace(L, -2);
+ // pass userdata to lua_emplace_gc<T>()
return lua_emplace_gc<T>(L);
}
@@ -325,23 +383,12 @@ template <class T>
T* lua_toclass(lua_State* L, int index)
{
using optT = std::optional<T>;
- luaL_checkstack(L, 2, nullptr);
+ // recreate the name lua_emplace<T>() uses for its metatable
+ auto metaname{ lua_emplace_metaname<T>() };
// get void* pointer to userdata (if that's what it is)
- auto ptr{ lua_touserdata(L, index) };
+ void* ptr{ luaL_checkudata(L, index, metaname.c_str()) };
if (! ptr)
return nullptr;
- // push the metatable for this userdata, if any
- if (! lua_getmetatable(L, index))
- return nullptr;
- // now push the metatable created by lua_emplace<T>()
- auto metaname{ lua_emplace_metaname<T>() };
- luaL_getmetatable(L, metaname.c_str());
- auto equal{ lua_equal(L, -1, -2) };
- // Having compared the userdata's metatable with the one set by
- // lua_emplace<T>(), we no longer need either metatable on the stack.
- lua_pop(L, 2);
- if (! equal)
- return nullptr;
// Derive the optT* from ptr. If in future lua_emplace() must manually
// align our optT* within the Lua-provided void*, adjust accordingly.
optT* tptr(static_cast<optT*>(ptr));