diff options
Diffstat (limited to 'indra/llcommon/lua_function.h')
-rw-r--r-- | indra/llcommon/lua_function.h | 269 |
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; } |