summaryrefslogtreecommitdiff
path: root/indra/llcommon
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llcommon')
-rw-r--r--indra/llcommon/lua_function.h223
1 files changed, 48 insertions, 175 deletions
diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h
index 9cdd5665dc..284f911fa8 100644
--- a/indra/llcommon/lua_function.h
+++ b/indra/llcommon/lua_function.h
@@ -21,8 +21,9 @@
#include "stringize.h"
#include <exception> // std::uncaught_exceptions()
#include <memory> // std::shared_ptr
-#include <optional>
+#include <typeindex>
#include <typeinfo>
+#include <unordered_map>
#include <utility> // std::pair
class LuaListener;
@@ -196,28 +197,33 @@ int name##_luasub::call(lua_State* L)
*****************************************************************************/
namespace {
-// this closure function retrieves its bound argument to pass to
-// lua_emplace_gc<T>()
-template <class T>
-int lua_emplace_call_gc(lua_State* L);
-// this will be the function called by the new userdata's metatable's __gc()
-template <class T>
-int lua_emplace_gc(lua_State* L);
-// name by which we'll store the new userdata's metatable in the Registry
-template <class T>
-std::string lua_emplace_metaname(const std::string& Tname = LLError::Log::classname<T>());
+// If we start engaging lua_emplace<T>() from more than one thread, type_tags
+// will need locking.
+std::unordered_map<std::type_index, int> type_tags;
+
+// find or create a new Luau userdata "tag" for type T
+template <typename T>
+int type_tag()
+{
+ // The first time we encounter a given type T, assign a new distinct tag
+ // value based on the number of previously-created tags. But avoid tag 0,
+ // which is evidently the default for userdata objects created without
+ // explicit tags. Don't try to destroy a nonexistent T object in a random
+ // userdata object!
+ auto [entry, created] = type_tags.emplace(std::type_index(typeid(T)), int(type_tags.size()+1));
+ // Luau only permits up to LUA_UTAG_LIMIT distinct userdata tags (ca. 128)
+ llassert(entry->second < LUA_UTAG_LIMIT);
+ return entry->second;
+}
} // anonymous namespace
/**
* 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. 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.
+ * containing a newly-constructed C++ object T(args...). The userdata has a
+ * Luau destructor guaranteeing that the new T instance is destroyed when the
+ * userdata is garbage-collected, no later than when the LuaState is
+ * destroyed.
*
* Usage:
* lua_emplace<T>(L, T constructor args...);
@@ -226,178 +232,45 @@ std::string lua_emplace_metaname(const std::string& Tname = LLError::Log::classn
template <class T, typename... ARGS>
void lua_emplace(lua_State* L, ARGS&&... args)
{
- using optT = std::optional<T>;
- luaL_checkstack(L, 5, nullptr);
- auto ptr = lua_newuserdata(L, sizeof(optT));
+ luaL_checkstack(L, 1, nullptr);
+ int tag{ type_tag<T>() };
+ if (! lua_getuserdatadtor(L, tag))
+ {
+ // We haven't yet told THIS lua_State the destructor to use for this tag.
+ lua_setuserdatadtor(
+ L, tag,
+ [](lua_State*, void* ptr)
+ {
+ // destroy the contained T instance
+ static_cast<T*>(ptr)->~T();
+ });
+ }
+ auto ptr = lua_newuserdatatagged(L, sizeof(T), tag);
// stack is uninitialized userdata
// For now, assume (but verify) that lua_newuserdata() returns a
// conservatively-aligned ptr. If that turns out not to be the case, we
// might have to discard the new userdata, overallocate its successor and
// perform manual alignment -- but only if we must.
- llassert((uintptr_t(ptr) % alignof(optT)) == 0);
+ llassert((uintptr_t(ptr) % alignof(T)) == 0);
// Construct our T there using placement new
- new (ptr) optT(std::in_place, std::forward<ARGS>(args)...);
- // stack is now initialized userdata containing our T instance
-
- // Find or create the metatable shared by all userdata instances holding
- // C++ type T. We want it to be shared across instances, but it must be
- // type-specific because its __gc field is lua_emplace_gc<T>.
- auto Tname{ LLError::Log::classname<T>() };
- auto metaname{ lua_emplace_metaname<T>(Tname) };
- if (luaL_newmetatable(L, metaname.c_str()))
- {
- // just created it: populate it
- auto gcname{ stringize("lua_emplace_gc<", Tname, ">") };
- lua_pushcfunction(L, lua_emplace_gc<T>, gcname.c_str());
- // stack is userdata, metatable, lua_emplace_gc<T>
- lua_setfield(L, -2, "__gc");
- }
- // stack is userdata, metatable
- 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 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, -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.atexit, closure
- // Call LL.atexit(closure)
- lua_call(L, 1, 0);
- // stack contains userdata -- return that
-}
-
-namespace {
-
-// 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, 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);
-}
-
-// set as metatable(userdata).__gc to be called by the garbage collector
-template <class T>
-int lua_emplace_gc(lua_State* L)
-{
- using optT = std::optional<T>;
- // We're called with userdata on the stack holding an instance of type T.
- auto ptr = lua_touserdata(L, -1);
- llassert(ptr);
- // Destroy the T object contained in optT at the void* address ptr. If
- // in future lua_emplace() must manually align our optT* within the
- // Lua-provided void*, derive optT* from ptr.
- static_cast<optT*>(ptr)->reset();
- // pop the userdata
- lua_pop(L, 1);
- return 0;
-}
-
-template <class T>
-std::string lua_emplace_metaname(const std::string& Tname)
-{
- return stringize("lua_emplace_", Tname, "_meta");
+ new (ptr) T(std::forward<ARGS>(args)...);
+ // stack is now initialized userdata containing our T instance -- return
+ // that
}
-} // anonymous namespace
-
/**
* If the value at the passed acceptable index is a full userdata created by
- * lua_emplace<T>() -- that is, the userdata contains a non-empty
- * std::optional<T> -- return a pointer to the contained T instance. Otherwise
- * (index is not a full userdata; userdata is not of type std::optional<T>;
- * std::optional<T> is empty) return nullptr.
+ * lua_emplace<T>(), return a pointer to the contained T instance. Otherwise
+ * (index is not a full userdata; userdata is not of type T) return nullptr.
*/
template <class T>
T* lua_toclass(lua_State* L, int index)
{
- using optT = std::optional<T>;
- // 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)
- void* ptr{ luaL_checkudata(L, index, metaname.c_str()) };
- if (! ptr)
- 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));
- // make sure our optT isn't empty
- if (! *tptr)
- return nullptr;
- // looks like we still have a non-empty optT: return the *address* of the
- // value() reference
- return &tptr->value();
+ void* ptr{ lua_touserdatatagged(L, index, type_tag<T>()) };
+ // Derive the T* from ptr. If in future lua_emplace() must manually
+ // align our T* within the Lua-provided void*, adjust accordingly.
+ return static_cast<T*>(ptr);
}
/*****************************************************************************