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.h269
1 files changed, 89 insertions, 180 deletions
diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h
index 7a3d9e7dd7..7f7a7566f3 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;
@@ -86,8 +87,6 @@ public:
~LuaState();
- void initLuaState();
-
bool checkLua(const std::string& desc, int r);
// expr() is for when we want to capture any results left on the stack
@@ -117,11 +116,12 @@ private:
* LuaPopper
*****************************************************************************/
/**
- * LuaPopper is an RAII struct whose role is to pop some number of entries
+ * LuaPopper is an RAII class whose role is to pop some number of entries
* from the Lua stack if the calling function exits early.
*/
-struct LuaPopper
+class LuaPopper
{
+public:
LuaPopper(lua_State* L, int count):
mState(L),
mCount(count)
@@ -135,11 +135,40 @@ struct LuaPopper
void disarm() { set(0); }
void set(int count) { mCount = count; }
+private:
lua_State* mState;
int mCount;
};
/*****************************************************************************
+* LuaRemover
+*****************************************************************************/
+/**
+ * Remove a particular stack index on exit from enclosing scope.
+ * If you pass a negative index (meaning relative to the current stack top),
+ * converts to an absolute index. The point of LuaRemover is to remove the
+ * entry at the specified index regardless of subsequent pushes to the stack.
+ */
+class LuaRemover
+{
+public:
+ LuaRemover(lua_State* L, int index):
+ mState(L),
+ mIndex(lua_absindex(L, index))
+ {}
+ LuaRemover(const LuaRemover&) = delete;
+ LuaRemover& operator=(const LuaRemover&) = delete;
+ ~LuaRemover()
+ {
+ lua_remove(mState, mIndex);
+ }
+
+private:
+ lua_State* mState;
+ int mIndex;
+};
+
+/*****************************************************************************
* lua_function (and helper class LuaFunction)
*****************************************************************************/
/**
@@ -194,30 +223,43 @@ int name##_luasub::call(lua_State* L)
/*****************************************************************************
* lua_emplace<T>(), lua_toclass<T>()
*****************************************************************************/
+// Every instance of DistinctInt has a different int value, barring int
+// wraparound.
+class DistinctInt
+{
+public:
+ DistinctInt(): mValue(++mValues) {}
+ int get() const { return mValue; }
+ operator int() const { return mValue; }
+private:
+ static int mValues;
+ int mValue;
+};
+
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>());
+template <typename T>
+struct TypeTag
+{
+ // For (std::is_same<T, U>), &TypeTag<T>::value == &TypeTag<U>::value.
+ // For (! std::is_same<T, U>), &TypeTag<T>::value != &TypeTag<U>::value.
+ // And every distinct instance of DistinctInt has a distinct value.
+ // Therefore, TypeTag<T>::value is an int uniquely associated with each
+ // distinct T.
+ static DistinctInt value;
+};
+
+template <typename T>
+DistinctInt TypeTag<T>::value;
} // 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 +268,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{ TypeTag<T>::value };
+ 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
+ new (ptr) T(std::forward<ARGS>(args)...);
+ // stack is now initialized userdata containing our T instance -- 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");
-}
-
-} // 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, TypeTag<T>::value) };
+ // 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);
}
/*****************************************************************************
@@ -473,7 +382,7 @@ public:
template <typename... ARGS>
void operator()(ARGS&&... args)
{
- LL_INFOS("Lua") << mBlock << ' ';
+ LL_DEBUGS("Lua") << mBlock << ' ';
stream_to(LL_CONT, std::forward<ARGS>(args)...);
LL_ENDL;
}