From ffdcf33364ebfdc1829cb7c7eea6ae4c908d12f1 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 12 Jun 2024 16:42:39 -0400 Subject: Extract TempSet from llcallbacklist.cpp into its own tempset.h. --- indra/llcommon/CMakeLists.txt | 1 + indra/llcommon/llcallbacklist.cpp | 27 +------------------------- indra/llcommon/tempset.h | 41 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 26 deletions(-) create mode 100755 indra/llcommon/tempset.h (limited to 'indra/llcommon') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index d5440d6bc8..20670d7ebe 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -259,6 +259,7 @@ set(llcommon_HEADER_FILES lualistener.h stdtypes.h stringize.h + tempset.h threadpool.h threadpool_fwd.h threadsafeschedule.h diff --git a/indra/llcommon/llcallbacklist.cpp b/indra/llcommon/llcallbacklist.cpp index 015475a903..555c793333 100644 --- a/indra/llcommon/llcallbacklist.cpp +++ b/indra/llcommon/llcallbacklist.cpp @@ -29,6 +29,7 @@ #include "llerror.h" #include "llexception.h" #include "llsdutil.h" +#include "tempset.h" #include #include #include @@ -292,32 +293,6 @@ void Timers::setTimeslice(F32 timeslice) } } -// RAII class to set specified variable to specified value -// only for the duration of containing scope -template -class TempSet -{ -public: - TempSet(VAR& var, const VALUE& value): - mVar(var), - mOldValue(mVar) - { - mVar = value; - } - - TempSet(const TempSet&) = delete; - TempSet& operator=(const TempSet&) = delete; - - ~TempSet() - { - mVar = mOldValue; - } - -private: - VAR& mVar; - VALUE mOldValue; -}; - bool Timers::tick() { // Fetch current time only on entry, even though running some mQueue task diff --git a/indra/llcommon/tempset.h b/indra/llcommon/tempset.h new file mode 100755 index 0000000000..e1496bd5fc --- /dev/null +++ b/indra/llcommon/tempset.h @@ -0,0 +1,41 @@ +/** + * @file tempset.h + * @author Nat Goodspeed + * @date 2024-06-12 + * @brief Temporarily override a variable for scope duration, then restore + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_TEMPSET_H) +#define LL_TEMPSET_H + +// RAII class to set specified variable to specified value +// only for the duration of containing scope +template +class TempSet +{ +public: + TempSet(VAR& var, const VALUE& value): + mVar(var), + mOldValue(mVar) + { + mVar = value; + } + + TempSet(const TempSet&) = delete; + TempSet& operator=(const TempSet&) = delete; + + ~TempSet() + { + mVar = mOldValue; + } + +private: + VAR& mVar; + VALUE mOldValue; +}; + +#endif /* ! defined(LL_TEMPSET_H) */ -- cgit v1.2.3 From 128514da9e1b24e8d817ec90b53dea9506f31101 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 12 Jun 2024 16:51:33 -0400 Subject: LuaState::expr() has log messages for ending, add for starting. It's helpful to see when expr() is actually going to start running a particular Lua chunk. We already report not only when it's done, but also if/when we start and finish a p.s. fiber.run() call. --- indra/llcommon/lua_function.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index 08bc65e0c5..7c80f65ff9 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -529,6 +529,7 @@ std::pair LuaState::expr(const std::string& desc, const std::string& lluau::check_interrupts_counter(L); }; + LL_INFOS("Lua") << desc << " run" << LL_ENDL; if (! checkLua(desc, lluau::dostring(mState, desc, text))) { LL_WARNS("Lua") << desc << " error: " << mError << LL_ENDL; -- cgit v1.2.3 From d3b4b77a95baf66dcdb90c4312332bc2ac2c7663 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 12 Jun 2024 17:32:18 -0400 Subject: Add LL_DEBUGS("LLCoros") start/end messages. We have log messages when a coroutine terminates abnormally, but we don't report either when it starts or when it terminates normally. Address that. --- indra/llcommon/llcoros.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp index a6d7988256..c28baa5747 100644 --- a/indra/llcommon/llcoros.cpp +++ b/indra/llcommon/llcoros.cpp @@ -270,7 +270,7 @@ std::string LLCoros::launch(const std::string& prefix, const callable_t& callabl // std::allocator_arg is a flag to indicate that the following argument is // a StackAllocator. // protected_fixedsize_stack sets a guard page past the end of the new - // stack so that stack underflow will result in an access violation + // stack so that stack overflow will result in an access violation // instead of weird, subtle, possibly undiagnosed memory stomps. try @@ -355,10 +355,12 @@ void LLCoros::toplevel(std::string name, callable_t callable) // set it as current mCurrent.reset(&corodata); + LL_DEBUGS("LLCoros") << "entering " << name << LL_ENDL; // run the code the caller actually wants in the coroutine try { sehandle(callable); + LL_DEBUGS("LLCoros") << "done " << name << LL_ENDL; } catch (const Stop& exc) { @@ -370,7 +372,7 @@ void LLCoros::toplevel(std::string name, callable_t callable) // Any uncaught exception derived from LLContinueError will be caught // here and logged. This coroutine will terminate but the rest of the // viewer will carry on. - LOG_UNHANDLED_EXCEPTION(stringize("coroutine ", name)); + LOG_UNHANDLED_EXCEPTION("coroutine " + name); } catch (...) { -- cgit v1.2.3 From ab9cb6fcd96c1c29650d844b5fd76e2ebbf5f2df Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 14 Jun 2024 20:43:09 -0400 Subject: Introduce LL.atexit(), internal lua_emplace(), lua_toclass(). Publish new LL.atexit() function that accepts a Lua function (or C++ closure) and saves it (in Registry["atexit"] table) to call later. Make ~LuaState() walk the Registry["atexit"] table, if it exists, calling each function appended to that table. (Consider using that mechanism to clean up a LuaListener, if one was instantiated. Possibly also use for p.s. leap.run()? But that's run after every expr() call, instead of only at ~LuaState() time. Pragmatically, though, the distinction only matters for a LUA Debug Console LUA string with "clean lua_State" unchecked.) For use by future lua_function() entry points, lua_emplace(ctor args...) pushes a Lua userdata object containing a newly-constructed T instance -- actually a std::optional to avoid double destruction. lua_emplace() is specifically intended to be usable even for T with a nontrivial destructor: it gives the userdata a metatable with a __gc function that destroys the contained T instance when the userdata is garbage collected. But since garbage collection doesn't guarantee to clean up global variables with __gc methods, lua_emplace() also uses LL.atexit() to ensure that ~T() will run when the LuaState is destroyed. The companion to lua_emplace() is lua_toclass(), which returns a non-nullptr T* if the referenced index is in fact a userdata created by lua_emplace() for the same T, that has not yet been destroyed. This lets C++ code access a T previously embedded in Lua userdata. --- indra/llcommon/lua_function.cpp | 76 +++++++++++++++- indra/llcommon/lua_function.h | 186 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 261 insertions(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index 7c80f65ff9..edd49feed9 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -489,6 +489,37 @@ void LuaState::initLuaState() LuaState::~LuaState() { + // We're just about to destroy this lua_State mState. lua_close() doesn't + // implicitly garbage-collect everything, so (for instance) any lingering + // objects with __gc metadata methods aren't cleaned up. This is why we + // provide atexit(). + luaL_checkstack(mState, 3, nullptr); + // look up Registry["atexit"] + lua_getfield(mState, LUA_REGISTRYINDEX, "atexit"); + // stack contains Registry["atexit"] + if (lua_istable(mState, -1)) + { + lua_pushnil(mState); // first key + while (lua_next(mState, -2)) + { + // stack contains Registry["atexit"], key, value + // Call value(), no args, no return values. + // Use lua_pcall() because errors in any one atexit() function + // shouldn't cancel the rest of them. + if (lua_pcall(mState, 0, 0, 0) != LUA_OK) + { + auto error{ lua_tostdstring(mState, -1) }; + LL_WARNS("Lua") << "atexit() function error: " << error << LL_ENDL; + // pop error message + lua_pop(mState, 1); + } + // Normally we would pop value, keeping the key for the next + // iteration. But lua_pcall() has already popped the value. + } + } + // pop Registry["atexit"] (either table or nil) + lua_pop(mState, 1); + // Did somebody call obtainListener() on this LuaState? // That is, is there a LuaListener key in its registry? LuaListener::destruct(getListener()); @@ -509,7 +540,7 @@ bool LuaState::checkLua(const std::string& desc, int r) mError = lua_tostring(mState, -1); lua_pop(mState, 1); - LL_WARNS() << desc << ": " << mError << LL_ENDL; + LL_WARNS("Lua") << desc << ": " << mError << LL_ENDL; return false; } return true; @@ -685,6 +716,49 @@ LuaListener::ptr_t LuaState::obtainListener(lua_State* L) return listener; } +/***************************************************************************** +* atexit() +*****************************************************************************/ +lua_function(atexit, "register a Lua function to be called at script termination") +{ + luaL_checkstack(L, 4, nullptr); + // look up the global name "table" + lua_getglobal(L, "table"); + // stack contains function, "table" + // look up table.insert + lua_getfield(L, -1, "insert"); + // stack contains function, "table", "insert" + // look up the "atexit" table in the Registry + lua_getfield(L, LUA_REGISTRYINDEX, "atexit"); + // stack contains function, "table", "insert", Registry["atexit"] + if (! lua_istable(L, -1)) + { + llassert(lua_isnil(L, -1)); + // stack contains function, "table", "insert", nil + lua_pop(L, 1); + // make a new, empty table + lua_newtable(L); + // stack contains function, "table", "insert", {} + // duplicate the table reference on the stack + lua_pushvalue(L, -1); + // stack contains function, "table", "insert", {}, {} + // store the new empty "atexit" table to the Registry, leaving a + // reference on the stack + lua_setfield(L, LUA_REGISTRYINDEX, "atexit"); + } + // stack contains function, "table", "insert", Registry["atexit"] + // we were called with a Lua function to append to that Registry["atexit"] + // table -- push that function + lua_pushvalue(L, 1); // or -4 + // stack contains function, "table", "insert", Registry["atexit"], function + // call table.insert(atexit, function) + // don't use pcall(): if there's an error, let it propagate + lua_call(L, 2, 0); + // stack contains function, "table" -- pop everything + lua_settop(L, 0); + return 0; +} + /***************************************************************************** * LuaPopper class *****************************************************************************/ diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index e7013f92c6..5bbcbc441f 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -17,13 +17,19 @@ #include "luau/luaconf.h" #include "luau/lualib.h" #include "fsyspath.h" +#include "llerror.h" #include "stringize.h" #include // std::uncaught_exceptions() #include // std::shared_ptr +#include +#include #include // std::pair class LuaListener; +/***************************************************************************** +* lluau namespace utility functions +*****************************************************************************/ namespace lluau { // luau defines luaL_error() as void, but we want to use the Lua idiom of @@ -62,6 +68,9 @@ void lua_pushstdstring(lua_State* L, const std::string& str); LLSD lua_tollsd(lua_State* L, int index); void lua_pushllsd(lua_State* L, const LLSD& data); +/***************************************************************************** +* LuaState +*****************************************************************************/ /** * RAII class to manage the lifespan of a lua_State */ @@ -110,6 +119,9 @@ private: std::string mError; }; +/***************************************************************************** +* LuaPopper +*****************************************************************************/ /** * LuaPopper is an RAII struct whose role is to pop some number of entries * from the Lua stack if the calling function exits early. @@ -133,6 +145,9 @@ struct LuaPopper int mCount; }; +/***************************************************************************** +* lua_function (and helper class LuaFunction) +*****************************************************************************/ /** * LuaFunction is a base class containing a static registry of its static * subclass call() methods. call() is NOT virtual: instead, each subclass @@ -182,6 +197,171 @@ int name##_luasub::call(lua_State* L) // ... supply method body here, referencing 'L' ... // } +/***************************************************************************** +* lua_emplace(), lua_toclass() +*****************************************************************************/ +namespace { + +// this closure function retrieves its bound argument to pass to +// lua_emplace_gc() +template +int lua_emplace_call_gc(lua_State* L); +// this will be the function called by the new userdata's metatable's __gc() +template +int lua_emplace_gc(lua_State* L); +// name by which we'll store the new userdata's metatable in the Registry +template +std::string lua_emplace_metaname(const std::string& Tname = LLError::Log::classname()); + +} // anonymous namespace + +/** + * On the stack belonging to the passed lua_State, push a Lua userdata object + * with a newly-constructed C++ object std::optional(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. + * + * We wrap the userdata object as std::optional so we can explicitly + * destroy the contained T, and detect that we've done so. + * + * Usage: + * lua_emplace(L, T constructor args...); + */ +template +void lua_emplace(lua_State* L, ARGS&&... args) +{ + using optT = std::optional; + luaL_checkstack(L, 3, nullptr); + auto ptr = lua_newuserdata(L, sizeof(optT)); + // 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); + // Construct our T there using placement new + new (ptr) optT(std::in_place, std::forward(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. + auto Tname{ LLError::Log::classname() }; + auto metaname{ lua_emplace_metaname(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, gcname.c_str()); + // stack is userdata, metatable, lua_emplace_gc + 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. + // 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 + // gets around to it. + lua_getglobal(L, "LL"); + // stack contains userdata, LL + lua_getfield(L, -1, "atexit"); + // stack contains userdata, LL, LL.atexit + // duplicate userdata + lua_pushvalue(L, -3); + // stack contains userdata, LL, LL.atexit, userdata + // push a closure binding (lua_emplace_call_gc, userdata) + auto callgcname{ stringize("lua_emplace_call_gc<", Tname, ">") }; + lua_pushcclosure(L, lua_emplace_call_gc, callgcname.c_str(), 1); + // stack contains userdata, LL, 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, userdata)); +// retrieves bound userdata to pass to lua_emplace_gc() +template +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() + lua_pushvalue(L, lua_upvalueindex(1)); + return lua_emplace_gc(L); +} + +// set as metatable(userdata).__gc to be called by the garbage collector +template +int lua_emplace_gc(lua_State* L) +{ + using optT = std::optional; + // 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(ptr)->reset(); + // pop the userdata + lua_pop(L, 1); + return 0; +} + +template +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() -- that is, the userdata contains a non-empty + * std::optional -- return a pointer to the contained T instance. Otherwise + * (index is not a full userdata; userdata is not of type std::optional; + * std::optional is empty) return nullptr. + */ +template +T* lua_toclass(lua_State* L, int index) +{ + using optT = std::optional; + luaL_checkstack(L, 2, nullptr); + // get void* pointer to userdata (if that's what it is) + auto ptr{ lua_touserdata(L, index) }; + 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() + auto metaname{ lua_emplace_metaname() }; + 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(), 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(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(); +} + +/***************************************************************************** +* lua_what() +*****************************************************************************/ // Usage: std::cout << lua_what(L, stackindex) << ...; // Reports on the Lua value found at the passed stackindex. // If cast to std::string, returns the corresponding string value. @@ -202,6 +382,9 @@ private: int index; }; +/***************************************************************************** +* lua_stack() +*****************************************************************************/ // Usage: std::cout << lua_stack(L) << ...; // Reports on the contents of the Lua stack. // If cast to std::string, returns the corresponding string value. @@ -220,6 +403,9 @@ private: lua_State* L; }; +/***************************************************************************** +* LuaLog +*****************************************************************************/ // adapted from indra/test/debug.h // can't generalize Debug::operator() target because it's a variadic template class LuaLog -- cgit v1.2.3 From 5b6a5c757deaba3c2b361eb49f2e61630fe3eb47 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 17 Jun 2024 11:18:09 -0400 Subject: Store script's LuaListener in userdata in lua_State's Registry. Instead of deriving LuaListener from LLInstanceTracker with an int key, generating a unique int key and storing that key in the Registry, use new lua_emplace() to store the LuaListener directly in a Lua userdata object in the Lua Registry. Because lua_emplace() uses LL.atexit() to guarantee that ~LuaState will destroy the T object, we no longer need ~LuaState() to make a special call specifically to destroy the LuaListener, if any. So we no longer need LuaState::getListener() separate from obtainListener(). Since LuaListener is no longer an LLInstanceTracker subclass, make LuaState::obtainListener() return LuaListener& rather than LuaListener::ptr_t. --- indra/llcommon/lua_function.cpp | 61 +++++++++++++++-------------------------- indra/llcommon/lua_function.h | 16 ++++------- indra/llcommon/lualistener.cpp | 30 ++++++-------------- indra/llcommon/lualistener.h | 20 ++------------ 4 files changed, 38 insertions(+), 89 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index edd49feed9..cd1a0cd562 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -520,10 +520,6 @@ LuaState::~LuaState() // pop Registry["atexit"] (either table or nil) lua_pop(mState, 1); - // Did somebody call obtainListener() on this LuaState? - // That is, is there a LuaListener key in its registry? - LuaListener::destruct(getListener()); - lua_close(mState); if (mCallback) @@ -677,43 +673,30 @@ std::pair LuaState::expr(const std::string& desc, const std::string& return result; } -LuaListener::ptr_t LuaState::getListener(lua_State* L) +LuaListener& LuaState::obtainListener(lua_State* L) { - // have to use one more stack slot - luaL_checkstack(L, 1, nullptr); - LuaListener::ptr_t listener; - // Does this lua_State already have a LuaListener stored in the registry? - auto keytype{ lua_getfield(L, LUA_REGISTRYINDEX, "event.listener") }; - llassert(keytype == LUA_TNIL || keytype == LUA_TNUMBER); - if (keytype == LUA_TNUMBER) + luaL_checkstack(L, 2, nullptr); + lua_getfield(L, LUA_REGISTRYINDEX, "LuaListener"); + // compare lua_type() because lua_isuserdata() also accepts light userdata + if (lua_type(L, -1) != LUA_TUSERDATA) { - // We do already have a LuaListener. Retrieve it. - int isint; - listener = LuaListener::getInstance(lua_tointegerx(L, -1, &isint)); - // Nobody should have destroyed this LuaListener instance! - llassert(isint && listener); + llassert(lua_type(L, -1) == LUA_TNIL); + lua_pop(L, 1); + // push a userdata containing new LuaListener, binding L + lua_emplace(L, L); + // duplicate the top stack entry so we can store one copy + lua_pushvalue(L, -1); + lua_setfield(L, LUA_REGISTRYINDEX, "LuaListener"); } - // pop the int "event.listener" key + // At this point, one way or the other, the stack top should be (a Lua + // userdata containing) our LuaListener. + LuaListener* listener{ lua_toclass(L, -1) }; + // userdata objects created by lua_emplace() are bound on the atexit() + // queue, and are thus never garbage collected: they're destroyed only + // when ~LuaState() walks that queue. That's why we dare pop the userdata + // value off the stack while still depending on a pointer into its data. lua_pop(L, 1); - return listener; -} - -LuaListener::ptr_t LuaState::obtainListener(lua_State* L) -{ - auto listener{ getListener(L) }; - if (! listener) - { - // have to use one more stack slot - luaL_checkstack(L, 1, nullptr); - // instantiate a new LuaListener, binding the L state -- but use a - // no-op deleter: we do NOT want this ptr_t to manage the lifespan of - // this new LuaListener! - listener.reset(new LuaListener(L), [](LuaListener*){}); - // set its key in the field where we'll look for it later - lua_pushinteger(L, listener->getKey()); - lua_setfield(L, LUA_REGISTRYINDEX, "event.listener"); - } - return listener; + return *listener; } /***************************************************************************** @@ -938,10 +921,10 @@ lua_function( lua_settop(L, 0); auto& outpump{ LLEventPumps::instance().obtain("lua output") }; - auto listener{ LuaState::obtainListener(L) }; + auto& listener{ LuaState::obtainListener(L) }; LLEventStream replyPump("leaphelp", true); // ask the LuaListener's LeapListener and suspend calling coroutine until reply - auto reply{ llcoro::postAndSuspend(request, listener->getCommandName(), replyPump, "reply") }; + auto reply{ llcoro::postAndSuspend(request, listener.getCommandName(), replyPump, "reply") }; reply.erase("reqid"); if (auto error = reply["error"]; error.isString()) diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 5bbcbc441f..8b93053a46 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -102,16 +102,10 @@ public: operator lua_State*() const { return mState; } - // Return LuaListener for this LuaState if we already have one, else empty - // shared_ptr. - std::shared_ptr getListener() { return getListener(mState); } - // Find or create LuaListener for this LuaState, returning its ptr_t. - std::shared_ptr obtainListener() { return obtainListener(mState); } - // Return LuaListener for passed lua_State if we already have one, else - // empty shared_ptr. - static std::shared_ptr getListener(lua_State* L); - // Find or create LuaListener for passed lua_State, returning its ptr_t. - static std::shared_ptr obtainListener(lua_State* L); + // Find or create LuaListener for this LuaState. + LuaListener& obtainListener() { return obtainListener(mState); } + // Find or create LuaListener for passed lua_State. + static LuaListener& obtainListener(lua_State* L); private: script_finished_fn mCallback; @@ -350,7 +344,7 @@ T* lua_toclass(lua_State* L, int index) 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(ptr); + optT* tptr(static_cast(ptr)); // make sure our optT isn't empty if (! *tptr) return nullptr; diff --git a/indra/llcommon/lualistener.cpp b/indra/llcommon/lualistener.cpp index 5c4989e891..6cb87e8af2 100644 --- a/indra/llcommon/lualistener.cpp +++ b/indra/llcommon/lualistener.cpp @@ -15,8 +15,7 @@ #include "lualistener.h" // STL headers // std headers -#include // std::rand() -#include // std::memcpy() +#include // std::quoted() // external library headers #include "luau/lua.h" // other Linden headers @@ -28,11 +27,11 @@ const int MAX_QSIZE = 1000; std::ostream& operator<<(std::ostream& out, const LuaListener& self) { - return out << "LuaListener(" << self.getReplyName() << ", " << self.getCommandName() << ")"; + return out << "LuaListener(" << std::quoted(self.mCoroName) << ", " + << self.getReplyName() << ", " << self.getCommandName() << ")"; } LuaListener::LuaListener(lua_State* L): - super(getUniqueKey()), mCoroName(LLCoros::getName()), mListener(new LLLeapListener( "LuaListener", @@ -49,24 +48,13 @@ LuaListener::LuaListener(lua_State* L): // viewer shutdown, close the queue to wake up getNext(). mQueue.close(); })) -{} +{ + LL_DEBUGS("Lua") << "LuaListener(" << std::quoted(mCoroName) << ")" << LL_ENDL; +} LuaListener::~LuaListener() -{} - -int LuaListener::getUniqueKey() { - // Find a random key that does NOT already correspond to a LuaListener - // instance. Passing a duplicate key to LLInstanceTracker would do Bad - // Things. - int key; - do - { - key = std::rand(); - } while (LuaListener::getInstance(key)); - // This is theoretically racy, if we were instantiating new - // LuaListeners on multiple threads. Don't. - return key; + LL_DEBUGS("Lua") << "~LuaListener(" << std::quoted(mCoroName) << ")" << LL_ENDL; } std::string LuaListener::getReplyName() const @@ -86,7 +74,7 @@ bool LuaListener::queueEvent(const std::string& pump, const LLSD& data) // capacity or we'd block the post() call trying to propagate this event! if (auto size = mQueue.size(); size > MAX_QSIZE) { - LL_WARNS("Lua") << "LuaListener queue for " << getReplyName() + LL_WARNS("Lua") << "LuaListener queue for " << mCoroName << " exceeds " << MAX_QSIZE << ": " << size << " -- discarding event" << LL_ENDL; } @@ -107,7 +95,7 @@ LuaListener::PumpData LuaListener::getNext() catch (const LLThreadSafeQueueInterrupt&) { // mQueue has been closed. The only way that happens is when we detect - // viewer shutdown. Terminate the calling coroutine. + // viewer shutdown. Terminate the calling Lua coroutine. LLCoros::checkStop(); return {}; } diff --git a/indra/llcommon/lualistener.h b/indra/llcommon/lualistener.h index 85fb093cd6..68131dfa27 100644 --- a/indra/llcommon/lualistener.h +++ b/indra/llcommon/lualistener.h @@ -12,8 +12,7 @@ #if ! defined(LL_LUALISTENER_H) #define LL_LUALISTENER_H -#include "llevents.h" -#include "llinstancetracker.h" +#include "llevents.h" // LLTempBoundListener #include "llsd.h" #include "llthreadsafequeue.h" #include // std::ostream @@ -27,25 +26,11 @@ class LLLeapListener; /** * LuaListener is based on LLLeap. It serves an analogous function. * - * Each LuaListener instance has an int key, generated randomly to - * inconvenience malicious Lua scripts wanting to mess with others. The idea - * is that a given lua_State stores in its Registry: - * - "event.listener": the int key of the corresponding LuaListener, if any - * The original thought was that LuaListener would itself store the Lua - * function -- but surprisingly, there is no C/C++ type in the API that stores - * a Lua function. - * - * (We considered storing in "event.listener" the LuaListener pointer itself - * as a light userdata, but the problem would be if Lua code overwrote that. - * We want to prevent any Lua script from crashing the viewer, intentionally - * or otherwise. Safer to use a key lookup.) - * * Like LLLeap, each LuaListener instance also has an associated * LLLeapListener to respond to LLEventPump management commands. */ -class LuaListener: public LLInstanceTracker +class LuaListener { - using super = LLInstanceTracker; public: LuaListener(lua_State* L); @@ -68,7 +53,6 @@ public: friend std::ostream& operator<<(std::ostream& out, const LuaListener& self); private: - static int getUniqueKey(); bool queueEvent(const std::string& pump, const LLSD& data); LLThreadSafeQueue mQueue; -- cgit v1.2.3 From aff78224a026bbf17e6ac4818228c0e1814c4226 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 18 Jun 2024 09:11:29 -0400 Subject: Make lluau::source_path() report top-level script path. source_path() previously reported the path of the module containing the current (lowest-level) Lua function. The effect was that the Floater.lua module would always try to look up the XUI file relative to scripts/lua/require. It makes more intuitive sense to make source_path() return the path containing the top-level script, so that a script engaging the Floater.lua module looks for the XUI file relative to the script. --- indra/llcommon/lua_function.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index cd1a0cd562..3e3934c9c1 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -75,8 +75,11 @@ fsyspath lluau::source_path(lua_State* L) { //Luau lua_Debug and lua_getinfo() are different compared to default Lua: //see https://github.com/luau-lang/luau/blob/80928acb92d1e4b6db16bada6d21b1fb6fa66265/VM/include/lua.h + // In particular: + // passing level=1 gets you info about the deepest function call + // passing level=lua_stackdepth() gets you info about the topmost script lua_Debug ar; - lua_getinfo(L, 1, "s", &ar); + lua_getinfo(L, lua_stackdepth(L), "s", &ar); return ar.source; } -- cgit v1.2.3 From 6bbd39f54a71a1d223c6e74b47c6b0cf9f72eb7e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 18 Jun 2024 09:13:44 -0400 Subject: lua_emplace() should permit GC despite LL.atexit() safety net. lua_emplace() was passing LL.atexit() a closure binding the new userdata with a cleanup function. The trouble with that was that a strong reference to the new userdata would prevent it ever being garbage collected, even if that was the only remaining reference. Instead, create a new weak table referencing the userdata, and bind that into the cleanup function's closure. Then if the only remaining reference to the userdata is from the weak table, the userdata can be collected. Make lua_emplace_call_gc() check the bound weak table in case the userdata has in fact been collected. Also, in lua_toclass(), use luaL_checkudata() to synopsize comparing the putative userdata's metatable against the one synthesized by lua_emplace(). This saves several explicit steps. --- indra/llcommon/lua_function.h | 101 +++++++++++++++++++++++++++++++----------- 1 file changed, 74 insertions(+), 27 deletions(-) (limited to 'indra/llcommon') 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(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(object)) to make ~LuaState() call ~T(). * * We wrap the userdata object as std::optional so we can explicitly * destroy the contained T, and detect that we've done so. * * Usage: * lua_emplace(L, T constructor args...); + * // L's Lua stack top is now a userdata containing T */ template void lua_emplace(lua_State* L, ARGS&&... args) { using optT = std::optional; - 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. + // 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, 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, weak_table) auto callgcname{ stringize("lua_emplace_call_gc<", Tname, ">") }; lua_pushcclosure(L, lua_emplace_call_gc, 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, userdata)); +// passed to LL.atexit(closure(lua_emplace_call_gc, weak_table{userdata})); // retrieves bound userdata to pass to lua_emplace_gc() template 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() + 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(). Its one and only + // entry should be the lua_emplace() 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() return lua_emplace_gc(L); } @@ -325,23 +383,12 @@ template T* lua_toclass(lua_State* L, int index) { using optT = std::optional; - luaL_checkstack(L, 2, nullptr); + // recreate the name lua_emplace() uses for its metatable + auto metaname{ lua_emplace_metaname() }; // 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() - auto metaname{ lua_emplace_metaname() }; - 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(), 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(ptr)); -- cgit v1.2.3 From 5cc4b42a3001be120e22f745460dbb76d8d8d018 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 18 Jun 2024 13:11:07 -0400 Subject: Make ~LuaState() walk Registry.atexit table backwards so cleanup happens in reverse order, as is conventional. Streamline LL.atexit() function: luaL_newmetatable() performs all the find-or-create named Registry table logic. --- indra/llcommon/lua_function.cpp | 67 ++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 37 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index 3e3934c9c1..7894c7b96a 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -496,17 +496,24 @@ LuaState::~LuaState() // implicitly garbage-collect everything, so (for instance) any lingering // objects with __gc metadata methods aren't cleaned up. This is why we // provide atexit(). - luaL_checkstack(mState, 3, nullptr); - // look up Registry["atexit"] + luaL_checkstack(mState, 2, nullptr); + // look up Registry.atexit lua_getfield(mState, LUA_REGISTRYINDEX, "atexit"); - // stack contains Registry["atexit"] + // stack contains Registry.atexit if (lua_istable(mState, -1)) { - lua_pushnil(mState); // first key - while (lua_next(mState, -2)) + // We happen to know that Registry.atexit is built by appending array + // entries using table.insert(). That's important because it means + // there are no holes, and therefore lua_objlen() should be correct. + // That's important because we walk the atexit table backwards, to + // destroy last the things we created (passed to LL.atexit()) first. + for (int i(lua_objlen(mState, -1)); i >= 1; --i) { - // stack contains Registry["atexit"], key, value - // Call value(), no args, no return values. + lua_pushinteger(mState, i); + // stack contains Registry.atexit, i + lua_gettable(mState, -2); + // stack contains Registry.atexit, atexit[i] + // Call atexit[i](), no args, no return values. // Use lua_pcall() because errors in any one atexit() function // shouldn't cancel the rest of them. if (lua_pcall(mState, 0, 0, 0) != LUA_OK) @@ -516,11 +523,10 @@ LuaState::~LuaState() // pop error message lua_pop(mState, 1); } - // Normally we would pop value, keeping the key for the next - // iteration. But lua_pcall() has already popped the value. + // lua_pcall() has already popped atexit[i]: stack contains atexit } } - // pop Registry["atexit"] (either table or nil) + // pop Registry.atexit (either table or nil) lua_pop(mState, 1); lua_close(mState); @@ -710,37 +716,24 @@ lua_function(atexit, "register a Lua function to be called at script termination luaL_checkstack(L, 4, nullptr); // look up the global name "table" lua_getglobal(L, "table"); - // stack contains function, "table" + // stack contains function, table // look up table.insert lua_getfield(L, -1, "insert"); - // stack contains function, "table", "insert" - // look up the "atexit" table in the Registry - lua_getfield(L, LUA_REGISTRYINDEX, "atexit"); - // stack contains function, "table", "insert", Registry["atexit"] - if (! lua_istable(L, -1)) - { - llassert(lua_isnil(L, -1)); - // stack contains function, "table", "insert", nil - lua_pop(L, 1); - // make a new, empty table - lua_newtable(L); - // stack contains function, "table", "insert", {} - // duplicate the table reference on the stack - lua_pushvalue(L, -1); - // stack contains function, "table", "insert", {}, {} - // store the new empty "atexit" table to the Registry, leaving a - // reference on the stack - lua_setfield(L, LUA_REGISTRYINDEX, "atexit"); - } - // stack contains function, "table", "insert", Registry["atexit"] - // we were called with a Lua function to append to that Registry["atexit"] - // table -- push that function - lua_pushvalue(L, 1); // or -4 - // stack contains function, "table", "insert", Registry["atexit"], function - // call table.insert(atexit, function) + // stack contains function, table, table.insert + // ditch table + lua_replace(L, -2); + // stack contains function, table.insert + // find or create the "atexit" table in the Registry + luaL_newmetatable(L, "atexit"); + // stack contains function, table.insert, Registry.atexit + // we were called with a Lua function to append to that Registry.atexit + // table -- push function + lua_pushvalue(L, 1); // or -3 + // stack contains function, table.insert, Registry.atexit, function + // call table.insert(Registry.atexit, function) // don't use pcall(): if there's an error, let it propagate lua_call(L, 2, 0); - // stack contains function, "table" -- pop everything + // stack contains function -- pop everything lua_settop(L, 0); return 0; } -- cgit v1.2.3 From bac8ced279bb4b3ec49e497b75021b86a0b4d857 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 18 Jun 2024 18:02:47 -0400 Subject: Use LL_DEBUGS("Lua") for LuaLog. We might decide to leave some of them in place. --- indra/llcommon/lua_function.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 7a3d9e7dd7..9cdd5665dc 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -473,7 +473,7 @@ public: template void operator()(ARGS&&... args) { - LL_INFOS("Lua") << mBlock << ' '; + LL_DEBUGS("Lua") << mBlock << ' '; stream_to(LL_CONT, std::forward(args)...); LL_ENDL; } -- cgit v1.2.3 From b7a70eb7ee3451dae596ddd691666eb83ac9350c Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 18 Jun 2024 18:09:16 -0400 Subject: Initialize lua_Debug lluau::source_path() passes to lua_getinfo(). On Mac it doesn't seem to matter, but on Windows, leaving it uninitialized can produce garbage results and even crash the coroutine. This seems strange, since we've been assuming lua_getinfo() treats its lua_Debug* as output-only. --- indra/llcommon/lua_function.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index 3e3934c9c1..283dfa3c94 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -78,7 +78,7 @@ fsyspath lluau::source_path(lua_State* L) // In particular: // passing level=1 gets you info about the deepest function call // passing level=lua_stackdepth() gets you info about the topmost script - lua_Debug ar; + lua_Debug ar{}; lua_getinfo(L, lua_stackdepth(L), "s", &ar); return ar.source; } -- cgit v1.2.3 From 2739154eaa04877b0d19d1dfc56fc1679aa6bb98 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 19 Jun 2024 08:56:51 -0400 Subject: Try harder to keep Luau's lua_getinfo() from crashing. --- indra/llcommon/lua_function.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index 7e99480201..e76bd55dbb 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -78,8 +78,13 @@ fsyspath lluau::source_path(lua_State* L) // In particular: // passing level=1 gets you info about the deepest function call // passing level=lua_stackdepth() gets you info about the topmost script + // Empirically, lua_getinfo(level > 1) behaves strangely (including + // crashing the program) unless you iterate from 1 to desired level. lua_Debug ar{}; - lua_getinfo(L, lua_stackdepth(L), "s", &ar); + for (int i(0), depth(lua_stackdepth(L)); i <= depth; ++i) + { + lua_getinfo(L, i, "s", &ar); + } return ar.source; } -- cgit v1.2.3 From ef596c44fce4a1059b8d79aff1c73db5ce628169 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 19 Jun 2024 09:43:27 -0400 Subject: Improve LL.help() function. The help string for each lua_function() must restate the function name and its arguments. The help string is all that's shown; unless it restates the function name, LL.help() output lists terse explanations for functions whose names are not shown. Make help() prepend "LL." to help output, because these functions must be accessed via the "builtin" LL table instead of directly populating the global Lua namespace. Similarly, before string name lookup, remove "LL." prefix if specified. --- indra/llcommon/lua_function.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index e76bd55dbb..255385b8c4 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -28,6 +28,7 @@ #include "lleventcoro.h" #include "llsd.h" #include "llsdutil.h" +#include "llstring.h" #include "lualistener.h" #include "stringize.h" @@ -716,7 +717,8 @@ LuaListener& LuaState::obtainListener(lua_State* L) /***************************************************************************** * atexit() *****************************************************************************/ -lua_function(atexit, "register a Lua function to be called at script termination") +lua_function(atexit, "atexit(function): " + "register Lua function to be called at script termination") { luaL_checkstack(L, 4, nullptr); // look up the global name "table" @@ -804,7 +806,7 @@ std::pair LuaFunction::getState() /***************************************************************************** * source_path() *****************************************************************************/ -lua_function(source_path, "return the source path of the running Lua script") +lua_function(source_path, "source_path(): return the source path of the running Lua script") { luaL_checkstack(L, 1, nullptr); lua_pushstdstring(L, lluau::source_path(L).u8string()); @@ -814,7 +816,7 @@ lua_function(source_path, "return the source path of the running Lua script") /***************************************************************************** * source_dir() *****************************************************************************/ -lua_function(source_dir, "return the source directory of the running Lua script") +lua_function(source_dir, "source_dir(): return the source directory of the running Lua script") { luaL_checkstack(L, 1, nullptr); lua_pushstdstring(L, lluau::source_path(L).parent_path().u8string()); @@ -824,7 +826,7 @@ lua_function(source_dir, "return the source directory of the running Lua script" /***************************************************************************** * abspath() *****************************************************************************/ -lua_function(abspath, +lua_function(abspath, "abspath(path): " "for given filesystem path relative to running script, return absolute path") { auto path{ lua_tostdstring(L, 1) }; @@ -836,7 +838,7 @@ lua_function(abspath, /***************************************************************************** * check_stop() *****************************************************************************/ -lua_function(check_stop, "ensure that a Lua script responds to viewer shutdown") +lua_function(check_stop, "check_stop(): ensure that a Lua script responds to viewer shutdown") { LLCoros::checkStop(); return 0; @@ -857,7 +859,7 @@ lua_function(help, for (const auto& [name, pair] : registry) { const auto& [fptr, helptext] = pair; - luapump.post(helptext); + luapump.post("LL." + helptext); } } else @@ -869,6 +871,7 @@ lua_function(help, if (lua_type(L, idx) == LUA_TSTRING) { arg = lua_tostdstring(L, idx); + LLStringUtil::removePrefix(arg, "LL."); } else if (lua_type(L, idx) == LUA_TFUNCTION) { @@ -887,7 +890,7 @@ lua_function(help, if (auto found = registry.find(arg); found != registry.end()) { - luapump.post(found->second.second); + luapump.post("LL." + found->second.second); } else { -- cgit v1.2.3