From b4583fac09657cb64ed02e82e12ce69c4ace225d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 12 Feb 2024 17:10:42 -0500 Subject: WIP: Changes towards supporting Lua console help text. --- indra/llcommon/lua_function.cpp | 65 +++++++++++++++++++++++++++++++++++++++-- indra/llcommon/lua_function.h | 4 ++- 2 files changed, 66 insertions(+), 3 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index 07e0c1fac2..455e853e8f 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -25,6 +25,9 @@ #include "llsdutil.h" #include "lualistener.h" +/***************************************************************************** +* luau namespace +*****************************************************************************/ namespace { // can't specify free function free() as a unique_ptr deleter @@ -53,6 +56,9 @@ int lluau::dostring(lua_State* L, const std::string& desc, const std::string& te return lua_pcall(L, 0, LUA_MULTRET, 0); } +/***************************************************************************** +* Lua <=> C++ conversions +*****************************************************************************/ std::string lua_tostdstring(lua_State* L, int index) { size_t len; @@ -414,6 +420,9 @@ void lua_pushllsd(lua_State* L, const LLSD& data) } } +/***************************************************************************** +* LuaState class +*****************************************************************************/ LuaState::LuaState(script_finished_fn cb): mCallback(cb), mState(luaL_newstate()) @@ -499,7 +508,9 @@ std::pair LuaState::expr(const std::string& desc, const std::string& return result; } - +/***************************************************************************** +* LuaPopper class +*****************************************************************************/ LuaPopper::~LuaPopper() { if (mCount) @@ -508,6 +519,9 @@ LuaPopper::~LuaPopper() } } +/***************************************************************************** +* LuaFunction class +*****************************************************************************/ LuaFunction::LuaFunction(const std::string_view& name, lua_CFunction function, const std::string_view& helptext) { @@ -539,7 +553,9 @@ LuaFunction::Registry& LuaFunction::getRegistry() return registry; } - +/***************************************************************************** +* lua_what +*****************************************************************************/ std::ostream& operator<<(std::ostream& out, const lua_what& self) { switch (lua_type(self.L, self.index)) @@ -595,6 +611,9 @@ std::ostream& operator<<(std::ostream& out, const lua_what& self) return out; } +/***************************************************************************** +* lua_stack +*****************************************************************************/ std::ostream& operator<<(std::ostream& out, const lua_stack& self) { const char* sep = "stack: ["; @@ -607,7 +626,49 @@ std::ostream& operator<<(std::ostream& out, const lua_stack& self) return out; } +/***************************************************************************** +* DebugExit +*****************************************************************************/ DebugExit::~DebugExit() { LL_DEBUGS("Lua") << "exit " << mName << LL_ENDL; } + +/***************************************************************************** +* help() +*****************************************************************************/ +lua_function(help, + "help(): list viewer's Lua functions\n" + "help(function): show help string for specific function") +{ + auto& luapump{ LLEventPumps::instance().obtain("lua output") }; + const auto& registered{ LuaFunction::getRegistered() }; + if (! lua_gettop(L)) + { + // no arguments passed: list all lua_functions + for (const auto& [name, pair] : registered) + { + const auto& [fptr, helptext] = pair; + luapump.post(helptext); + } + } + else + { + // arguments passed: list each of the specified lua_functions + for (int idx = 1, top = lua_gettop(L); idx <= top; ++idx) + { + auto arg{ lua_tostdstring(L, idx) }; + if (auto found = registered.find(arg); found != registered.end()) + { + luapump.post(found->second.second); + } + else + { + luapump.post(arg + ": NOT FOUND"); + } + } + // pop all arguments + lua_settop(L, 0); + } + return 0; // void return +} diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index c23bf533ba..3129b5eaca 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -131,8 +131,10 @@ public: static lua_CFunction get(const std::string& key); -private: using Registry = std::map>; + static const Registry& getRegistered() { return getRegistry(); } + +private: static Registry& getRegistry(); }; -- cgit v1.2.3 From cd7e0531c0928bc61f8c4a56ee4747f214100d3c Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 13 Feb 2024 10:40:57 -0500 Subject: Change ll_convert(string) to just ll_convert(string). As a function parameter, an assignment expression or a `return` expression, `ll_convert()` can infer its target type. When it's important to specify the TOTYPE explicitly, rename the old `ll_convert()` function template to `ll_convert_to()`. Fix existing usage. --- indra/llcommon/llstring.h | 39 +++++++++++++++++++++++++++++++++------ indra/llcommon/stringize.h | 6 +++--- 2 files changed, 36 insertions(+), 9 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index b43093fbfc..dbd60bc9c7 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -516,9 +516,36 @@ struct ll_convert_impl TO operator()(const FROM& in) const; }; -// Use a function template to get the nice ll_convert(from_value) API. +/** + * somefunction(ll_convert(data)) + * target = ll_convert(data) + * totype otherfunc(const fromtype& data) + * { + * // ... + * return ll_convert(data); + * } + * all infer both the FROM type and the TO type. + */ +template +class ll_convert +{ +private: + const FROM& mRef; + +public: + ll_convert(const FROM& ref): mRef(ref) {} + + template + inline operator TO() const + { + return ll_convert_impl()(mRef); + } +}; + +// When the TO type must be explicit, use a function template to get +// ll_convert_to(from_value) API. template -TO ll_convert(const FROM& in) +TO ll_convert_to(const FROM& in) { return ll_convert_impl()(in); } @@ -574,8 +601,8 @@ inline size_t ll_convert_length (const char* zstr) { return std::strl // and longname(const string&, len) so calls written pre-ll_convert() will // work. Most of these overloads will be unified once we turn on C++17 and can // use std::string_view. -// It also uses aliasmacro to ensure that both ll_convert(const char*) -// and ll_convert(const string&) will work. +// It also uses aliasmacro to ensure that both ll_convert(const char*) +// and ll_convert(const string&) will work. #define ll_convert_forms(aliasmacro, OUTSTR, INSTR, longname) \ LL_COMMON_API OUTSTR longname(const INSTR::value_type* in, size_t len); \ inline auto longname(const INSTR& in, size_t len) \ @@ -807,7 +834,7 @@ LL_COMMON_API std::string ll_convert_string_to_utf8_string(const std::string& in template STRING windows_message(unsigned long error) { - return ll_convert(windows_message(error)); + return ll_convert(windows_message(error)); } /// There's only one real implementation @@ -1780,7 +1807,7 @@ auto LLStringUtilBase::getoptenv(const std::string& key) -> boost::optional(*found) }; + return { ll_convert_to(*found) }; } else { diff --git a/indra/llcommon/stringize.h b/indra/llcommon/stringize.h index c0b13135f9..63d44a7272 100644 --- a/indra/llcommon/stringize.h +++ b/indra/llcommon/stringize.h @@ -88,13 +88,13 @@ struct gstringize_impl }; // partially specialize for a single STRING argument - -// note that ll_convert(T) already handles the trivial case +// note that ll_convert_to(T) already handles the trivial case template struct gstringize_impl> { auto operator()(const std::basic_string& arg) { - return ll_convert>(arg); + return ll_convert_to>(arg); } }; @@ -105,7 +105,7 @@ struct gstringize_impl { auto operator()(const INCHAR* arg) { - return ll_convert>(arg); + return ll_convert_to>(arg); } }; -- cgit v1.2.3 From 3044a6e62e628fdb3c4b8832fd23e566216d7bb9 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 13 Feb 2024 12:24:13 -0500 Subject: Add help() function to Lua "builtins." help() with no argument lists all our viewer builtins. help(function, function, ...) shows help text for each named function. Each argument can be either a string or the function in question (e.g. help(help)). To support Lua-related text containing line breaks, make LLTextEditor:: pasteTextWithLinebreaks() a public template method. Change the existing implementation, which specifically accepts (const LLWString&), into its LLWString specialization. The generic template passes llconvert(arg) to that specialization, the one real implementation. Make LLFloaterLUADebug methods call pasteTextWithLinebreaks() instead of insertText(), which ignores newline characters. To allow help() to accept an actual function as well as a string name, add a lookup-by-function-pointer map to LuaFunction. (A Lua function does not store a name.) Make the constructor store an entry in the new lookup map as well as in the original registry map. Change LuaFunction::getRegistry() and getRegistered() to getState() and getRState(), respectively. Each returns a std::pair, but the first binds non-const references while the second binds const references. --- indra/llcommon/lua_function.cpp | 113 ++++++++++++++++++++++++---------------- indra/llcommon/lua_function.h | 6 ++- 2 files changed, 72 insertions(+), 47 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index 455e853e8f..467ca04449 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -525,12 +525,15 @@ LuaPopper::~LuaPopper() LuaFunction::LuaFunction(const std::string_view& name, lua_CFunction function, const std::string_view& helptext) { - getRegistry().emplace(name, Registry::mapped_type{ function, helptext }); + const auto& [registry, lookup] = getState(); + registry.emplace(name, Registry::mapped_type{ function, helptext }); + lookup.emplace(function, name); } void LuaFunction::init(lua_State* L) { - for (const auto& [name, pair]: getRegistry()) + const auto& [registry, lookup] = getRState(); + for (const auto& [name, pair]: registry) { const auto& [funcptr, helptext] = pair; lua_register(L, name.c_str(), funcptr); @@ -541,16 +544,75 @@ lua_CFunction LuaFunction::get(const std::string& key) { // use find() instead of subscripting to avoid creating an entry for // unknown key - const auto& registry{ getRegistry() }; + const auto& [registry, lookup] = getState(); auto found{ registry.find(key) }; return (found == registry.end())? nullptr : found->second.first; } -LuaFunction::Registry& LuaFunction::getRegistry() +std::pair LuaFunction::getState() { - // use a function-local static to ensure it's initialized + // use function-local statics to ensure they're initialized static Registry registry; - return registry; + static Lookup lookup; + return { registry, lookup }; +} + +/***************************************************************************** +* help() +*****************************************************************************/ +lua_function(help, + "help(): list viewer's Lua functions\n" + "help(function): show help string for specific function") +{ + auto& luapump{ LLEventPumps::instance().obtain("lua output") }; + const auto& [registry, lookup]{ LuaFunction::getRState() }; + if (! lua_gettop(L)) + { + // no arguments passed: list all lua_functions + for (const auto& [name, pair] : registry) + { + const auto& [fptr, helptext] = pair; + luapump.post(helptext); + } + } + else + { + // arguments passed: list each of the specified lua_functions + for (int idx = 1, top = lua_gettop(L); idx <= top; ++idx) + { + std::string arg{ stringize("") }; + if (lua_type(L, idx) == LUA_TSTRING) + { + arg = lua_tostdstring(L, idx); + } + else if (lua_type(L, idx) == LUA_TFUNCTION) + { + // Caller passed the actual function instead of its string + // name. A Lua function is an anonymous callable object; it + // has a name only by assigment. You can't ask Lua for a + // function's name, which is why our constructor maintains a + // reverse Lookup map. + auto function{ lua_tocfunction(L, idx) }; + if (auto found = lookup.find(function); found != lookup.end()) + { + // okay, pass found name to lookup below + arg = found->second; + } + } + + if (auto found = registry.find(arg); found != registry.end()) + { + luapump.post(found->second.second); + } + else + { + luapump.post(arg + ": NOT FOUND"); + } + } + // pop all arguments + lua_settop(L, 0); + } + return 0; // void return } /***************************************************************************** @@ -633,42 +695,3 @@ DebugExit::~DebugExit() { LL_DEBUGS("Lua") << "exit " << mName << LL_ENDL; } - -/***************************************************************************** -* help() -*****************************************************************************/ -lua_function(help, - "help(): list viewer's Lua functions\n" - "help(function): show help string for specific function") -{ - auto& luapump{ LLEventPumps::instance().obtain("lua output") }; - const auto& registered{ LuaFunction::getRegistered() }; - if (! lua_gettop(L)) - { - // no arguments passed: list all lua_functions - for (const auto& [name, pair] : registered) - { - const auto& [fptr, helptext] = pair; - luapump.post(helptext); - } - } - else - { - // arguments passed: list each of the specified lua_functions - for (int idx = 1, top = lua_gettop(L); idx <= top; ++idx) - { - auto arg{ lua_tostdstring(L, idx) }; - if (auto found = registered.find(arg); found != registered.end()) - { - luapump.post(found->second.second); - } - else - { - luapump.post(arg + ": NOT FOUND"); - } - } - // pop all arguments - lua_settop(L, 0); - } - return 0; // void return -} diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 3129b5eaca..b3a0bb0d7e 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -131,11 +131,13 @@ public: static lua_CFunction get(const std::string& key); +protected: using Registry = std::map>; - static const Registry& getRegistered() { return getRegistry(); } + using Lookup = std::map; + static std::pair getRState() { return getState(); } private: - static Registry& getRegistry(); + static std::pair getState(); }; /** -- cgit v1.2.3 From d583fb8badd8060c0f74bcb6e99bb6e7d08a67d0 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 13 Feb 2024 17:34:00 -0500 Subject: Add leaphelp() Lua builtin function for help on LEAP operations. leaphelp() (no argument) shows a list of all LEAP APIs. leaphelp(API) shows further help for a specific API. Both forms query LuaListener's LeapListener and report its responses. In future we might reimplement leaphelp() as a Lua function. Add LuaState::getListener() method, which checks whether there's a LuaListener associated with this LuaState and returns a pointer if so. Add LuaState::obtainListener() method, which finds or creates a LuaListener for this LuaState and returns its pointer. Both the above use logic migrated from the Lua listen_events() entry point, which now calls obtainListener() instead. --- indra/llcommon/lua_function.cpp | 130 +++++++++++++++++++++++++++++++++++----- indra/llcommon/lua_function.h | 14 +++++ 2 files changed, 128 insertions(+), 16 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index 467ca04449..13210fe5a6 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -21,6 +21,7 @@ // external library headers // other Linden headers #include "hexdump.h" +#include "lleventcoro.h" #include "llsd.h" #include "llsdutil.h" #include "lualistener.h" @@ -435,25 +436,15 @@ LuaState::LuaState(script_finished_fn cb): LuaState::~LuaState() { - // Did somebody call listen_events() on this LuaState? + // Did somebody call obtainListener() on this LuaState? // That is, is there a LuaListener key in its registry? - auto keytype{ lua_getfield(mState, LUA_REGISTRYINDEX, "event.listener") }; - if (keytype == LUA_TNUMBER) + auto listener{ getListener() }; + if (listener) { - // We do have a LuaListener. Retrieve it. - int isint; - auto listener{ LuaListener::getInstance(lua_tointegerx(mState, -1, &isint)) }; - // pop the int "event.listener" key - lua_pop(mState, 1); // if we got a LuaListener instance, destroy it - // (if (! isint), lua_tointegerx() returned 0, but key 0 might - // validly designate someone ELSE's LuaListener) - if (isint && listener) - { - auto lptr{ listener.get() }; - listener.reset(); - delete lptr; - } + auto lptr{ listener.get() }; + listener.reset(); + delete lptr; } lua_close(mState); @@ -508,6 +499,45 @@ std::pair LuaState::expr(const std::string& desc, const std::string& return result; } +LuaListener::ptr_t LuaState::getListener(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) + { + // 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); + } + // pop the int "event.listener" key + 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; +} + /***************************************************************************** * LuaPopper class *****************************************************************************/ @@ -615,6 +645,74 @@ lua_function(help, return 0; // void return } +/***************************************************************************** +* leaphelp() +*****************************************************************************/ +lua_function( + leaphelp, + "leaphelp(): list viewer's LEAP APIs\n" + "leaphelp(api): show help for specific api string name") +{ + LLSD request; + int top{ lua_gettop(L) }; + if (top) + { + request = llsd::map("op", "getAPI", "api", lua_tostdstring(L, 1)); + } + else + { + request = llsd::map("op", "getAPIs"); + } + // pop all args + lua_settop(L, 0); + + auto& outpump{ LLEventPumps::instance().obtain("lua output") }; + 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") }; + reply.erase("reqid"); + + if (auto error = reply["error"]; error.isString()) + { + outpump.post(error.asString()); + return 0; + } + + if (top) + { + // caller wants a specific API + outpump.post(stringize(reply["name"].asString(), ":\n", reply["desc"].asString())); + for (const auto& opmap : llsd::inArray(reply["ops"])) + { + std::ostringstream reqstr; + auto req{ opmap["required"] }; + if (req.isArray()) + { + const char* sep = " (requires "; + for (const auto& [reqkey, reqval] : llsd::inMap(req)) + { + reqstr << sep << reqkey; + sep = ", "; + } + reqstr << ")"; + } + outpump.post(stringize("---- ", reply["key"].asString(), " == '", + opmap["name"].asString(), "'", reqstr.str(), ":\n", + opmap["desc"].asString())); + } + } + else + { + // caller wants a list of APIs + for (const auto& [name, data] : llsd::inMap(reply)) + { + outpump.post(stringize("==== ", name, ":\n", data["desc"].asString())); + } + } + return 0; // void return +} + /***************************************************************************** * lua_what *****************************************************************************/ diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index b3a0bb0d7e..7973a769be 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -17,8 +17,11 @@ #include "luau/luaconf.h" #include "luau/lualib.h" #include "stringize.h" +#include // std::shared_ptr #include // std::pair +class LuaListener; + #define lua_register(L, n, f) (lua_pushcfunction(L, (f), n), lua_setglobal(L, (n))) #define lua_rawlen lua_objlen @@ -83,6 +86,17 @@ 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); + private: script_finished_fn mCallback; lua_State* mState; -- cgit v1.2.3 From 484b91dd65c8029ad5a52a6b4684d9046df709ac Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 22 Feb 2024 19:51:35 -0500 Subject: Add diagnostic logging to LLEventPumps::post(). If post() can't find the requested pump, say so. --- indra/llcommon/llevents.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp index deb0776887..1eeb18c40d 100644 --- a/indra/llcommon/llevents.cpp +++ b/indra/llcommon/llevents.cpp @@ -189,8 +189,13 @@ bool LLEventPumps::post(const std::string&name, const LLSD&message) PumpMap::iterator found = mPumpMap.find(name); if (found == mPumpMap.end()) + { + LL_DEBUGS("LLEventPumps") << "LLEventPump(" << std::quoted(name) << ") not found" + << LL_ENDL; return false; + } +// LL_DEBUGS("LLEventPumps") << "posting to " << name << ": " << message << LL_ENDL; return (*found).second->post(message); } @@ -405,7 +410,7 @@ LLBoundListener LLEventPump::listen_impl(const std::string& name, const LLEventL { if (!mSignal) { - LL_WARNS() << "Can't connect listener" << LL_ENDL; + LL_WARNS("LLEventPump") << "Can't connect listener" << LL_ENDL; // connect will fail, return dummy return LLBoundListener(); } -- cgit v1.2.3 From 85ef003ed7836cb351ee62ed44a4837a305e9dbd Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 22 Feb 2024 20:42:08 -0500 Subject: Lua listen_events(), await_event() => get_event_{pumps,next}(). Don't set up a Lua callback to receive incoming events, a la listen_events(). Don't listen on an arbitrary event pump, a la await_event(). Instead, the new get_event_pumps() entry point simply delivers the reply pump and command pump names (as listen_events() did) without storing a Lua callback. Make LuaListener capture incoming events on the reply pump in a queue. This avoids the problem of multiple events arriving too quickly for the Lua script to retrieve. If the queue gets too big, discard the excess instead of blocking the caller of post(). Then the new get_event_next() entry point retrieves the next (pump, data) pair from the queue, blocking the Lua script until a suitable event arrives. This is closer to the use of stdin for a LEAP plugin. It also addresses the question: what should the Lua script's C++ coroutine do while waiting for an incoming reply pump event? Recast llluamanager_test.cpp for this new, more straightforward API. Move LLLeap's and LuaListener's reply LLEventPump into LLLeapListener, which they both use. This simplifies LLLeapListener's API, which was a little convoluted: the caller supplied a connect callback to allow LLLeapListener to connect some listener to the caller's reply pump. Now, instead, the caller simply passes a bool(pumpname, data) callback to receive events incoming on LLLeapListener's own reply pump. Fix a latent bug in LLLeapListener: if a plugin called listen() more than once with the same listener name, the new connection would not have been saved. While at it, replace some older Boost features in LLLeapListener and LLLeap. --- indra/llcommon/llleap.cpp | 61 ++++++++++++------------------- indra/llcommon/llleaplistener.cpp | 38 ++++++++++++++++---- indra/llcommon/llleaplistener.h | 27 +++++++------- indra/llcommon/lualistener.cpp | 76 +++++++++++++++++---------------------- indra/llcommon/lualistener.h | 27 ++++++++------ 5 files changed, 116 insertions(+), 113 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llleap.cpp b/indra/llcommon/llleap.cpp index 8f88e728ce..f7f2981638 100644 --- a/indra/llcommon/llleap.cpp +++ b/indra/llcommon/llleap.cpp @@ -14,13 +14,11 @@ // associated header #include "llleap.h" // STL headers -#include #include +#include +#include // std headers // external library headers -#include -#include -#include // other Linden headers #include "llerror.h" #include "llstring.h" @@ -53,18 +51,19 @@ public: // We expect multiple LLLeapImpl instances. Definitely tweak // mDonePump's name for uniqueness. mDonePump("LLLeap", true), - // Troubling thought: what if one plugin intentionally messes with - // another plugin? LLEventPump names are in a single global namespace. - // Try to make that more difficult by generating a UUID for the reply- - // pump name -- so it should NOT need tweaking for uniqueness. - mReplyPump(LLUUID::generateNewID().asString()), mExpect(0), // Instantiate a distinct LLLeapListener for this plugin. (Every // plugin will want its own collection of managed listeners, etc.) - // Pass it a callback to our connect() method, so it can send events + // Pass it our wstdin() method as its callback, so it can send events // from a particular LLEventPump to the plugin without having to know // this class or method name. - mListener(new LLLeapListener(boost::bind(&LLLeapImpl::connect, this, _1, _2))) + mListener( + new LLLeapListener( + "LLLeap", + // Serialize any reply event to our child's stdin, suitably + // enriched with the pump name on which it was received. + [this](const std::string& pump, const LLSD& data) + { return wstdin(pump, data); })) { // Rule out unpopulated Params block if (! cparams.executable.isProvided()) @@ -93,7 +92,7 @@ public: } // Listen for child "termination" right away to catch launch errors. - mDonePump.listen("LLLeap", boost::bind(&LLLeapImpl::bad_launch, this, _1)); + mDonePump.listen("LLLeap", [this](const LLSD& data){ return bad_launch(data); }); // Okay, launch child. // Get a modifiable copy of params block to set files and postend. @@ -113,7 +112,7 @@ public: // Okay, launch apparently worked. Change our mDonePump listener. mDonePump.stopListening("LLLeap"); - mDonePump.listen("LLLeap", boost::bind(&LLLeapImpl::done, this, _1)); + mDonePump.listen("LLLeap", [this](const LLSD& data){ return done(data); }); // Child might pump large volumes of data through either stdout or // stderr. Don't bother copying all that data into notification event. @@ -123,17 +122,14 @@ public: childout.setLimit(20); childerr.setLimit(20); - // Serialize any event received on mReplyPump to our child's stdin. - mStdinConnection = connect(mReplyPump, "LLLeap"); - // Listening on stdout is stateful. In general, we're either waiting // for the length prefix or waiting for the specified length of data. // We address that with two different listener methods -- one of which // is blocked at any given time. mStdoutConnection = childout.getPump() - .listen("prefix", boost::bind(&LLLeapImpl::rstdout, this, _1)); + .listen("prefix", [this](const LLSD& data){ return rstdout(data); }); mStdoutDataConnection = childout.getPump() - .listen("data", boost::bind(&LLLeapImpl::rstdoutData, this, _1)); + .listen("data", [this](const LLSD& data){ return rstdoutData(data); }); mBlocker.reset(new LLEventPump::Blocker(mStdoutDataConnection)); // Log anything sent up through stderr. When a typical program @@ -142,7 +138,7 @@ public: // interpreter behaves that way. More generally, though, a plugin // author can log whatever s/he wants to the viewer log using stderr. mStderrConnection = childerr.getPump() - .listen("LLLeap", boost::bind(&LLLeapImpl::rstderr, this, _1)); + .listen("LLLeap", [this](const LLSD& data){ return rstderr(data); }); // For our lifespan, intercept any LL_ERRS so we can notify plugin mRecorder = LLError::addGenericRecorder( @@ -151,7 +147,7 @@ public: // Send child a preliminary event reporting our own reply-pump name -- // which would otherwise be pretty tricky to guess! - wstdin(mReplyPump.getName(), + wstdin(mListener->getReplyPump().getName(), LLSDMap ("command", mListener->getName()) // Include LLLeap features -- this may be important for child to @@ -198,7 +194,7 @@ public: return false; } - // Listener for events on mReplyPump: send to child stdin + // Listener for reply events: send to child stdin bool wstdin(const std::string& pump, const LLSD& data) { LLSD packet(LLSDMap("pump", pump)("data", data)); @@ -355,7 +351,7 @@ public: // request, send a reply. We happen to know who originated // this request, and the reply LLEventPump of interest. // Not our problem if the plugin ignores the reply event. - data["reply"] = mReplyPump.getName(); + data["reply"] = mListener->getReplyPump().getName(); sendReply(llsd::map("error", stringize(LLError::Log::classname(err), ": ", err.what())), data); @@ -428,7 +424,7 @@ public: LLSD event; event["type"] = "error"; event["error"] = error; - mReplyPump.post(event); + mListener->getReplyPump().post(event); // All the above really accomplished was to buffer the serialized // event in our WritePipe. Have to pump mainloop a couple times to @@ -445,27 +441,14 @@ public: } private: - /// We always want to listen on mReplyPump with wstdin(); under some - /// circumstances we'll also echo other LLEventPumps to the plugin. - LLBoundListener connect(LLEventPump& pump, const std::string& listener) - { - // Serialize any event received on the specified LLEventPump to our - // child's stdin, suitably enriched with the pump name on which it was - // received. - return pump.listen(listener, - boost::bind(&LLLeapImpl::wstdin, this, pump.getName(), _1)); - } - std::string mDesc; LLEventStream mDonePump; - LLEventStream mReplyPump; LLProcessPtr mChild; - LLTempBoundListener - mStdinConnection, mStdoutConnection, mStdoutDataConnection, mStderrConnection; - boost::scoped_ptr mBlocker; + LLTempBoundListener mStdoutConnection, mStdoutDataConnection, mStderrConnection; + std::unique_ptr mBlocker; LLProcess::ReadPipe::size_type mExpect; LLError::RecorderPtr mRecorder; - boost::scoped_ptr mListener; + std::unique_ptr mListener; }; // These must follow the declaration of LLLeapImpl, so they may as well be last. diff --git a/indra/llcommon/llleaplistener.cpp b/indra/llcommon/llleaplistener.cpp index 471f52e91c..3dec5e6215 100644 --- a/indra/llcommon/llleaplistener.cpp +++ b/indra/llcommon/llleaplistener.cpp @@ -16,6 +16,7 @@ // STL headers #include // std::find_if #include +#include // std::quoted #include #include // std headers @@ -54,12 +55,19 @@ return features; } -LLLeapListener::LLLeapListener(const ConnectFunc& connect): +LLLeapListener::LLLeapListener(const std::string_view& caller, const Callback& callback): // Each LEAP plugin has an instance of this listener. Make the command // pump name difficult for other such plugins to guess. LLEventAPI(LLUUID::generateNewID().asString(), "Operations relating to the LLSD Event API Plugin (LEAP) protocol"), - mConnect(connect) + mCaller(caller), + mCallback(callback), + // Troubling thought: what if one plugin intentionally messes with + // another plugin? LLEventPump names are in a single global namespace. + // Try to make that more difficult by generating a UUID for the reply- + // pump name -- so it should NOT need tweaking for uniqueness. + mReplyPump(LLUUID::generateNewID().asString()), + mReplyConn(connect(mReplyPump, mCaller)) { LLSD need_name(LLSDMap("name", LLSD())); add("newpump", @@ -133,8 +141,7 @@ void LLLeapListener::newpump(const LLSD& request) reply["name"] = name; // Now listen on this new pump with our plugin listener - std::string myname("llleap"); - saveListener(name, myname, mConnect(new_pump, myname)); + saveListener(name, mCaller, connect(new_pump, mCaller)); } catch (const LLEventPumps::BadType& error) { @@ -170,7 +177,7 @@ void LLLeapListener::listen(const LLSD& request) { // "dest" unspecified means to direct events on "source" // to our plugin listener. - saveListener(source_name, listener_name, mConnect(source, listener_name)); + saveListener(source_name, listener_name, connect(source, listener_name)); } reply["status"] = true; } @@ -292,10 +299,27 @@ void LLLeapListener::getFeature(const LLSD& request) const } } +LLBoundListener LLLeapListener::connect(LLEventPump& pump, const std::string& listener) +{ + // Connect to source pump with an adapter that calls our callback with the + // pump name as well as the event data. + return pump.listen( + listener, + [callback=mCallback, pump=pump.getName()] + (const LLSD& data) + { return callback(pump, data); }); +} + void LLLeapListener::saveListener(const std::string& pump_name, const std::string& listener_name, const LLBoundListener& listener) { - mListeners.insert(ListenersMap::value_type(ListenersMap::key_type(pump_name, listener_name), - listener)); + // Don't use insert() or emplace() because, if this (pump_name, + // listener_name) pair is already in mListeners, we *want* to overwrite it. + auto& listener_entry{ mListeners[ListenersMap::key_type(pump_name, listener_name)] }; + // If we already stored a connection for this pump and listener name, + // disconnect it before overwriting it. But if this entry was newly + // created, disconnect() will be a no-op. + listener_entry.disconnect(); + listener_entry = listener; } diff --git a/indra/llcommon/llleaplistener.h b/indra/llcommon/llleaplistener.h index 0ca5893657..040fb737b7 100644 --- a/indra/llcommon/llleaplistener.h +++ b/indra/llcommon/llleaplistener.h @@ -13,10 +13,9 @@ #define LL_LLLEAPLISTENER_H #include "lleventapi.h" +#include #include #include -#include -#include /// Listener class implementing LLLeap query/control operations. /// See https://jira.lindenlab.com/jira/browse/DEV-31978. @@ -24,18 +23,16 @@ class LLLeapListener: public LLEventAPI { public: /** - * Decouple LLLeap by dependency injection. Certain LLLeapListener - * operations must be able to cause LLLeap to listen on a specified - * LLEventPump with the LLLeap listener that wraps incoming events in an - * outer (pump=, data=) map and forwards them to the plugin. Very well, - * define the signature for a function that will perform that, and make - * our constructor accept such a function. + * Certain LLLeapListener operations listen on a specified LLEventPump. + * Accept a bool(pump, data) callback from our caller for when any such + * event is received. */ - typedef boost::function - ConnectFunc; - LLLeapListener(const ConnectFunc& connect); + using Callback = std::function; + LLLeapListener(const std::string_view& caller, const Callback& callback); ~LLLeapListener(); + LLEventPump& getReplyPump() { return mReplyPump; } + static LLSD getFeatures(); private: @@ -48,10 +45,16 @@ private: void getFeatures(const LLSD&) const; void getFeature(const LLSD&) const; + LLBoundListener connect(LLEventPump& pump, const std::string& listener); void saveListener(const std::string& pump_name, const std::string& listener_name, const LLBoundListener& listener); - ConnectFunc mConnect; + // The relative order of these next declarations is important because the + // constructor will initialize in this order. + std::string mCaller; + Callback mCallback; + LLEventStream mReplyPump; + LLTempBoundListener mReplyConn; // In theory, listen() could simply call the relevant LLEventPump's // listen() method, stoplistening() likewise. Lifespan issues make us diff --git a/indra/llcommon/lualistener.cpp b/indra/llcommon/lualistener.cpp index 0fa03ffb3b..ed34133924 100644 --- a/indra/llcommon/lualistener.cpp +++ b/indra/llcommon/lualistener.cpp @@ -24,20 +24,23 @@ #include "llleaplistener.h" #include "lua_function.h" -LuaListener::LuaListener(lua_State* L): - super(getUniqueKey()), - mListener( - new LLLeapListener( - [L](LLEventPump& pump, const std::string& listener) - { return connect(L, pump, listener); })) +const int MAX_QSIZE = 1000; + +std::ostream& operator<<(std::ostream& out, const LuaListener& self) { - mReplyConnection = connect(L, mReplyPump, "LuaListener"); + return out << "LuaListener(" << self.getReplyName() << ", " << self.getCommandName() << ")"; } +LuaListener::LuaListener(lua_State* L): + super(getUniqueKey()), + mListener(new LLLeapListener( + "LuaListener", + [this](const std::string& pump, const LLSD& data) + { return queueEvent(pump, data); })) +{} + LuaListener::~LuaListener() -{ - LL_DEBUGS("Lua") << "~LuaListener('" << mReplyPump.getName() << "')" << LL_ENDL; -} +{} int LuaListener::getUniqueKey() { @@ -54,50 +57,35 @@ int LuaListener::getUniqueKey() return key; } -LLBoundListener LuaListener::connect(lua_State* L, LLEventPump& pump, const std::string& listener) +std::string LuaListener::getReplyName() const { - return pump.listen( - listener, - [L, pumpname=pump.getName()](const LLSD& data) - { return call_lua(L, pumpname, data); }); + return mListener->getReplyPump().getName(); } -bool LuaListener::call_lua(lua_State* L, const std::string& pump, const LLSD& data) +std::string LuaListener::getCommandName() const { - LL_INFOS("Lua") << "LuaListener::call_lua('" << pump << "', " << data << ")" << LL_ENDL; - if (! lua_checkstack(L, 3)) - { - LL_WARNS("Lua") << "Cannot extend Lua stack to call listen_events() callback" - << LL_ENDL; - return false; - } - // push the registered Lua callback function stored in our registry as - // "event.function" - lua_getfield(L, LUA_REGISTRYINDEX, "event.function"); - llassert(lua_isfunction(L, -1)); - // pass pump name - lua_pushstdstring(L, pump); - // then the data blob - lua_pushllsd(L, data); - // call the registered Lua listener function; allow it to return bool; - // no message handler - auto status = lua_pcall(L, 2, 1, 0); - bool result{ false }; - if (status != LUA_OK) + return mListener->getPumpName(); +} + +bool LuaListener::queueEvent(const std::string& pump, const LLSD& data) +{ + // Our Lua script might be stalled, or just fail to retrieve events. Don't + // grow this queue indefinitely. But don't set MAX_QSIZE as the queue + // capacity or we'd block the post() call trying to propagate this event! + if (auto size = mQueue.size(); size > MAX_QSIZE) { - LL_WARNS("Lua") << "Error in listen_events() callback: " - << lua_tostdstring(L, -1) << LL_ENDL; + LL_WARNS("Lua") << "LuaListener queue for " << getReplyName() + << " exceeds " << MAX_QSIZE << ": " << size + << " -- discarding event" << LL_ENDL; } else { - result = lua_toboolean(L, -1); + mQueue.push(decltype(mQueue)::value_type(pump, data)); } - // discard either the error message or the bool return value - lua_pop(L, 1); - return result; + return false; } -std::string LuaListener::getCommandName() const +LuaListener::PumpData LuaListener::getNext() { - return mListener->getPumpName(); + return mQueue.pop(); } diff --git a/indra/llcommon/lualistener.h b/indra/llcommon/lualistener.h index df88d55dd5..d70d500221 100644 --- a/indra/llcommon/lualistener.h +++ b/indra/llcommon/lualistener.h @@ -14,7 +14,10 @@ #include "llevents.h" #include "llinstancetracker.h" +#include "llsd.h" +#include "llthreadsafequeue.h" #include "lluuid.h" +#include #include // std::unique_ptr #ifdef LL_TEST @@ -31,7 +34,6 @@ class LLLeapListener; * 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 - * - "event.function": the Lua function to be called with incoming events * 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. @@ -55,22 +57,25 @@ public: ~LuaListener(); - std::string getReplyName() const { return mReplyPump.getName(); } + std::string getReplyName() const; std::string getCommandName() const; + /** + * LuaListener enqueues reply events from its LLLeapListener on mQueue. + * Call getNext() to retrieve the next such event. Blocks the calling + * coroutine if the queue is empty. + */ + using PumpData = std::pair; + PumpData getNext(); + + friend std::ostream& operator<<(std::ostream& out, const LuaListener& self); + private: static int getUniqueKey(); + bool queueEvent(const std::string& pump, const LLSD& data); - static LLBoundListener connect(lua_State* L, LLEventPump& pump, const std::string& listener); + LLThreadSafeQueue mQueue; - static bool call_lua(lua_State* L, const std::string& pump, const LLSD& data); - -#ifndef LL_TEST - LLEventStream mReplyPump{ LLUUID::generateNewID().asString() }; -#else - LLEventLogProxyFor mReplyPump{ "luapump", false }; -#endif - LLTempBoundListener mReplyConnection; std::unique_ptr mListener; }; -- cgit v1.2.3 From 48b20d8a2a9354469ad05b83e8b129d44ac1fdc3 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 22 Feb 2024 20:56:35 -0500 Subject: #include where std::quoted() is referenced. Remove where it isn't. --- indra/llcommon/llevents.cpp | 1 + indra/llcommon/llleaplistener.cpp | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp index 1eeb18c40d..01bba7a620 100644 --- a/indra/llcommon/llevents.cpp +++ b/indra/llcommon/llevents.cpp @@ -43,6 +43,7 @@ #include #include #include +#include // std::quoted // external library headers #include #if LL_WINDOWS diff --git a/indra/llcommon/llleaplistener.cpp b/indra/llcommon/llleaplistener.cpp index 3dec5e6215..55e4752c5d 100644 --- a/indra/llcommon/llleaplistener.cpp +++ b/indra/llcommon/llleaplistener.cpp @@ -16,7 +16,6 @@ // STL headers #include // std::find_if #include -#include // std::quoted #include #include // std headers -- cgit v1.2.3 From 43b039dc7f83702fd2da9cb4fad64965d3f15f74 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 23 Feb 2024 09:54:48 -0500 Subject: Clean up #includes in lualistener.h --- indra/llcommon/lualistener.h | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lualistener.h b/indra/llcommon/lualistener.h index d70d500221..c13b7bbd5f 100644 --- a/indra/llcommon/lualistener.h +++ b/indra/llcommon/lualistener.h @@ -12,17 +12,13 @@ #if ! defined(LL_LUALISTENER_H) #define LL_LUALISTENER_H -#include "llevents.h" #include "llinstancetracker.h" #include "llsd.h" #include "llthreadsafequeue.h" -#include "lluuid.h" -#include +#include // std::ostream #include // std::unique_ptr - -#ifdef LL_TEST -#include "lleventfilter.h" -#endif +#include +#include // std::pair struct lua_State; class LLLeapListener; -- cgit v1.2.3 From d7e411d8fb355b7e4198c3521144e3fee2b8e62c Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 23 Feb 2024 09:56:16 -0500 Subject: Ditch DebugExit: we already have Debug (in debug.h) --- indra/llcommon/lua_function.cpp | 10 +--------- indra/llcommon/lua_function.h | 12 ------------ 2 files changed, 1 insertion(+), 21 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index 13210fe5a6..906a3f379c 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -16,6 +16,7 @@ // STL headers // std headers #include +#include // std::quoted #include #include // std::unique_ptr // external library headers @@ -92,7 +93,6 @@ LLSD lua_tollsd(lua_State* L, int index) { LL_DEBUGS("Lua") << "lua_tollsd(" << index << ") of " << lua_gettop(L) << " stack entries: " << lua_what(L, index) << LL_ENDL; - DebugExit log_exit("lua_tollsd()"); switch (lua_type(L, index)) { case LUA_TNONE: @@ -785,11 +785,3 @@ std::ostream& operator<<(std::ostream& out, const lua_stack& self) out << ']'; return out; } - -/***************************************************************************** -* DebugExit -*****************************************************************************/ -DebugExit::~DebugExit() -{ - LL_DEBUGS("Lua") << "exit " << mName << LL_ENDL; -} diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 7973a769be..eccc92ec09 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -213,16 +213,4 @@ private: lua_State* L; }; -// log exit from any block declaring an instance of DebugExit, regardless of -// how control leaves that block -struct DebugExit -{ - DebugExit(const std::string& name): mName(name) {} - DebugExit(const DebugExit&) = delete; - DebugExit& operator=(const DebugExit&) = delete; - ~DebugExit(); - - std::string mName; -}; - #endif /* ! defined(LL_LUA_FUNCTION_H) */ -- cgit v1.2.3 From 904d82402c8b927f5e09830d10247079fb4a94f4 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 23 Feb 2024 10:11:21 -0500 Subject: Allow debug.h to be #included even in normal viewer code. debug.h #defines a couple of macros intended to enclose the entire body of a function to track its entry and (possibly exceptional) exit. The trouble is that these macros used to be called BEGIN and END, which is far too generic -- especially considering that END is used as an enum value in some parts of the viewer. Rename them DEBUGIN and DEBUGEND, which is ugly but unlikely to collide with anything else. --- indra/llcommon/tests/lleventcoro_test.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/lleventcoro_test.cpp b/indra/llcommon/tests/lleventcoro_test.cpp index 032923a108..c7a958da49 100644 --- a/indra/llcommon/tests/lleventcoro_test.cpp +++ b/indra/llcommon/tests/lleventcoro_test.cpp @@ -113,7 +113,7 @@ namespace tut void test_data::explicit_wait(boost::shared_ptr>& cbp) { - BEGIN + DEBUGIN { mSync.bump(); // The point of this test is to verify / illustrate suspending a @@ -136,7 +136,7 @@ namespace tut mSync.bump(); ensure_equals("Got it", stringdata, "received"); } - END + DEBUGEND } template<> template<> @@ -163,13 +163,13 @@ namespace tut void test_data::waitForEventOn1() { - BEGIN + DEBUGIN { mSync.bump(); result = suspendUntilEventOn("source"); mSync.bump(); } - END + DEBUGEND } template<> template<> @@ -189,7 +189,7 @@ namespace tut void test_data::coroPump() { - BEGIN + DEBUGIN { mSync.bump(); LLCoroEventPump waiter; @@ -197,7 +197,7 @@ namespace tut result = waiter.suspend(); mSync.bump(); } - END + DEBUGEND } template<> template<> @@ -217,7 +217,7 @@ namespace tut void test_data::postAndWait1() { - BEGIN + DEBUGIN { mSync.bump(); result = postAndSuspend(LLSDMap("value", 17), // request event @@ -226,7 +226,7 @@ namespace tut "reply"); // request["reply"] = name mSync.bump(); } - END + DEBUGEND } template<> template<> @@ -240,7 +240,7 @@ namespace tut void test_data::coroPumpPost() { - BEGIN + DEBUGIN { mSync.bump(); LLCoroEventPump waiter; @@ -248,7 +248,7 @@ namespace tut immediateAPI.getPump(), "reply"); mSync.bump(); } - END + DEBUGEND } template<> template<> -- cgit v1.2.3