From a4ff9caf69cb8fbbf3e5d40a258ec99a070b0f94 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 15 Jun 2022 19:43:53 -0400 Subject: DRTVWR-564: WIP: add LLEventPumps::registerPumpFactory() and registerTypeFactory(). Untested. This will support registering just-in-time LLEventAPI instances, instantiated on demand. --- indra/llcommon/llevents.cpp | 44 ++++++++++++++++++++++++++++++++++++++------ indra/llcommon/llevents.h | 39 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 76 insertions(+), 7 deletions(-) diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp index 0a213bddef..5725dad9cc 100644 --- a/indra/llcommon/llevents.cpp +++ b/indra/llcommon/llevents.cpp @@ -68,19 +68,51 @@ LLEventPumps::LLEventPumps(): mFactories { - { "LLEventStream", [](const std::string& name, bool tweak) + { "LLEventStream", [](const std::string& name, bool tweak, const std::string& /*type*/) { return new LLEventStream(name, tweak); } }, - { "LLEventMailDrop", [](const std::string& name, bool tweak) + { "LLEventMailDrop", [](const std::string& name, bool tweak, const std::string& /*type*/) { return new LLEventMailDrop(name, tweak); } } }, mTypes { - // LLEventStream is the default for obtain(), so even if somebody DOES - // call obtain("placeholder"), this sample entry won't break anything. - { "placeholder", "LLEventStream" } +// { "placeholder", "LLEventStream" } } {} +bool LLEventPumps::registerTypeFactory(const std::string& type, const TypeFactory& factory) +{ + auto found = mFactories.find(type); + // can't re-register a TypeFactory for a type name that's already registered + if (found != mFactories.end()) + return false; + // doesn't already exist, go ahead and register + mFactories[type] = factory; + return true; +} + +bool LLEventPumps::registerPumpFactory(const std::string& name, const PumpFactory& factory) +{ + // Do we already have a pump by this name? + if (mPumpMap.find(name) != mPumpMap.end()) + return false; + // Do we already have an override for this pump name? + if (mTypes.find(name) != mTypes.end()) + return false; + // Leverage the two-level lookup implemented by mTypes (pump name -> type + // name) and mFactories (type name -> factory). We could instead create a + // whole separate (pump name -> factory) map, and look in both; or we + // could change mTypes to (pump name -> factory) and, for typical type- + // based lookups, use a "factory" that looks up the real factory in + // mFactories. But this works, and we don't expect many calls to make() - + // either explicit or implicit via obtain(). + // Create a bogus type name extremely unlikely to collide with an actual type. + static std::string nul(1, '\0'); + std::string type_name{ nul + name }; + mTypes[name] = type_name; + mFactories[type_name] = factory; + return true; +} + LLEventPump& LLEventPumps::obtain(const std::string& name) { PumpMap::iterator found = mPumpMap.find(name); @@ -114,7 +146,7 @@ LLEventPump& LLEventPumps::make(const std::string& name, bool tweak, // Passing an unrecognized type name is a no-no LLTHROW(BadType(type)); } - auto newInstance = (found->second)(name, tweak); + auto newInstance = (found->second)(name, tweak, type); // LLEventPump's constructor implicitly registers each new instance in // mPumpMap. But remember that we instantiated it (in mOurPumps) so we'll // delete it later. diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h index ae6e5aabc9..38adc31121 100644 --- a/indra/llcommon/llevents.h +++ b/indra/llcommon/llevents.h @@ -268,6 +268,43 @@ public: LLEventPump& make(const std::string& name, bool tweak=false, const std::string& type=std::string()); + /// function passed to registerTypeFactory() + typedef std::function TypeFactory; + + /** + * Register a TypeFactory for use with make(). When make() is called with + * the specified @a type string, call @a factory(name, tweak, type) to + * instantiate it. + * + * Returns true if successfully registered, false if there already exists + * a TypeFactory for the specified @a type name. + */ + bool registerTypeFactory(const std::string& type, const TypeFactory& factory); + + /// function passed to registerPumpFactory() + typedef std::function PumpFactory; + + /** + * Register a PumpFactory for use with obtain(). When obtain() is called + * with the specified @a name string, if an LLEventPump with the specified + * @a name doesn't already exist, call @a factory(name) to instantiate it. + * + * Returns true if successfully registered, false if there already exists + * a factory override for the specified @a name. + * + * PumpFactory does not support @a tweak because it's only called when + * that particular @a name is passed to obtain(). Bear in mind that + * obtain(name) might still bypass the caller's PumpFactory for a + * couple different reasons: + * + * * registerPumpFactory() returns false because there's already a factory + * override for the specified @name + * * between a successful registerPumpFactory(name) call (returns + * true) and a call to obtain(name), someone explicitly + * instantiated an LLEventPump(name), so obtain(name) returned that. + */ + bool registerPumpFactory(const std::string& name, const PumpFactory& factory); + /** * Find the named LLEventPump instance. If it exists post the message to it. * If the pump does not exist, do nothing. @@ -325,7 +362,7 @@ testable: typedef std::set PumpSet; PumpSet mOurPumps; // for make(), map string type name to LLEventPump subclass factory function - typedef std::map> PumpFactories; + typedef std::map PumpFactories; // Data used by make(). // One might think mFactories and mTypes could reasonably be static. So // they could -- if not for the fact that make() or obtain() might be -- cgit v1.2.3 From b26e516d2b93a442d09f5c3b1b4d8d60139c42f5 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 6 Apr 2022 17:34:28 -0400 Subject: DRTVWR-558: Change LLEventDispatcher error action (also LLEventAPI). Originally the LLEventAPI mechanism was primarily used for VITA testing. In that case it was okay for the viewer to crash with LL_ERRS if the test script passed a bad request. With puppetry, hopefully new LEAP scripts will be written to engage LLEventAPIs in all sorts of interesting ways. Change error handling from LL_ERRS to LL_WARNS. Furthermore, if the incoming request contains a "reply" key, send back an error response to the requester. Update lleventdispatcher_test.cpp accordingly. (cherry picked from commit de0539fcbe815ceec2041ecc9981e3adf59f2806) --- indra/llcommon/lleventdispatcher.cpp | 127 +++++++++++++++++------- indra/llcommon/lleventdispatcher.h | 29 ++++-- indra/llcommon/tests/lleventdispatcher_test.cpp | 62 ++++++++---- 3 files changed, 152 insertions(+), 66 deletions(-) diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index 5b6d4efbe9..742d6cf51f 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -40,15 +40,24 @@ // other Linden headers #include "llevents.h" #include "llerror.h" +#include "llexception.h" #include "llsdutil.h" #include "stringize.h" #include // std::auto_ptr +/***************************************************************************** +* DispatchError +*****************************************************************************/ +struct DispatchError: public LLException +{ + DispatchError(const std::string& what): LLException(what) {} +}; + /***************************************************************************** * LLSDArgsSource *****************************************************************************/ /** - * Store an LLSD array, producing its elements one at a time. Die with LL_ERRS + * Store an LLSD array, producing its elements one at a time. It is an error * if the consumer requests more elements than the array contains. */ class LL_COMMON_API LLSDArgsSource @@ -74,8 +83,7 @@ LLSDArgsSource::LLSDArgsSource(const std::string function, const LLSD& args): { if (! (_args.isUndefined() || _args.isArray())) { - LL_ERRS("LLSDArgsSource") << _function << " needs an args array instead of " - << _args << LL_ENDL; + LLTHROW(DispatchError(stringize(_function, " needs an args array instead of ", _args))); } } @@ -88,8 +96,8 @@ LLSD LLSDArgsSource::next() { if (_index >= _args.size()) { - LL_ERRS("LLSDArgsSource") << _function << " requires more arguments than the " - << _args.size() << " provided: " << _args << LL_ENDL; + LLTHROW(DispatchError(stringize(_function, " requires more arguments than the ", + _args.size(), " provided: ", _args))); } return _args[_index++]; } @@ -163,7 +171,8 @@ public: /// default values LLSDArgsMapper(const std::string& function, const LLSD& names, const LLSD& defaults); - /// Given arguments map, return LLSD::Array of parameter values, or LL_ERRS. + /// Given arguments map, return LLSD::Array of parameter values, or + /// trigger error. LLSD map(const LLSD& argsmap) const; private: @@ -195,7 +204,7 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function, { if (! (_names.isUndefined() || _names.isArray())) { - LL_ERRS("LLSDArgsMapper") << function << " names must be an array, not " << names << LL_ENDL; + LLTHROW(DispatchError(stringize(function, " names must be an array, not ", names))); } LLSD::Integer nparams(_names.size()); // From _names generate _indexes. @@ -218,8 +227,8 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function, // defaults is a (possibly empty) array. Right-align it with names. if (ndefaults > nparams) { - LL_ERRS("LLSDArgsMapper") << function << " names array " << names - << " shorter than defaults array " << defaults << LL_ENDL; + LLTHROW(DispatchError(stringize(function, " names array ", names, + " shorter than defaults array ", defaults))); } // Offset by which we slide defaults array right to right-align with @@ -256,14 +265,14 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function, } if (bogus.size()) { - LL_ERRS("LLSDArgsMapper") << function << " defaults specified for nonexistent params " - << formatlist(bogus) << LL_ENDL; + LLTHROW(DispatchError(stringize(function, " defaults specified for nonexistent params ", + formatlist(bogus)))); } } else { - LL_ERRS("LLSDArgsMapper") << function << " defaults must be a map or an array, not " - << defaults << LL_ENDL; + LLTHROW(DispatchError(stringize(function, " defaults must be a map or an array, not ", + defaults))); } } @@ -271,8 +280,8 @@ LLSD LLSDArgsMapper::map(const LLSD& argsmap) const { if (! (argsmap.isUndefined() || argsmap.isMap() || argsmap.isArray())) { - LL_ERRS("LLSDArgsMapper") << _function << " map() needs a map or array, not " - << argsmap << LL_ENDL; + LLTHROW(DispatchError(stringize(_function, " map() needs a map or array, not ", + argsmap))); } // Initialize the args array. Indexing a non-const LLSD array grows it // to appropriate size, but we don't want to resize this one on each @@ -369,8 +378,8 @@ LLSD LLSDArgsMapper::map(const LLSD& argsmap) const // by argsmap, that's a problem. if (unfilled.size()) { - LL_ERRS("LLSDArgsMapper") << _function << " missing required arguments " - << formatlist(unfilled) << " from " << argsmap << LL_ENDL; + LLTHROW(DispatchError(stringize(_function, " missing required arguments ", + formatlist(unfilled), " from ", argsmap))); } // done @@ -420,7 +429,7 @@ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchE std::string mismatch(llsd_matches(mRequired, event)); if (! mismatch.empty()) { - LL_ERRS("LLEventDispatcher") << desc << ": bad request: " << mismatch << LL_ENDL; + LLTHROW(DispatchError(stringize(desc, ": bad request: ", mismatch))); } // Event syntax looks good, go for it! mFunc(event); @@ -596,48 +605,90 @@ bool LLEventDispatcher::remove(const std::string& name) return true; } -/// Call a registered callable with an explicitly-specified name. If no -/// such callable exists, die with LL_ERRS. +/// Call a registered callable with an explicitly-specified name. It is an +/// error if no such callable exists. void LLEventDispatcher::operator()(const std::string& name, const LLSD& event) const { - if (! try_call(name, event)) + std::string error{ try_call_log(std::string(), name, event) }; + if (! error.empty()) { - LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): '" << name - << "' not found" << LL_ENDL; + callFail(event, error); } } -/// Extract the @a key value from the incoming @a event, and call the -/// callable whose name is specified by that map @a key. If no such -/// callable exists, die with LL_ERRS. +/// Extract the @a key value from the incoming @a event, and call the callable +/// whose name is specified by that map @a key. It is an error if no such +/// callable exists. void LLEventDispatcher::operator()(const LLSD& event) const { - // This could/should be implemented in terms of the two-arg overload. - // However -- we can produce a more informative error message. - std::string name(event[mKey]); - if (! try_call(name, event)) + std::string error{ try_call_log(mKey, event[mKey], event) }; + if (! error.empty()) + { + callFail(event, error); + } +} + +void LLEventDispatcher::callFail(const LLSD& event, const std::string& msg) const +{ + static LLSD::String key{ "reply" }; + if (event.has(key)) { - LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): bad " << mKey - << " value '" << name << "'" << LL_ENDL; + // Oh good, the incoming event specifies a reply pump -- pass back a + // response that includes an "error" key with the message. + sendReply(llsd::map("error", msg), event, key); } } bool LLEventDispatcher::try_call(const LLSD& event) const { - return try_call(event[mKey], event); + return try_call_log(mKey, event[mKey], event).empty(); } bool LLEventDispatcher::try_call(const std::string& name, const LLSD& event) const +{ + return try_call_log(std::string(), name, event).empty(); +} + +std::string LLEventDispatcher::try_call_log(const std::string& key, const std::string& name, + const LLSD& event) const +{ + std::string error{ try_call(key, name, event) }; + if (! error.empty()) + { + LL_WARNS("LLEventDispatcher") << error << LL_ENDL; + } + return error; +} + +// This internal method returns empty string if the call succeeded, else +// non-empty error message. +std::string LLEventDispatcher::try_call(const std::string& key, const std::string& name, + const LLSD& event) const { DispatchMap::const_iterator found = mDispatch.find(name); if (found == mDispatch.end()) { - return false; + if (key.empty()) + { + return stringize("LLEventDispatcher(", mDesc, "): '", name, "' not found"); + } + else + { + return stringize("LLEventDispatcher(", mDesc, "): bad ", key, " value '", name, "'"); + } + } + + try + { + // Found the name, so it's plausible to even attempt the call. + found->second->call(stringize("LLEventDispatcher(", mDesc, ") calling '", name, "'"), + event); + } + catch (const DispatchError& err) + { + return err.what(); } - // Found the name, so it's plausible to even attempt the call. - found->second->call(STRINGIZE("LLEventDispatcher(" << mDesc << ") calling '" << name << "'"), - event); - return true; // tell caller we were able to call + return {}; // tell caller we were able to call } LLSD LLEventDispatcher::getMetadata(const std::string& name) const diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index 9e1244ef5b..b78c77f8b3 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -254,28 +254,28 @@ public: /// Unregister a callable bool remove(const std::string& name); - /// Call a registered callable with an explicitly-specified name. If no - /// such callable exists, die with LL_ERRS. If the @a event fails to match - /// the @a required prototype specified at add() time, die with LL_ERRS. + /// Call a registered callable with an explicitly-specified name. It is an + /// error if no such callable exists. It is an error if the @a event fails + /// to match the @a required prototype specified at add() time. void operator()(const std::string& name, const LLSD& event) const; /// Call a registered callable with an explicitly-specified name and /// return true. If no such callable exists, return - /// false. If the @a event fails to match the @a required - /// prototype specified at add() time, die with LL_ERRS. + /// false. It is an error if the @a event fails to match the @a + /// required prototype specified at add() time. bool try_call(const std::string& name, const LLSD& event) const; /// Extract the @a key value from the incoming @a event, and call the - /// callable whose name is specified by that map @a key. If no such - /// callable exists, die with LL_ERRS. If the @a event fails to match the - /// @a required prototype specified at add() time, die with LL_ERRS. + /// callable whose name is specified by that map @a key. It is an error if + /// no such callable exists. It is an error if the @a event fails to match + /// the @a required prototype specified at add() time. void operator()(const LLSD& event) const; /// Extract the @a key value from the incoming @a event, call the callable /// whose name is specified by that map @a key and return true. - /// If no such callable exists, return false. If the @a event - /// fails to match the @a required prototype specified at add() time, die - /// with LL_ERRS. + /// If no such callable exists, return false. It is an error if + /// the @a event fails to match the @a required prototype specified at + /// add() time. bool try_call(const LLSD& event) const; /// @name Iterate over defined names @@ -340,6 +340,13 @@ private: } } void addFail(const std::string& name, const std::string& classname) const; + std::string try_call_log(const std::string& key, const std::string& name, + const LLSD& event) const; + std::string try_call(const std::string& key, const std::string& name, + const LLSD& event) const; + // Implement "it is an error" semantics for attempted call operations: if + // the incoming event includes a "reply" key, log and send an error reply. + void callFail(const LLSD& event, const std::string& msg) const; std::string mDesc, mKey; DispatchMap mDispatch; diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index 9da1ecfd67..82a0ddf61b 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -20,6 +20,7 @@ #include "../test/lltut.h" #include "llsd.h" #include "llsdutil.h" +#include "llevents.h" #include "stringize.h" #include "tests/wrapllerrs.h" #include "../test/catch_and_store_what_in.h" @@ -644,12 +645,45 @@ namespace tut outer.find(inner) != std::string::npos); } - void call_exc(const std::string& func, const LLSD& args, const std::string& exc_frag) + std::string call_exc(const std::string& func, const LLSD& args, const std::string& exc_frag) { - std::string threw = catch_what([this, &func, &args](){ - work(func, args); - }); - ensure_has(threw, exc_frag); + // This method was written when LLEventDispatcher responded to + // name or argument errors with LL_ERRS, hence the name: we used + // to have to intercept LL_ERRS by making it throw. Now we set up + // to catch an error response instead. But -- for that we need to + // be able to sneak a "reply" key into args, which must be a Map. + if (! (args.isUndefined() or args.isMap())) + fail(stringize("can't test call_exc() with ", args)); + LLEventStream replypump("reply"); + LLSD reply; + LLTempBoundListener bound{ + replypump.listen( + "listener", + [&reply](const LLSD& event) + { + reply = event; + return false; + }) }; + LLSD modargs{ args }; + modargs["reply"] = replypump.getName(); + if (func.empty()) + { + work(modargs); + } + else + { + work(func, modargs); + } + ensure("no error response", reply.has("error")); + ensure_has(reply["error"], exc_frag); + return reply["error"]; + } + + void call_logerr(const std::string& func, const LLSD& args, const std::string& frag) + { + CaptureLog capture; + work(func, args); + capture.messageWith(frag); } LLSD getMetadata(const std::string& name) @@ -1031,13 +1065,7 @@ namespace tut { set_test_name("call with bad name"); call_exc("freek", LLSD(), "not found"); - // We don't have a comparable helper function for the one-arg - // operator() method, and it's not worth building one just for this - // case. Write it out. - std::string threw = catch_what([this](){ - work(LLSDMap("op", "freek")); - }); - ensure_has(threw, "bad"); + std::string threw = call_exc("", LLSDMap("op", "freek"), "bad"); ensure_has(threw, "op"); ensure_has(threw, "freek"); } @@ -1087,7 +1115,7 @@ namespace tut ensure_equals("answer mismatch", tr.llsd, answer); // Should NOT be able to pass 'answer' to Callables registered // with 'required'. - call_exc(tr.name_req, answer, "bad request"); + call_logerr(tr.name_req, answer, "bad request"); // But SHOULD be able to pass 'matching' to Callables registered // with 'required'. work(tr.name_req, matching); @@ -1107,11 +1135,11 @@ namespace tut // args. We should only need to engage it for one map-style // registration and one array-style registration. std::string array_exc("needs an args array"); - call_exc("free0_array", 17, array_exc); - call_exc("free0_array", LLSDMap("pi", 3.14), array_exc); + call_logerr("free0_array", 17, array_exc); + call_logerr("free0_array", LLSDMap("pi", 3.14), array_exc); std::string map_exc("needs a map"); - call_exc("free0_map", 17, map_exc); + call_logerr("free0_map", 17, map_exc); // Passing an array to a map-style function works now! No longer an // error case! // call_exc("free0_map", LLSDArray("a")("b"), map_exc); @@ -1158,7 +1186,7 @@ namespace tut { foreach(const llsd::MapEntry& e, inMap(funcsab)) { - call_exc(e.second, tooshort, "requires more arguments"); + call_logerr(e.second, tooshort, "requires more arguments"); } } } -- cgit v1.2.3 From f134eace911eca8a231a7c1d556d7d891e8b21ca Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 6 Apr 2022 21:49:03 -0400 Subject: DRTVWR-558: LLEventAPI allows all LLEventDispatcher add() overloads. Previously, LLEventAPI intentionally hid all but one of the many add() overloads supported by its LLEventDispatcher base class. The reason was that certain of the add() methods take an optional fourth parameter that's an LLSD::Map describing the expected parameter structure, while others take a fourth templated parameter that's an instance getter callable. This led to ambiguity, especially when passed an LLSDMap instance that's convertible to LLSD but isn't literally LLSD. At the time, it was simpler to constrain the add() methods inherited from LLEventDispatcher. But by adding new std::enable_if constraints to certain LLEventDispatcher add() methods, we've resolved the ambiguities, so LLEventAPI subclasses can now use any add() overload (as claimed on the relevant Confluence page). LLEventDispatcher comments have always loftily claimed that an instance getter callable may return either a pointer or a reference, doesn't matter. But it does when trying to pass the getter's result to boost::fusion::push_back(): a reference must be wrapped with std::ref() while a pointer cannot be. std::ref(pointer) produces errors. Introduce LLEventDispatcher::invoker:: bindable() overloads to Do The Right Thing whether passed a pointer or a reference. (cherry picked from commit 743f487c2e123171c9fc6d5b84d768f1d856d569) --- indra/llcommon/lleventapi.h | 13 ------ indra/llcommon/lleventdispatcher.h | 89 ++++++++++++++++++++++++++------------ 2 files changed, 61 insertions(+), 41 deletions(-) diff --git a/indra/llcommon/lleventapi.h b/indra/llcommon/lleventapi.h index 5991fe8fd5..ed62fa064a 100644 --- a/indra/llcommon/lleventapi.h +++ b/indra/llcommon/lleventapi.h @@ -64,19 +64,6 @@ public: /// Get the documentation string std::string getDesc() const { return mDesc; } - /** - * Publish only selected add() methods from LLEventDispatcher. - * Every LLEventAPI add() @em must have a description string. - */ - template - void add(const std::string& name, - const std::string& desc, - CALLABLE callable, - const LLSD& required=LLSD()) - { - LLEventDispatcher::add(name, desc, callable, required); - } - /** * Instantiate a Response object in any LLEventAPI subclass method that * wants to guarantee a reply (if requested) will be sent on exit from the diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index b78c77f8b3..f1e4fe2df7 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -61,7 +61,6 @@ static const auto& nil(nil_); #include #include #include -#include #include #include #include @@ -167,10 +166,11 @@ public: * converted to the corresponding parameter type using LLSDParam. */ template - typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin - >::type add(const std::string& name, - const std::string& desc, - Function f); + typename std::enable_if< + boost::function_types::is_nonmember_callable_builtin::value + >::type add(const std::string& name, + const std::string& desc, + Function f); /** * Register a nonstatic class method with arbitrary parameters. @@ -190,11 +190,14 @@ public: * converted to the corresponding parameter type using LLSDParam. */ template - typename boost::enable_if< boost::function_types::is_member_function_pointer - >::type add(const std::string& name, - const std::string& desc, - Method f, - const InstanceGetter& getter); + typename std::enable_if< + boost::function_types::is_member_function_pointer::value && + ! std::is_same::value && + ! std::is_same::value + >::type add(const std::string& name, + const std::string& desc, + Method f, + const InstanceGetter& getter); /** * Register a free function with arbitrary parameters. (This also works @@ -212,12 +215,13 @@ public: * to the corresponding parameter type using LLSDParam. */ template - typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin - >::type add(const std::string& name, - const std::string& desc, - Function f, - const LLSD& params, - const LLSD& defaults=LLSD()); + typename std::enable_if< + boost::function_types::is_nonmember_callable_builtin::value + >::type add(const std::string& name, + const std::string& desc, + Function f, + const LLSD& params, + const LLSD& defaults=LLSD()); /** * Register a nonstatic class method with arbitrary parameters. @@ -241,13 +245,16 @@ public: * to the corresponding parameter type using LLSDParam. */ template - typename boost::enable_if< boost::function_types::is_member_function_pointer - >::type add(const std::string& name, - const std::string& desc, - Method f, - const InstanceGetter& getter, - const LLSD& params, - const LLSD& defaults=LLSD()); + typename std::enable_if< + boost::function_types::is_member_function_pointer::value && + ! std::is_same::value && + ! std::is_same::value + >::type add(const std::string& name, + const std::string& desc, + Method f, + const InstanceGetter& getter, + const LLSD& params, + const LLSD& defaults=LLSD()); //@} @@ -434,7 +441,25 @@ struct LLEventDispatcher::invoker // Instead of grabbing the first item from argsrc and making an // LLSDParam of it, call getter() and pass that as the instance param. invoker::apply - ( func, argsrc, boost::fusion::push_back(boost::fusion::nil(), boost::ref(getter()))); + ( func, argsrc, boost::fusion::push_back(boost::fusion::nil(), bindable(getter()))); + } + + template + static inline + auto bindable(T&& value, + typename std::enable_if::value, bool>::type=true) + { + // if passed a pointer, just return that pointer + return std::forward(value); + } + + template + static inline + auto bindable(T&& value, + typename std::enable_if::value, bool>::type=true) + { + // if passed a reference, wrap it for binding + return std::ref(std::forward(value)); } }; @@ -454,7 +479,7 @@ struct LLEventDispatcher::invoker }; template -typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin >::type +typename std::enable_if< boost::function_types::is_nonmember_callable_builtin::value >::type LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f) { // Construct an invoker_function, a callable accepting const args_source&. @@ -465,7 +490,11 @@ LLEventDispatcher::add(const std::string& name, const std::string& desc, Functio } template -typename boost::enable_if< boost::function_types::is_member_function_pointer >::type +typename std::enable_if< + boost::function_types::is_member_function_pointer::value && + ! std::is_same::value && + ! std::is_same::value +>::type LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, const InstanceGetter& getter) { @@ -476,7 +505,7 @@ LLEventDispatcher::add(const std::string& name, const std::string& desc, Method } template -typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin >::type +typename std::enable_if< boost::function_types::is_nonmember_callable_builtin::value >::type LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f, const LLSD& params, const LLSD& defaults) { @@ -485,7 +514,11 @@ LLEventDispatcher::add(const std::string& name, const std::string& desc, Functio } template -typename boost::enable_if< boost::function_types::is_member_function_pointer >::type +typename std::enable_if< + boost::function_types::is_member_function_pointer::value && + ! std::is_same::value && + ! std::is_same::value +>::type LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, const InstanceGetter& getter, const LLSD& params, const LLSD& defaults) -- cgit v1.2.3 From 4e7b4bab79be8cf2de9af242e5cd23347fba8bb2 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 6 Apr 2022 22:19:10 -0400 Subject: DRTVWR-558: Generalize LLEventDispatcher::add() constraints. Instead of checking whether an add() parameter is exactly LLSD or LLSDMap, check whether it's convertible to LLSD -- which handles those cases and more. (cherry picked from commit fa168c11f64771dadc5df86d14ca2f07eba3b8ba) --- indra/llcommon/lleventdispatcher.h | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index f1e4fe2df7..1b3e834aeb 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -192,8 +192,7 @@ public: template typename std::enable_if< boost::function_types::is_member_function_pointer::value && - ! std::is_same::value && - ! std::is_same::value + ! std::is_convertible::value >::type add(const std::string& name, const std::string& desc, Method f, @@ -247,8 +246,7 @@ public: template typename std::enable_if< boost::function_types::is_member_function_pointer::value && - ! std::is_same::value && - ! std::is_same::value + ! std::is_convertible::value >::type add(const std::string& name, const std::string& desc, Method f, @@ -492,8 +490,7 @@ LLEventDispatcher::add(const std::string& name, const std::string& desc, Functio template typename std::enable_if< boost::function_types::is_member_function_pointer::value && - ! std::is_same::value && - ! std::is_same::value + ! std::is_convertible::value >::type LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, const InstanceGetter& getter) @@ -516,8 +513,7 @@ LLEventDispatcher::add(const std::string& name, const std::string& desc, Functio template typename std::enable_if< boost::function_types::is_member_function_pointer::value && - ! std::is_same::value && - ! std::is_same::value + ! std::is_convertible::value >::type LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, const InstanceGetter& getter, -- cgit v1.2.3 From dc2e2cd76f387ea6e80787fb94adcbc269cd1f25 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 18 Jun 2022 11:53:57 -0400 Subject: DRTVWR-564: Add LL::apply(): call function, passing args from tuple. This anticipates C++17's std::apply(), and in fact once we detect C++17, we'll just use that. But in C++14 we must still provide our own implementation. --- indra/llcommon/apply.h | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 indra/llcommon/apply.h diff --git a/indra/llcommon/apply.h b/indra/llcommon/apply.h new file mode 100644 index 0000000000..ef4a8fd68b --- /dev/null +++ b/indra/llcommon/apply.h @@ -0,0 +1,51 @@ +/** + * @file apply.h + * @author Nat Goodspeed + * @date 2022-06-18 + * @brief C++14 version of std::apply() + * + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Copyright (c) 2022, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_APPLY_H) +#define LL_APPLY_H + +#include + +namespace LL +{ + +#if __cplusplus >= 201703L + +// C++17 implementation +using std::apply; + +#else // C++14 + +// Derived from https://stackoverflow.com/a/20441189 +// and https://en.cppreference.com/w/cpp/utility/apply +template +auto apply_impl(CALLABLE&& func, TUPLE&& args, std::index_sequence) +{ + // call func(unpacked args) + return std::forward(func)(std::move(std::get(args))...); +} + +template +auto apply(CALLABLE&& func, std::tuple&& args) +{ + // std::index_sequence_for is the magic sauce here, generating an argument + // pack of indexes for each entry in args. apply_impl() can then pass + // those to std::get() to unpack args into individual arguments. + return apply_impl(std::forward(func), + std::forward>(args), + std::index_sequence_for{}); +} + +#endif // C++14 + +} // namespace LL + +#endif /* ! defined(LL_APPLY_H) */ -- cgit v1.2.3 From af4fbc1f8a99a3c5370cb6db45435e67f9ce92d2 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 18 Jun 2022 11:57:10 -0400 Subject: DRTVWR-564: WIP: Add LazyEventAPI and tests. Tests don't yet pass. LazyEventAPI is a registrar that implicitly instantiates some particular LLEventAPI subclass on demand: that is, when LLEventPumps::obtain() tries to find an LLEventPump by the registered name. This leverages the new LLEventPumps::registerPumpFactory() machinery. Fix registerPumpFactory() to adapt the passed PumpFactory to accept TypeFactory parameters (two of which it ignores). Supplement it with unregisterPumpFactory() to support LazyEventAPI instances with lifespans shorter than the process -- which may be mostly test programs, but still a hole worth closing. Similarly, add unregisterTypeFactory(). A LazyEventAPI subclass takes over responsibility for specifying the LLEventAPI's name, desc, field, plus whatever add() calls will be needed to register the LLEventAPI's operations. This is so we can (later) enhance LLLeapListener to consult LazyEventAPI instances for not-yet-instantiated LLEventAPI metadata, as well as enumerating existing LLEventAPI instances. The trickiest part of this is capturing calls to the various LLEventDispatcher::add() overloads in such a way that, when the LLEventAPI subclass is eventually instantiated, we can replay them in the new instance. LLEventAPI acquires a new protected constructor specifically for use by a subclass registered by a companion LazyEventAPI. It accepts a const reference to LazyEventAPIParams, intended to be opaque to the LLEventAPI subclass; the subclass must declare a constructor that accepts and forwards the parameter block to the new LLEventAPI constructor. The implementation delegates to the existing LLEventAPI constructor, plus it runs deferred add() calls. LLDispatchListener now derives from LLEventStream instead of containing it as a data member. The reason is that if LLEventPumps::obtain() implicitly instantiates it, LLEventPumps's destructor will try to destroy it by deleting the LLEventPump*. If the LLEventPump returned by the factory function is a data member of an outer class, that won't work so well. But if LLDispatchListener (and by implication, LLEventAPI and any subclass) is derived from LLEventPump, then the virtual destructor will Do The Right Thing. Change LLDispatchListener to *not* allow tweaking the LLEventPump name. Since the overwhelming use case for LLDispatchListener is LLEventAPI, accepting but silently renaming an LLEventAPI subclass would ensure nobody could reach it. Change LLEventDispatcher's use of std::enable_if to control the set of add() overloads available for the intended use cases. Apparently this formulation is just as functional at the method declaration point, while avoiding the need to restate the whole enable_if expression at the method definition point. Add lazyeventapi_test.cpp to exercise. --- indra/llcommon/CMakeLists.txt | 4 + indra/llcommon/lazyeventapi.cpp | 53 ++++++++ indra/llcommon/lazyeventapi.h | 204 +++++++++++++++++++++++++++++ indra/llcommon/lleventapi.cpp | 8 ++ indra/llcommon/lleventapi.h | 23 +++- indra/llcommon/lleventdispatcher.cpp | 13 +- indra/llcommon/lleventdispatcher.h | 105 +++++++-------- indra/llcommon/llevents.cpp | 29 +++- indra/llcommon/llevents.h | 6 +- indra/llcommon/tests/lazyeventapi_test.cpp | 89 +++++++++++++ 10 files changed, 466 insertions(+), 68 deletions(-) create mode 100644 indra/llcommon/lazyeventapi.cpp create mode 100644 indra/llcommon/lazyeventapi.h create mode 100644 indra/llcommon/tests/lazyeventapi_test.cpp diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index ca8b5e946f..36b2e09dc5 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -30,6 +30,7 @@ include_directories( set(llcommon_SOURCE_FILES indra_constants.cpp + lazyeventapi.cpp llallocator.cpp llallocator_heap_profile.cpp llapp.cpp @@ -128,10 +129,12 @@ set(llcommon_SOURCE_FILES set(llcommon_HEADER_FILES CMakeLists.txt + apply.h chrono.h ctype_workaround.h fix_macros.h indra_constants.h + lazyeventapi.h linden_common.h llalignedarray.h llallocator.h @@ -338,6 +341,7 @@ if (LL_TESTS) ${BOOST_SYSTEM_LIBRARY}) LL_ADD_INTEGRATION_TEST(commonmisc "" "${test_libs}") LL_ADD_INTEGRATION_TEST(bitpack "" "${test_libs}") + LL_ADD_INTEGRATION_TEST(lazyeventapi "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llbase64 "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llcond "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lldate "" "${test_libs}") diff --git a/indra/llcommon/lazyeventapi.cpp b/indra/llcommon/lazyeventapi.cpp new file mode 100644 index 0000000000..aefc2db6da --- /dev/null +++ b/indra/llcommon/lazyeventapi.cpp @@ -0,0 +1,53 @@ +/** + * @file lazyeventapi.cpp + * @author Nat Goodspeed + * @date 2022-06-17 + * @brief Implementation for lazyeventapi. + * + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Copyright (c) 2022, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "lazyeventapi.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "llevents.h" + +LL::LazyEventAPIBase::LazyEventAPIBase( + const std::string& name, const std::string& desc, const std::string& field) +{ + // populate embedded LazyEventAPIParams instance + mParams.name = name; + mParams.desc = desc; + mParams.field = field; + // mParams.init and mOperations are populated by subsequent add() calls. + + // Our raison d'etre: register as an LLEventPumps::PumpFactory + // so obtain() will notice any request for this name and call us. + // Of course, our subclass constructor must finish running (making add() + // calls) before mParams will be fully populated, but we expect that to + // happen well before the first LLEventPumps::obtain(name) call. + mRegistered = LLEventPumps::instance().registerPumpFactory( + name, + [this](const std::string& name){ return construct(name); }); +} + +LL::LazyEventAPIBase::~LazyEventAPIBase() +{ + // If our constructor's registerPumpFactory() call was unsuccessful, that + // probably means somebody else claimed the name first. If that's the + // case, do NOT unregister their name out from under them! + // If this is a static instance being destroyed at process shutdown, + // LLEventPumps will probably have been cleaned up already. + if (mRegistered && ! LLEventPumps::wasDeleted()) + { + // unregister the callback to this doomed instance + LLEventPumps::instance().unregisterPumpFactory(mParams.name); + } +} diff --git a/indra/llcommon/lazyeventapi.h b/indra/llcommon/lazyeventapi.h new file mode 100644 index 0000000000..2e947899dc --- /dev/null +++ b/indra/llcommon/lazyeventapi.h @@ -0,0 +1,204 @@ +/** + * @file lazyeventapi.h + * @author Nat Goodspeed + * @date 2022-06-16 + * @brief Declaring a static module-scope LazyEventAPI registers a specific + * LLEventAPI for future on-demand instantiation. + * + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Copyright (c) 2022, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LAZYEVENTAPI_H) +#define LL_LAZYEVENTAPI_H + +#include "apply.h" +#include "lleventapi.h" +#include "llinstancetracker.h" +#include +#include +#include +#include // std::pair +#include + +namespace LL +{ + /** + * Bundle params we want to pass to LLEventAPI's protected constructor. We + * package them this way so a subclass constructor can simply forward an + * opaque reference to the LLEventAPI constructor. + */ + // This is a class instead of a plain struct mostly so when we forward- + // declare it we don't have to remember the distinction. + class LazyEventAPIParams + { + public: + // package the parameters used by the normal LLEventAPI constructor + std::string name, desc, field; + // bundle LLEventAPI::add() calls collected by LazyEventAPI::add(), so + // the special LLEventAPI constructor we engage can "play back" those + // add() calls + boost::signals2::signal init; + }; + + // The tricky part is: can we capture a sequence of add() calls in the + // LazyEventAPI subclass constructor and then, in effect, replay those + // add() calls on instantiation of the registered LLEventAPI subclass? so + // we don't have to duplicate the add() calls in both constructors? + + // Derive a subclass from LazyEventAPI. Its constructor must pass + // LazyEventAPI's constructor the name, desc, field params. Moreover the + // constructor body must call add(name, desc, *args) for any of the + // LLEventDispatcher add() methods, referencing the LLEventAPI subclass + // methods. + + // LazyEventAPI will store the name, desc, field params for the overall + // LLEventAPI. It will support a single generic add() call accepting name, + // desc, parameter pack. + + // It will hold a std::vector> for each operation. + // It will make all these strings available to LLLeapListener. + + // Maybe what we want is to store a vector of callables (a + // boost::signals2!) and populate it with lambdas, each of which accepts + // LLEventAPI* and calls the relevant add() method by forwarding exactly + // the name, desc and parameter pack. Then, on constructing the target + // LLEventAPI, we just fire the signal, passing the new instance pointer. + + /** + * LazyEventAPIBase implements most of the functionality of LazyEventAPI + * (q.v.), but we need the LazyEventAPI template subclass so we can accept + * the specific LLEventAPI subclass type. + */ + // No LLInstanceTracker key: we don't need to find a specific instance, + // LLLeapListener just needs to be able to enumerate all instances. + class LazyEventAPIBase: public LLInstanceTracker + { + public: + LazyEventAPIBase(const std::string& name, const std::string& desc, + const std::string& field); + virtual ~LazyEventAPIBase(); + + // Do not copy or move: once constructed, LazyEventAPIBase must stay + // put: we bind its instance pointer into a callback. + LazyEventAPIBase(const LazyEventAPIBase&) = delete; + LazyEventAPIBase(LazyEventAPIBase&&) = delete; + LazyEventAPIBase& operator=(const LazyEventAPIBase&) = delete; + LazyEventAPIBase& operator=(LazyEventAPIBase&&) = delete; + + // actually instantiate the companion LLEventAPI subclass + virtual LLEventPump* construct(const std::string& name) = 0; + + // capture add() calls we want to play back on LLEventAPI construction + template + void add(const std::string& name, const std::string& desc, ARGS&&... rest) + { + // capture the metadata separately + mOperations.push_back(std::make_pair(name, desc)); + // Use connect_extended() so the lambda is passed its own + // connection. + // We can't bind an unexpanded parameter pack into a lambda -- + // shame really. Instead, capture it as a std::tuple and then, in + // the lambda, use apply() to convert back to function args. + mParams.init.connect_extended( + [name, desc, rest = std::make_tuple(std::forward(rest)...)] + (const boost::signals2::connection& conn, LLEventAPI* instance) + { + // we only need this connection once + conn.disconnect(); + // Our add() method distinguishes name and desc because we + // capture them separately. But now, because apply() + // expects a tuple specifying ALL the arguments, expand to + // a tuple including add_trampoline() arguments: instance, + // name, desc, rest. + // apply() can't accept a template per se; it needs a + // particular specialization. + apply(&LazyEventAPIBase::add_trampoline, + std::tuple_cat(std::make_tuple(instance, name, desc), + rest)); + }); + } + + // metadata that might be queried by LLLeapListener + std::vector> mOperations; + // Params with which to instantiate the companion LLEventAPI subclass + LazyEventAPIParams mParams; + + private: + // Passing an overloaded function to any function that accepts an + // arbitrary callable is a PITB because you have to specify the + // correct overload. What we want is for the compiler to select the + // correct overload, based on the carefully-wrought enable_ifs in + // LLEventDispatcher. This (one and only) add_trampoline() method + // exists solely to pass to LL::apply(). Once add_trampoline() is + // called with the expanded arguments, we hope the compiler will Do + // The Right Thing in selecting the correct LLEventAPI::add() + // overload. + template + static + void add_trampoline(LLEventAPI* instance, ARGS&&... args) + { + instance->add(std::forward(args)...); + } + + bool mRegistered; + }; + + /** + * LazyEventAPI provides a way to register a particular LLEventAPI to be + * instantiated on demand, that is, when its name is passed to + * LLEventPumps::obtain(). + * + * Derive your listener from LLEventAPI as usual, with its various + * operation methods, but code your constructor to accept + * (const LL::LazyEventAPIParams& params) + * and forward that reference to (the protected) + * LLEventAPI(const LL::LazyEventAPIParams&) constructor. + * + * Then derive your listener registrar from + * LazyEventAPI. The constructor should + * look very like a traditional LLEventAPI constructor: + * + * * pass (name, desc [, field]) to LazyEventAPI's constructor + * * in the body, make a series of add() calls referencing your LLEventAPI + * subclass methods. + * + * You may use any LLEventAPI::add() methods, that is, any + * LLEventDispatcher::add() methods. But the target methods you pass to + * add() must belong to your LLEventAPI subclass, not the LazyEventAPI + * subclass. + * + * Declare a static instance of your LazyEventAPI listener registrar + * class. When it's constructed at static initialization time, it will + * register your LLEventAPI subclass with LLEventPumps. It will also + * collect metadata for the LLEventAPI and its operations to provide to + * LLLeapListener's introspection queries. + * + * When someone later calls LLEventPumps::obtain() to post an event to + * your LLEventAPI subclass, obtain() will instantiate it using + * LazyEventAPI's name, desc, field and add() calls. + */ + template + class LazyEventAPI: public LazyEventAPIBase + { + public: + // for subclass constructor to reference handler methods + using listener = EVENTAPI; + + LazyEventAPI(const std::string& name, const std::string& desc, + const std::string& field="op"): + // Forward ctor params to LazyEventAPIBase + LazyEventAPIBase(name, desc, field) + {} + + LLEventPump* construct(const std::string& /*name*/) override + { + // base class has carefully assembled LazyEventAPIParams embedded + // in this instance, just pass to LLEventAPI subclass constructor + return new EVENTAPI(mParams); + } + }; +} // namespace LL + +#endif /* ! defined(LL_LAZYEVENTAPI_H) */ diff --git a/indra/llcommon/lleventapi.cpp b/indra/llcommon/lleventapi.cpp index ff5459c1eb..3d46ef1034 100644 --- a/indra/llcommon/lleventapi.cpp +++ b/indra/llcommon/lleventapi.cpp @@ -35,6 +35,7 @@ // external library headers // other Linden headers #include "llerror.h" +#include "lazyeventapi.h" LLEventAPI::LLEventAPI(const std::string& name, const std::string& desc, const std::string& field): lbase(name, field), @@ -43,6 +44,13 @@ LLEventAPI::LLEventAPI(const std::string& name, const std::string& desc, const s { } +LLEventAPI::LLEventAPI(const LL::LazyEventAPIParams& params): + LLEventAPI(params.name, params.desc, params.field) +{ + // call initialization functions with our brand-new instance pointer + params.init(this); +} + LLEventAPI::~LLEventAPI() { } diff --git a/indra/llcommon/lleventapi.h b/indra/llcommon/lleventapi.h index ed62fa064a..a019458553 100644 --- a/indra/llcommon/lleventapi.h +++ b/indra/llcommon/lleventapi.h @@ -35,6 +35,13 @@ #include "llinstancetracker.h" #include +namespace LL +{ + template + class LazyEventAPI; + class LazyEventAPIParams; +} + /** * LLEventAPI not only provides operation dispatch functionality, inherited * from LLDispatchListener -- it also gives us event API introspection. @@ -45,6 +52,8 @@ class LL_COMMON_API LLEventAPI: public LLDispatchListener, { typedef LLDispatchListener lbase; typedef LLInstanceTracker ibase; + template + friend class LL::LazyEventAPI; public: @@ -137,16 +146,20 @@ public: * @endcode */ LLSD& operator[](const LLSD::String& key) { return mResp[key]; } - - /** - * set the response to the given data - */ - void setResponse(LLSD const & response){ mResp = response; } + + /** + * set the response to the given data + */ + void setResponse(LLSD const & response){ mResp = response; } LLSD mResp, mReq; LLSD::String mKey; }; +protected: + // constructor used only by subclasses registered by LazyEventAPI + LLEventAPI(const LL::LazyEventAPIParams&); + private: std::string mDesc; }; diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index 742d6cf51f..bc53ec3da0 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -706,8 +706,17 @@ LLSD LLEventDispatcher::getMetadata(const std::string& name) const LLDispatchListener::LLDispatchListener(const std::string& pumpname, const std::string& key): LLEventDispatcher(pumpname, key), - mPump(pumpname, true), // allow tweaking for uniqueness - mBoundListener(mPump.listen("self", boost::bind(&LLDispatchListener::process, this, _1))) + // Do NOT tweak the passed pumpname. In practice, when someone + // instantiates a subclass of our LLEventAPI subclass, they intend to + // claim that LLEventPump name in the global LLEventPumps namespace. It + // would be mysterious and distressing if we allowed name tweaking, and + // someone else claimed pumpname first for a completely unrelated + // LLEventPump. Posted events would never reach our subclass listener + // because we would have silently changed its name; meanwhile listeners + // (if any) on that other LLEventPump would be confused by the events + // intended for our subclass. + LLEventStream(pumpname, false), + mBoundListener(listen("self", [this](const LLSD& event){ return process(event); })) { } diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index 1b3e834aeb..ce9d3775cc 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -165,12 +165,12 @@ public: * When calling this name, pass an LLSD::Array. Each entry in turn will be * converted to the corresponding parameter type using LLSDParam. */ - template - typename std::enable_if< - boost::function_types::is_nonmember_callable_builtin::value - >::type add(const std::string& name, - const std::string& desc, - Function f); + // enable_if usage per https://stackoverflow.com/a/39913395/5533635 + template::value + >::type> + void add(const std::string& name, const std::string& desc, Function f); /** * Register a nonstatic class method with arbitrary parameters. @@ -189,14 +189,13 @@ public: * When calling this name, pass an LLSD::Array. Each entry in turn will be * converted to the corresponding parameter type using LLSDParam. */ - template - typename std::enable_if< - boost::function_types::is_member_function_pointer::value && - ! std::is_convertible::value - >::type add(const std::string& name, - const std::string& desc, - Method f, - const InstanceGetter& getter); + template::value && + ! std::is_convertible::value + >::type> + void add(const std::string& name, const std::string& desc, Method f, + const InstanceGetter& getter); /** * Register a free function with arbitrary parameters. (This also works @@ -213,14 +212,12 @@ public: * an LLSD::Array using LLSDArgsMapper and then convert each entry in turn * to the corresponding parameter type using LLSDParam. */ - template - typename std::enable_if< - boost::function_types::is_nonmember_callable_builtin::value - >::type add(const std::string& name, - const std::string& desc, - Function f, - const LLSD& params, - const LLSD& defaults=LLSD()); + template::value + >::type> + void add(const std::string& name, const std::string& desc, Function f, + const LLSD& params, const LLSD& defaults=LLSD()); /** * Register a nonstatic class method with arbitrary parameters. @@ -243,16 +240,14 @@ public: * an LLSD::Array using LLSDArgsMapper and then convert each entry in turn * to the corresponding parameter type using LLSDParam. */ - template - typename std::enable_if< - boost::function_types::is_member_function_pointer::value && - ! std::is_convertible::value - >::type add(const std::string& name, - const std::string& desc, - Method f, - const InstanceGetter& getter, - const LLSD& params, - const LLSD& defaults=LLSD()); + template::value && + ! std::is_convertible::value + >::type> + void add(const std::string& name, const std::string& desc, Method f, + const InstanceGetter& getter, const LLSD& params, + const LLSD& defaults=LLSD()); //@} @@ -476,9 +471,8 @@ struct LLEventDispatcher::invoker } }; -template -typename std::enable_if< boost::function_types::is_nonmember_callable_builtin::value >::type -LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f) +template +void LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f) { // Construct an invoker_function, a callable accepting const args_source&. // Add to DispatchMap an ArrayParamsDispatchEntry that will handle the @@ -487,13 +481,9 @@ LLEventDispatcher::add(const std::string& name, const std::string& desc, Functio boost::function_types::function_arity::value); } -template -typename std::enable_if< - boost::function_types::is_member_function_pointer::value && - ! std::is_convertible::value ->::type -LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, - const InstanceGetter& getter) +template +void LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, + const InstanceGetter& getter) { // Subtract 1 from the compile-time arity because the getter takes care of // the first parameter. We only need (arity - 1) additional arguments. @@ -501,23 +491,18 @@ LLEventDispatcher::add(const std::string& name, const std::string& desc, Method boost::function_types::function_arity::value - 1); } -template -typename std::enable_if< boost::function_types::is_nonmember_callable_builtin::value >::type -LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f, - const LLSD& params, const LLSD& defaults) +template +void LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f, + const LLSD& params, const LLSD& defaults) { // See comments for previous is_nonmember_callable_builtin add(). addMapParamsDispatchEntry(name, desc, make_invoker(f), params, defaults); } -template -typename std::enable_if< - boost::function_types::is_member_function_pointer::value && - ! std::is_convertible::value ->::type -LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, - const InstanceGetter& getter, - const LLSD& params, const LLSD& defaults) +template +void LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, + const InstanceGetter& getter, + const LLSD& params, const LLSD& defaults) { addMapParamsDispatchEntry(name, desc, make_invoker(f, getter), params, defaults); } @@ -560,17 +545,21 @@ LLEventDispatcher::make_invoker(Method f, const InstanceGetter& getter) * LLEventPump name and dispatch key, and add() its methods. Incoming events * will automatically be dispatched. */ -class LL_COMMON_API LLDispatchListener: public LLEventDispatcher +// Instead of containing an LLEventStream, LLDispatchListener derives from it. +// This allows an LLEventPumps::PumpFactory to return a pointer to an +// LLDispatchListener (subclass) instance, and still have ~LLEventPumps() +// properly clean it up. +class LL_COMMON_API LLDispatchListener: + public LLEventDispatcher, + public LLEventStream { public: LLDispatchListener(const std::string& pumpname, const std::string& key); - - std::string getPumpName() const { return mPump.getName(); } + virtual ~LLDispatchListener() {} private: bool process(const LLSD& event); - LLEventStream mPump; LLTempBoundListener mBoundListener; }; diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp index 5725dad9cc..1a305ec3dc 100644 --- a/indra/llcommon/llevents.cpp +++ b/indra/llcommon/llevents.cpp @@ -90,6 +90,13 @@ bool LLEventPumps::registerTypeFactory(const std::string& type, const TypeFactor return true; } +void LLEventPumps::unregisterTypeFactory(const std::string& type) +{ + auto found = mFactories.find(type); + if (found != mFactories.end()) + mFactories.erase(found); +} + bool LLEventPumps::registerPumpFactory(const std::string& name, const PumpFactory& factory) { // Do we already have a pump by this name? @@ -109,10 +116,30 @@ bool LLEventPumps::registerPumpFactory(const std::string& name, const PumpFactor static std::string nul(1, '\0'); std::string type_name{ nul + name }; mTypes[name] = type_name; - mFactories[type_name] = factory; + // TypeFactory is called with (name, tweak, type), whereas PumpFactory + // accepts only name. We could adapt with std::bind(), but this lambda + // does the trick. + mFactories[type_name] = + [factory] + (const std::string& name, bool /*tweak*/, const std::string& /*type*/) + { return factory(name); }; return true; } +void LLEventPumps::unregisterPumpFactory(const std::string& name) +{ + auto tfound = mTypes.find(name); + if (tfound != mTypes.end()) + { + auto ffound = mFactories.find(tfound->second); + if (ffound != mFactories.end()) + { + mFactories.erase(ffound); + } + mTypes.erase(tfound); + } +} + LLEventPump& LLEventPumps::obtain(const std::string& name) { PumpMap::iterator found = mPumpMap.find(name); diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h index 38adc31121..c1dbf4392f 100644 --- a/indra/llcommon/llevents.h +++ b/indra/llcommon/llevents.h @@ -280,6 +280,7 @@ public: * a TypeFactory for the specified @a type name. */ bool registerTypeFactory(const std::string& type, const TypeFactory& factory); + void unregisterTypeFactory(const std::string& type); /// function passed to registerPumpFactory() typedef std::function PumpFactory; @@ -304,6 +305,7 @@ public: * instantiated an LLEventPump(name), so obtain(name) returned that. */ bool registerPumpFactory(const std::string& name, const PumpFactory& factory); + void unregisterPumpFactory(const std::string& name); /** * Find the named LLEventPump instance. If it exists post the message to it. @@ -362,13 +364,13 @@ testable: typedef std::set PumpSet; PumpSet mOurPumps; // for make(), map string type name to LLEventPump subclass factory function - typedef std::map PumpFactories; + typedef std::map TypeFactories; // Data used by make(). // One might think mFactories and mTypes could reasonably be static. So // they could -- if not for the fact that make() or obtain() might be // called before this module's static variables have been initialized. // This is why we use singletons in the first place. - PumpFactories mFactories; + TypeFactories mFactories; // for obtain(), map desired string instance name to string type when // obtain() must create the instance diff --git a/indra/llcommon/tests/lazyeventapi_test.cpp b/indra/llcommon/tests/lazyeventapi_test.cpp new file mode 100644 index 0000000000..6639c5e540 --- /dev/null +++ b/indra/llcommon/tests/lazyeventapi_test.cpp @@ -0,0 +1,89 @@ +/** + * @file lazyeventapi_test.cpp + * @author Nat Goodspeed + * @date 2022-06-18 + * @brief Test for lazyeventapi. + * + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Copyright (c) 2022, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "lazyeventapi.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "../test/lltut.h" +#include "llevents.h" + +// LLEventAPI listener subclass +class MyListener: public LLEventAPI +{ +public: + MyListener(const LL::LazyEventAPIParams& params): + LLEventAPI(params) + {} + + void get(const LLSD& event) + { + std::cout << "MyListener::get() got " << event << std::endl; + } +}; + +// LazyEventAPI registrar subclass +class MyRegistrar: public LL::LazyEventAPI +{ + using super = LL::LazyEventAPI; + using super::listener; +public: + MyRegistrar(): + super("Test", "This is a test LLEventAPI") + { + add("get", "This is a get operation", &listener::get); + } +}; +// Normally we'd declare a static instance of MyRegistrar -- but because we +// may want to test with and without, defer declaration to individual test +// methods. + +/***************************************************************************** +* TUT +*****************************************************************************/ +namespace tut +{ + struct lazyeventapi_data + { + ~lazyeventapi_data() + { + // after every test, reset LLEventPumps + LLEventPumps::deleteSingleton(); + } + }; + typedef test_group lazyeventapi_group; + typedef lazyeventapi_group::object object; + lazyeventapi_group lazyeventapigrp("lazyeventapi"); + + template<> template<> + void object::test<1>() + { + set_test_name("LazyEventAPI"); + // this is where the magic (should) happen + // 'register' still a keyword until C++17 + MyRegistrar regster; + LLEventPumps::instance().obtain("Test").post("hey"); + } + + template<> template<> + void object::test<2>() + { + set_test_name("No LazyEventAPI"); + // Because the MyRegistrar declaration in test<1>() is local, because + // it has been destroyed, we fully expect NOT to reach a MyListener + // instance with this post. + LLEventPumps::instance().obtain("Test").post("moot"); + } +} // namespace tut -- cgit v1.2.3 From 490de3ab6e3a2b9dd8668c2093e265f36324f82e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 21 Jun 2022 14:46:59 -0400 Subject: DRTVWR-564: We don't need LLEventAPI to befriend LazyEventAPI. --- indra/llcommon/lleventapi.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/indra/llcommon/lleventapi.h b/indra/llcommon/lleventapi.h index a019458553..25f6becd8b 100644 --- a/indra/llcommon/lleventapi.h +++ b/indra/llcommon/lleventapi.h @@ -37,8 +37,6 @@ namespace LL { - template - class LazyEventAPI; class LazyEventAPIParams; } @@ -52,8 +50,6 @@ class LL_COMMON_API LLEventAPI: public LLDispatchListener, { typedef LLDispatchListener lbase; typedef LLInstanceTracker ibase; - template - friend class LL::LazyEventAPI; public: -- cgit v1.2.3 From fdc0257acbde5a2d5bb201efcc8bb723df09daf8 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 21 Jun 2022 15:23:29 -0400 Subject: DRTVWR-564: Fix LLEventDispatcher::addMethod() for LazyEventAPI. A classic LLEventAPI subclass calls LLEventDispatcher::add() methods in its own constructor. At that point, addMethod() can reliably dynamic_cast its 'this' pointer to the new subclass. But because of the way LazyEventAPI queues up add() calls, they're invoked in the (new) LLEventAPI constructor itself. The subclass constructor body hasn't even started running, and LLEventDispatcher::addMethod()'s dynamic_cast to the LLEventAPI subclass returns nullptr. addMethod() claims the new subclass isn't derived from LLEventDispatcher, which is confusing since it is. It works to change addMethod()'s dynamic_cast to static_cast. Flesh out lazyeventapi_test.cpp. post() maps with "op" keys to actually try to engage the registered operation. Give the operation an observable side effect; use ensure_mumble() to verify. Also verify that LazyEventAPI has captured the subject LLEventAPI's metadata in a way we can retrieve. --- indra/llcommon/lleventdispatcher.h | 2 +- indra/llcommon/tests/lazyeventapi_test.cpp | 47 ++++++++++++++++++++++++++---- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index ce9d3775cc..2e140329f3 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -329,7 +329,7 @@ private: void addMethod(const std::string& name, const std::string& desc, const METHOD& method, const LLSD& required) { - CLASS* downcast = dynamic_cast(this); + CLASS* downcast = static_cast(this); if (! downcast) { addFail(name, typeid(CLASS).name()); diff --git a/indra/llcommon/tests/lazyeventapi_test.cpp b/indra/llcommon/tests/lazyeventapi_test.cpp index 6639c5e540..4c78fd7105 100644 --- a/indra/llcommon/tests/lazyeventapi_test.cpp +++ b/indra/llcommon/tests/lazyeventapi_test.cpp @@ -19,18 +19,25 @@ // other Linden headers #include "../test/lltut.h" #include "llevents.h" +#include "llsdutil.h" + +// observable side effect, solely for testing +static LLSD data; // LLEventAPI listener subclass class MyListener: public LLEventAPI { public: + // need this trivial forwarding constructor + // (of course do any other initialization your subclass requires) MyListener(const LL::LazyEventAPIParams& params): LLEventAPI(params) {} - void get(const LLSD& event) + // example operation, registered by LazyEventAPI subclass below + void set_data(const LLSD& event) { - std::cout << "MyListener::get() got " << event << std::endl; + data = event["data"]; } }; @@ -40,14 +47,17 @@ class MyRegistrar: public LL::LazyEventAPI using super = LL::LazyEventAPI; using super::listener; public: + // LazyEventAPI subclass initializes like a classic LLEventAPI subclass + // constructor, with API name and desc plus add() calls for the defined + // operations MyRegistrar(): super("Test", "This is a test LLEventAPI") { - add("get", "This is a get operation", &listener::get); + add("set", "This is a set operation", &listener::set_data); } }; // Normally we'd declare a static instance of MyRegistrar -- but because we -// may want to test with and without, defer declaration to individual test +// want to test both with and without, defer declaration to individual test // methods. /***************************************************************************** @@ -57,6 +67,11 @@ namespace tut { struct lazyeventapi_data { + lazyeventapi_data() + { + // before every test, reset 'data' + data.clear(); + } ~lazyeventapi_data() { // after every test, reset LLEventPumps @@ -74,7 +89,8 @@ namespace tut // this is where the magic (should) happen // 'register' still a keyword until C++17 MyRegistrar regster; - LLEventPumps::instance().obtain("Test").post("hey"); + LLEventPumps::instance().obtain("Test").post(llsd::map("op", "set", "data", "hey")); + ensure_equals("failed to set data", data.asString(), "hey"); } template<> template<> @@ -84,6 +100,25 @@ namespace tut // Because the MyRegistrar declaration in test<1>() is local, because // it has been destroyed, we fully expect NOT to reach a MyListener // instance with this post. - LLEventPumps::instance().obtain("Test").post("moot"); + LLEventPumps::instance().obtain("Test").post(llsd::map("op", "set", "data", "moot")); + ensure("accidentally set data", ! data.isDefined()); + } + + template<> template<> + void object::test<3>() + { + set_test_name("LazyEventAPI metadata"); + MyRegistrar regster; + const MyRegistrar* found = nullptr; + for (const auto& registrar : LL::LazyEventAPIBase::instance_snapshot()) + if ((found = dynamic_cast(®istrar))) + break; + ensure("Failed to find MyRegistrar via LLInstanceTracker", found); + ensure_equals("wrong API name", found->mParams.name, "Test"); + ensure_contains("wrong API desc", found->mParams.desc, "test LLEventAPI"); + ensure_equals("wrong API field", found->mParams.field, "op"); + ensure_equals("failed to find operations", found->mOperations.size(), 1); + ensure_equals("wrong operation name", found->mOperations[0].first, "set"); + ensure_contains("wrong operation desc", found->mOperations[0].second, "set operation"); } } // namespace tut -- cgit v1.2.3 From fa3a67f56b15d81bfd22f744314a7d9aa35bf90e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 21 Jun 2022 15:50:08 -0400 Subject: DRTVWR-564: Remove implementation notes from before implementation. --- indra/llcommon/lazyeventapi.h | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/indra/llcommon/lazyeventapi.h b/indra/llcommon/lazyeventapi.h index 2e947899dc..7267a3e4ec 100644 --- a/indra/llcommon/lazyeventapi.h +++ b/indra/llcommon/lazyeventapi.h @@ -42,30 +42,6 @@ namespace LL boost::signals2::signal init; }; - // The tricky part is: can we capture a sequence of add() calls in the - // LazyEventAPI subclass constructor and then, in effect, replay those - // add() calls on instantiation of the registered LLEventAPI subclass? so - // we don't have to duplicate the add() calls in both constructors? - - // Derive a subclass from LazyEventAPI. Its constructor must pass - // LazyEventAPI's constructor the name, desc, field params. Moreover the - // constructor body must call add(name, desc, *args) for any of the - // LLEventDispatcher add() methods, referencing the LLEventAPI subclass - // methods. - - // LazyEventAPI will store the name, desc, field params for the overall - // LLEventAPI. It will support a single generic add() call accepting name, - // desc, parameter pack. - - // It will hold a std::vector> for each operation. - // It will make all these strings available to LLLeapListener. - - // Maybe what we want is to store a vector of callables (a - // boost::signals2!) and populate it with lambdas, each of which accepts - // LLEventAPI* and calls the relevant add() method by forwarding exactly - // the name, desc and parameter pack. Then, on constructing the target - // LLEventAPI, we just fire the signal, passing the new instance pointer. - /** * LazyEventAPIBase implements most of the functionality of LazyEventAPI * (q.v.), but we need the LazyEventAPI template subclass so we can accept -- cgit v1.2.3 From f9d810ac2a02ef96c843e214c7479146dd4f4157 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 21 Jun 2022 17:24:03 -0400 Subject: DRTVWR-564: Per NickyD, need not test static_cast result for nullptr. --- indra/llcommon/lleventdispatcher.cpp | 7 ------- indra/llcommon/lleventdispatcher.h | 10 +--------- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index bc53ec3da0..7ba8c5ada7 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -586,13 +586,6 @@ void LLEventDispatcher::add(const std::string& name, const std::string& desc, new LLSDDispatchEntry(desc, callable, required)))); } -void LLEventDispatcher::addFail(const std::string& name, const std::string& classname) const -{ - LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << ")::add(" << name - << "): " << classname << " is not a subclass " - << "of LLEventDispatcher" << LL_ENDL; -} - /// Unregister a callable bool LLEventDispatcher::remove(const std::string& name) { diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index 2e140329f3..6d1df86fea 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -330,16 +330,8 @@ private: const METHOD& method, const LLSD& required) { CLASS* downcast = static_cast(this); - if (! downcast) - { - addFail(name, typeid(CLASS).name()); - } - else - { - add(name, desc, boost::bind(method, downcast, _1), required); - } + add(name, desc, boost::bind(method, downcast, _1), required); } - void addFail(const std::string& name, const std::string& classname) const; std::string try_call_log(const std::string& key, const std::string& name, const LLSD& event) const; std::string try_call(const std::string& key, const std::string& name, -- cgit v1.2.3 From 6b53036f7499a4e42813378009050eaf02c0b69d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 22 Jun 2022 10:51:11 -0400 Subject: DRTVWR-564: Allow LLLeapListener to report LazyEventAPIs too. One important factor in the design of LazyEventAPI was the desire to allow LLLeapListener to query metadata for an LLEventAPI even if it hasn't yet been instantiated by LazyEventAPI. That's why LazyEventAPI requires the same metadata required by a classic LLEventAPI. Instead of just publicly exposing its data members, give LazyEventAPI a query API mimicking LLEventAPI / LLEventDispatcher. Protect data members and private methods. Adapt lazyeventapi_test.cpp accordingly. Extend LLLeapListener::getAPIs() and getAPI() to look through LazyEventAPIBase instances after first checking existing LLEventAPI instances. Because the query API for LazyEventAPIBase mimics LLEventAPI's, extract getAPI()'s actual metadata reporting to a new internal template function reportAPI(). While we're touching LLLeapListener, we no longer need BOOST_FOREACH(). --- indra/llcommon/lazyeventapi.cpp | 19 ++++++++ indra/llcommon/lazyeventapi.h | 36 ++++++++++++--- indra/llcommon/llleaplistener.cpp | 70 ++++++++++++++++++++++-------- indra/llcommon/tests/lazyeventapi_test.cpp | 24 +++++++--- 4 files changed, 120 insertions(+), 29 deletions(-) diff --git a/indra/llcommon/lazyeventapi.cpp b/indra/llcommon/lazyeventapi.cpp index aefc2db6da..028af9f33f 100644 --- a/indra/llcommon/lazyeventapi.cpp +++ b/indra/llcommon/lazyeventapi.cpp @@ -15,9 +15,11 @@ #include "lazyeventapi.h" // STL headers // std headers +#include // std::find_if // external library headers // other Linden headers #include "llevents.h" +#include "llsdutil.h" LL::LazyEventAPIBase::LazyEventAPIBase( const std::string& name, const std::string& desc, const std::string& field) @@ -51,3 +53,20 @@ LL::LazyEventAPIBase::~LazyEventAPIBase() LLEventPumps::instance().unregisterPumpFactory(mParams.name); } } + +LLSD LL::LazyEventAPIBase::getMetadata(const std::string& name) const +{ + // Since mOperations is a vector rather than a map, just search. + auto found = std::find_if(mOperations.begin(), mOperations.end(), + [&name](const auto& namedesc) + { return (namedesc.first == name); }); + if (found == mOperations.end()) + return {}; + + // LLEventDispatcher() supplements the returned metadata in different + // ways, depending on metadata provided to the specific add() method. + // Don't try to emulate all that. At some point we might consider more + // closely unifying LLEventDispatcher machinery with LazyEventAPI, but for + // now this will have to do. + return llsd::map("name", found->first, "desc", found->second); +} diff --git a/indra/llcommon/lazyeventapi.h b/indra/llcommon/lazyeventapi.h index 7267a3e4ec..a815b119f0 100644 --- a/indra/llcommon/lazyeventapi.h +++ b/indra/llcommon/lazyeventapi.h @@ -63,9 +63,6 @@ namespace LL LazyEventAPIBase& operator=(const LazyEventAPIBase&) = delete; LazyEventAPIBase& operator=(LazyEventAPIBase&&) = delete; - // actually instantiate the companion LLEventAPI subclass - virtual LLEventPump* construct(const std::string& name) = 0; - // capture add() calls we want to play back on LLEventAPI construction template void add(const std::string& name, const std::string& desc, ARGS&&... rest) @@ -96,12 +93,40 @@ namespace LL }); } + // The following queries mimic the LLEventAPI / LLEventDispatcher + // query API. + + // Get the string name of the subject LLEventAPI + std::string getName() const { return mParams.name; } + // Get the documentation string + std::string getDesc() const { return mParams.desc; } + // Retrieve the LLSD key we use for dispatching + std::string getDispatchKey() const { return mParams.field; } + + // operations + using NameDesc = std::pair; + + private: // metadata that might be queried by LLLeapListener - std::vector> mOperations; + std::vector mOperations; + + public: + using const_iterator = decltype(mOperations)::const_iterator; + const_iterator begin() const { return mOperations.begin(); } + const_iterator end() const { return mOperations.end(); } + LLSD getMetadata(const std::string& name) const; + + protected: // Params with which to instantiate the companion LLEventAPI subclass LazyEventAPIParams mParams; private: + // true if we successfully registered our LLEventAPI on construction + bool mRegistered; + + // actually instantiate the companion LLEventAPI subclass + virtual LLEventPump* construct(const std::string& name) = 0; + // Passing an overloaded function to any function that accepts an // arbitrary callable is a PITB because you have to specify the // correct overload. What we want is for the compiler to select the @@ -117,8 +142,6 @@ namespace LL { instance->add(std::forward(args)...); } - - bool mRegistered; }; /** @@ -168,6 +191,7 @@ namespace LL LazyEventAPIBase(name, desc, field) {} + private: LLEventPump* construct(const std::string& /*name*/) override { // base class has carefully assembled LazyEventAPIParams embedded diff --git a/indra/llcommon/llleaplistener.cpp b/indra/llcommon/llleaplistener.cpp index 11bfec1b31..471f52e91c 100644 --- a/indra/llcommon/llleaplistener.cpp +++ b/indra/llcommon/llleaplistener.cpp @@ -14,14 +14,16 @@ // associated header #include "llleaplistener.h" // STL headers -#include +#include // std::find_if #include +#include +#include // std headers // external library headers -#include // other Linden headers -#include "lluuid.h" +#include "lazyeventapi.h" #include "llsdutil.h" +#include "lluuid.h" #include "stringize.h" /***************************************************************************** @@ -110,7 +112,7 @@ LLLeapListener::~LLLeapListener() // value_type, and Bad Things would happen if you copied an // LLTempBoundListener. (Destruction of the original would disconnect the // listener, invalidating every stored connection.) - BOOST_FOREACH(ListenersMap::value_type& pair, mListeners) + for (ListenersMap::value_type& pair : mListeners) { pair.second.disconnect(); } @@ -208,31 +210,65 @@ void LLLeapListener::getAPIs(const LLSD& request) const { Response reply(LLSD(), request); + // first, traverse existing LLEventAPI instances + std::set instances; for (auto& ea : LLEventAPI::instance_snapshot()) { - LLSD info; - info["desc"] = ea.getDesc(); - reply[ea.getName()] = info; + // remember which APIs are actually instantiated + instances.insert(ea.getName()); + reply[ea.getName()] = llsd::map("desc", ea.getDesc()); + } + // supplement that with *potential* instances: that is, instances of + // LazyEventAPI that can each instantiate an LLEventAPI on demand + for (const auto& lea : LL::LazyEventAPIBase::instance_snapshot()) + { + // skip any LazyEventAPI that's already instantiated its LLEventAPI + if (instances.find(lea.getName()) == instances.end()) + { + reply[lea.getName()] = llsd::map("desc", lea.getDesc()); + } } } +// Because LazyEventAPI deliberately mimics LLEventAPI's query API, this +// function can be passed either -- even though they're unrelated types. +template +void reportAPI(LLEventAPI::Response& reply, const API& api) +{ + reply["name"] = api.getName(); + reply["desc"] = api.getDesc(); + reply["key"] = api.getDispatchKey(); + LLSD ops; + for (const auto& namedesc : api) + { + ops.append(api.getMetadata(namedesc.first)); + } + reply["ops"] = ops; +} + void LLLeapListener::getAPI(const LLSD& request) const { Response reply(LLSD(), request); - auto found = LLEventAPI::getInstance(request["api"]); - if (found) + // check first among existing LLEventAPI instances + auto foundea = LLEventAPI::getInstance(request["api"]); + if (foundea) + { + reportAPI(reply, *foundea); + } + else { - reply["name"] = found->getName(); - reply["desc"] = found->getDesc(); - reply["key"] = found->getDispatchKey(); - LLSD ops; - for (LLEventAPI::const_iterator oi(found->begin()), oend(found->end()); - oi != oend; ++oi) + // Here the requested LLEventAPI doesn't yet exist, but do we have a + // registered LazyEventAPI for it? + LL::LazyEventAPIBase::instance_snapshot snap; + auto foundlea = std::find_if(snap.begin(), snap.end(), + [api = request["api"].asString()] + (const auto& lea) + { return (lea.getName() == api); }); + if (foundlea != snap.end()) { - ops.append(found->getMetadata(oi->first)); + reportAPI(reply, *foundlea); } - reply["ops"] = ops; } } diff --git a/indra/llcommon/tests/lazyeventapi_test.cpp b/indra/llcommon/tests/lazyeventapi_test.cpp index 4c78fd7105..31b2d6d17f 100644 --- a/indra/llcommon/tests/lazyeventapi_test.cpp +++ b/indra/llcommon/tests/lazyeventapi_test.cpp @@ -109,16 +109,28 @@ namespace tut { set_test_name("LazyEventAPI metadata"); MyRegistrar regster; + // Of course we have 'regster' in hand; we don't need to search for + // it. But this next test verifies that we can find (all) LazyEventAPI + // instances using LazyEventAPIBase::instance_snapshot. Normally we + // wouldn't search; normally we'd just look at each instance in the + // loop body. const MyRegistrar* found = nullptr; for (const auto& registrar : LL::LazyEventAPIBase::instance_snapshot()) if ((found = dynamic_cast(®istrar))) break; ensure("Failed to find MyRegistrar via LLInstanceTracker", found); - ensure_equals("wrong API name", found->mParams.name, "Test"); - ensure_contains("wrong API desc", found->mParams.desc, "test LLEventAPI"); - ensure_equals("wrong API field", found->mParams.field, "op"); - ensure_equals("failed to find operations", found->mOperations.size(), 1); - ensure_equals("wrong operation name", found->mOperations[0].first, "set"); - ensure_contains("wrong operation desc", found->mOperations[0].second, "set operation"); + + ensure_equals("wrong API name", found->getName(), "Test"); + ensure_contains("wrong API desc", found->getDesc(), "test LLEventAPI"); + ensure_equals("wrong API field", found->getDispatchKey(), "op"); + // Normally we'd just iterate over *found. But for test purposes, + // actually capture the range of NameDesc pairs in a vector. + std::vector ops{ found->begin(), found->end() }; + ensure_equals("failed to find operations", ops.size(), 1); + ensure_equals("wrong operation name", ops[0].first, "set"); + ensure_contains("wrong operation desc", ops[0].second, "set operation"); + LLSD metadata{ found->getMetadata(ops[0].first) }; + ensure_equals("bad metadata name", metadata["name"].asString(), ops[0].first); + ensure_equals("bad metadata desc", metadata["desc"].asString(), ops[0].second); } } // namespace tut -- cgit v1.2.3 From 8e70575be833cf2056b1ceef7ef842cbc7700ce0 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 12 May 2023 09:42:19 -0400 Subject: SL-18837: Remove C++17 override, given build-variables change. Until now, the viewer has explicitly set(CMAKE_CXX_STANDARD 17) with a comment explaining that we hadn't dared add -std=c++17 to build-variables/variables because we didn't know if we could build all the component autobuild packages with that switch. Now that we've successfully built all of them with that switch, we've updated the viewer branch of build-variables, so we can remove the viewer's CMAKE_CXX_STANDARD override. --- indra/CMakeLists.txt | 9 --------- 1 file changed, 9 deletions(-) diff --git a/indra/CMakeLists.txt b/indra/CMakeLists.txt index 205ce402a0..500ffa3e8b 100644 --- a/indra/CMakeLists.txt +++ b/indra/CMakeLists.txt @@ -29,15 +29,6 @@ else() set( USE_AUTOBUILD_3P ON ) endif() -# The viewer code base can now be successfully compiled with -std=c++14. But -# turning that on in the generic viewer-build-variables/variables file would -# potentially require tweaking each of our ~50 third-party library builds. -# Until we decide to set -std=c++14 in viewer-build-variables/variables, set -# it locally here: we want to at least prevent inadvertently reintroducing -# viewer code that would fail with C++14. -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - include(Variables) include(BuildVersion) -- cgit v1.2.3 From 3f34acaa18fbe44241ab5fc9042d3b7e9f611fe1 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Fri, 30 Jun 2023 14:01:30 +0200 Subject: SL-19816 Home location on world map for invalid locations --- indra/newview/llagent.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index 8cc9be7244..a80c285a6e 100644 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -2534,12 +2534,6 @@ void LLAgent::setStartPosition( U32 location_id ) if (!requestPostCapability("HomeLocation", body, boost::bind(&LLAgent::setStartPositionSuccess, this, _1))) LL_WARNS() << "Unable to post to HomeLocation capability." << LL_ENDL; - - const U32 HOME_INDEX = 1; - if( HOME_INDEX == location_id ) - { - setHomePosRegion( mRegionp->getHandle(), getPositionAgent() ); - } } void LLAgent::setStartPositionSuccess(const LLSD &result) -- cgit v1.2.3 From bea1bbe955ef466fc9be81415cef72030e326e99 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Fri, 30 Jun 2023 15:50:00 +0200 Subject: SL-19565 Prefs Bug when searching in Prefs --- indra/llui/lltabcontainer.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/indra/llui/lltabcontainer.cpp b/indra/llui/lltabcontainer.cpp index 8c841540a5..f48fc567b2 100644 --- a/indra/llui/lltabcontainer.cpp +++ b/indra/llui/lltabcontainer.cpp @@ -2143,14 +2143,19 @@ void LLTabContainer::commitHoveredButton(S32 x, S32 y) { if (!getTabsHidden() && hasMouseCapture()) { - for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) + for (tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) { - LLTabTuple* tuple = *iter; - S32 local_x = x - tuple->mButton->getRect().mLeft; - S32 local_y = y - tuple->mButton->getRect().mBottom; - if (tuple->mButton->pointInView(local_x, local_y) && tuple->mButton->getEnabled() && !tuple->mTabPanel->getVisible()) + LLButton* button = (*iter)->mButton; + LLPanel* panel = (*iter)->mTabPanel; + if (button->getEnabled() && button->getVisible() && !panel->getVisible()) { - tuple->mButton->onCommit(); + S32 local_x = x - button->getRect().mLeft; + S32 local_y = y - button->getRect().mBottom; + if (button->pointInView(local_x, local_y)) + { + button->onCommit(); + break; + } } } } -- cgit v1.2.3 From 9e703ac967a7805c6776cc983a4291a75e594e44 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 5 Jul 2023 02:27:42 +0300 Subject: SL-19716 and SL-19437 viewer_manager --- autobuild.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/autobuild.xml b/autobuild.xml index b1cfe79275..acab5e7bb2 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -2700,9 +2700,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive hash - 9e1b5515ab59b4e9cfeef6626d65d03d + 273d73ca6d34cf03a3ac8e9d2b5b2355 url - https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/108609/945996/viewer_manager-3.0.577252-darwin64-577252.tar.bz2 + https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/116533/1002590/viewer_manager-3.0.580869-darwin64-580869.tar.bz2 name darwin64 @@ -2712,9 +2712,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive hash - a3c599595ecc8fb987a5499fca42520a + 6bc35b062677b04ab83c15be1be2fced url - https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/108610/946003/viewer_manager-3.0.577252-windows-577252.tar.bz2 + https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/116534/1002596/viewer_manager-3.0.580869-windows-580869.tar.bz2 name windows @@ -2725,7 +2725,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors source_type hg version - 3.0.577252 + 3.0.580869 vlc-bin -- cgit v1.2.3 From 374de322bf8777121bc6ad12455980261a8b4b86 Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Wed, 5 Jul 2023 20:48:54 +0300 Subject: SL-19950 don't try to change fly state if it's not needed --- indra/newview/llagent.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index a80c285a6e..289a9caea4 100644 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -481,7 +481,11 @@ void LLAgent::init() // *Note: this is where LLViewerCamera::getInstance() used to be constructed. - setFlying( gSavedSettings.getBOOL("FlyingAtExit") ); + bool is_flying = gSavedSettings.getBOOL("FlyingAtExit"); + if(is_flying) + { + setFlying(is_flying); + } *mEffectColor = LLUIColorTable::instance().getColor("EffectColor"); -- cgit v1.2.3 From 5928afda8a5db7875a9d4743fb04daf3c8c51be4 Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Thu, 6 Jul 2023 21:46:19 +0300 Subject: SL-19702 restore previous double clicking behavior for objects (#266) --- indra/newview/llviewerinput.cpp | 5 +++-- indra/newview/llviewerwindow.cpp | 3 ++- indra/newview/llviewerwindow.h | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/indra/newview/llviewerinput.cpp b/indra/newview/llviewerinput.cpp index 6bab2c2100..226e0a9a56 100644 --- a/indra/newview/llviewerinput.cpp +++ b/indra/newview/llviewerinput.cpp @@ -1597,7 +1597,8 @@ bool LLViewerInput::scanKey(KEY key, BOOL key_down, BOOL key_up, BOOL key_level) BOOL LLViewerInput::handleMouse(LLWindow *window_impl, LLCoordGL pos, MASK mask, EMouseClickType clicktype, BOOL down) { - BOOL handled = gViewerWindow->handleAnyMouseClick(window_impl, pos, mask, clicktype, down); + bool is_toolmgr_action = false; + BOOL handled = gViewerWindow->handleAnyMouseClick(window_impl, pos, mask, clicktype, down, is_toolmgr_action); if (clicktype != CLICK_NONE) { @@ -1616,7 +1617,7 @@ BOOL LLViewerInput::handleMouse(LLWindow *window_impl, LLCoordGL pos, MASK mask, // If the first LMB click is handled by the menu, skip the following double click static bool skip_double_click = false; - if (clicktype == CLICK_LEFT && down ) + if (clicktype == CLICK_LEFT && down && !is_toolmgr_action) { skip_double_click = handled; } diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index b9fcc25310..e8fd74b37b 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -1050,7 +1050,7 @@ void LLViewerWindow::handlePieMenu(S32 x, S32 y, MASK mask) } } -BOOL LLViewerWindow::handleAnyMouseClick(LLWindow *window, LLCoordGL pos, MASK mask, EMouseClickType clicktype, BOOL down) +BOOL LLViewerWindow::handleAnyMouseClick(LLWindow *window, LLCoordGL pos, MASK mask, EMouseClickType clicktype, BOOL down, bool& is_toolmgr_action) { const char* buttonname = ""; const char* buttonstatestr = ""; @@ -1199,6 +1199,7 @@ BOOL LLViewerWindow::handleAnyMouseClick(LLWindow *window, LLCoordGL pos, MASK m if(!gDisconnected && LLToolMgr::getInstance()->getCurrentTool()->handleAnyMouseClick( x, y, mask, clicktype, down ) ) { LLViewerEventRecorder::instance().clear_xui(); + is_toolmgr_action = true; return TRUE; } diff --git a/indra/newview/llviewerwindow.h b/indra/newview/llviewerwindow.h index 1927e01ddb..92905ef21a 100644 --- a/indra/newview/llviewerwindow.h +++ b/indra/newview/llviewerwindow.h @@ -179,7 +179,7 @@ public: void reshapeStatusBarContainer(); - BOOL handleAnyMouseClick(LLWindow *window, LLCoordGL pos, MASK mask, EMouseClickType clicktype, BOOL down); + BOOL handleAnyMouseClick(LLWindow *window, LLCoordGL pos, MASK mask, EMouseClickType clicktype, BOOL down, bool &is_toolmgr_action); // // LLWindowCallback interface implementation -- cgit v1.2.3 From f5385a80266e67bc8b2901e78ddfa886d8b61bbc Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Thu, 6 Jul 2023 21:32:44 +0300 Subject: SL-18164 Media type format not shown in the About Land's media tab --- indra/newview/llfloaterurlentry.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/indra/newview/llfloaterurlentry.cpp b/indra/newview/llfloaterurlentry.cpp index 917d6dfcd0..48d6e01d32 100644 --- a/indra/newview/llfloaterurlentry.cpp +++ b/indra/newview/llfloaterurlentry.cpp @@ -175,10 +175,9 @@ void LLFloaterURLEntry::onBtnOK( void* userdata ) // We assume that an empty scheme is an http url, as this is how we will treat it. if(scheme == "") { - scheme = "http"; + scheme = "https"; } - // Discover the MIME type only for "http" scheme. if(!media_url.empty() && (scheme == "http" || scheme == "https")) { @@ -204,13 +203,18 @@ void LLFloaterURLEntry::getMediaTypeCoro(std::string url, LLHandle pa LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getMediaTypeCoro", httpPolicy)); LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); LLCore::HttpOptions::ptr_t httpOpts = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); + httpOpts->setFollowRedirects(true); httpOpts->setHeadersOnly(true); + httpHeaders->append(HTTP_OUT_HEADER_ACCEPT, "*/*"); + httpHeaders->append(HTTP_OUT_HEADER_COOKIE, ""); + LL_INFOS("HttpCoroutineAdapter", "genericPostCoro") << "Generic POST for " << url << LL_ENDL; - LLSD result = httpAdapter->getAndSuspend(httpRequest, url, httpOpts); + LLSD result = httpAdapter->getRawAndSuspend(httpRequest, url, httpOpts, httpHeaders); LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); @@ -226,12 +230,6 @@ void LLFloaterURLEntry::getMediaTypeCoro(std::string url, LLHandle pa // which have no mime type set. std::string resolvedMimeType = LLMIMETypes::getDefaultMimeType(); - if (!status) - { - floaterUrlEntry->headerFetchComplete(status.getType(), resolvedMimeType); - return; - } - LLSD resultHeaders = httpResults[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_HEADERS]; if (resultHeaders.has(HTTP_IN_HEADER_CONTENT_TYPE)) -- cgit v1.2.3 From d61e6fc7eca41bba6def1d5904ec829dd71f011f Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Fri, 7 Jul 2023 20:26:51 +0300 Subject: SL-19963 Group Profile's money details have misleading date --- indra/newview/llpanelgrouplandmoney.cpp | 4 ++-- indra/newview/skins/default/xui/en/strings.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/indra/newview/llpanelgrouplandmoney.cpp b/indra/newview/llpanelgrouplandmoney.cpp index a2e136bd5a..f276d6d785 100644 --- a/indra/newview/llpanelgrouplandmoney.cpp +++ b/indra/newview/llpanelgrouplandmoney.cpp @@ -1077,7 +1077,7 @@ void LLGroupMoneyDetailsTabEventHandler::processReply(LLMessageSystem* msg, msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_CurrentInterval, current_interval ); msg->getStringFast(_PREHASH_MoneyData, _PREHASH_StartDate, start_date); - std::string time_str = LLTrans::getString("GroupMoneyDate"); + std::string time_str = LLTrans::getString("GroupMoneyStartDate"); LLSD substitution; // We don't do time zone corrections of the calculated number of seconds @@ -1232,7 +1232,7 @@ void LLGroupMoneySalesTabEventHandler::processReply(LLMessageSystem* msg, // Start with the date. if (text == mImplementationp->mLoadingText) { - std::string time_str = LLTrans::getString("GroupMoneyDate"); + std::string time_str = LLTrans::getString("GroupMoneyStartDate"); LLSD substitution; // We don't do time zone corrections of the calculated number of seconds diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index 01f5b513c7..2be13d9818 100644 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -2847,7 +2847,7 @@ If you continue to receive this message, please contact Second Life support for Balance Credits Debits - [weekday,datetime,utc] [mth,datetime,utc] [day,datetime,utc], [year,datetime,utc] + Transactions since [weekday,datetime,utc] [mth,datetime,utc] [day,datetime,utc], [year,datetime,utc] Acquired Items -- cgit v1.2.3 From e80f0f331dbfca686e0a80c557e9b1e455c8d167 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 11 Jul 2023 03:05:07 +0300 Subject: SL-19986 Crash at LLConversationItemSession::findParticipant Observed on bugsplat, no repro. Something destroys LLConversationItem without cleaning list (some sessions reuse the item, but they aren't supposed to remove it). Either item should inform floater to be properly removed or should be stored as an LLPointer. --- indra/newview/llconversationmodel.h | 2 +- indra/newview/llfloaterimcontainer.cpp | 22 ++++++++++++++++++---- indra/newview/llfloaterimcontainer.h | 2 +- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/indra/newview/llconversationmodel.h b/indra/newview/llconversationmodel.h index 7c6980a7e6..457b2e83fb 100644 --- a/indra/newview/llconversationmodel.h +++ b/indra/newview/llconversationmodel.h @@ -40,7 +40,7 @@ class LLConversationItem; class LLConversationItemSession; class LLConversationItemParticipant; -typedef std::map conversations_items_map; +typedef std::map > conversations_items_map; typedef std::map conversations_widgets_map; typedef std::vector menuentry_vec_t; diff --git a/indra/newview/llfloaterimcontainer.cpp b/indra/newview/llfloaterimcontainer.cpp index 2720b7fcf7..172e672dc5 100644 --- a/indra/newview/llfloaterimcontainer.cpp +++ b/indra/newview/llfloaterimcontainer.cpp @@ -155,6 +155,20 @@ void LLFloaterIMContainer::sessionIDUpdated(const LLUUID& old_session_id, const LLFloaterIMSessionTab::addToHost(new_session_id); } + +LLConversationItem* LLFloaterIMContainer::getSessionModel(const LLUUID& session_id) +{ + conversations_items_map::iterator iter = mConversationsItems.find(session_id); + if (iter == mConversationsItems.end()) + { + return NULL; + } + else + { + return iter->second.get(); + } +} + void LLFloaterIMContainer::sessionRemoved(const LLUUID& session_id) { removeConversationListItem(session_id); @@ -608,7 +622,8 @@ void LLFloaterIMContainer::handleConversationModelEvent(const LLSD& event) } else if (type == "add_participant") { - LLConversationItemSession* session_model = dynamic_cast(mConversationsItems[session_id]); + LLConversationItem* item = getSessionModel(session_id); + LLConversationItemSession* session_model = dynamic_cast(item); LLConversationItemParticipant* participant_model = (session_model ? session_model->findParticipant(participant_id) : NULL); LLIMModel::LLIMSession * im_sessionp = LLIMModel::getInstance()->findIMSession(session_id); if (!participant_view && session_model && participant_model) @@ -1749,10 +1764,9 @@ BOOL LLFloaterIMContainer::selectConversationPair(const LLUUID& session_id, bool void LLFloaterIMContainer::setTimeNow(const LLUUID& session_id, const LLUUID& participant_id) { - LLConversationItemSession* item = dynamic_cast(get_ptr_in_map(mConversationsItems,session_id)); + LLConversationItemSession* item = dynamic_cast(getSessionModel(session_id)); if (item) { - item->setTimeNow(participant_id); mConversationViewModel.requestSortAll(); mConversationsRoot->arrangeAll(); } @@ -1761,7 +1775,7 @@ void LLFloaterIMContainer::setTimeNow(const LLUUID& session_id, const LLUUID& pa void LLFloaterIMContainer::setNearbyDistances() { // Get the nearby chat session: that's the one with uuid nul - LLConversationItemSession* item = dynamic_cast(get_ptr_in_map(mConversationsItems,LLUUID())); + LLConversationItemSession* item = dynamic_cast(getSessionModel(LLUUID())); if (item) { // Get the positions of the nearby avatars and their ids diff --git a/indra/newview/llfloaterimcontainer.h b/indra/newview/llfloaterimcontainer.h index b4a9d377ab..82f3b00ebc 100644 --- a/indra/newview/llfloaterimcontainer.h +++ b/indra/newview/llfloaterimcontainer.h @@ -106,7 +106,7 @@ public: LLConversationViewModel& getRootViewModel() { return mConversationViewModel; } LLUUID getSelectedSession() { return mSelectedSession; } void setSelectedSession(LLUUID sessionID) { mSelectedSession = sessionID; } - LLConversationItem* getSessionModel(const LLUUID& session_id) { return get_ptr_in_map(mConversationsItems,session_id); } + LLConversationItem* getSessionModel(const LLUUID& session_id); LLConversationSort& getSortOrder() { return mConversationViewModel.getSorter(); } // Handling of lists of participants is public so to be common with llfloatersessiontab -- cgit v1.2.3 From 324f0d9b8abad3a74a7c19a6e28f8c77c76b3b83 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 22 Aug 2022 21:00:42 -0400 Subject: DRTVWR-558: Fix builds on macOS 12.5 Monterey. Always search for python3[.exe] instead of plain 'python'. macOS Monterey no longer bundles Python 2 at all. Explicitly make PYTHON_EXECUTABLE a cached value so if the user edits it in CMakeCache.txt, it won't be overwritten by indra/cmake/Python.cmake. Do NOT set DYLD_LIBRARY_PATH for test executables! That has Bad Effects, as discussed in https://stackoverflow.com/q/73418423/5533635. Instead, create symlinks from build-mumble/sharedlibs/Resources -> Release/Resources and from build-mumble/test/Resources -> ../sharedlibs/Release/Resources. For test executables in sharedlibs/RelWithDebInfo and test/RelWithDebInfo, this supports our dylibs' baked-in load path @executable_path/../Resources. That load path assumes running in a standard app bundle (which the viewer in fact does), but we've been avoiding creating an app bundle for every test program. These symlinks allow us to continue doing that while avoiding DYLD_LIBRARY_PATH. Add indra/llcommon/apply.h. The LL::apply() function and its wrapper macro VAPPLY were very useful in diagnosing the problem. Tweak llleap_test.cpp. This source was modified extensively for diagnostic purposes; these are the small improvements that remain. (cherry picked from commit 15d37713b9113a6f70dde48c764df02c76e18cbc) (cherry picked from commit a1adcf1905d1fbc5fe07ff5a627295ccfe461ac4) --- indra/llcommon/tests/llleap_test.cpp | 1 + indra/test/CMakeLists.txt | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index 7ee36a9ea6..168825a8cd 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -17,6 +17,7 @@ // std headers #include // external library headers +//#include #include #include // other Linden headers diff --git a/indra/test/CMakeLists.txt b/indra/test/CMakeLists.txt index b7a39a7450..4a0a8716c4 100644 --- a/indra/test/CMakeLists.txt +++ b/indra/test/CMakeLists.txt @@ -74,6 +74,13 @@ if (WINDOWS) LINK_FLAGS "/NODEFAULTLIB:LIBCMT" LINK_FLAGS_DEBUG "/NODEFAULTLIB:\"LIBCMT;LIBCMTD;MSVCRT\"" ) +elseif (DARWIN) + # Support our "@executable_path/../Resources" load path for our test + # executable. This SHOULD properly be "$/Resources", + # but the CMake $ generator expression isn't evaluated by + # CREATE_LINK, so fudge it. + file(CREATE_LINK "../sharedlibs/Release/Resources" "${CMAKE_BINARY_DIR}/test/Resources" + SYMBOLIC) endif (WINDOWS) set(TEST_EXE $) -- cgit v1.2.3 From c682603417e1ef8290aacf274ff49821bd204c0b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 19 Dec 2022 16:29:06 -0500 Subject: DRTVWR-558: Extend LL::apply() to LLSD array arguments. Make apply(function, std::array) and apply(function, std::vector) available even when we borrow the C++17 implementation of apply(function, std::tuple). Add apply(function, LLSD) with interpretations: * isUndefined() is treated as an empty array, for calling a nullary function * scalar LLSD is treated as a single-entry array, for calling a unary function * isArray() converts function parameters using LLSDParam * isMap() is an error. Add unit tests for all flavors of LL::apply(). (cherry picked from commit 3006c24251c6259d00df9e0f4f66b8a617e6026d) --- indra/llcommon/CMakeLists.txt | 2 + indra/llcommon/apply.h | 23 +++-- indra/llcommon/llsdutil.h | 81 ++++++++++++++++- indra/llcommon/tests/apply_test.cpp | 171 ++++++++++++++++++++++++++++++++++++ 4 files changed, 269 insertions(+), 8 deletions(-) create mode 100644 indra/llcommon/tests/apply_test.cpp diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 91ae2440fa..941d2d7baf 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -293,6 +293,8 @@ if (LL_TESTS) #set(TEST_DEBUG on) set(test_libs llcommon) + LL_ADD_INTEGRATION_TEST(apply "" "${test_libs}") + LL_ADD_INTEGRATION_TEST(bitpack "" "${test_libs}") LL_ADD_INTEGRATION_TEST(classic_callback "" "${test_libs}") LL_ADD_INTEGRATION_TEST(commonmisc "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lazyeventapi "" "${test_libs}") diff --git a/indra/llcommon/apply.h b/indra/llcommon/apply.h index 7c58d63bc0..26753f5017 100644 --- a/indra/llcommon/apply.h +++ b/indra/llcommon/apply.h @@ -13,6 +13,7 @@ #define LL_APPLY_H #include +#include #include namespace LL @@ -54,6 +55,9 @@ namespace LL }, \ (ARGS)) +/***************************************************************************** +* apply(function, tuple) +*****************************************************************************/ #if __cplusplus >= 201703L // C++17 implementation @@ -63,8 +67,8 @@ using std::apply; // Derived from https://stackoverflow.com/a/20441189 // and https://en.cppreference.com/w/cpp/utility/apply -template -auto apply_impl(CALLABLE&& func, TUPLE&& args, std::index_sequence) +template +auto apply_impl(CALLABLE&& func, const std::tuple& args, std::index_sequence) { // call func(unpacked args) return std::forward(func)(std::move(std::get(args))...); @@ -81,6 +85,11 @@ auto apply(CALLABLE&& func, const std::tuple& args) std::index_sequence_for{}); } +#endif // C++14 + +/***************************************************************************** +* apply(function, std::array) +*****************************************************************************/ // per https://stackoverflow.com/a/57510428/5533635 template auto apply(CALLABLE&& func, const std::array& args) @@ -88,13 +97,15 @@ auto apply(CALLABLE&& func, const std::array& args) return apply(std::forward(func), std::tuple_cat(args)); } +/***************************************************************************** +* apply(function, std::vector) +*****************************************************************************/ // per https://stackoverflow.com/a/28411055/5533635 template auto apply_impl(CALLABLE&& func, const std::vector& args, std::index_sequence) { - return apply_impl(std::forward(func), - std::make_tuple(std::forward(args[I])...), - I...); + return apply(std::forward(func), + std::make_tuple(args[I]...)); } // this goes beyond C++17 std::apply() @@ -108,8 +119,6 @@ auto apply(CALLABLE&& func, const std::vector& args) std::make_index_sequence()); } -#endif // C++14 - } // namespace LL #endif /* ! defined(LL_APPLY_H) */ diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h index 372278c51a..eaf8825791 100644 --- a/indra/llcommon/llsdutil.h +++ b/indra/llcommon/llsdutil.h @@ -31,6 +31,7 @@ #include "llsd.h" #include +#include // U32 LL_COMMON_API LLSD ll_sd_from_U32(const U32); @@ -332,6 +333,31 @@ private: T _value; }; +/** + * LLSDParam is for when you don't already have the target parameter + * type in hand. Instantiate LLSDParam(your LLSD object), and the + * templated conversion operator will try to select a more specific LLSDParam + * specialization. + */ +template <> +class LLSDParam +{ +private: + LLSD value_; + +public: + LLSDParam(const LLSD& value): value_(value) {} + + /// if we're literally being asked for an LLSD parameter, avoid infinite + /// recursion + operator LLSD() const { return value_; } + + /// otherwise, instantiate a more specific LLSDParam to convert; that + /// preserves the existing customization mechanism + template + operator T() const { return LLSDParam(value_); } +}; + /** * Turns out that several target types could accept an LLSD param using any of * a few different conversions, e.g. LLUUID's constructor can accept LLUUID or @@ -350,7 +376,7 @@ class LLSDParam \ { \ public: \ LLSDParam(const LLSD& value): \ - _value((T)value.AS()) \ + _value((T)value.AS()) \ {} \ \ operator T() const { return _value; } \ @@ -555,4 +581,57 @@ struct hash } }; } + +namespace LL +{ + +/***************************************************************************** +* apply(function, LLSD array) +*****************************************************************************/ +// Derived from https://stackoverflow.com/a/20441189 +// and https://en.cppreference.com/w/cpp/utility/apply . +// We can't simply make a tuple from the LLSD array and then apply() that +// tuple to the function -- how would make_tuple() deduce the correct +// parameter type for each entry? We must go directly to the target function. +template +auto apply_impl(CALLABLE&& func, const LLSD& array, std::index_sequence) +{ + // call func(unpacked args), using generic LLSDParam to convert each + // entry in 'array' to the target parameter type + return std::forward(func)(LLSDParam(array[I])...); +} + +template +auto apply(CALLABLE&& func, const LLSD& args) +{ + LLSD array; + constexpr auto arity = boost::function_traits::arity; + // LLSD supports a number of types, two of which are aggregates: Map and + // Array. We don't try to support Map: supporting Map would seem to + // promise that we could somehow match the string key to 'func's parameter + // names. Uh sorry, maybe in some future version of C++ with reflection. + assert(! args.isMap()); + // We expect an LLSD array, but what the heck, treat isUndefined() as a + // zero-length array for calling a nullary 'func'. + if (args.isUndefined() || args.isArray()) + { + // this works because LLSD().size() == 0 + assert(args.size() == arity); + array = args; + } + else // args is one of the scalar types + { + // scalar_LLSD.size() == 0, so don't test that here. + // You can pass a scalar LLSD only to a unary 'func'. + assert(arity == 1); + // make an array of it + array = llsd::array(args); + } + return apply_impl(std::forward(func), + array, + std::make_index_sequence()); +} + +} // namespace LL + #endif // LL_LLSDUTIL_H diff --git a/indra/llcommon/tests/apply_test.cpp b/indra/llcommon/tests/apply_test.cpp new file mode 100644 index 0000000000..1f1085a702 --- /dev/null +++ b/indra/llcommon/tests/apply_test.cpp @@ -0,0 +1,171 @@ +/** + * @file apply_test.cpp + * @author Nat Goodspeed + * @date 2022-12-19 + * @brief Test for apply. + * + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Copyright (c) 2022, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "apply.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "../test/lltut.h" +#include "llsd.h" +#include "llsdutil.h" + +/***************************************************************************** +* TUT +*****************************************************************************/ +namespace tut +{ + namespace statics + { + /*------------------------------ data ------------------------------*/ + // Although we're using types from the LLSD namespace, we're not + // constructing LLSD values, but rather instances of the C++ types + // supported by LLSD. + static LLSD::Boolean b{true}; + static LLSD::Integer i{17}; + static LLSD::Real f{3.14}; + static LLSD::String s{ "hello" }; + static LLSD::UUID uu{ "baadf00d-dead-beef-baad-feedb0ef" }; + static LLSD::Date dt{ "2022-12-19" }; + static LLSD::URI uri{ "http://secondlife.com" }; + static LLSD::Binary bin{ 0x01, 0x02, 0x03, 0x04, 0x05 }; + + static std::vector quick + { + "The", "quick", "brown", "fox", "etc." + }; + + static std::array fibs + { + 0, 1, 1, 2, 3 + }; + + // ensure that apply() actually reaches the target method -- + // lack of ensure_equals() failure could be due to no-op apply() + bool called{ false }; + + /*------------------------- test functions -------------------------*/ + void various(LLSD::Boolean b, LLSD::Integer i, LLSD::Real f, const LLSD::String& s, + const LLSD::UUID& uu, const LLSD::Date& dt, + const LLSD::URI& uri, const LLSD::Binary& bin) + { + called = true; + ensure_equals( "b mismatch", b, statics::b); + ensure_equals( "i mismatch", i, statics::i); + ensure_equals( "f mismatch", f, statics::f); + ensure_equals( "s mismatch", s, statics::s); + ensure_equals( "uu mismatch", uu, statics::uu); + ensure_equals( "dt mismatch", dt, statics::dt); + ensure_equals("uri mismatch", uri, statics::uri); + ensure_equals("bin mismatch", bin, statics::bin); + } + + void strings(std::string s0, std::string s1, std::string s2, std::string s3, std::string s4) + { + called = true; + ensure_equals("s0 mismatch", s0, statics::quick[0]); + ensure_equals("s1 mismatch", s1, statics::quick[1]); + ensure_equals("s2 mismatch", s2, statics::quick[2]); + ensure_equals("s3 mismatch", s3, statics::quick[3]); + ensure_equals("s4 mismatch", s4, statics::quick[4]); + } + + void ints(int i0, int i1, int i2, int i3, int i4) + { + called = true; + ensure_equals("i0 mismatch", i0, statics::fibs[0]); + ensure_equals("i1 mismatch", i1, statics::fibs[1]); + ensure_equals("i2 mismatch", i2, statics::fibs[2]); + ensure_equals("i3 mismatch", i3, statics::fibs[3]); + ensure_equals("i4 mismatch", i4, statics::fibs[4]); + } + + void intfunc(int i) + { + called = true; + ensure_equals("i mismatch", i, statics::i); + } + + void voidfunc() + { + called = true; + } + } // namespace statics + + struct apply_data + { + apply_data() + { + // reset called before each test + statics::called = false; + } + }; + typedef test_group apply_group; + typedef apply_group::object object; + apply_group applygrp("apply"); + + template<> template<> + void object::test<1>() + { + set_test_name("apply(tuple)"); + LL::apply(statics::various, + std::make_tuple(statics::b, statics::i, statics::f, statics::s, + statics::uu, statics::dt, statics::uri, statics::bin)); + ensure("apply(tuple) failed", statics::called); + } + + template<> template<> + void object::test<2>() + { + set_test_name("apply(array)"); + LL::apply(statics::ints, statics::fibs); + ensure("apply(array) failed", statics::called); + } + + template<> template<> + void object::test<3>() + { + set_test_name("apply(vector)"); + LL::apply(statics::strings, statics::quick); + ensure("apply(vector) failed", statics::called); + } + + // The various apply(LLSD) tests exercise only the success cases because + // the failure cases trigger assert() fail, which is hard to catch. + template<> template<> + void object::test<4>() + { + set_test_name("apply(LLSD())"); + LL::apply(statics::voidfunc, LLSD()); + ensure("apply(LLSD()) failed", statics::called); + } + + template<> template<> + void object::test<5>() + { + set_test_name("apply(LLSD scalar)"); + LL::apply(statics::intfunc, LLSD(statics::i)); + ensure("apply(LLSD scalar) failed", statics::called); + } + + template<> template<> + void object::test<6>() + { + set_test_name("apply(LLSD array)"); + LL::apply(statics::various, + llsd::array(statics::b, statics::i, statics::f, statics::s, + statics::uu, statics::dt, statics::uri, statics::bin)); + ensure("apply(LLSD array) failed", statics::called); + } +} // namespace tut -- cgit v1.2.3 From 196e49c1f8bd58ab9ce81843c10d452284ca7569 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 19 Dec 2022 17:27:36 -0500 Subject: DRTVWR-558: Add unit test for VAPPLY(). Add to apply_test.cpp a collect() function that incrementally accumulates an arbitrary number of arguments into a std::vector. Construct a std::array to pass it, using VAPPLY(). Clarify in header comments that LL::apply() can't call a variadic function with arguments of dynamic size: std::vector or LLSD. The compiler can deduce how many arguments to pass to a function with a fixed argument list; it can deduce how many arguments to pass to a variadic function with a fixed number of arguments. But it can't compile a call to a variadic function with an arguments data structure whose size can vary at runtime. (cherry picked from commit ceed33396266b123896f7cfb9b90abdf240e1eec) --- indra/llcommon/apply.h | 6 ++++- indra/llcommon/llsdutil.h | 6 +++++ indra/llcommon/tests/apply_test.cpp | 52 ++++++++++++++++++++++++++++++++++++- 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/indra/llcommon/apply.h b/indra/llcommon/apply.h index 26753f5017..9f4c268895 100644 --- a/indra/llcommon/apply.h +++ b/indra/llcommon/apply.h @@ -108,7 +108,11 @@ auto apply_impl(CALLABLE&& func, const std::vector& args, std::index_sequence std::make_tuple(args[I]...)); } -// this goes beyond C++17 std::apply() +/** + * apply(function, std::vector) goes beyond C++17 std::apply(). For this case + * @a function @emph cannot be variadic: the compiler must know at compile + * time how many arguments to pass. This isn't Python. + */ template auto apply(CALLABLE&& func, const std::vector& args) { diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h index eaf8825791..eddaa64bd2 100644 --- a/indra/llcommon/llsdutil.h +++ b/indra/llcommon/llsdutil.h @@ -31,6 +31,7 @@ #include "llsd.h" #include +#include #include // U32 @@ -601,6 +602,11 @@ auto apply_impl(CALLABLE&& func, const LLSD& array, std::index_sequence) return std::forward(func)(LLSDParam(array[I])...); } +/** + * apply(function, LLSD) goes beyond C++17 std::apply(). For this case + * @a function @emph cannot be variadic: the compiler must know at compile + * time how many arguments to pass. This isn't Python. + */ template auto apply(CALLABLE&& func, const LLSD& args) { diff --git a/indra/llcommon/tests/apply_test.cpp b/indra/llcommon/tests/apply_test.cpp index 1f1085a702..28ee3f9c81 100644 --- a/indra/llcommon/tests/apply_test.cpp +++ b/indra/llcommon/tests/apply_test.cpp @@ -15,12 +15,27 @@ #include "apply.h" // STL headers // std headers +#include // external library headers // other Linden headers -#include "../test/lltut.h" #include "llsd.h" #include "llsdutil.h" +// for ensure_equals +std::ostream& operator<<(std::ostream& out, const std::vector& stringvec) +{ + const char* delim = "["; + for (const auto& str : stringvec) + { + out << delim << std::quoted(str); + delim = ", "; + } + return out << ']'; +} + +// the above must be declared BEFORE ensure_equals(std::vector) +#include "../test/lltut.h" + /***************************************************************************** * TUT *****************************************************************************/ @@ -54,6 +69,8 @@ namespace tut // ensure that apply() actually reaches the target method -- // lack of ensure_equals() failure could be due to no-op apply() bool called{ false }; + // capture calls from collect() + std::vector collected; /*------------------------- test functions -------------------------*/ void various(LLSD::Boolean b, LLSD::Integer i, LLSD::Real f, const LLSD::String& s, @@ -101,6 +118,20 @@ namespace tut { called = true; } + + // recursion tail + void collect() + { + called = true; + } + + // collect(arbitrary) + template + void collect(const std::string& first, ARGS&&... rest) + { + statics::collected.push_back(first); + collect(std::forward(rest)...); + } } // namespace statics struct apply_data @@ -109,6 +140,7 @@ namespace tut { // reset called before each test statics::called = false; + statics::collected.clear(); } }; typedef test_group apply_group; @@ -168,4 +200,22 @@ namespace tut statics::uu, statics::dt, statics::uri, statics::bin)); ensure("apply(LLSD array) failed", statics::called); } + + template<> template<> + void object::test<7>() + { + set_test_name("VAPPLY()"); + // Make a std::array from statics::quick. We can't call a + // variadic function with a data structure of dynamic length. + std::array strray; + for (size_t i = 0; i < strray.size(); ++i) + strray[i] = statics::quick[i]; + // This doesn't work: the compiler doesn't know which overload of + // collect() to pass to LL::apply(). + // LL::apply(statics::collect, strray); + // That's what VAPPLY() is for. + VAPPLY(statics::collect, strray); + ensure("VAPPLY() failed", statics::called); + ensure_equals("collected mismatch", statics::collected, statics::quick); + } } // namespace tut -- cgit v1.2.3 From 8855a82f512286bce6bd131d87dcafd303f2a5f6 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 20 Dec 2022 09:48:36 -0500 Subject: DRTVWR-558: Add LL::apply() test for function(const LLSD&). (cherry picked from commit 7d33e00d925614911a7602da1bd79916cc849ad7) --- indra/llcommon/tests/apply_test.cpp | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/indra/llcommon/tests/apply_test.cpp b/indra/llcommon/tests/apply_test.cpp index 28ee3f9c81..9a17afc18c 100644 --- a/indra/llcommon/tests/apply_test.cpp +++ b/indra/llcommon/tests/apply_test.cpp @@ -108,6 +108,12 @@ namespace tut ensure_equals("i4 mismatch", i4, statics::fibs[4]); } + void sdfunc(const LLSD& sd) + { + called = true; + ensure_equals("sd mismatch", sd.asInteger(), statics::i); + } + void intfunc(int i) { called = true; @@ -186,13 +192,23 @@ namespace tut template<> template<> void object::test<5>() { - set_test_name("apply(LLSD scalar)"); + set_test_name("apply(fn(int), LLSD scalar)"); LL::apply(statics::intfunc, LLSD(statics::i)); - ensure("apply(LLSD scalar) failed", statics::called); + ensure("apply(fn(int), LLSD scalar) failed", statics::called); } template<> template<> void object::test<6>() + { + set_test_name("apply(fn(LLSD), LLSD scalar)"); + // This test verifies that LLSDParam doesn't send the compiler + // into infinite recursion when the target is itself LLSD. + LL::apply(statics::sdfunc, LLSD(statics::i)); + ensure("apply(fn(LLSD), LLSD scalar) failed", statics::called); + } + + template<> template<> + void object::test<7>() { set_test_name("apply(LLSD array)"); LL::apply(statics::various, @@ -202,7 +218,7 @@ namespace tut } template<> template<> - void object::test<7>() + void object::test<8>() { set_test_name("VAPPLY()"); // Make a std::array from statics::quick. We can't call a -- cgit v1.2.3 From cc3d21cceac7d6c1b2fc9297330fa819855f6e5b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 20 Dec 2022 09:53:58 -0500 Subject: DRTVWR-558: Tweak LLEventDispatcher. Instead of std::map, use std::unique_ptr as the mapped_type, using emplace() to store new entries. This more correctly captures the desired semantics: we have no intention of passing around the pointers in the map, we just want the map to delete them on destruction. Use std::function instead of boost::function. (cherry picked from commit 7ba53ef82db5683756e296225f0c8b838420a26e) --- indra/llcommon/lleventdispatcher.cpp | 12 +++--------- indra/llcommon/lleventdispatcher.h | 21 +++++++-------------- 2 files changed, 10 insertions(+), 23 deletions(-) diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index 0c3bb35cfe..3e45601429 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -561,9 +561,7 @@ void LLEventDispatcher::addArrayParamsDispatchEntry(const std::string& name, const invoker_function& invoker, LLSD::Integer arity) { - mDispatch.insert( - DispatchMap::value_type(name, DispatchMap::mapped_type( - new ArrayParamsDispatchEntry(desc, invoker, arity)))); + mDispatch.emplace(name, new ArrayParamsDispatchEntry(desc, invoker, arity)); } void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name, @@ -572,18 +570,14 @@ void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name, const LLSD& params, const LLSD& defaults) { - mDispatch.insert( - DispatchMap::value_type(name, DispatchMap::mapped_type( - new MapParamsDispatchEntry(name, desc, invoker, params, defaults)))); + mDispatch.emplace(name, new MapParamsDispatchEntry(name, desc, invoker, params, defaults)); } /// Register a callable by name void LLEventDispatcher::add(const std::string& name, const std::string& desc, const Callable& callable, const LLSD& required) { - mDispatch.insert( - DispatchMap::value_type(name, DispatchMap::mapped_type( - new LLSDDispatchEntry(desc, callable, required)))); + mDispatch.emplace(name, new LLSDDispatchEntry(desc, callable, required)); } /// Unregister a callable diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index 6d1df86fea..cf88dced12 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -56,9 +56,6 @@ static const auto nil_(nil); static const auto& nil(nil_); #endif -#include -#include -#include #include #include #include @@ -73,6 +70,9 @@ static const auto& nil(nil_); #include #include #include +#include // std::function +#include // std::unique_ptr +#include #include #include "llevents.h" #include "llsdutil.h" @@ -96,7 +96,7 @@ public: /// Accept any C++ callable with the right signature, typically a /// boost::bind() expression - typedef boost::function Callable; + typedef std::function Callable; /** * Register a @a callable by @a name. The passed @a callable accepts a @@ -293,14 +293,7 @@ private: virtual void call(const std::string& desc, const LLSD& event) const = 0; virtual LLSD addMetadata(LLSD) const = 0; }; - // Tried using boost::ptr_map, but ptr_map<> - // wants its value type to be "clonable," even just to dereference an - // iterator. I don't want to clone entries -- if I have to copy an entry - // around, I want it to continue pointing to the same DispatchEntry - // subclass object. However, I definitely want DispatchMap to destroy - // DispatchEntry if no references are outstanding at the time an entry is - // removed. This looks like a job for boost::shared_ptr. - typedef std::map > DispatchMap; + typedef std::map > DispatchMap; public: /// We want the flexibility to redefine what data we store per name, @@ -363,10 +356,10 @@ private: struct invoker; // deliver LLSD arguments one at a time - typedef boost::function args_source; + typedef std::function args_source; // obtain args from an args_source to build param list and call target // function - typedef boost::function invoker_function; + typedef std::function invoker_function; template invoker_function make_invoker(Function f); -- cgit v1.2.3 From ee7fc1d14b1a9b677129bb20714b79343a3bad94 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 6 Apr 2022 17:34:28 -0400 Subject: DRTVWR-558: Change LLEventDispatcher error action (also LLEventAPI). Originally the LLEventAPI mechanism was primarily used for VITA testing. In that case it was okay for the viewer to crash with LL_ERRS if the test script passed a bad request. With puppetry, hopefully new LEAP scripts will be written to engage LLEventAPIs in all sorts of interesting ways. Change error handling from LL_ERRS to LL_WARNS. Furthermore, if the incoming request contains a "reply" key, send back an error response to the requester. Update lleventdispatcher_test.cpp accordingly. (cherry picked from commit de0539fcbe815ceec2041ecc9981e3adf59f2806) (cherry picked from commit 4b60941952e97691f11806062f4bc66dd5ac8dae) --- indra/llcommon/lleventdispatcher.h | 1 + 1 file changed, 1 insertion(+) diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index cf88dced12..09b786b69e 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -325,6 +325,7 @@ private: CLASS* downcast = static_cast(this); add(name, desc, boost::bind(method, downcast, _1), required); } + void addFail(const std::string& name, const std::string& classname) const; std::string try_call_log(const std::string& key, const std::string& name, const LLSD& event) const; std::string try_call(const std::string& key, const std::string& name, -- cgit v1.2.3 From 1ef06b04a3b43581d44d41e417fe8b290a4cfde2 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 6 Apr 2022 21:49:03 -0400 Subject: DRTVWR-558: LLEventAPI allows all LLEventDispatcher add() overloads. Previously, LLEventAPI intentionally hid all but one of the many add() overloads supported by its LLEventDispatcher base class. The reason was that certain of the add() methods take an optional fourth parameter that's an LLSD::Map describing the expected parameter structure, while others take a fourth templated parameter that's an instance getter callable. This led to ambiguity, especially when passed an LLSDMap instance that's convertible to LLSD but isn't literally LLSD. At the time, it was simpler to constrain the add() methods inherited from LLEventDispatcher. But by adding new std::enable_if constraints to certain LLEventDispatcher add() methods, we've resolved the ambiguities, so LLEventAPI subclasses can now use any add() overload (as claimed on the relevant Confluence page). LLEventDispatcher comments have always loftily claimed that an instance getter callable may return either a pointer or a reference, doesn't matter. But it does when trying to pass the getter's result to boost::fusion::push_back(): a reference must be wrapped with std::ref() while a pointer cannot be. std::ref(pointer) produces errors. Introduce LLEventDispatcher::invoker:: bindable() overloads to Do The Right Thing whether passed a pointer or a reference. (cherry picked from commit 743f487c2e123171c9fc6d5b84d768f1d856d569) (cherry picked from commit 8618e41b3489e321ecd70eb65ec4d9ca7e2f75c6) --- indra/llcommon/lleventdispatcher.h | 132 +++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index 09b786b69e..3f328dce9a 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -165,12 +165,27 @@ public: * When calling this name, pass an LLSD::Array. Each entry in turn will be * converted to the corresponding parameter type using LLSDParam. */ +<<<<<<< variant A // enable_if usage per https://stackoverflow.com/a/39913395/5533635 template::value >::type> void add(const std::string& name, const std::string& desc, Function f); +>>>>>>> variant B + template + typename std::enable_if< + boost::function_types::is_nonmember_callable_builtin::value + >::type add(const std::string& name, + const std::string& desc, + Function f); +####### Ancestor + template + typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin + >::type add(const std::string& name, + const std::string& desc, + Function f); +======= end /** * Register a nonstatic class method with arbitrary parameters. @@ -189,6 +204,7 @@ public: * When calling this name, pass an LLSD::Array. Each entry in turn will be * converted to the corresponding parameter type using LLSDParam. */ +<<<<<<< variant A template::value && @@ -196,6 +212,24 @@ public: >::type> void add(const std::string& name, const std::string& desc, Method f, const InstanceGetter& getter); +>>>>>>> variant B + template + typename std::enable_if< + boost::function_types::is_member_function_pointer::value && + ! std::is_same::value && + ! std::is_same::value + >::type add(const std::string& name, + const std::string& desc, + Method f, + const InstanceGetter& getter); +####### Ancestor + template + typename boost::enable_if< boost::function_types::is_member_function_pointer + >::type add(const std::string& name, + const std::string& desc, + Method f, + const InstanceGetter& getter); +======= end /** * Register a free function with arbitrary parameters. (This also works @@ -212,12 +246,31 @@ public: * an LLSD::Array using LLSDArgsMapper and then convert each entry in turn * to the corresponding parameter type using LLSDParam. */ +<<<<<<< variant A template::value >::type> void add(const std::string& name, const std::string& desc, Function f, const LLSD& params, const LLSD& defaults=LLSD()); +>>>>>>> variant B + template + typename std::enable_if< + boost::function_types::is_nonmember_callable_builtin::value + >::type add(const std::string& name, + const std::string& desc, + Function f, + const LLSD& params, + const LLSD& defaults=LLSD()); +####### Ancestor + template + typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin + >::type add(const std::string& name, + const std::string& desc, + Function f, + const LLSD& params, + const LLSD& defaults=LLSD()); +======= end /** * Register a nonstatic class method with arbitrary parameters. @@ -240,6 +293,7 @@ public: * an LLSD::Array using LLSDArgsMapper and then convert each entry in turn * to the corresponding parameter type using LLSDParam. */ +<<<<<<< variant A template::value && @@ -248,6 +302,28 @@ public: void add(const std::string& name, const std::string& desc, Method f, const InstanceGetter& getter, const LLSD& params, const LLSD& defaults=LLSD()); +>>>>>>> variant B + template + typename std::enable_if< + boost::function_types::is_member_function_pointer::value && + ! std::is_same::value && + ! std::is_same::value + >::type add(const std::string& name, + const std::string& desc, + Method f, + const InstanceGetter& getter, + const LLSD& params, + const LLSD& defaults=LLSD()); +####### Ancestor + template + typename boost::enable_if< boost::function_types::is_member_function_pointer + >::type add(const std::string& name, + const std::string& desc, + Method f, + const InstanceGetter& getter, + const LLSD& params, + const LLSD& defaults=LLSD()); +======= end //@} @@ -457,8 +533,18 @@ struct LLEventDispatcher::invoker } }; +<<<<<<< variant A template void LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f) +>>>>>>> variant B +template +typename std::enable_if< boost::function_types::is_nonmember_callable_builtin::value >::type +LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f) +####### Ancestor +template +typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin >::type +LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f) +======= end { // Construct an invoker_function, a callable accepting const args_source&. // Add to DispatchMap an ArrayParamsDispatchEntry that will handle the @@ -467,9 +553,25 @@ void LLEventDispatcher::add(const std::string& name, const std::string& desc, Fu boost::function_types::function_arity::value); } +<<<<<<< variant A template void LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, const InstanceGetter& getter) +>>>>>>> variant B +template +typename std::enable_if< + boost::function_types::is_member_function_pointer::value && + ! std::is_same::value && + ! std::is_same::value +>::type +LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, + const InstanceGetter& getter) +####### Ancestor +template +typename boost::enable_if< boost::function_types::is_member_function_pointer >::type +LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, + const InstanceGetter& getter) +======= end { // Subtract 1 from the compile-time arity because the getter takes care of // the first parameter. We only need (arity - 1) additional arguments. @@ -477,18 +579,48 @@ void LLEventDispatcher::add(const std::string& name, const std::string& desc, Me boost::function_types::function_arity::value - 1); } +<<<<<<< variant A template void LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f, const LLSD& params, const LLSD& defaults) +>>>>>>> variant B +template +typename std::enable_if< boost::function_types::is_nonmember_callable_builtin::value >::type +LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f, + const LLSD& params, const LLSD& defaults) +####### Ancestor +template +typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin >::type +LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f, + const LLSD& params, const LLSD& defaults) +======= end { // See comments for previous is_nonmember_callable_builtin add(). addMapParamsDispatchEntry(name, desc, make_invoker(f), params, defaults); } +<<<<<<< variant A template void LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, const InstanceGetter& getter, const LLSD& params, const LLSD& defaults) +>>>>>>> variant B +template +typename std::enable_if< + boost::function_types::is_member_function_pointer::value && + ! std::is_same::value && + ! std::is_same::value +>::type +LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, + const InstanceGetter& getter, + const LLSD& params, const LLSD& defaults) +####### Ancestor +template +typename boost::enable_if< boost::function_types::is_member_function_pointer >::type +LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, + const InstanceGetter& getter, + const LLSD& params, const LLSD& defaults) +======= end { addMapParamsDispatchEntry(name, desc, make_invoker(f, getter), params, defaults); } -- cgit v1.2.3 From b6bbd86e5e6a23ef8bc81fa2374a82f05b4d18ed Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 6 Apr 2022 22:19:10 -0400 Subject: DRTVWR-558: Generalize LLEventDispatcher::add() constraints. Instead of checking whether an add() parameter is exactly LLSD or LLSDMap, check whether it's convertible to LLSD -- which handles those cases and more. (cherry picked from commit fa168c11f64771dadc5df86d14ca2f07eba3b8ba) (cherry picked from commit 6b5bfc1cf674fc568d86d7ed623fd7bb3ee2f646) --- indra/llcommon/lleventdispatcher.h | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index 3f328dce9a..99f03fe0bc 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -216,8 +216,7 @@ public: template typename std::enable_if< boost::function_types::is_member_function_pointer::value && - ! std::is_same::value && - ! std::is_same::value + ! std::is_convertible::value >::type add(const std::string& name, const std::string& desc, Method f, @@ -306,8 +305,7 @@ public: template typename std::enable_if< boost::function_types::is_member_function_pointer::value && - ! std::is_same::value && - ! std::is_same::value + ! std::is_convertible::value >::type add(const std::string& name, const std::string& desc, Method f, @@ -561,8 +559,7 @@ void LLEventDispatcher::add(const std::string& name, const std::string& desc, Me template typename std::enable_if< boost::function_types::is_member_function_pointer::value && - ! std::is_same::value && - ! std::is_same::value + ! std::is_convertible::value >::type LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, const InstanceGetter& getter) @@ -608,8 +605,7 @@ void LLEventDispatcher::add(const std::string& name, const std::string& desc, Me template typename std::enable_if< boost::function_types::is_member_function_pointer::value && - ! std::is_same::value && - ! std::is_same::value + ! std::is_convertible::value >::type LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, const InstanceGetter& getter, -- cgit v1.2.3 From 45464ee2d2b83b750d45b860e6117a4b74242ead Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 20 Dec 2022 13:09:42 -0500 Subject: DRTVWR-558: Pull in LLEventDispatcher / LLDispatchListener fixes. For LLEventDispatcher::add(), use simpler std::enable_if construct that avoids the need to restate the whole conditional. Derive LLDispatchListener from LLEventStream, instead of containing an instance. This sets up for LazyEventAPI. Don't allow tweaking an LLDispatchListener (or subclass LLEventAPI) name. (cherry-picked from af4fbc1f8a9 on the lazy-eventpump branch) (cherry picked from commit 419e7a4230ae662b035ae771af8e7d8bceb2c8b1) --- indra/llcommon/lleventdispatcher.h | 128 ------------------------------------- 1 file changed, 128 deletions(-) diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index 99f03fe0bc..09b786b69e 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -165,27 +165,12 @@ public: * When calling this name, pass an LLSD::Array. Each entry in turn will be * converted to the corresponding parameter type using LLSDParam. */ -<<<<<<< variant A // enable_if usage per https://stackoverflow.com/a/39913395/5533635 template::value >::type> void add(const std::string& name, const std::string& desc, Function f); ->>>>>>> variant B - template - typename std::enable_if< - boost::function_types::is_nonmember_callable_builtin::value - >::type add(const std::string& name, - const std::string& desc, - Function f); -####### Ancestor - template - typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin - >::type add(const std::string& name, - const std::string& desc, - Function f); -======= end /** * Register a nonstatic class method with arbitrary parameters. @@ -204,7 +189,6 @@ public: * When calling this name, pass an LLSD::Array. Each entry in turn will be * converted to the corresponding parameter type using LLSDParam. */ -<<<<<<< variant A template::value && @@ -212,23 +196,6 @@ public: >::type> void add(const std::string& name, const std::string& desc, Method f, const InstanceGetter& getter); ->>>>>>> variant B - template - typename std::enable_if< - boost::function_types::is_member_function_pointer::value && - ! std::is_convertible::value - >::type add(const std::string& name, - const std::string& desc, - Method f, - const InstanceGetter& getter); -####### Ancestor - template - typename boost::enable_if< boost::function_types::is_member_function_pointer - >::type add(const std::string& name, - const std::string& desc, - Method f, - const InstanceGetter& getter); -======= end /** * Register a free function with arbitrary parameters. (This also works @@ -245,31 +212,12 @@ public: * an LLSD::Array using LLSDArgsMapper and then convert each entry in turn * to the corresponding parameter type using LLSDParam. */ -<<<<<<< variant A template::value >::type> void add(const std::string& name, const std::string& desc, Function f, const LLSD& params, const LLSD& defaults=LLSD()); ->>>>>>> variant B - template - typename std::enable_if< - boost::function_types::is_nonmember_callable_builtin::value - >::type add(const std::string& name, - const std::string& desc, - Function f, - const LLSD& params, - const LLSD& defaults=LLSD()); -####### Ancestor - template - typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin - >::type add(const std::string& name, - const std::string& desc, - Function f, - const LLSD& params, - const LLSD& defaults=LLSD()); -======= end /** * Register a nonstatic class method with arbitrary parameters. @@ -292,7 +240,6 @@ public: * an LLSD::Array using LLSDArgsMapper and then convert each entry in turn * to the corresponding parameter type using LLSDParam. */ -<<<<<<< variant A template::value && @@ -301,27 +248,6 @@ public: void add(const std::string& name, const std::string& desc, Method f, const InstanceGetter& getter, const LLSD& params, const LLSD& defaults=LLSD()); ->>>>>>> variant B - template - typename std::enable_if< - boost::function_types::is_member_function_pointer::value && - ! std::is_convertible::value - >::type add(const std::string& name, - const std::string& desc, - Method f, - const InstanceGetter& getter, - const LLSD& params, - const LLSD& defaults=LLSD()); -####### Ancestor - template - typename boost::enable_if< boost::function_types::is_member_function_pointer - >::type add(const std::string& name, - const std::string& desc, - Method f, - const InstanceGetter& getter, - const LLSD& params, - const LLSD& defaults=LLSD()); -======= end //@} @@ -531,18 +457,8 @@ struct LLEventDispatcher::invoker } }; -<<<<<<< variant A template void LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f) ->>>>>>> variant B -template -typename std::enable_if< boost::function_types::is_nonmember_callable_builtin::value >::type -LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f) -####### Ancestor -template -typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin >::type -LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f) -======= end { // Construct an invoker_function, a callable accepting const args_source&. // Add to DispatchMap an ArrayParamsDispatchEntry that will handle the @@ -551,24 +467,9 @@ LLEventDispatcher::add(const std::string& name, const std::string& desc, Functio boost::function_types::function_arity::value); } -<<<<<<< variant A template void LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, const InstanceGetter& getter) ->>>>>>> variant B -template -typename std::enable_if< - boost::function_types::is_member_function_pointer::value && - ! std::is_convertible::value ->::type -LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, - const InstanceGetter& getter) -####### Ancestor -template -typename boost::enable_if< boost::function_types::is_member_function_pointer >::type -LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, - const InstanceGetter& getter) -======= end { // Subtract 1 from the compile-time arity because the getter takes care of // the first parameter. We only need (arity - 1) additional arguments. @@ -576,47 +477,18 @@ LLEventDispatcher::add(const std::string& name, const std::string& desc, Method boost::function_types::function_arity::value - 1); } -<<<<<<< variant A template void LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f, const LLSD& params, const LLSD& defaults) ->>>>>>> variant B -template -typename std::enable_if< boost::function_types::is_nonmember_callable_builtin::value >::type -LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f, - const LLSD& params, const LLSD& defaults) -####### Ancestor -template -typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin >::type -LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f, - const LLSD& params, const LLSD& defaults) -======= end { // See comments for previous is_nonmember_callable_builtin add(). addMapParamsDispatchEntry(name, desc, make_invoker(f), params, defaults); } -<<<<<<< variant A template void LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, const InstanceGetter& getter, const LLSD& params, const LLSD& defaults) ->>>>>>> variant B -template -typename std::enable_if< - boost::function_types::is_member_function_pointer::value && - ! std::is_convertible::value ->::type -LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, - const InstanceGetter& getter, - const LLSD& params, const LLSD& defaults) -####### Ancestor -template -typename boost::enable_if< boost::function_types::is_member_function_pointer >::type -LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, - const InstanceGetter& getter, - const LLSD& params, const LLSD& defaults) -======= end { addMapParamsDispatchEntry(name, desc, make_invoker(f, getter), params, defaults); } -- cgit v1.2.3 From 07c5645f5f9130a7fc338df0bc2bb791d43bd702 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 22 Dec 2022 14:53:29 -0500 Subject: DRTVWR-558: LLEventDispatcher uses LL::apply(), not boost::fusion. While calling a C++ function with arguments taken from a runtime-variable data structure necessarily involves a bit of hocus-pocus, the best you can say for the boost::fusion based implementation is that it worked. Sadly, template recursion limited its applicability to a handful of function arguments. Now that we have LL::apply(), use that instead. This implementation is much more straightforward. In particular, the LLSDArgsSource class, whose job was to dole out elements of an LLSD array one at a time for the template recursion, goes away entirely. Make virtual LLEventDispatcher::DispatchEntry::call() return LLSD instead of void. All LLEventDispatcher target functions so far have been void; any function that wants to respond to its invoker must do so explicitly by calling sendReply() or constructing an LLEventAPI::Response instance. Supporting non- void functions permits LLEventDispatcher to respond implicitly with the returned value. Of course this requires a wrapper for void target functions that returns LLSD::isUndefined(). Break out LLEventDispatcher::reply() from callFail(), so we can reply with success as well as failure. Make LLEventDispatcher::try_call_log() prepend the actual leaf class name and description to any error returned by three-arg try_call(). That try_call() overload reported "LLEventDispatcher(desc): " for a couple specific errors, but no others. Hoist to try_call_log() to apply uniformly. Introduce new try_call_one() method to diagnose name-not-found errors and catch internal DispatchError and LL::apply_error exceptions. try_call_one() returns a std::pair, containing either an error message or an LLSD value. Make try_call_log() and three-arg try_call() accept LLSD 'name' instead of plain std::string, allowing for the possibility of an array or map. That lets us extend three-arg try_call() to break out new cases for the function selector LLSD: isUndefined(), isArray(), isMap() and (current case) scalar String. If try_call_one() reports an error, log it and try to send reply, as now. If it returns LLSD::isUndefined(), e.g. from a void target function wrapper, do nothing. But if it returns an LLSD map, try to send that back to the invoker. And if it returns an LLSD scalar or array, wrap it in a map with key "data" to respond to the invoker. Allowing a target function to return its result rather than explicitly sending it opens the possibility of batched requests (aggregate 'name') returning batched responses. Almost every place that constructs LLEventDispatcher's internal DispatchError exception called stringize() to format the what() string. Simplify calls by making DispatchError accept variadic arguments and forward to stringize(). Add LL::invoke() to apply.h. Like LL::apply(), this is a (limited) C++14 foreshadowing of std::invoke(), with preprocessor conditionals to switch to std::invoke() when that's available. Introduce LL::invoke() to handle a callable that's actually a pointer to method. Now our C++14 apply() implementation can accept pointer to method, using invoke() to generalize the actual function call. Also anticipate std::bind_front() with LL::bind_front(). For apply(func, std::array) and our extensions apply(func, std::vector) and apply(func, LLSD), we can't pass a pointer to method as the func unless the second argument happens to be an array or vector of pointers (or references) to instances of exactly the right class -- and of course LLSD can't store such at all. It's tempting to pass std::bind(std::mem_fn(ptr_to_method), instance), but that won't work: std::bind() requires a value or placeholder for each argument to pass to the bound function. The bind() expression above would only work for a nullary method. std::bind_front() would work, but that doesn't arrive until C++20. Again, once we get there we'll defer to the std:: implementation. Instead of the generic __cplusplus, check the appropriate feature-test macro for availability of each of std::invoke(), std::apply() and std::bind_front(). Change apply() error handling from assert() to new LL::apply_error exception. LLEventDispatcher must be able to intercept apply() errors. Move validation and synthesis of the relevant error message to new apply.cpp source file. Add to llptrto.h new LL::get_ref() and LL::get_ptr() template functions to unify the cases of a calling template accepting either a pointer or a reference. Wrapping the parameter in either get_ref() or get_ptr() allows dereferencing the parameter as desired. Move LL::apply(function, LLSD) argument validation/manipulation to a non- template function in llsdutil.cpp: no need to replicate that logic in the template for every CALLABLE specialization. The trouble with passing bind_front(std::mem_fn(ptr_to_method), instance) to apply() is that since bind_front() accepts and forwards variadic additional arguments, apply() can't infer the arity of the bound ptr_to_method. Address that by introducing apply_n(function, LLSD), permitting a caller to infer the arity of ptr_to_method and explicitly pass it to apply_n(). Polish up lleventdispatcher_test.cpp accordingly. Wrong LLSD type and wrong number of arguments now produce different (somewhat more informative) error messages. Moreover, passing too many entries in an LLSD array used to work: the extra arguments used to be ignored. Now we require that the size of the array match the arity of the target function. Change the too-many-arguments tests from success testing to error testing. Replace 'foreach' aka BOOST_FOREACH macro invocations with range 'for'. Replace STRINGIZE(item0 << item1 << ...) with stringize(item0, item1, ...). (cherry picked from commit 9c049563b5480bb7e8ed87d9313822595b479c3b) --- indra/llcommon/CMakeLists.txt | 1 + indra/llcommon/apply.cpp | 29 +++ indra/llcommon/apply.h | 100 +++++++++- indra/llcommon/lleventdispatcher.cpp | 224 ++++++++++++--------- indra/llcommon/lleventdispatcher.h | 255 ++++++++---------------- indra/llcommon/llptrto.h | 88 +++++++- indra/llcommon/llsdutil.cpp | 35 ++++ indra/llcommon/llsdutil.h | 49 ++--- indra/llcommon/tests/lleventdispatcher_test.cpp | 141 ++++++------- 9 files changed, 543 insertions(+), 379 deletions(-) create mode 100644 indra/llcommon/apply.cpp diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 941d2d7baf..33e8301e12 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -16,6 +16,7 @@ include(Tracy) set(llcommon_SOURCE_FILES + apply.cpp indra_constants.cpp lazyeventapi.cpp llallocator.cpp diff --git a/indra/llcommon/apply.cpp b/indra/llcommon/apply.cpp new file mode 100644 index 0000000000..417e23d3b4 --- /dev/null +++ b/indra/llcommon/apply.cpp @@ -0,0 +1,29 @@ +/** + * @file apply.cpp + * @author Nat Goodspeed + * @date 2022-12-21 + * @brief Implementation for apply. + * + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Copyright (c) 2022, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "apply.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "stringize.h" + +void LL::apply_validate_size(size_t size, size_t arity) +{ + if (size != arity) + { + LLTHROW(apply_error(stringize("LL::apply(func(", arity, " args), " + "std::vector(", size, " elements))"))); + } +} diff --git a/indra/llcommon/apply.h b/indra/llcommon/apply.h index 9f4c268895..357887dfd7 100644 --- a/indra/llcommon/apply.h +++ b/indra/llcommon/apply.h @@ -12,9 +12,11 @@ #if ! defined(LL_APPLY_H) #define LL_APPLY_H +#include "llexception.h" #include -#include +#include // std::mem_fn() #include +#include // std::is_member_pointer namespace LL { @@ -56,9 +58,39 @@ namespace LL (ARGS)) /***************************************************************************** -* apply(function, tuple) +* invoke() *****************************************************************************/ -#if __cplusplus >= 201703L +#if __cpp_lib_invoke >= 201411L + +// C++17 implementation +using std::invoke; + +#else // no std::invoke + +// Use invoke() to handle pointer-to-method: +// derived from https://stackoverflow.com/a/38288251 +template::type>::value, + int>::type = 0 > +auto invoke(Fn&& f, Args&&... args) +{ + return std::mem_fn(f)(std::forward(args)...); +} + +template::type>::value, + int>::type = 0 > +auto invoke(Fn&& f, Args&&... args) +{ + return std::forward(f)(std::forward(args)...); +} + +#endif // no std::invoke + +/***************************************************************************** +* apply(function, tuple); apply(function, array) +*****************************************************************************/ +#if __cpp_lib_apply >= 201603L // C++17 implementation using std::apply; @@ -71,7 +103,7 @@ template auto apply_impl(CALLABLE&& func, const std::tuple& args, std::index_sequence) { // call func(unpacked args) - return std::forward(func)(std::move(std::get(args))...); + return invoke(std::forward(func), std::get(args)...); } template @@ -85,11 +117,6 @@ auto apply(CALLABLE&& func, const std::tuple& args) std::index_sequence_for{}); } -#endif // C++14 - -/***************************************************************************** -* apply(function, std::array) -*****************************************************************************/ // per https://stackoverflow.com/a/57510428/5533635 template auto apply(CALLABLE&& func, const std::array& args) @@ -97,6 +124,50 @@ auto apply(CALLABLE&& func, const std::array& args) return apply(std::forward(func), std::tuple_cat(args)); } +#endif // C++14 + +/***************************************************************************** +* bind_front() +*****************************************************************************/ +// To invoke a non-static member function with a tuple, you need a callable +// that binds your member function with an instance pointer or reference. +// std::bind_front() is perfect: std::bind_front(&cls::method, instance). +// Unfortunately bind_front() only enters the standard library in C++20. +#if __cpp_lib_bind_front >= 201907L + +// C++20 implementation +using std::bind_front; + +#else // no std::bind_front() + +template::type>::value, + int>::type = 0 > +auto bind_front(Fn&& f, Args&&... args) +{ + // Don't use perfect forwarding for f or args: we must bind them for later. + return [f, pfx_args=std::make_tuple(args...)] + (auto&&... sfx_args) + { + // Use perfect forwarding for sfx_args because we use them as soon as + // we receive them. + return apply( + f, + std::tuple_cat(pfx_args, + std::make_tuple(std::forward(sfx_args)...))); + }; +} + +template::type>::value, + int>::type = 0 > +auto bind_front(Fn&& f, Args&&... args) +{ + return bind_front(std::mem_fn(std::forward(f)), std::forward(args)...); +} + +#endif // C++20 with std::bind_front() + /***************************************************************************** * apply(function, std::vector) *****************************************************************************/ @@ -108,6 +179,15 @@ auto apply_impl(CALLABLE&& func, const std::vector& args, std::index_sequence std::make_tuple(args[I]...)); } +// produce suitable error if apply(func, vector) is the wrong size for func() +void apply_validate_size(size_t size, size_t arity); + +/// possible exception from apply() validation +struct apply_error: public LLException +{ + apply_error(const std::string& what): LLException(what) {} +}; + /** * apply(function, std::vector) goes beyond C++17 std::apply(). For this case * @a function @emph cannot be variadic: the compiler must know at compile @@ -117,7 +197,7 @@ template auto apply(CALLABLE&& func, const std::vector& args) { constexpr auto arity = boost::function_traits::arity; - assert(args.size() == arity); + apply_validate_size(args.size(), arity); return apply_impl(std::forward(func), args, std::make_index_sequence()); diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index 3e45601429..e7e73125a7 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -50,68 +50,13 @@ *****************************************************************************/ struct DispatchError: public LLException { - DispatchError(const std::string& what): LLException(what) {} + // template constructor involving strings passes all arguments to + // stringize() to construct LLException's what() string + template + DispatchError(const std::string& arg0, ARGS&&... args): + LLException(stringize(arg0, std::forward(args)...)) {} }; -/***************************************************************************** -* LLSDArgsSource -*****************************************************************************/ -/** - * Store an LLSD array, producing its elements one at a time. It is an error - * if the consumer requests more elements than the array contains. - */ -class LL_COMMON_API LLSDArgsSource -{ -public: - LLSDArgsSource(const std::string function, const LLSD& args); - ~LLSDArgsSource(); - - LLSD next(); - - void done() const; - -private: - std::string _function; - LLSD _args; - LLSD::Integer _index; -}; - -LLSDArgsSource::LLSDArgsSource(const std::string function, const LLSD& args): - _function(function), - _args(args), - _index(0) -{ - if (! (_args.isUndefined() || _args.isArray())) - { - LLTHROW(DispatchError(stringize(_function, " needs an args array instead of ", _args))); - } -} - -LLSDArgsSource::~LLSDArgsSource() -{ - done(); -} - -LLSD LLSDArgsSource::next() -{ - if (_index >= _args.size()) - { - LLTHROW(DispatchError(stringize(_function, " requires more arguments than the ", - _args.size(), " provided: ", _args))); - } - return _args[_index++]; -} - -void LLSDArgsSource::done() const -{ - if (_index < _args.size()) - { - LL_WARNS("LLSDArgsSource") << _function << " only consumed " << _index - << " of the " << _args.size() << " arguments provided: " - << _args << LL_ENDL; - } -} - /***************************************************************************** * LLSDArgsMapper *****************************************************************************/ @@ -204,7 +149,7 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function, { if (! (_names.isUndefined() || _names.isArray())) { - LLTHROW(DispatchError(stringize(function, " names must be an array, not ", names))); + LLTHROW(DispatchError(function, " names must be an array, not ", names)); } auto nparams(_names.size()); // From _names generate _indexes. @@ -227,8 +172,8 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function, // defaults is a (possibly empty) array. Right-align it with names. if (ndefaults > nparams) { - LLTHROW(DispatchError(stringize(function, " names array ", names, - " shorter than defaults array ", defaults))); + LLTHROW(DispatchError(function, " names array ", names, + " shorter than defaults array ", defaults)); } // Offset by which we slide defaults array right to right-align with @@ -265,14 +210,14 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function, } if (bogus.size()) { - LLTHROW(DispatchError(stringize(function, " defaults specified for nonexistent params ", - formatlist(bogus)))); + LLTHROW(DispatchError(function, " defaults specified for nonexistent params ", + formatlist(bogus))); } } else { - LLTHROW(DispatchError(stringize(function, " defaults must be a map or an array, not ", - defaults))); + LLTHROW(DispatchError(function, " defaults must be a map or an array, not ", + defaults)); } } @@ -280,8 +225,8 @@ LLSD LLSDArgsMapper::map(const LLSD& argsmap) const { if (! (argsmap.isUndefined() || argsmap.isMap() || argsmap.isArray())) { - LLTHROW(DispatchError(stringize(_function, " map() needs a map or array, not ", - argsmap))); + LLTHROW(DispatchError(_function, " map() needs a map or array, not ", + argsmap)); } // Initialize the args array. Indexing a non-const LLSD array grows it // to appropriate size, but we don't want to resize this one on each @@ -378,8 +323,8 @@ LLSD LLSDArgsMapper::map(const LLSD& argsmap) const // by argsmap, that's a problem. if (unfilled.size()) { - LLTHROW(DispatchError(stringize(_function, " missing required arguments ", - formatlist(unfilled), " from ", argsmap))); + LLTHROW(DispatchError(_function, " missing required arguments ", + formatlist(unfilled), " from ", argsmap)); } // done @@ -399,6 +344,9 @@ std::string LLSDArgsMapper::formatlist(const LLSD& list) return out.str(); } +/***************************************************************************** +* LLEventDispatcher +*****************************************************************************/ LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key): mDesc(desc), mKey(key) @@ -409,6 +357,10 @@ LLEventDispatcher::~LLEventDispatcher() { } +LLEventDispatcher::DispatchEntry::DispatchEntry(const std::string& desc): + mDesc(desc) +{} + /** * DispatchEntry subclass used for callables accepting(const LLSD&) */ @@ -423,16 +375,17 @@ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchE Callable mFunc; LLSD mRequired; - virtual void call(const std::string& desc, const LLSD& event) const + virtual LLSD call(const std::string& desc, const LLSD& event) const { // Validate the syntax of the event itself. std::string mismatch(llsd_matches(mRequired, event)); if (! mismatch.empty()) { - LLTHROW(DispatchError(stringize(desc, ": bad request: ", mismatch))); + LLTHROW(DispatchError(desc, ": bad request: ", mismatch)); } // Event syntax looks good, go for it! mFunc(event); + return {}; } virtual LLSD addMetadata(LLSD meta) const @@ -455,10 +408,9 @@ struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::Dispatc invoker_function mInvoker; - virtual void call(const std::string& desc, const LLSD& event) const + virtual LLSD call(const std::string&, const LLSD& event) const { - LLSDArgsSource src(desc, event); - mInvoker(boost::bind(&LLSDArgsSource::next, boost::ref(src))); + return mInvoker(event); } }; @@ -541,11 +493,11 @@ struct LLEventDispatcher::MapParamsDispatchEntry: public LLEventDispatcher::Para LLSD mRequired; LLSD mOptional; - virtual void call(const std::string& desc, const LLSD& event) const + virtual LLSD call(const std::string& desc, const LLSD& event) const { // Just convert from LLSD::Map to LLSD::Array using mMapper, then pass // to base-class call() method. - ParamsDispatchEntry::call(desc, mMapper.map(event)); + return ParamsDispatchEntry::call(desc, mMapper.map(event)); } virtual LLSD addMetadata(LLSD meta) const @@ -616,13 +568,19 @@ void LLEventDispatcher::operator()(const LLSD& event) const } void LLEventDispatcher::callFail(const LLSD& event, const std::string& msg) const +{ + // pass back a response that includes an "error" key with the message. + reply(llsd::map("error", msg), event); +} + +void LLEventDispatcher::reply(const LLSD& response, const LLSD& event) const { static LLSD::String key{ "reply" }; if (event.has(key)) { - // Oh good, the incoming event specifies a reply pump -- pass back a - // response that includes an "error" key with the message. - sendReply(llsd::map("error", msg), event, key); + // Oh good, the incoming event specifies a reply pump -- pass back + // our response. + sendReply(response, event, key); } } @@ -631,17 +589,30 @@ bool LLEventDispatcher::try_call(const LLSD& event) const return try_call_log(mKey, event[mKey], event).empty(); } +/*==========================================================================*| + TODO: + +* When mInvoker returns result.isDefined(), sendReply(llsd::map("data", result)) +* When try_call finds name.isArray(), construct response array from + dispatching each call, sendReply() as above +* When try_call finds name.isMap(), construct response map from dispatching + each call, sendReply() as above -- note, caller can't care about order +* Possible future transactional behavior: look up all names before calling any + +|*==========================================================================*/ bool LLEventDispatcher::try_call(const std::string& name, const LLSD& event) const { return try_call_log(std::string(), name, event).empty(); } -std::string LLEventDispatcher::try_call_log(const std::string& key, const std::string& name, +std::string LLEventDispatcher::try_call_log(const std::string& key, const LLSD& name, const LLSD& event) const { std::string error{ try_call(key, name, event) }; if (! error.empty()) { + // If we're a subclass of LLEventDispatcher, e.g. LLEventAPI, report that. + error = stringize(LLError::Log::classname(this), "(", mDesc, "): ", error); LL_WARNS("LLEventDispatcher") << error << LL_ENDL; } return error; @@ -649,33 +620,100 @@ std::string LLEventDispatcher::try_call_log(const std::string& key, const std::s // This internal method returns empty string if the call succeeded, else // non-empty error message. -std::string LLEventDispatcher::try_call(const std::string& key, const std::string& name, +std::string LLEventDispatcher::try_call(const std::string& key, const LLSD& name, const LLSD& event) const +{ + if (name.isUndefined()) + { + if (key.empty()) + { + return "attempting to call with no name"; + } + else + { + return stringize("no ", key); + } + } + else if (name.isArray()) + { + return stringize(key, " array dispatch ", name, " not yet implemented"); + } + else if (name.isMap()) + { + return stringize(key, " map dispatch ", name, " not yet implemented"); + } + else if (! name.isString()) + { + return stringize(key, " bad type ", LLSD::typeString(name.type()), ' ', name, + " -- function names are String"); + } + else // name is an LLSD::String + { + auto success{ try_call_one(key, name, event) }; + // pretend to unpack + std::string& error{ success.first }; + LLSD& result{ success.second }; + // did try_call_one() report an error? + if (! error.empty()) + { + // if we have a reply key, respond to invoker + reply(llsd::map("error", error), event); + // now tell caller + return error; + } + // try_call_one() succeeded in calling the target function -- + // should we reply to invoker? + if (result.isUndefined()) + { + // We would get result.isUndefined() if the target function has + // void return. In any case, even if the target function returns + // LLSD, isUndefined() means "don't bother sending response." + return {}; + } + // result.isDefined(): the target function returned something. + // Respond to invoker if we have a "reply" key. + if (! result.isMap()) + { + // wrap result in a map to play well with sendReply() + result = llsd::map("data", result); + } + reply(result, event); + return {}; + } +} + +std::pair +LLEventDispatcher::try_call_one(const std::string& key, const std::string& name, + const LLSD& event) const { DispatchMap::const_iterator found = mDispatch.find(name); if (found == mDispatch.end()) { if (key.empty()) { - return stringize("LLEventDispatcher(", mDesc, "): '", name, "' not found"); + return { stringize("'", name, "' not found"), {} }; } else { - return stringize("LLEventDispatcher(", mDesc, "): bad ", key, " value '", name, "'"); + return { stringize("bad ", key, " value '", name, "'"), {} }; } } try { // Found the name, so it's plausible to even attempt the call. - found->second->call(stringize("LLEventDispatcher(", mDesc, ") calling '", name, "'"), - event); + return { {}, found->second->call(stringize("calling '", name, "'"), event) }; } catch (const DispatchError& err) { - return err.what(); + // trouble preparing arguments + return { err.what(), {} }; + } + catch (const LL::apply_error& err) + { + // could also hit runtime errors with LL::apply() + return { err.what(), {} }; } - return {}; // tell caller we were able to call } LLSD LLEventDispatcher::getMetadata(const std::string& name) const @@ -691,6 +729,9 @@ LLSD LLEventDispatcher::getMetadata(const std::string& name) const return found->second->addMetadata(meta); } +/***************************************************************************** +* LLDispatchListener +*****************************************************************************/ LLDispatchListener::LLDispatchListener(const std::string& pumpname, const std::string& key): LLEventDispatcher(pumpname, key), // Do NOT tweak the passed pumpname. In practice, when someone @@ -712,8 +753,3 @@ bool LLDispatchListener::process(const LLSD& event) (*this)(event); return false; } - -LLEventDispatcher::DispatchEntry::DispatchEntry(const std::string& desc): - mDesc(desc) -{} - diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index 09b786b69e..cebce618df 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -27,54 +27,23 @@ * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ - * - * The invoker machinery that constructs a boost::fusion argument list for use - * with boost::fusion::invoke() is derived from - * http://www.boost.org/doc/libs/1_45_0/libs/function_types/example/interpreter.hpp - * whose license information is copied below: - * - * "(C) Copyright Tobias Schwinger - * - * Use modification and distribution are subject to the boost Software License, - * Version 1.0. (See http://www.boost.org/LICENSE_1_0.txt)." */ #if ! defined(LL_LLEVENTDISPATCHER_H) #define LL_LLEVENTDISPATCHER_H -// nil is too generic a term to be allowed to be a global macro. In -// particular, boost::fusion defines a 'class nil' (properly encapsulated in a -// namespace) that a global 'nil' macro breaks badly. -#if defined(nil) -// Capture the value of the macro 'nil', hoping int is an appropriate type. -static const auto nil_(nil); -// Now forget the macro. -#undef nil -// Finally, reintroduce 'nil' as a properly-scoped alias for the previously- -// defined const 'nil_'. Make it static since otherwise it produces duplicate- -// symbol link errors later. -static const auto& nil(nil_); -#endif - -#include #include +#include #include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include #include // std::function #include // std::unique_ptr #include #include +#include #include "llevents.h" +#include "llptrto.h" #include "llsdutil.h" class LLSD; @@ -94,8 +63,7 @@ public: /// @name Register functions accepting(const LLSD&) //@{ - /// Accept any C++ callable with the right signature, typically a - /// boost::bind() expression + /// Accept any C++ callable with the right signature typedef std::function Callable; /** @@ -126,7 +94,7 @@ public: /** * Special case: a subclass of this class can pass an unbound member * function pointer (of an LLEventDispatcher subclass) without explicitly - * specifying the boost::bind() expression. The passed @a method + * specifying a std::bind() expression. The passed @a method * accepts a single LLSD value, presumably containing other parameters. */ template @@ -158,10 +126,6 @@ public: * Register a free function with arbitrary parameters. (This also works * for static class methods.) * - * @note This supports functions with up to about 6 parameters -- after - * that you start getting dismaying compile errors in which - * boost::fusion::joint_view is mentioned a surprising number of times. - * * When calling this name, pass an LLSD::Array. Each entry in turn will be * converted to the corresponding parameter type using LLSDParam. */ @@ -175,10 +139,6 @@ public: /** * Register a nonstatic class method with arbitrary parameters. * - * @note This supports functions with up to about 6 parameters -- after - * that you start getting dismaying compile errors in which - * boost::fusion::joint_view is mentioned a surprising number of times. - * * To cover cases such as a method on an LLSingleton we don't yet want to * instantiate, instead of directly storing an instance pointer, accept a * nullary callable returning a pointer/reference to the desired class @@ -201,10 +161,6 @@ public: * Register a free function with arbitrary parameters. (This also works * for static class methods.) * - * @note This supports functions with up to about 6 parameters -- after - * that you start getting dismaying compile errors in which - * boost::fusion::joint_view is mentioned a surprising number of times. - * * Pass an LLSD::Array of parameter names, and optionally another * LLSD::Array of default parameter values, a la LLSDArgsMapper. * @@ -222,10 +178,6 @@ public: /** * Register a nonstatic class method with arbitrary parameters. * - * @note This supports functions with up to about 6 parameters -- after - * that you start getting dismaying compile errors in which - * boost::fusion::joint_view is mentioned a surprising number of times. - * * To cover cases such as a method on an LLSingleton we don't yet want to * instantiate, instead of directly storing an instance pointer, accept a * nullary callable returning a pointer/reference to the desired class @@ -290,7 +242,7 @@ private: std::string mDesc; - virtual void call(const std::string& desc, const LLSD& event) const = 0; + virtual LLSD call(const std::string& desc, const LLSD& event) const = 0; virtual LLSD addMetadata(LLSD) const = 0; }; typedef std::map > DispatchMap; @@ -322,17 +274,28 @@ private: void addMethod(const std::string& name, const std::string& desc, const METHOD& method, const LLSD& required) { - CLASS* downcast = static_cast(this); - add(name, desc, boost::bind(method, downcast, _1), required); + CLASS* downcast = dynamic_cast(this); + if (! downcast) + { + addFail(name, typeid(CLASS).name()); + } + else + { + add(name, desc, std::bind(method, downcast, std::placeholders::_1), required); + } } void addFail(const std::string& name, const std::string& classname) const; - std::string try_call_log(const std::string& key, const std::string& name, + std::string try_call_log(const std::string& key, const LLSD& name, const LLSD& event) const; - std::string try_call(const std::string& key, const std::string& name, + std::string try_call(const std::string& key, const LLSD& name, const LLSD& event) const; + // returns either (empty string, LLSD) or (error message, isUndefined) + std::pair + try_call_one(const std::string& key, const std::string& name, const LLSD& event) const; // Implement "it is an error" semantics for attempted call operations: if // the incoming event includes a "reply" key, log and send an error reply. void callFail(const LLSD& event, const std::string& msg) const; + void reply(const LLSD& response, const LLSD& event) const; std::string mDesc, mKey; DispatchMap mDispatch; @@ -347,20 +310,8 @@ private: struct ArrayParamsDispatchEntry; struct MapParamsDispatchEntry; - // Step 2 of parameter analysis. Instantiating invoker - // implicitly sets its From and To parameters to the (compile time) begin - // and end iterators over that function's parameter types. - template< typename Function - , class From = typename boost::mpl::begin< boost::function_types::parameter_types >::type - , class To = typename boost::mpl::end< boost::function_types::parameter_types >::type - > - struct invoker; - - // deliver LLSD arguments one at a time - typedef std::function args_source; - // obtain args from an args_source to build param list and call target - // function - typedef std::function invoker_function; + // call target function with args from LLSD array + typedef std::function invoker_function; template invoker_function make_invoker(Function f); @@ -375,92 +326,17 @@ private: const invoker_function& invoker, const LLSD& params, const LLSD& defaults); + template + struct ReturnLLSD; }; /***************************************************************************** * LLEventDispatcher template implementation details *****************************************************************************/ -// Step 3 of parameter analysis, the recursive case. -template -struct LLEventDispatcher::invoker -{ - template - struct remove_cv_ref - : boost::remove_cv< typename boost::remove_reference::type > - { }; - - // apply() accepts an arbitrary boost::fusion sequence as args. It - // examines the next parameter type in the parameter-types sequence - // bounded by From and To, obtains the next LLSD object from the passed - // args_source and constructs an LLSDParam of appropriate type to try - // to convert the value. It then recurs with the next parameter-types - // iterator, passing the args sequence thus far. - template - static inline - void apply(Function func, const args_source& argsrc, Args const & args) - { - typedef typename boost::mpl::deref::type arg_type; - typedef typename boost::mpl::next::type next_iter_type; - typedef typename remove_cv_ref::type plain_arg_type; - - invoker::apply - ( func, argsrc, boost::fusion::push_back(args, LLSDParam(argsrc()))); - } - - // Special treatment for instance (first) parameter of a non-static member - // function. Accept the instance-getter callable, calling that to produce - // the first args value. Since we know we're at the top of the recursion - // chain, we need not also require a partial args sequence from our caller. - template - static inline - void method_apply(Function func, const args_source& argsrc, const InstanceGetter& getter) - { - typedef typename boost::mpl::next::type next_iter_type; - - // Instead of grabbing the first item from argsrc and making an - // LLSDParam of it, call getter() and pass that as the instance param. - invoker::apply - ( func, argsrc, boost::fusion::push_back(boost::fusion::nil(), bindable(getter()))); - } - - template - static inline - auto bindable(T&& value, - typename std::enable_if::value, bool>::type=true) - { - // if passed a pointer, just return that pointer - return std::forward(value); - } - - template - static inline - auto bindable(T&& value, - typename std::enable_if::value, bool>::type=true) - { - // if passed a reference, wrap it for binding - return std::ref(std::forward(value)); - } -}; - -// Step 4 of parameter analysis, the leaf case. When the general -// invoker logic has advanced From until it matches To, -// the compiler will pick this template specialization. -template -struct LLEventDispatcher::invoker -{ - // the argument list is complete, now call the function - template - static inline - void apply(Function func, const args_source&, Args const & args) - { - boost::fusion::invoke(func, args); - } -}; - template void LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f) { - // Construct an invoker_function, a callable accepting const args_source&. + // Construct an invoker_function, a callable accepting const LLSD&. // Add to DispatchMap an ArrayParamsDispatchEntry that will handle the // caller's LLSD::Array. addArrayParamsDispatchEntry(name, desc, make_invoker(f), @@ -493,33 +369,76 @@ void LLEventDispatcher::add(const std::string& name, const std::string& desc, Me addMapParamsDispatchEntry(name, desc, make_invoker(f, getter), params, defaults); } +// general case, when f() has a non-void return type +template +struct LLEventDispatcher::ReturnLLSD +{ + template + LLSD operator()(Function f, const LLSD& args) + { + return { LL::apply(f, args) }; + } + + template + LLSD operator()(Method f, const InstanceGetter& getter, const LLSD& args) + { + constexpr auto arity = boost::function_types::function_arity< + typename std::remove_reference::type>::value - 1; + + // Use bind_front() to bind the method to (a pointer to) the object + // returned by getter(). It's okay to capture and bind a pointer + // because this bind_front() object will last only as long as this + // operator() call. + return { LL::apply_n(LL::bind_front(f, LL::get_ptr(getter())), args) }; + } +}; + +// specialize for void return type +template <> +struct LLEventDispatcher::ReturnLLSD +{ + template + LLSD operator()(Function f, const LLSD& args) + { + LL::apply(f, args); + return {}; + } + + template + LLSD operator()(Method f, const InstanceGetter& getter, const LLSD& args) + { + constexpr auto arity = boost::function_types::function_arity< + typename std::remove_reference::type>::value - 1; + + // Use bind_front() to bind the method to (a pointer to) the object + // returned by getter(). It's okay to capture and bind a pointer + // because this bind_front() object will last only as long as this + // operator() call. + LL::apply_n(LL::bind_front(f, LL::get_ptr(getter())), args); + return {}; + } +}; + template LLEventDispatcher::invoker_function LLEventDispatcher::make_invoker(Function f) { - // Step 1 of parameter analysis, the top of the recursion. Passing a - // suitable f (see add()'s enable_if condition) to this method causes it - // to infer the function type; specifying that function type to invoker<> - // causes it to fill in the begin/end MPL iterators over the function's - // list of parameter types. - // While normally invoker::apply() could infer its template type from the - // boost::fusion::nil parameter value, here we must be explicit since - // we're boost::bind()ing it rather than calling it directly. - return boost::bind(&invoker::template apply, - f, - _1, - boost::fusion::nil()); + return [f](const LLSD& args) + { + return ReturnLLSD::type>() + (f, args); + }; } template LLEventDispatcher::invoker_function LLEventDispatcher::make_invoker(Method f, const InstanceGetter& getter) { - // Use invoker::method_apply() to treat the instance (first) arg specially. - return boost::bind(&invoker::template method_apply, - f, - _1, - getter); + return [f, getter](const LLSD& args) + { + return ReturnLLSD::type>() + (f, getter, args); + }; } /***************************************************************************** diff --git a/indra/llcommon/llptrto.h b/indra/llcommon/llptrto.h index 4082e30de6..9ef279fdbf 100644 --- a/indra/llcommon/llptrto.h +++ b/indra/llcommon/llptrto.h @@ -33,9 +33,12 @@ #include "llpointer.h" #include "llrefcount.h" // LLRefCount +#include +#include #include #include -#include +#include // std::shared_ptr, std::unique_ptr +#include /** * LLPtrTo::type is either of two things: @@ -55,14 +58,14 @@ struct LLPtrTo /// specialize for subclasses of LLRefCount template -struct LLPtrTo >::type> +struct LLPtrTo::value >::type> { typedef LLPointer type; }; /// specialize for subclasses of LLThreadSafeRefCount template -struct LLPtrTo >::type> +struct LLPtrTo::value >::type> { typedef LLPointer type; }; @@ -83,4 +86,83 @@ struct LLRemovePointer< LLPointer > typedef SOMECLASS type; }; +namespace LL +{ + +/***************************************************************************** +* get_ref() +*****************************************************************************/ + template + struct GetRef + { + // return const ref or non-const ref, depending on whether we can bind + // a non-const lvalue ref to the argument + const auto& operator()(const T& obj) const { return obj; } + auto& operator()(T& obj) const { return obj; } + }; + + template + struct GetRef + { + const auto& operator()(const T* ptr) const { return *ptr; } + }; + + template + struct GetRef + { + auto& operator()(T* ptr) const { return *ptr; } + }; + + template + struct GetRef< LLPointer > + { + auto& operator()(LLPointer ptr) const { return *ptr; } + }; + + /// whether we're passed a pointer or a reference, return a reference + template + auto& get_ref(T& ptr_or_ref) + { + return GetRef::type>()(ptr_or_ref); + } + + template + const auto& get_ref(const T& ptr_or_ref) + { + return GetRef::type>()(ptr_or_ref); + } + +/***************************************************************************** +* get_ptr() +*****************************************************************************/ + // if T is any pointer type we recognize, return it unchanged + template + const T* get_ptr(const T* ptr) { return ptr; } + + template + T* get_ptr(T* ptr) { return ptr; } + + template + const std::shared_ptr& get_ptr(const std::shared_ptr& ptr) { return ptr; } + + template + const std::unique_ptr& get_ptr(const std::unique_ptr& ptr) { return ptr; } + + template + const boost::shared_ptr& get_ptr(const boost::shared_ptr& ptr) { return ptr; } + + template + const boost::intrusive_ptr& get_ptr(const boost::intrusive_ptr& ptr) { return ptr; } + + template + const LLPointer& get_ptr(const LLPointer& ptr) { return ptr; } + + // T is not any pointer type we recognize, take a pointer to the parameter + template + const T* get_ptr(const T& obj) { return &obj; } + + template + T* get_ptr(T& obj) { return &obj; } +} // namespace LL + #endif /* ! defined(LL_LLPTRTO_H) */ diff --git a/indra/llcommon/llsdutil.cpp b/indra/llcommon/llsdutil.cpp index f70bee9903..e98fc0285a 100644 --- a/indra/llcommon/llsdutil.cpp +++ b/indra/llcommon/llsdutil.cpp @@ -1046,3 +1046,38 @@ LLSD llsd_shallow(LLSD value, LLSD filter) return shallow; } + +LLSD LL::apply_llsd_fix(size_t arity, const LLSD& args) +{ + // LLSD supports a number of types, two of which are aggregates: Map and + // Array. We don't try to support Map: supporting Map would seem to + // promise that we could somehow match the string key to 'func's parameter + // names. Uh sorry, maybe in some future version of C++ with reflection. + if (args.isMap()) + { + LLTHROW(LL::apply_error("LL::apply(function, Map LLSD) unsupported")); + } + // We expect an LLSD array, but what the heck, treat isUndefined() as a + // zero-length array for calling a nullary 'func'. + if (args.isUndefined() || args.isArray()) + { + // this works because LLSD().size() == 0 + if (args.size() != arity) + { + LLTHROW(LL::apply_error(stringize("LL::apply(function(", arity, " args), ", + args.size(), "-entry LLSD array)"))); + } + return args; + } + + // args is one of the scalar types + // scalar_LLSD.size() == 0, so don't test that here. + // You can pass a scalar LLSD only to a unary 'func'. + if (arity != 1) + { + LLTHROW(LL::apply_error(stringize("LL::apply(function(", arity, " args), " + "LLSD ", LLSD::typeString(args.type()), ")"))); + } + // make an array of it + return llsd::array(args); +} diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h index eddaa64bd2..546e27930d 100644 --- a/indra/llcommon/llsdutil.h +++ b/indra/llcommon/llsdutil.h @@ -29,10 +29,12 @@ #ifndef LL_LLSDUTIL_H #define LL_LLSDUTIL_H +#include "apply.h" // LL::invoke() #include "llsd.h" #include -#include +#include #include +#include // U32 LL_COMMON_API LLSD ll_sd_from_U32(const U32); @@ -589,6 +591,10 @@ namespace LL /***************************************************************************** * apply(function, LLSD array) *****************************************************************************/ +// validate incoming LLSD blob, and return an LLSD array suitable to pass to +// apply_impl() +LLSD apply_llsd_fix(size_t arity, const LLSD& args); + // Derived from https://stackoverflow.com/a/20441189 // and https://en.cppreference.com/w/cpp/utility/apply . // We can't simply make a tuple from the LLSD array and then apply() that @@ -602,6 +608,16 @@ auto apply_impl(CALLABLE&& func, const LLSD& array, std::index_sequence) return std::forward(func)(LLSDParam(array[I])...); } +// use apply_n(function, LLSD) to call a specific arity of a variadic +// function with (that many) items from the passed LLSD array +template +auto apply_n(CALLABLE&& func, const LLSD& args) +{ + return apply_impl(std::forward(func), + apply_llsd_fix(ARITY, args), + std::make_index_sequence()); +} + /** * apply(function, LLSD) goes beyond C++17 std::apply(). For this case * @a function @emph cannot be variadic: the compiler must know at compile @@ -610,32 +626,11 @@ auto apply_impl(CALLABLE&& func, const LLSD& array, std::index_sequence) template auto apply(CALLABLE&& func, const LLSD& args) { - LLSD array; - constexpr auto arity = boost::function_traits::arity; - // LLSD supports a number of types, two of which are aggregates: Map and - // Array. We don't try to support Map: supporting Map would seem to - // promise that we could somehow match the string key to 'func's parameter - // names. Uh sorry, maybe in some future version of C++ with reflection. - assert(! args.isMap()); - // We expect an LLSD array, but what the heck, treat isUndefined() as a - // zero-length array for calling a nullary 'func'. - if (args.isUndefined() || args.isArray()) - { - // this works because LLSD().size() == 0 - assert(args.size() == arity); - array = args; - } - else // args is one of the scalar types - { - // scalar_LLSD.size() == 0, so don't test that here. - // You can pass a scalar LLSD only to a unary 'func'. - assert(arity == 1); - // make an array of it - array = llsd::array(args); - } - return apply_impl(std::forward(func), - array, - std::make_index_sequence()); + // infer arity from the definition of func + constexpr auto arity = boost::function_types::function_arity< + typename std::remove_reference::type>::value; + // now that we have a compile-time arity, apply_n() works + return apply_n(std::forward(func), args); } } // namespace LL diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index b38e47a773..f09dd63316 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -33,8 +33,6 @@ #include #include #include -#include -#define foreach BOOST_FOREACH #include @@ -206,7 +204,7 @@ struct Vars void methodnb(NPARAMSb) { std::ostringstream vbin; - foreach(U8 byte, bin) + for (U8 byte: bin) { vbin << std::hex << std::setfill('0') << std::setw(2) << unsigned(byte); } @@ -462,7 +460,7 @@ namespace tut debug("dft_array_full:\n", dft_array_full); // Partial defaults arrays. - foreach(LLSD::String a, ab) + for (LLSD::String a: ab) { LLSD::Integer partition(std::min(partial_offset, dft_array_full[a].size())); dft_array_partial[a] = @@ -472,7 +470,7 @@ namespace tut debug("dft_array_partial:\n", dft_array_partial); - foreach(LLSD::String a, ab) + for(LLSD::String a: ab) { // Generate full defaults maps by zipping (params, dft_array_full). dft_map_full[a] = zipmap(params[a], dft_array_full[a]); @@ -599,19 +597,14 @@ namespace tut { // Copy descs to a temp map of same type. DescMap forgotten(descs.begin(), descs.end()); - // LLEventDispatcher intentionally provides only const_iterator: - // since dereferencing that iterator generates values on the fly, - // it's meaningless to have a modifiable iterator. But since our - // 'work' object isn't const, by default BOOST_FOREACH() wants to - // use non-const iterators. Persuade it to use the const_iterator. - foreach(LLEventDispatcher::NameDesc nd, const_cast(work)) + for (LLEventDispatcher::NameDesc nd: work) { DescMap::iterator found = forgotten.find(nd.first); - ensure(STRINGIZE("LLEventDispatcher records function '" << nd.first - << "' we didn't enter"), + ensure(stringize("LLEventDispatcher records function '", nd.first, + "' we didn't enter"), found != forgotten.end()); - ensure_equals(STRINGIZE("LLEventDispatcher desc '" << nd.second << - "' doesn't match what we entered: '" << found->second << "'"), + ensure_equals(stringize("LLEventDispatcher desc '", nd.second, + "' doesn't match what we entered: '", found->second, "'"), nd.second, found->second); // found in our map the name from LLEventDispatcher, good, erase // our map entry @@ -622,26 +615,26 @@ namespace tut std::ostringstream out; out << "LLEventDispatcher failed to report"; const char* delim = ": "; - foreach(const DescMap::value_type& fme, forgotten) + for (const DescMap::value_type& fme: forgotten) { out << delim << fme.first; delim = ", "; } - ensure(out.str(), false); + throw failure(out.str()); } } Vars* varsfor(const std::string& name) { VarsMap::const_iterator found = funcvars.find(name); - ensure(STRINGIZE("No Vars* for " << name), found != funcvars.end()); - ensure(STRINGIZE("NULL Vars* for " << name), found->second); + ensure(stringize("No Vars* for ", name), found != funcvars.end()); + ensure(stringize("NULL Vars* for ", name), found->second); return found->second; } void ensure_has(const std::string& outer, const std::string& inner) { - ensure(STRINGIZE("'" << outer << "' does not contain '" << inner << "'").c_str(), + ensure(stringize("'", outer, "' does not contain '", inner, "'").c_str(), outer.find(inner) != std::string::npos); } @@ -689,7 +682,7 @@ namespace tut LLSD getMetadata(const std::string& name) { LLSD meta(work.getMetadata(name)); - ensure(STRINGIZE("No metadata for " << name), meta.isDefined()); + ensure(stringize("No metadata for ", name), meta.isDefined()); return meta; } @@ -869,12 +862,12 @@ namespace tut LLSD req(LLSD::emptyArray()); if (arity) req[arity - 1] = LLSD(); - foreach(LLSD nm, inArray(names)) + for (LLSD nm: inArray(names)) { LLSD metadata(getMetadata(nm)); ensure_equals("name mismatch", metadata["name"], nm); ensure_equals(metadata["desc"].asString(), descs[nm]); - ensure_equals(STRINGIZE("mismatched required for " << nm.asString()), + ensure_equals(stringize("mismatched required for ", nm.asString()), metadata["required"], req); ensure("should not have optional", metadata["optional"].isUndefined()); } @@ -932,8 +925,8 @@ namespace tut ensure_equals("mdft name", mdft, mmeta["name"]); ameta.erase("name"); mmeta.erase("name"); - ensure_equals(STRINGIZE("metadata for " << adft.asString() - << " vs. " << mdft.asString()), + ensure_equals(stringize("metadata for ", adft.asString(), + " vs. ", mdft.asString()), ameta, mmeta); } } @@ -949,7 +942,7 @@ namespace tut // params are required. Also maps containing left requirements for // partial defaults arrays. Also defaults maps from defaults arrays. LLSD allreq, leftreq, rightdft; - foreach(LLSD::String a, ab) + for (LLSD::String a: ab) { // The map in which all params are required uses params[a] as // keys, with all isUndefined() as values. We can accomplish that @@ -977,9 +970,9 @@ namespace tut // Generate maps containing parameter names not provided by the // dft_map_partial maps. LLSD skipreq(allreq); - foreach(LLSD::String a, ab) + for (LLSD::String a: ab) { - foreach(const MapEntry& me, inMap(dft_map_partial[a])) + for (const MapEntry& me: inMap(dft_map_partial[a])) { skipreq[a].erase(me.first); } @@ -1024,7 +1017,7 @@ namespace tut (llsd::array("freenb_map_mdft", "smethodnb_map_mdft", "methodnb_map_mdft"), llsd::array(LLSD::emptyMap(), dft_map_full["b"])))); // required, optional - foreach(LLSD grp, inArray(groups)) + for (LLSD grp: inArray(groups)) { // Internal structure of each group in 'groups': LLSD names(grp[0]); @@ -1037,14 +1030,14 @@ namespace tut optional); // Loop through 'names' - foreach(LLSD nm, inArray(names)) + for (LLSD nm: inArray(names)) { LLSD metadata(getMetadata(nm)); ensure_equals("name mismatch", metadata["name"], nm); ensure_equals(nm.asString(), metadata["desc"].asString(), descs[nm]); - ensure_equals(STRINGIZE(nm << " required mismatch"), + ensure_equals(stringize(nm, " required mismatch"), metadata["required"], required); - ensure_equals(STRINGIZE(nm << " optional mismatch"), + ensure_equals(stringize(nm, " optional mismatch"), metadata["optional"], optional); } } @@ -1107,7 +1100,7 @@ namespace tut // LLSD value matching 'required' according to llsd_matches() rules. LLSD matching(LLSDMap("d", 3.14)("array", llsd::array("answer", true, answer))); // Okay, walk through 'tests'. - foreach(const CallablesTriple& tr, tests) + for (const CallablesTriple& tr: tests) { // Should be able to pass 'answer' to Callables registered // without 'required'. @@ -1129,14 +1122,17 @@ namespace tut set_test_name("passing wrong args to (map | array)-style registrations"); // Pass scalar/map to array-style functions, scalar/array to map-style - // functions. As that validation happens well before we engage the - // argument magic, it seems pointless to repeat this with every - // variation: (free function | non-static method), (no | arbitrary) - // args. We should only need to engage it for one map-style - // registration and one array-style registration. - std::string array_exc("needs an args array"); - call_logerr("free0_array", 17, array_exc); - call_logerr("free0_array", LLSDMap("pi", 3.14), array_exc); + // functions. It seems pointless to repeat this with every variation: + // (free function | non-static method), (no | arbitrary) args. We + // should only need to engage it for one map-style registration and + // one array-style registration. + // Now that LLEventDispatcher has been extended to treat an LLSD + // scalar as a single-entry array, the error we expect in this case is + // that apply() is trying to pass that non-empty array to a nullary + // function. + call_logerr("free0_array", 17, "LL::apply"); + // similarly, apply() doesn't accept an LLSD Map + call_logerr("free0_array", LLSDMap("pi", 3.14), "unsupported"); std::string map_exc("needs a map"); call_logerr("free0_map", 17, map_exc); @@ -1178,15 +1174,21 @@ namespace tut template<> template<> void object::test<19>() { - set_test_name("call array-style functions with too-short arrays"); - // Could have two different too-short arrays, one for *na and one for - // *nb, but since they both take 5 params... + set_test_name("call array-style functions with wrong-length arrays"); + // Could have different wrong-length arrays for *na and for *nb, but + // since they both take 5 params... LLSD tooshort(llsd::array("this", "array", "too", "short")); - foreach(const LLSD& funcsab, inArray(array_funcs)) + LLSD toolong (llsd::array("this", "array", "is", "one", "too", "long")); + LLSD badargs (llsd::array(tooshort, toolong)); + for (const LLSD& toosomething: inArray(badargs)) { - foreach(const llsd::MapEntry& e, inMap(funcsab)) + for (const LLSD& funcsab: inArray(array_funcs)) { - call_logerr(e.second, tooshort, "requires more arguments"); + for (const llsd::MapEntry& e: inMap(funcsab)) + { + // apply() complains about wrong number of array entries + call_logerr(e.second, toosomething, "LL::apply"); + } } } } @@ -1206,40 +1208,25 @@ namespace tut LLDate("2011-02-03T15:07:00Z"), LLURI("http://secondlife.com"), binary))); - LLSD argsplus(args); - argsplus["a"].append("bogus"); - argsplus["b"].append("bogus"); LLSD expect; - foreach(LLSD::String a, ab) + for (LLSD::String a: ab) { expect[a] = zipmap(params[a], args[a]); } // Adjust expect["a"]["cp"] for special Vars::cp treatment. - expect["a"]["cp"] = std::string("'") + expect["a"]["cp"].asString() + "'"; + expect["a"]["cp"] = stringize("'", expect["a"]["cp"].asString(), "'"); debug("expect: ", expect); - // Use substantially the same logic for args and argsplus - LLSD argsarrays(llsd::array(args, argsplus)); - // So i==0 selects 'args', i==1 selects argsplus - for (LLSD::Integer i(0), iend(argsarrays.size()); i < iend; ++i) + for (const LLSD& funcsab: inArray(array_funcs)) { - foreach(const LLSD& funcsab, inArray(array_funcs)) + for (LLSD::String a: ab) { - foreach(LLSD::String a, ab) - { - // Reset the Vars instance before each call - Vars* vars(varsfor(funcsab[a])); - *vars = Vars(); - work(funcsab[a], argsarrays[i][a]); - ensure_llsd(STRINGIZE(funcsab[a].asString() << - ": expect[\"" << a << "\"] mismatch"), - vars->inspect(), expect[a], 7); // 7 bits ~= 2 decimal digits - - // TODO: in the i==1 or argsplus case, intercept LL_WARNS - // output? Even without that, using argsplus verifies that - // passing too many args isn't fatal; it works -- but - // would be nice to notice the warning too. - } + // Reset the Vars instance before each call + Vars* vars(varsfor(funcsab[a])); + *vars = Vars(); + work(funcsab[a], args[a]); + ensure_llsd(stringize(funcsab[a].asString(), ": expect[\"", a, "\"] mismatch"), + vars->inspect(), expect[a], 7); // 7 bits ~= 2 decimal digits } } } @@ -1267,7 +1254,7 @@ namespace tut ("a", llsd::array(false, 255, 98.6, 1024.5, "pointer")) ("b", llsd::array("object", LLUUID::generateNewID(), LLDate::now(), LLURI("http://wiki.lindenlab.com/wiki"), LLSD::Binary(boost::begin(binary), boost::end(binary))))); LLSD array_overfull(array_full); - foreach(LLSD::String a, ab) + for (LLSD::String a: ab) { array_overfull[a].append("bogus"); } @@ -1281,7 +1268,7 @@ namespace tut ensure_not_equals("UUID collision", array_full["b"][1].asUUID(), dft_array_full["b"][1].asUUID()); LLSD map_full, map_overfull; - foreach(LLSD::String a, ab) + for (LLSD::String a: ab) { map_full[a] = zipmap(params[a], array_full[a]); map_overfull[a] = map_full[a]; @@ -1324,15 +1311,15 @@ namespace tut LLSD argssets(llsd::array(array_full, array_overfull, map_full, map_overfull)); foreach(const LLSD& args, inArray(argssets)) { - foreach(LLSD::String a, ab) + for (LLSD::String a: ab) { - foreach(LLSD::String name, inArray(names[a])) + for (LLSD::String name: inArray(names[a])) { // Reset the Vars instance Vars* vars(varsfor(name)); *vars = Vars(); work(name, args[a]); - ensure_llsd(STRINGIZE(name << ": expect[\"" << a << "\"] mismatch"), + ensure_llsd(stringize(name, ": expect[\"", a, "\"] mismatch"), vars->inspect(), expect[a], 7); // 7 bits, 2 decimal digits // intercept LL_WARNS for the two overfull cases? } -- cgit v1.2.3 From 8ac35b626072bd94178861dbbaf7d835354c9765 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 22 Dec 2022 15:51:56 -0500 Subject: DRTVWR-558: Add apply_n(function, std::vector) for variadics. apply_n(function, LLSD array) has been useful, so for completeness, add the corresponding function for std::vector. Add a reference to apply_n() in comments for both apply() functions. (cherry picked from commit dfb63a92e0e9a419931caf5112e1f590924e0867) --- indra/llcommon/apply.h | 19 ++++++++++++++----- indra/llcommon/llsdutil.h | 3 ++- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/indra/llcommon/apply.h b/indra/llcommon/apply.h index 357887dfd7..123813bcec 100644 --- a/indra/llcommon/apply.h +++ b/indra/llcommon/apply.h @@ -188,19 +188,28 @@ struct apply_error: public LLException apply_error(const std::string& what): LLException(what) {} }; +template +auto apply_n(CALLABLE&& func, const std::vector& args) +{ + apply_validate_size(args.size(), ARITY); + return apply_impl(std::forward(func), + args, + std::make_index_sequence()); +} + /** * apply(function, std::vector) goes beyond C++17 std::apply(). For this case * @a function @emph cannot be variadic: the compiler must know at compile - * time how many arguments to pass. This isn't Python. + * time how many arguments to pass. This isn't Python. (But see apply_n() to + * pass a specific number of args to a variadic function.) */ template auto apply(CALLABLE&& func, const std::vector& args) { + // infer arity from the definition of func constexpr auto arity = boost::function_traits::arity; - apply_validate_size(args.size(), arity); - return apply_impl(std::forward(func), - args, - std::make_index_sequence()); + // now that we have a compile-time arity, apply_n() works + return apply_n(std::forward(func), args); } } // namespace LL diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h index 546e27930d..baf4400768 100644 --- a/indra/llcommon/llsdutil.h +++ b/indra/llcommon/llsdutil.h @@ -621,7 +621,8 @@ auto apply_n(CALLABLE&& func, const LLSD& args) /** * apply(function, LLSD) goes beyond C++17 std::apply(). For this case * @a function @emph cannot be variadic: the compiler must know at compile - * time how many arguments to pass. This isn't Python. + * time how many arguments to pass. This isn't Python. (But see apply_n() to + * pass a specific number of args to a variadic function.) */ template auto apply(CALLABLE&& func, const LLSD& args) -- cgit v1.2.3 From a02ccbf1b81549cd44ff5088c94dbb5fc756fea0 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 6 Jan 2023 17:04:12 -0500 Subject: DRTVWR-558: Support LLStoreListener, add LLCaptureListener. LLStoreListener didn't work because of an ambiguity problem. Resolve that by introducing an internal storeTarget() method. Introduce LLCaptureListener that's both an LLStoreListener and the variable into which to capture the expected result. Introduce LLVarHolder to contain the variable, so we can guarantee the actual data member will be fully constructed by the time we want to pass it to the LLStoreListener base class. (cherry picked from commit a894703188a7755bb9acb897d6c31ae1af6efce0) --- indra/llcommon/lleventfilter.h | 47 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/indra/llcommon/lleventfilter.h b/indra/llcommon/lleventfilter.h index 7613850fb2..1fb41e0297 100644 --- a/indra/llcommon/lleventfilter.h +++ b/indra/llcommon/lleventfilter.h @@ -435,16 +435,61 @@ public: // generic type-appropriate store through mTarget, construct an // LLSDParam and store that, thus engaging LLSDParam's custom // conversions. - mTarget = LLSDParam(llsd::drill(event, mPath)); + storeTarget(LLSDParam(llsd::drill(event, mPath))); return mConsume; } private: + // This method disambiguates LLStoreListener. Directly assigning + // some_LLSD_var = LLSDParam(some_LLSD_value); + // is problematic because the compiler has too many choices: LLSD has + // multiple assignment operator overloads, and LLSDParam has a + // templated conversion operator. But LLSDParam can convert to a + // (const LLSD&) parameter, and LLSD::operator=(const LLSD&) works. + void storeTarget(const T& value) + { + mTarget = value; + } + T& mTarget; const LLSD mPath; const bool mConsume; }; +/** + * LLVarHolder bundles a target variable of the specified type. We use it as a + * base class so the target variable will be fully constructed by the time a + * subclass constructor tries to pass a reference to some other base class. + */ +template +struct LLVarHolder +{ + T mVar; +}; + +/** + * LLCaptureListener isa LLStoreListener that bundles the target variable of + * interest. + */ +template +class LLCaptureListener: public LLVarHolder, + public LLStoreListener +{ +private: + using holder = LLVarHolder; + using super = LLStoreListener; + +public: + LLCaptureListener(const LLSD& path=LLSD(), bool consume=false): + super(*this, holder::mVar, path, consume) + {} + + void set(T&& newval=T()) { holder::mVar = std::forward(newval); } + + const T& get() const { return holder::mVar; } + operator const T&() { return holder::mVar; } +}; + /***************************************************************************** * LLEventLogProxy *****************************************************************************/ -- cgit v1.2.3 From 2f39489f2cdafed08c13d5011d9538b17e64d345 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 6 Jan 2023 17:05:37 -0500 Subject: DRTVWR-558: Demangle names of exceptions reported by TUT tests. (cherry picked from commit 2ffbadc7a1275f662eb97ff9ce163a18cdb62c7c) --- indra/test/test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/test/test.cpp b/indra/test/test.cpp index bb48216b2b..1161a6d8e4 100644 --- a/indra/test/test.cpp +++ b/indra/test/test.cpp @@ -259,7 +259,7 @@ public: break; case tut::test_result::ex: ++mFailedTests; - out << "exception: " << tr.exception_typeid; + out << "exception: " << LLError::Log::demangle(tr.exception_typeid.c_str()); break; case tut::test_result::warn: ++mFailedTests; -- cgit v1.2.3 From 8e61a8f7c0ebcc72a1c348e790eeb73b9388ccde Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 6 Jan 2023 17:06:39 -0500 Subject: DRTVWR-558: Allow directly streaming test helper class CaptureLog. (cherry picked from commit 374eb409b98795158b36e232f670d1302f31b9ff) --- indra/llcommon/tests/wrapllerrs.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/indra/llcommon/tests/wrapllerrs.h b/indra/llcommon/tests/wrapllerrs.h index 3779fb41bc..d657b329bb 100644 --- a/indra/llcommon/tests/wrapllerrs.h +++ b/indra/llcommon/tests/wrapllerrs.h @@ -226,6 +226,11 @@ public: return boost::dynamic_pointer_cast(mRecorder)->streamto(out); } + friend inline std::ostream& operator<<(std::ostream& out, const CaptureLog& self) + { + return self.streamto(out); + } + private: LLError::FatalFunction mFatalFunction; LLError::SettingsStoragePtr mOldSettings; -- cgit v1.2.3 From b2205bde52acf82575757f74a642c40b7433bf6b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 6 Jan 2023 19:58:12 -0500 Subject: DRTVWR-558: Clean up LLEventDispatcher argument and result handling. Add a new LLEventDispatcher constructor accepting not only the map key to extract a requested function name, but a second map key to extract the arguments -- when required. In Doxygen comments, clarify the difference between the two constructors. Move interaction with the LLEventPump subsystem to LLDispatchListener. LLEventDispatcher is intended to be directly called. On error, instead of looking for a "reply" key in the invocation LLSD, throw DispatchError. Publish DispatchError, formerly an implementation detail, and its new subclass DispatchMissing. Make both LLEventDispatcher::operator()() overloads return LLSD, leveraging the new internal ReturnLLSD logic that returns a degenerate LLSD blob for a void target callable and, for compatible types, converts the returned value to LLSD. Notably, the public try_call() overloads still return bool; any value returned by the target callable is discarded. Clarify the operator() and try_call() argument requirements for target callables registered to accept an LLSD array, in Doxygen comments and in code. In particular, the 'event' passed to (event) overloads (vs. the (name, event) overloads) must be an LLSD map, so it must contain an "args" key (or the new arguments map key specified to the constructor) containing the LLSD args array. Since the use of the new args key depends on whether the target callable is registered to accept an array or a map, pass it into DispatchEntry::call() (and all subclass overrides), along with a bool to disambiguate whether we reached that method from an LLEventDispatcher (event) invocation method or a (name, event) invocation method. Allow streaming an LLEventDispatcher instance to std::ostream, primarily to facilitate construction of proper error messages. Revert the 'name' argument of internal try_call(key, name, event) to std::string. Ditch try_call_log(), try_call_one() and reply(). Fold try_call_one() logic into three-argument try_call(). Refactor callFail() as a template method accepting both the exception to throw and arbitrary stringize() arguments from which to construct the exception message. Non-static callFail() implicitly prepends the instance and a colon to the rest of the arguments, and calls static sCallFail(). The latter constructs the exception message, logs it and throws the specified exception. This obviates try_call_log(). Make implementation detail helper class LLSDArgsMapper a private member of LLEventDispatcher so it can access sCallFail(): we now want all error handling to go through that method. Add LLSDArgsMapper::callFail() resembling LLSDEventDispatcher::callFail(), but without having to specify the exception: only LLEventDispatcher will throw anything but generic DispatchError. Give LLEventDispatcher::ParamsDispatchEntry and its subclasses ArrayParamsDispatchEntry and MapParamsDispatchEntry a new 'name' argument to identify error messages. Store it and use it implicitly in new callFail() method, very like LLSDArgsMapper::callFail(). Make LLEventDispatcher:: addArrayParamsDispatchEntry() and addMapParamsDispatchEntry() pass a 'name' that includes the LLEventDispatcher instance name as well as the name of the specific registered callable. This way we need not intercept a low-level error and annotate it with contextual data: we can just let the exception propagate. Make ParamsDispatchEntry::call() override catch LL::apply_error thrown by an invoker_function, and pass its message to callFail(), i.e. rethrow as LLEventDispatcher::DispatchError. Introduce ArrayParamsDispatchEntry::call() override for the special logic to extract an arguments array from a passed LLSD map -- but only under the circumstances described in the Doxygen comment. Add similar logic to MapParamsDispatchEntry::call(), but with both argskey itself and a value for argskey optional in the passed LLSD map. Because LLEventDispatcher now has two constructor overloads, allow subclass constructor LLDispatchListener() to accept zero or more trailing arguments. This is different than giving LLDispatchListener's constructor a default final argument, in that the subclass doesn't need to specify its default value: that's up to the base-class constructor. But it does require that the subclass constructor move to the header file. Move private LLEventDispatcher::reply() method to LLDispatchListener. Extend LLDispatchListener::process() to handle DispatchError by attempting to reply with a map containing an "error" key, per convention. (In other words, move that logic from LLEventDispatcher to LLDispatchListener.) Also, for a map LLSD result, attempt to reply with that result; for other defined LLSD types, attempt to reply with a map containing a "data" key. This is backwards compatible with previous behavior because all previous LLDispatchListener subclass methods returned void, which now produces an undefined LLSD blob, which we don't bother trying to send in reply. In lleventdispatcher_test.cpp, rework tut::lleventdispatcher_data::call_exc() yet again to catch DispatchError instead of listening for an LLEventPump reply event. Similarly, make call_logerr() catch DispatchError. Since the exception should also be logged, we ignore it and focus on the log, as before. Add tests <23> to <27>, exercising calls to new class DispatchResult methods returning string, int, LLSD map, LLSD array and void. (cherry picked from commit 2f9c915dd3d5137b5b2b1a57f0179e1f7a090f8c) --- indra/llcommon/lleventdispatcher.cpp | 428 +++++++++++++----------- indra/llcommon/lleventdispatcher.h | 166 +++++++-- indra/llcommon/tests/lleventdispatcher_test.cpp | 126 +++++-- 3 files changed, 463 insertions(+), 257 deletions(-) diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index e7e73125a7..7e5723c503 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -43,20 +43,9 @@ #include "llexception.h" #include "llsdutil.h" #include "stringize.h" +#include // std::quoted() #include // std::auto_ptr -/***************************************************************************** -* DispatchError -*****************************************************************************/ -struct DispatchError: public LLException -{ - // template constructor involving strings passes all arguments to - // stringize() to construct LLException's what() string - template - DispatchError(const std::string& arg0, ARGS&&... args): - LLException(stringize(arg0, std::forward(args)...)) {} -}; - /***************************************************************************** * LLSDArgsMapper *****************************************************************************/ @@ -109,7 +98,7 @@ struct DispatchError: public LLException * - Holes are filled with the default values. * - Any remaining holes constitute an error. */ -class LL_COMMON_API LLSDArgsMapper +class LL_COMMON_API LLEventDispatcher::LLSDArgsMapper { public: /// Accept description of function: function name, param names, param @@ -122,6 +111,8 @@ public: private: static std::string formatlist(const LLSD&); + template + void callFail(ARGS&&... args) const; // The function-name string is purely descriptive. We want error messages // to be able to indicate which function's LLSDArgsMapper has the problem. @@ -141,15 +132,16 @@ private: FilledVector _has_dft; }; -LLSDArgsMapper::LLSDArgsMapper(const std::string& function, - const LLSD& names, const LLSD& defaults): +LLEventDispatcher::LLSDArgsMapper::LLSDArgsMapper(const std::string& function, + const LLSD& names, + const LLSD& defaults): _function(function), _names(names), _has_dft(names.size()) { if (! (_names.isUndefined() || _names.isArray())) { - LLTHROW(DispatchError(function, " names must be an array, not ", names)); + callFail(" names must be an array, not ", names); } auto nparams(_names.size()); // From _names generate _indexes. @@ -172,8 +164,7 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function, // defaults is a (possibly empty) array. Right-align it with names. if (ndefaults > nparams) { - LLTHROW(DispatchError(function, " names array ", names, - " shorter than defaults array ", defaults)); + callFail(" names array ", names, " shorter than defaults array ", defaults); } // Offset by which we slide defaults array right to right-align with @@ -210,23 +201,20 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function, } if (bogus.size()) { - LLTHROW(DispatchError(function, " defaults specified for nonexistent params ", - formatlist(bogus))); + callFail(" defaults specified for nonexistent params ", formatlist(bogus)); } } else { - LLTHROW(DispatchError(function, " defaults must be a map or an array, not ", - defaults)); + callFail(" defaults must be a map or an array, not ", defaults); } } -LLSD LLSDArgsMapper::map(const LLSD& argsmap) const +LLSD LLEventDispatcher::LLSDArgsMapper::map(const LLSD& argsmap) const { if (! (argsmap.isUndefined() || argsmap.isMap() || argsmap.isArray())) { - LLTHROW(DispatchError(_function, " map() needs a map or array, not ", - argsmap)); + callFail(" map() needs a map or array, not ", argsmap); } // Initialize the args array. Indexing a non-const LLSD array grows it // to appropriate size, but we don't want to resize this one on each @@ -323,15 +311,14 @@ LLSD LLSDArgsMapper::map(const LLSD& argsmap) const // by argsmap, that's a problem. if (unfilled.size()) { - LLTHROW(DispatchError(_function, " missing required arguments ", - formatlist(unfilled), " from ", argsmap)); + callFail(" missing required arguments ", formatlist(unfilled), " from ", argsmap); } // done return args; } -std::string LLSDArgsMapper::formatlist(const LLSD& list) +std::string LLEventDispatcher::LLSDArgsMapper::formatlist(const LLSD& list) { std::ostringstream out; const char* delim = ""; @@ -344,14 +331,26 @@ std::string LLSDArgsMapper::formatlist(const LLSD& list) return out.str(); } +template +void LLEventDispatcher::LLSDArgsMapper::callFail(ARGS&&... args) const +{ + LLEventDispatcher::sCallFail + (_function, std::forward(args)...); +} + /***************************************************************************** * LLEventDispatcher *****************************************************************************/ LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key): + LLEventDispatcher(desc, key, "args") +{} + +LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key, + const std::string& argskey): mDesc(desc), - mKey(key) -{ -} + mKey(key), + mArgskey(argskey) +{} LLEventDispatcher::~LLEventDispatcher() { @@ -375,20 +374,21 @@ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchE Callable mFunc; LLSD mRequired; - virtual LLSD call(const std::string& desc, const LLSD& event) const + LLSD call(const std::string& desc, const LLSD& event, bool, const std::string&) const override { // Validate the syntax of the event itself. std::string mismatch(llsd_matches(mRequired, event)); if (! mismatch.empty()) { - LLTHROW(DispatchError(desc, ": bad request: ", mismatch)); + LLEventDispatcher::sCallFail + (desc, ": bad request: ", mismatch); } // Event syntax looks good, go for it! mFunc(event); return {}; } - virtual LLSD addMetadata(LLSD meta) const + LLSD addMetadata(LLSD meta) const override { meta["required"] = mRequired; return meta; @@ -401,16 +401,35 @@ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchE */ struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::DispatchEntry { - ParamsDispatchEntry(const std::string& desc, const invoker_function& func): + ParamsDispatchEntry(const std::string& name, const std::string& desc, + const invoker_function& func): DispatchEntry(desc), + mName(name), mInvoker(func) {} + std::string mName; invoker_function mInvoker; - virtual LLSD call(const std::string&, const LLSD& event) const + LLSD call(const std::string&, const LLSD& event, bool, const std::string&) const override + { + try + { + return mInvoker(event); + } + catch (const LL::apply_error& err) + { + // could hit runtime errors with LL::apply() + return callFail(err.what()); + } + } + + template + LLSD callFail(ARGS&&... args) const { - return mInvoker(event); + LLEventDispatcher::sCallFail(mName, ": ", std::forward(args)...); + // pacify the compiler + return {}; } }; @@ -420,15 +439,48 @@ struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::Dispatc */ struct LLEventDispatcher::ArrayParamsDispatchEntry: public LLEventDispatcher::ParamsDispatchEntry { - ArrayParamsDispatchEntry(const std::string& desc, const invoker_function& func, - LLSD::Integer arity): - ParamsDispatchEntry(desc, func), + ArrayParamsDispatchEntry(const std::string& name, const std::string& desc, + const invoker_function& func, LLSD::Integer arity): + ParamsDispatchEntry(name, desc, func), mArity(arity) {} LLSD::Integer mArity; - virtual LLSD addMetadata(LLSD meta) const + LLSD call(const std::string& desc, const LLSD& event, bool fromMap, const std::string& argskey) const override + { +// std::string context { stringize(desc, "(", event, ") with argskey ", std::quoted(argskey), ": ") }; + // Whether we try to extract arguments from 'event' depends on whether + // the LLEventDispatcher consumer called one of the (name, event) + // methods (! fromMap) or one of the (event) methods (fromMap). If we + // were called with (name, event), the passed event must itself be + // suitable to pass to the registered callable, no args extraction + // required or even attempted. Only if called with plain (event) do we + // consider extracting args from that event. Initially assume 'event' + // itself contains the arguments. + LLSD args{ event }; + if (fromMap) + { + if (mArity) + { + // We only require/retrieve argskey if the target function + // isn't nullary. For all others, since we require an LLSD + // array, we must have an argskey. + if (argskey.empty()) + { + return callFail("LLEventDispatcher has no args key"); + } + if ((! event.has(argskey))) + { + return callFail("missing required key ", std::quoted(argskey)); + } + args = event[argskey]; + } + } + return ParamsDispatchEntry::call(desc, args, fromMap, argskey); + } + + LLSD addMetadata(LLSD meta) const override { LLSD array(LLSD::emptyArray()); // Resize to number of arguments required @@ -449,7 +501,7 @@ struct LLEventDispatcher::MapParamsDispatchEntry: public LLEventDispatcher::Para MapParamsDispatchEntry(const std::string& name, const std::string& desc, const invoker_function& func, const LLSD& params, const LLSD& defaults): - ParamsDispatchEntry(desc, func), + ParamsDispatchEntry(name, desc, func), mMapper(name, params, defaults), mRequired(LLSD::emptyMap()) { @@ -493,14 +545,25 @@ struct LLEventDispatcher::MapParamsDispatchEntry: public LLEventDispatcher::Para LLSD mRequired; LLSD mOptional; - virtual LLSD call(const std::string& desc, const LLSD& event) const + LLSD call(const std::string& desc, const LLSD& event, bool fromMap, const std::string& argskey) const override { - // Just convert from LLSD::Map to LLSD::Array using mMapper, then pass - // to base-class call() method. - return ParamsDispatchEntry::call(desc, mMapper.map(event)); + // by default, pass the whole event as the arguments map + LLSD args{ event }; + // Were we called by one of the (event) methods (instead of the (name, + // event) methods), do we have an argskey, and does the incoming event + // have that key? + if (fromMap && (! argskey.empty()) && event.has(argskey)) + { + // if so, extract the value of argskey from the incoming event, + // and use that as the arguments map + args = event[argskey]; + } + // Now convert args from LLSD map to LLSD array using mMapper, then + // pass to base-class call() method. + return ParamsDispatchEntry::call(desc, mMapper.map(args), fromMap, argskey); } - virtual LLSD addMetadata(LLSD meta) const + LLSD addMetadata(LLSD meta) const override { meta["required"] = mRequired; meta["optional"] = mOptional; @@ -513,7 +576,11 @@ void LLEventDispatcher::addArrayParamsDispatchEntry(const std::string& name, const invoker_function& invoker, LLSD::Integer arity) { - mDispatch.emplace(name, new ArrayParamsDispatchEntry(desc, invoker, arity)); + // The first parameter to ArrayParamsDispatchEntry is solely for error + // messages. Identify our instance and this entry. + mDispatch.emplace( + name, + new ArrayParamsDispatchEntry(stringize(*this, '[', name, ']'), desc, invoker, arity)); } void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name, @@ -522,7 +589,11 @@ void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name, const LLSD& params, const LLSD& defaults) { - mDispatch.emplace(name, new MapParamsDispatchEntry(name, desc, invoker, params, defaults)); + // Pass instance info as well as this entry name for error messages. + mDispatch.emplace( + name, + new MapParamsDispatchEntry(stringize(*this, '[', name, ']'), + desc, invoker, params, defaults)); } /// Register a callable by name @@ -546,174 +617,101 @@ bool LLEventDispatcher::remove(const std::string& name) /// Call a registered callable with an explicitly-specified name. It is an /// error if no such callable exists. -void LLEventDispatcher::operator()(const std::string& name, const LLSD& event) const +LLSD LLEventDispatcher::operator()(const std::string& name, const LLSD& event) const { - std::string error{ try_call_log(std::string(), name, event) }; - if (! error.empty()) - { - callFail(event, error); - } + return try_call(std::string(), name, event); } -/// Extract the @a key value from the incoming @a event, and call the callable -/// whose name is specified by that map @a key. It is an error if no such -/// callable exists. -void LLEventDispatcher::operator()(const LLSD& event) const +bool LLEventDispatcher::try_call(const std::string& name, const LLSD& event) const { - std::string error{ try_call_log(mKey, event[mKey], event) }; - if (! error.empty()) + try { - callFail(event, error); + try_call(std::string(), name, event); + return true; } -} - -void LLEventDispatcher::callFail(const LLSD& event, const std::string& msg) const -{ - // pass back a response that includes an "error" key with the message. - reply(llsd::map("error", msg), event); -} - -void LLEventDispatcher::reply(const LLSD& response, const LLSD& event) const -{ - static LLSD::String key{ "reply" }; - if (event.has(key)) + // Note that we don't catch the generic DispatchError, only the specific + // DispatchMissing. try_call() only promises to return false if the + // specified callable name isn't found -- not for general errors. + catch (const DispatchMissing&) { - // Oh good, the incoming event specifies a reply pump -- pass back - // our response. - sendReply(response, event, key); + return false; } } -bool LLEventDispatcher::try_call(const LLSD& event) const -{ - return try_call_log(mKey, event[mKey], event).empty(); -} - -/*==========================================================================*| - TODO: - -* When mInvoker returns result.isDefined(), sendReply(llsd::map("data", result)) -* When try_call finds name.isArray(), construct response array from - dispatching each call, sendReply() as above -* When try_call finds name.isMap(), construct response map from dispatching - each call, sendReply() as above -- note, caller can't care about order -* Possible future transactional behavior: look up all names before calling any - -|*==========================================================================*/ -bool LLEventDispatcher::try_call(const std::string& name, const LLSD& event) const +/// Extract the @a key value from the incoming @a event, and call the callable +/// whose name is specified by that map @a key. It is an error if no such +/// callable exists. +LLSD LLEventDispatcher::operator()(const LLSD& event) const { - return try_call_log(std::string(), name, event).empty(); + return try_call(mKey, event[mKey], event); } -std::string LLEventDispatcher::try_call_log(const std::string& key, const LLSD& name, - const LLSD& event) const +bool LLEventDispatcher::try_call(const LLSD& event) const { - std::string error{ try_call(key, name, event) }; - if (! error.empty()) + try { - // If we're a subclass of LLEventDispatcher, e.g. LLEventAPI, report that. - error = stringize(LLError::Log::classname(this), "(", mDesc, "): ", error); - LL_WARNS("LLEventDispatcher") << error << LL_ENDL; + try_call(mKey, event[mKey], event); + return true; + } + catch (const DispatchMissing&) + { + return false; } - return error; } -// This internal method returns empty string if the call succeeded, else -// non-empty error message. -std::string LLEventDispatcher::try_call(const std::string& key, const LLSD& name, - const LLSD& event) const +LLSD LLEventDispatcher::try_call(const std::string& key, const std::string& name, + const LLSD& event) const { - if (name.isUndefined()) + if (name.empty()) { if (key.empty()) { - return "attempting to call with no name"; + callFail("attempting to call with no name"); } else { - return stringize("no ", key); - } - } - else if (name.isArray()) - { - return stringize(key, " array dispatch ", name, " not yet implemented"); - } - else if (name.isMap()) - { - return stringize(key, " map dispatch ", name, " not yet implemented"); - } - else if (! name.isString()) - { - return stringize(key, " bad type ", LLSD::typeString(name.type()), ' ', name, - " -- function names are String"); - } - else // name is an LLSD::String - { - auto success{ try_call_one(key, name, event) }; - // pretend to unpack - std::string& error{ success.first }; - LLSD& result{ success.second }; - // did try_call_one() report an error? - if (! error.empty()) - { - // if we have a reply key, respond to invoker - reply(llsd::map("error", error), event); - // now tell caller - return error; - } - // try_call_one() succeeded in calling the target function -- - // should we reply to invoker? - if (result.isUndefined()) - { - // We would get result.isUndefined() if the target function has - // void return. In any case, even if the target function returns - // LLSD, isUndefined() means "don't bother sending response." - return {}; - } - // result.isDefined(): the target function returned something. - // Respond to invoker if we have a "reply" key. - if (! result.isMap()) - { - // wrap result in a map to play well with sendReply() - result = llsd::map("data", result); + callFail("no ", key); } - reply(result, event); - return {}; } -} -std::pair -LLEventDispatcher::try_call_one(const std::string& key, const std::string& name, - const LLSD& event) const -{ DispatchMap::const_iterator found = mDispatch.find(name); if (found == mDispatch.end()) { + // Here we were passed a valid name, but there's no registered + // callable with that name. This is the one case in which we throw + // DispatchMissing instead of the generic DispatchError. + // Distinguish the public method by which our caller reached here: + // key.empty() means the name was passed explicitly, non-empty means + // we extracted the name from the incoming event using that key. if (key.empty()) { - return { stringize("'", name, "' not found"), {} }; + callFail(std::quoted(name), " not found"); } else { - return { stringize("bad ", key, " value '", name, "'"), {} }; + callFail("bad ", key, " value ", std::quoted(name)); } } - try - { - // Found the name, so it's plausible to even attempt the call. - return { {}, found->second->call(stringize("calling '", name, "'"), event) }; - } - catch (const DispatchError& err) - { - // trouble preparing arguments - return { err.what(), {} }; - } - catch (const LL::apply_error& err) - { - // could also hit runtime errors with LL::apply() - return { err.what(), {} }; - } + // Found the name, so it's plausible to even attempt the call. + return found->second->call(stringize(*this, " calling ", std::quoted(name)), + event, (! key.empty()), mArgskey); +} + +template +//static +void LLEventDispatcher::sCallFail(ARGS&&... args) +{ + auto error = stringize(std::forward(args)...); + LL_WARNS("LLEventDispatcher") << error << LL_ENDL; + LLTHROW(EXCEPTION(error)); +} + +template +void LLEventDispatcher::callFail(ARGS&&... args) const +{ + // Describe this instance in addition to the error itself. + sCallFail(*this, ": ", std::forward(args)...); } LLSD LLEventDispatcher::getMetadata(const std::string& name) const @@ -729,27 +727,69 @@ LLSD LLEventDispatcher::getMetadata(const std::string& name) const return found->second->addMetadata(meta); } +std::ostream& operator<<(std::ostream& out, const LLEventDispatcher& self) +{ + // If we're a subclass of LLEventDispatcher, e.g. LLEventAPI, report that. + return out << LLError::Log::classname(self) << '(' << self.mDesc << ')'; +} + /***************************************************************************** * LLDispatchListener *****************************************************************************/ -LLDispatchListener::LLDispatchListener(const std::string& pumpname, const std::string& key): - LLEventDispatcher(pumpname, key), - // Do NOT tweak the passed pumpname. In practice, when someone - // instantiates a subclass of our LLEventAPI subclass, they intend to - // claim that LLEventPump name in the global LLEventPumps namespace. It - // would be mysterious and distressing if we allowed name tweaking, and - // someone else claimed pumpname first for a completely unrelated - // LLEventPump. Posted events would never reach our subclass listener - // because we would have silently changed its name; meanwhile listeners - // (if any) on that other LLEventPump would be confused by the events - // intended for our subclass. - LLEventStream(pumpname, false), - mBoundListener(listen("self", [this](const LLSD& event){ return process(event); })) +/*==========================================================================*| + TODO: +* When process() finds name.isArray(), construct response array from + dispatching each call -- args must also be (array of args structures) + (could also construct response map, IF array contains unique names) +* When process() finds name.isMap(), construct response map from dispatching + each call -- value of each key is its args struct -- argskey ignored -- + note, caller can't care about order +* Possible future transactional behavior: look up all names before calling any +|*==========================================================================*/ +std::string LLDispatchListener::mReplyKey{ "reply" }; + +bool LLDispatchListener::process(const LLSD& event) const { + LLSD result; + try + { + result = (*this)(event); + } + catch (const DispatchError& err) + { + // If the incoming event contains a "reply" key, we'll respond to the + // invoker with an error message (below). But if not -- silently + // ignoring an invocation request would be confusing at best. Escalate. + if (! event.has(mReplyKey)) + { + throw; + } + + // reply with a map containing an "error" key explaining the problem + reply(llsd::map("error", err.what()), event); + return false; + } + + // We seem to have gotten a valid result. But we don't know whether the + // registered callable is void or non-void. If it's void, + // LLEventDispatcher will return isUndefined(). Otherwise, try to send it + // back to our invoker. + if (result.isDefined()) + { + if (! result.isMap()) + { + // wrap the result in a map as the "data" key + result = llsd::map("data", result); + } + reply(result, event); + } + return false; } -bool LLDispatchListener::process(const LLSD& event) +void LLDispatchListener::reply(const LLSD& reply, const LLSD& request) const { - (*this)(event); - return false; + // Call sendReply() unconditionally: sendReply() itself tests whether the + // specified reply key is present in the incoming request, and does + // nothing if there's no such key. + sendReply(reply, request, mReplyKey); } diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index cebce618df..494fc6a366 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -57,7 +57,20 @@ class LLSD; class LL_COMMON_API LLEventDispatcher { public: + /** + * Pass description and the LLSD key used by try_call(const LLSD&) and + * operator()(const LLSD&) to extract the name of the registered callable + * to invoke. + */ LLEventDispatcher(const std::string& desc, const std::string& key); + /** + * Pass description, the LLSD key used by try_call(const LLSD&) and + * operator()(const LLSD&) to extract the name of the registered callable + * to invoke, and the LLSD key used by try_call(const LLSD&) and + * operator()(const LLSD&) to extract arguments LLSD. + */ + LLEventDispatcher(const std::string& desc, const std::string& key, + const std::string& argskey); virtual ~LLEventDispatcher(); /// @name Register functions accepting(const LLSD&) @@ -146,6 +159,8 @@ public: * boost::lambda::var(instance) or boost::lambda::constant(instance_ptr) * produce suitable callables. * + * TODO: variant accepting a method of the containing class, no getter. + * * When calling this name, pass an LLSD::Array. Each entry in turn will be * converted to the corresponding parameter type using LLSDParam. */ @@ -185,6 +200,8 @@ public: * boost::lambda::var(instance) or boost::lambda::constant(instance_ptr) * produce suitable callables. * + * TODO: variant accepting a method of the containing class, no getter. + * * Pass an LLSD::Array of parameter names, and optionally another * LLSD::Array of default parameter values, a la LLSDArgsMapper. * @@ -206,28 +223,82 @@ public: /// Unregister a callable bool remove(const std::string& name); - /// Call a registered callable with an explicitly-specified name. It is an - /// error if no such callable exists. It is an error if the @a event fails - /// to match the @a required prototype specified at add() time. - void operator()(const std::string& name, const LLSD& event) const; + /// Exception if an attempted call fails for any reason + struct DispatchError: public LLException + { + DispatchError(const std::string& what): LLException(what) {} + }; + + /// Specific exception for an attempt to call a nonexistent name + struct DispatchMissing: public DispatchError + { + DispatchMissing(const std::string& what): DispatchError(what) {} + }; + + /** + * Call a registered callable with an explicitly-specified name, + * converting its return value to LLSD (undefined for a void callable). + * It is an error if no such callable exists. It is an error if the @a + * event fails to match the @a required prototype specified at add() + * time. + * + * @a event must be an LLSD array for a callable registered to accept its + * arguments from such an array. It must be an LLSD map for a callable + * registered to accept its arguments from such a map. + */ + LLSD operator()(const std::string& name, const LLSD& event) const; - /// Call a registered callable with an explicitly-specified name and - /// return true. If no such callable exists, return - /// false. It is an error if the @a event fails to match the @a - /// required prototype specified at add() time. + /** + * Call a registered callable with an explicitly-specified name and + * return true. If no such callable exists, return + * false. It is an error if the @a event fails to match the @a + * required prototype specified at add() time. + * + * @a event must be an LLSD array for a callable registered to accept its + * arguments from such an array. It must be an LLSD map for a callable + * registered to accept its arguments from such a map. + */ bool try_call(const std::string& name, const LLSD& event) const; - /// Extract the @a key value from the incoming @a event, and call the - /// callable whose name is specified by that map @a key. It is an error if - /// no such callable exists. It is an error if the @a event fails to match - /// the @a required prototype specified at add() time. - void operator()(const LLSD& event) const; - - /// Extract the @a key value from the incoming @a event, call the callable - /// whose name is specified by that map @a key and return true. - /// If no such callable exists, return false. It is an error if - /// the @a event fails to match the @a required prototype specified at - /// add() time. + /** + * Extract the @a key specified to our constructor from the incoming LLSD + * map @a event, and call the callable whose name is specified by that @a + * key's value, converting its return value to LLSD (undefined for a void + * callable). It is an error if no such callable exists. It is an error if + * the @a event fails to match the @a required prototype specified at + * add() time. + * + * For a (non-nullary) callable registered to accept its arguments from an + * LLSD array, the @a event map must contain the key @a argskey specified to + * our constructor. The value of the @a argskey key must be an LLSD array + * containing the arguments to pass to the callable named by @a key. + * + * For a callable registered to accept its arguments from an LLSD map, if + * the @a event map contains the key @a argskey specified our constructor, + * extract the value of the @a argskey key and use it as the arguments map. + * If @a event contains no @a argskey key, use the whole @a event as the + * arguments map. + */ + LLSD operator()(const LLSD& event) const; + + /** + * Extract the @a key specified to our constructor from the incoming LLSD + * map @a event, call the callable whose name is specified by that @a + * key's value and return true. If no such callable exists, + * return false. It is an error if the @a event fails to match + * the @a required prototype specified at add() time. + * + * For a (non-nullary) callable registered to accept its arguments from an + * LLSD array, the @a event map must contain the key @a argskey specified to + * our constructor. The value of the @a argskey key must be an LLSD array + * containing the arguments to pass to the callable named by @a key. + * + * For a callable registered to accept its arguments from an LLSD map, if + * the @a event map contains the key @a argskey specified our constructor, + * extract the value of the @a argskey key and use it as the arguments map. + * If @a event contains no @a argskey key, use the whole @a event as the + * arguments map. + */ bool try_call(const LLSD& event) const; /// @name Iterate over defined names @@ -242,7 +313,8 @@ private: std::string mDesc; - virtual LLSD call(const std::string& desc, const LLSD& event) const = 0; + virtual LLSD call(const std::string& desc, const LLSD& event, + bool fromMap, const std::string& argskey) const = 0; virtual LLSD addMetadata(LLSD) const = 0; }; typedef std::map > DispatchMap; @@ -269,6 +341,9 @@ public: /// Retrieve the LLSD key we use for one-arg operator() method std::string getDispatchKey() const { return mKey; } + /// description of this instance's leaf class and description + friend std::ostream& operator<<(std::ostream&, const LLEventDispatcher&); + private: template void addMethod(const std::string& name, const std::string& desc, @@ -285,19 +360,16 @@ private: } } void addFail(const std::string& name, const std::string& classname) const; - std::string try_call_log(const std::string& key, const LLSD& name, - const LLSD& event) const; - std::string try_call(const std::string& key, const LLSD& name, - const LLSD& event) const; - // returns either (empty string, LLSD) or (error message, isUndefined) - std::pair - try_call_one(const std::string& key, const std::string& name, const LLSD& event) const; - // Implement "it is an error" semantics for attempted call operations: if - // the incoming event includes a "reply" key, log and send an error reply. - void callFail(const LLSD& event, const std::string& msg) const; - void reply(const LLSD& response, const LLSD& event) const; - - std::string mDesc, mKey; + LLSD try_call(const std::string& key, const std::string& name, + const LLSD& event) const; + // raise specified EXCEPTION with specified stringize(ARGS) + template + void callFail(ARGS&&... args) const; + template + static + void sCallFail(ARGS&&... args); + + std::string mDesc, mKey, mArgskey; DispatchMap mDispatch; static NameDesc makeNameDesc(const DispatchMap::value_type& item) @@ -305,6 +377,7 @@ private: return NameDesc(item.first, item.second->mDesc); } + class LLSDArgsMapper; struct LLSDDispatchEntry; struct ParamsDispatchEntry; struct ArrayParamsDispatchEntry; @@ -459,13 +532,36 @@ class LL_COMMON_API LLDispatchListener: public LLEventStream { public: - LLDispatchListener(const std::string& pumpname, const std::string& key); + template + LLDispatchListener(const std::string& pumpname, const std::string& key, + ARGS&&... args); virtual ~LLDispatchListener() {} private: - bool process(const LLSD& event); + bool process(const LLSD& event) const; + void reply(const LLSD& reply, const LLSD& request) const; LLTempBoundListener mBoundListener; + static std::string mReplyKey; }; +template +LLDispatchListener::LLDispatchListener(const std::string& pumpname, const std::string& key, + ARGS&&... args): + // pass through any additional arguments to LLEventDispatcher ctor + LLEventDispatcher(pumpname, key, std::forward(args)...), + // Do NOT tweak the passed pumpname. In practice, when someone + // instantiates a subclass of our LLEventAPI subclass, they intend to + // claim that LLEventPump name in the global LLEventPumps namespace. It + // would be mysterious and distressing if we allowed name tweaking, and + // someone else claimed pumpname first for a completely unrelated + // LLEventPump. Posted events would never reach our subclass listener + // because we would have silently changed its name; meanwhile listeners + // (if any) on that other LLEventPump would be confused by the events + // intended for our subclass. + LLEventStream(pumpname, false), + mBoundListener(listen("self", [this](const LLSD& event){ return process(event); })) +{ +} + #endif /* ! defined(LL_LLEVENTDISPATCHER_H) */ diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index f09dd63316..00bdff89e5 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -18,6 +18,7 @@ // external library headers // other Linden headers #include "../test/lltut.h" +#include "lleventfilter.h" #include "llsd.h" #include "llsdutil.h" #include "llevents.h" @@ -640,42 +641,38 @@ namespace tut std::string call_exc(const std::string& func, const LLSD& args, const std::string& exc_frag) { - // This method was written when LLEventDispatcher responded to - // name or argument errors with LL_ERRS, hence the name: we used - // to have to intercept LL_ERRS by making it throw. Now we set up - // to catch an error response instead. But -- for that we need to - // be able to sneak a "reply" key into args, which must be a Map. - if (! (args.isUndefined() or args.isMap())) - fail(stringize("can't test call_exc() with ", args)); - LLEventStream replypump("reply"); - LLSD reply; - LLTempBoundListener bound{ - replypump.listen( - "listener", - [&reply](const LLSD& event) - { - reply = event; - return false; - }) }; - LLSD modargs{ args }; - modargs["reply"] = replypump.getName(); - if (func.empty()) + std::string what; + try { - work(modargs); + if (func.empty()) + { + work(args); + } + else + { + work(func, args); + } } - else + catch (const LLEventDispatcher::DispatchError& err) { - work(func, modargs); + what = err.what(); } - ensure("no error response", reply.has("error")); - ensure_has(reply["error"], exc_frag); - return reply["error"]; + ensure_has(what, exc_frag); + return what; } void call_logerr(const std::string& func, const LLSD& args, const std::string& frag) { CaptureLog capture; - work(func, args); + try + { + work(func, args); + } + catch (const LLEventDispatcher::DispatchError& err) + { + // the error should also have been logged; we just need to + // stop the exception propagating + } capture.messageWith(frag); } @@ -1017,6 +1014,10 @@ namespace tut (llsd::array("freenb_map_mdft", "smethodnb_map_mdft", "methodnb_map_mdft"), llsd::array(LLSD::emptyMap(), dft_map_full["b"])))); // required, optional + llsd::array // group + (llsd::array("freenb_map_mdft", "smethodnb_map_mdft", "methodnb_map_mdft"), + llsd::array(LLSD::emptyMap(), dft_map_full["b"])))); // required, optional + for (LLSD grp: inArray(groups)) { // Internal structure of each group in 'groups': @@ -1196,7 +1197,7 @@ namespace tut template<> template<> void object::test<20>() { - set_test_name("call array-style functions with (just right | too long) arrays"); + set_test_name("call array-style functions with right-size arrays"); std::vector binary; for (size_t h(0x01), i(0); i < 5; h+= 0x22, ++i) { @@ -1326,4 +1327,73 @@ namespace tut } } } + + struct DispatchResult: public LLEventDispatcher + { + using DR = DispatchResult; + + DispatchResult(): LLEventDispatcher("expect result", "op") + { + // As of 2022-12-22, LLEventDispatcher's shorthand add() methods + // for pointer-to-method of same instance only support methods + // with signature void(const LLSD&). The generic add(pointer-to- + // method) requires an instance getter. + add("strfunc", "return string", &DR::strfunc, [this](){ return this; }); + add("voidfunc", "void function", &DR::voidfunc, [this](){ return this; }); + add("intfunc", "return Integer LLSD", &DR::intfunc, [this](){ return this; }); + add("mapfunc", "return map LLSD", &DR::mapfunc, [this](){ return this; }); + add("arrayfunc", "return array LLSD", &DR::arrayfunc, [this](){ return this; }); + } + + std::string strfunc(const LLSD&) const { return "a string"; } + void voidfunc() const {} + int intfunc(const LLSD&) const { return 17; } + LLSD mapfunc(const LLSD&) const { return llsd::map("key", "value"); } + LLSD arrayfunc(const LLSD&) const { return llsd::array("a", "b", "c"); } + }; + + template<> template<> + void object::test<23>() + { + set_test_name("string result"); + DispatchResult service; + LLSD result{ service("strfunc", "ignored") }; + ensure_equals("strfunc() mismatch", result.asString(), "a string"); + } + + template<> template<> + void object::test<24>() + { + set_test_name("void result"); + DispatchResult service; + LLSD result{ service("voidfunc", LLSD()) }; + ensure("voidfunc() returned defined", result.isUndefined()); + } + + template<> template<> + void object::test<25>() + { + set_test_name("Integer result"); + DispatchResult service; + LLSD result{ service("intfunc", "ignored") }; + ensure_equals("intfunc() mismatch", result.asInteger(), 17); + } + + template<> template<> + void object::test<26>() + { + set_test_name("map LLSD result"); + DispatchResult service; + LLSD result{ service("mapfunc", "ignored") }; + ensure_equals("mapfunc() mismatch", result, llsd::map("key", "value")); + } + + template<> template<> + void object::test<27>() + { + set_test_name("array LLSD result"); + DispatchResult service; + LLSD result{ service("arrayfunc", "ignored") }; + ensure_equals("arrayfunc() mismatch", result, llsd::array("a", "b", "c")); + } } // namespace tut -- cgit v1.2.3 From 9553965ad661b2753d13fa9b414f529ad440000f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 10 Jan 2023 17:38:01 -0500 Subject: DRTVWR-558: Add tests for LLDispatchListener functionality. Refine the special case of calling a nullary target function from an (event) method, notably via LLDispatchListener. (cherry picked from commit edcc52a9f60b1ec9b8f53603d6e2676558d41294) --- indra/llcommon/lleventdispatcher.cpp | 8 +- indra/llcommon/tests/lleventdispatcher_test.cpp | 152 ++++++++++++++++++------ 2 files changed, 123 insertions(+), 37 deletions(-) diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index 7e5723c503..7abdc8f57a 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -461,7 +461,13 @@ struct LLEventDispatcher::ArrayParamsDispatchEntry: public LLEventDispatcher::Pa LLSD args{ event }; if (fromMap) { - if (mArity) + if (! mArity) + { + // When the target function is nullary, and we're called from + // an (event) method, just ignore the rest of the map entries. + args.clear(); + } + else { // We only require/retrieve argskey if the target function // isn't nullary. For all others, since we require an LLSD diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index 00bdff89e5..179fab9fad 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -23,6 +23,7 @@ #include "llsdutil.h" #include "llevents.h" #include "stringize.h" +#include "StringVec.h" #include "tests/wrapllerrs.h" #include "../test/catch_and_store_what_in.h" #include "../test/debug.h" @@ -315,6 +316,31 @@ void freenb(NPARAMSb) *****************************************************************************/ namespace tut { + void ensure_has(const std::string& outer, const std::string& inner) + { + ensure(stringize("'", outer, "' does not contain '", inner, "'"), + outer.find(inner) != std::string::npos); + } + + template + std::string call_exc(CALLABLE&& func, const std::string& exc_frag) + { + std::string what = + catch_what(std::forward(func)); + ensure_has(what, exc_frag); + return what; + } + + template + void call_logerr(CALLABLE&& func, const std::string& frag) + { + CaptureLog capture; + // the error should be logged; we just need to stop the exception + // propagating + catch_what(std::forward(func)); + capture.messageWith(frag); + } + struct lleventdispatcher_data { Debug debug{"test"}; @@ -633,47 +659,26 @@ namespace tut return found->second; } - void ensure_has(const std::string& outer, const std::string& inner) - { - ensure(stringize("'", outer, "' does not contain '", inner, "'").c_str(), - outer.find(inner) != std::string::npos); - } - std::string call_exc(const std::string& func, const LLSD& args, const std::string& exc_frag) { - std::string what; - try - { - if (func.empty()) + return tut::call_exc( + [this, func, args]() { - work(args); - } - else - { - work(func, args); - } - } - catch (const LLEventDispatcher::DispatchError& err) - { - what = err.what(); - } - ensure_has(what, exc_frag); - return what; + if (func.empty()) + { + work(args); + } + else + { + work(func, args); + } + }, + exc_frag); } void call_logerr(const std::string& func, const LLSD& args, const std::string& frag) { - CaptureLog capture; - try - { - work(func, args); - } - catch (const LLEventDispatcher::DispatchError& err) - { - // the error should also have been logged; we just need to - // stop the exception propagating - } - capture.messageWith(frag); + tut::call_logerr([this, func, args](){ work(func, args); }, frag); } LLSD getMetadata(const std::string& name) @@ -1328,11 +1333,11 @@ namespace tut } } - struct DispatchResult: public LLEventDispatcher + struct DispatchResult: public LLDispatchListener { using DR = DispatchResult; - DispatchResult(): LLEventDispatcher("expect result", "op") + DispatchResult(): LLDispatchListener("results", "op") { // As of 2022-12-22, LLEventDispatcher's shorthand add() methods // for pointer-to-method of same instance only support methods @@ -1340,6 +1345,7 @@ namespace tut // method) requires an instance getter. add("strfunc", "return string", &DR::strfunc, [this](){ return this; }); add("voidfunc", "void function", &DR::voidfunc, [this](){ return this; }); + add("emptyfunc", "return empty LLSD", &DR::emptyfunc, [this](){ return this; }); add("intfunc", "return Integer LLSD", &DR::intfunc, [this](){ return this; }); add("mapfunc", "return map LLSD", &DR::mapfunc, [this](){ return this; }); add("arrayfunc", "return array LLSD", &DR::arrayfunc, [this](){ return this; }); @@ -1347,6 +1353,7 @@ namespace tut std::string strfunc(const LLSD&) const { return "a string"; } void voidfunc() const {} + LLSD emptyfunc() const { return {}; } int intfunc(const LLSD&) const { return 17; } LLSD mapfunc(const LLSD&) const { return llsd::map("key", "value"); } LLSD arrayfunc(const LLSD&) const { return llsd::array("a", "b", "c"); } @@ -1396,4 +1403,77 @@ namespace tut LLSD result{ service("arrayfunc", "ignored") }; ensure_equals("arrayfunc() mismatch", result, llsd::array("a", "b", "c")); } + + template<> template<> + void object::test<28>() + { + set_test_name("listener error, no reply"); + DispatchResult service; + tut::call_exc( + [&service]() + { service.post(llsd::map("op", "nosuchfunc", "reqid", 17)); }, + "nosuchfunc"); + } + + template<> template<> + void object::test<29>() + { + set_test_name("listener error with reply"); + DispatchResult service; + LLCaptureListener result; + service.post(llsd::map("op", "nosuchfunc", "reqid", 17, "reply", result.getName())); + LLSD reply{ result.get() }; + ensure("no reply", reply.isDefined()); + ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17); + ensure_has(reply["error"].asString(), "nosuchfunc"); + } + + template<> template<> + void object::test<30>() + { + set_test_name("listener call to void function"); + DispatchResult service; + LLCaptureListener result; + result.set("non-empty"); + for (const auto& func: StringVec{ "voidfunc", "emptyfunc" }) + { + service.post(llsd::map( + "op", func, + "reqid", 17, + "reply", result.getName())); + ensure_equals("reply from " + func, result.get().asString(), "non-empty"); + } + } + + template<> template<> + void object::test<31>() + { + set_test_name("listener call to string function"); + DispatchResult service; + LLCaptureListener result; + service.post(llsd::map( + "op", "strfunc", + "args", llsd::array(LLSD()), + "reqid", 17, + "reply", result.getName())); + LLSD reply{ result.get() }; + ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17); + ensure_equals("bad reply from strfunc", reply["data"].asString(), "a string"); + } + + template<> template<> + void object::test<32>() + { + set_test_name("listener call to map function"); + DispatchResult service; + LLCaptureListener result; + service.post(llsd::map( + "op", "mapfunc", + "args", llsd::array(LLSD()), + "reqid", 17, + "reply", result.getName())); + LLSD reply{ result.get() }; + ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17); + ensure_equals("bad reply from mapfunc", reply["key"], "value"); + } } // namespace tut -- cgit v1.2.3 From 27f19826b6ef9b4af5db8613c44988b5c973618b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Jan 2023 10:49:01 -0500 Subject: DRTVWR-558: Break out new LLDispatchListener::call() method. This captures logic we intend to reuse for forthcoming LLDispatchListener batched request support. (cherry picked from commit 3cb6d374cb76e4b00dc121255e8f5aa4e777fa27) --- indra/llcommon/lleventdispatcher.cpp | 69 +++++++++++++++++++++++++----------- indra/llcommon/lleventdispatcher.h | 36 +++++++++++++++++++ 2 files changed, 85 insertions(+), 20 deletions(-) diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index 7abdc8f57a..8e20d7184a 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -742,6 +742,8 @@ std::ostream& operator<<(std::ostream& out, const LLEventDispatcher& self) /***************************************************************************** * LLDispatchListener *****************************************************************************/ +std::string LLDispatchListener::mReplyKey{ "reply" }; + /*==========================================================================*| TODO: * When process() finds name.isArray(), construct response array from @@ -752,33 +754,27 @@ std::ostream& operator<<(std::ostream& out, const LLEventDispatcher& self) note, caller can't care about order * Possible future transactional behavior: look up all names before calling any |*==========================================================================*/ -std::string LLDispatchListener::mReplyKey{ "reply" }; - bool LLDispatchListener::process(const LLSD& event) const { - LLSD result; - try - { - result = (*this)(event); - } - catch (const DispatchError& err) - { - // If the incoming event contains a "reply" key, we'll respond to the - // invoker with an error message (below). But if not -- silently - // ignoring an invocation request would be confusing at best. Escalate. - if (! event.has(mReplyKey)) - { - throw; - } - - // reply with a map containing an "error" key explaining the problem - reply(llsd::map("error", err.what()), event); + // Collecting errors is only meaningful with a reply key. Without one, if + // an error occurs, let the exception propagate. + auto returned = call("", event, (! event.has(mReplyKey))); + std::string& error{ returned.first }; + LLSD& result{ returned.second }; + + if (! error.empty()) + { + // Here there was an error and the incoming event has mReplyKey -- + // else DispatchError would already have propagated out of the call() + // above. Reply with a map containing an "error" key explaining the + // problem. + reply(llsd::map("error", error), event); return false; } // We seem to have gotten a valid result. But we don't know whether the // registered callable is void or non-void. If it's void, - // LLEventDispatcher will return isUndefined(). Otherwise, try to send it + // LLEventDispatcher returned isUndefined(). Otherwise, try to send it // back to our invoker. if (result.isDefined()) { @@ -792,6 +788,39 @@ bool LLDispatchListener::process(const LLSD& event) const return false; } +// Pass empty name to call LLEventDispatcher::operator()(const LLSD&), +// non-empty name to call operator()(const std::string&, const LLSD&). +// Returns (empty string, return value) on successful call. +// Returns (error message, undefined) if error and 'exception' is false. +// Throws DispatchError if error and 'exception' is true. +std::pair LLDispatchListener::call(const std::string& name, const LLSD& event, + bool exception) const +{ + try + { + if (name.empty()) + { + // unless this throws, return (empty string, real return value) + return { {}, (*this)(event) }; + } + else + { + // unless this throws, return (empty string, real return value) + return { {}, (*this)(name, event) }; + } + } + catch (const DispatchError& err) + { + if (exception) + { + // Caller asked for an exception on error. Oblige. + throw; + } + // Caller does NOT want an exception: return (error message, undefined) + return { err.what(), LLSD() }; + } +} + void LLDispatchListener::reply(const LLSD& reply, const LLSD& request) const { // Call sendReply() unconditionally: sendReply() itself tests whether the diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index 494fc6a366..a348a6660f 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -42,6 +42,7 @@ #include #include #include +#include // std::pair #include "llevents.h" #include "llptrto.h" #include "llsdutil.h" @@ -522,6 +523,33 @@ LLEventDispatcher::make_invoker(Method f, const InstanceGetter& getter) * that contains (or derives from) LLDispatchListener need only specify the * LLEventPump name and dispatch key, and add() its methods. Incoming events * will automatically be dispatched. + * + * If the incoming event contains a "reply" key specifying the LLSD::String + * name of an LLEventPump to which to respond, LLDispatchListener will attempt + * to send a response to that LLEventPump. + * + * If some error occurs (e.g. nonexistent callable name, wrong params) and + * "reply" is present, LLDispatchListener will send a response map to the + * specified LLEventPump containing an "error" key whose value is the relevant + * error message. If "reply" is not present, the DispatchError exception will + * propagate. + * + * If LLDispatchListener successfully calls the target callable, but no + * "reply" key is present, any value returned by that callable is discarded. + * If a "reply" key is present, but the target callable is void -- or it + * returns LLSD::isUndefined() -- no response is sent. If a void callable + * wants to send a response, it must do so explicitly. + * + * If the target callable returns a type convertible to LLSD (and, if it + * directly returns LLSD, the return value isDefined()), and if a "reply" key + * is present in the incoming event, LLDispatchListener will post the returned + * value to the "reply" LLEventPump. If the returned value is an LLSD map, it + * will merge the echoed "reqid" key into the map and send that. Otherwise, it + * will send an LLSD map containing "reqid" and a "data" key whose value is + * the value returned by the target callable. + * + * (It is inadvisable for a target callable to return an LLSD map containing + * keys "reqid" or "error", as that will confuse the invoker.) */ // Instead of containing an LLEventStream, LLDispatchListener derives from it. // This allows an LLEventPumps::PumpFactory to return a pointer to an @@ -532,6 +560,7 @@ class LL_COMMON_API LLDispatchListener: public LLEventStream { public: + /// LLEventPump name, dispatch key [, arguments key (see LLEventDispatcher)] template LLDispatchListener(const std::string& pumpname, const std::string& key, ARGS&&... args); @@ -539,6 +568,13 @@ public: private: bool process(const LLSD& event) const; + // Pass empty name to call LLEventDispatcher::operator()(const LLSD&), + // non-empty name to call operator()(const std::string&, const LLSD&). + // Returns (empty string, return value) on successful call. + // Returns (error message, undefined) if error and 'exception' is false. + // Throws DispatchError if error and 'exception' is true. + std::pair call(const std::string& name, const LLSD& event, + bool exception) const; void reply(const LLSD& reply, const LLSD& request) const; LLTempBoundListener mBoundListener; -- cgit v1.2.3 From d637229beeb3b7fa2bc7adb850ce9337b119037c Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 13 Jan 2023 00:10:23 -0500 Subject: DRTVWR-558: Introduce LLDispatchListener batched requests. Now the value of the incoming event's dispatch key may be an LLSD::String (as before), a map or an array, as documented in the augmented Doxygen class comments. LLDispatchListener will attempt multiple calls before sending a reply. (cherry picked from commit 7671b37285c6cdf1afcddb0019311a822c8a4dc5) --- indra/llcommon/lleventdispatcher.cpp | 183 +++++++++++++++++++++++++++-------- indra/llcommon/lleventdispatcher.h | 93 ++++++++++++++---- 2 files changed, 216 insertions(+), 60 deletions(-) diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index 8e20d7184a..d10cf16b88 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -744,32 +744,44 @@ std::ostream& operator<<(std::ostream& out, const LLEventDispatcher& self) *****************************************************************************/ std::string LLDispatchListener::mReplyKey{ "reply" }; -/*==========================================================================*| - TODO: -* When process() finds name.isArray(), construct response array from - dispatching each call -- args must also be (array of args structures) - (could also construct response map, IF array contains unique names) -* When process() finds name.isMap(), construct response map from dispatching - each call -- value of each key is its args struct -- argskey ignored -- - note, caller can't care about order -* Possible future transactional behavior: look up all names before calling any -|*==========================================================================*/ bool LLDispatchListener::process(const LLSD& event) const { - // Collecting errors is only meaningful with a reply key. Without one, if - // an error occurs, let the exception propagate. - auto returned = call("", event, (! event.has(mReplyKey))); - std::string& error{ returned.first }; - LLSD& result{ returned.second }; + // Decide what to do based on the incoming value of the specified dispatch + // key. + LLSD name{ event[getDispatchKey()] }; + if (name.isMap()) + { + call_map(name, event); + } + else if (name.isArray()) + { + call_array(name, event); + } + else + { + call_one(name, event); + } + return false; +} - if (! error.empty()) +void LLDispatchListener::call_one(const LLSD& name, const LLSD& event) const +{ + LLSD result; + try { - // Here there was an error and the incoming event has mReplyKey -- - // else DispatchError would already have propagated out of the call() - // above. Reply with a map containing an "error" key explaining the - // problem. - reply(llsd::map("error", error), event); - return false; + result = (*this)(event); + } + catch (const DispatchError& err) + { + if (! event.has(mReplyKey)) + { + // Without a reply key, let the exception propagate. + throw; + } + + // Here there was an error and the incoming event has mReplyKey. Reply + // with a map containing an "error" key explaining the problem. + return reply(llsd::map("error", err.what()), event); } // We seem to have gotten a valid result. But we don't know whether the @@ -785,40 +797,127 @@ bool LLDispatchListener::process(const LLSD& event) const } reply(result, event); } - return false; } -// Pass empty name to call LLEventDispatcher::operator()(const LLSD&), -// non-empty name to call operator()(const std::string&, const LLSD&). -// Returns (empty string, return value) on successful call. -// Returns (error message, undefined) if error and 'exception' is false. -// Throws DispatchError if error and 'exception' is true. -std::pair LLDispatchListener::call(const std::string& name, const LLSD& event, - bool exception) const +void LLDispatchListener::call_map(const LLSD& reqmap, const LLSD& event) const { - try + // LLSD map containing returned values + LLSD result; + // collect any error messages here + std::ostringstream errors; + const char* delim = ""; + + for (const auto& pair : llsd::inMap(reqmap)) + { + const LLSD::String& name{ pair.first }; + const LLSD& args{ pair.second }; + try + { + // With this form, capture return value even if undefined: + // presence of the key in the response map can be used to detect + // which request keys succeeded. + result[name] = (*this)(name, args); + } + catch (const DispatchError& err) + { + // collect message in 'errors', with hint as to which request map + // entry failed + errors << delim << err.what() << " (" << getDispatchKey() + << '[' << name << "])"; + delim = "\n"; + } + } + + // so, were there any errors? + std::string error = errors.str(); + if (! error.empty()) { - if (name.empty()) + if (! event.has(mReplyKey)) { - // unless this throws, return (empty string, real return value) - return { {}, (*this)(event) }; + // can't send reply, throw + sCallFail(error); } else { - // unless this throws, return (empty string, real return value) - return { {}, (*this)(name, event) }; + // reply key present + result["error"] = error; } } - catch (const DispatchError& err) + + reply(result, event); +} + +void LLDispatchListener::call_array(const LLSD& reqarray, const LLSD& event) const +{ + // LLSD array containing returned values + LLSD results; + // arguments array, if present -- const because, if it's shorter than + // reqarray, we don't want to grow it + const LLSD argsarray{ event[getArgsKey()] }; + // error message, if any + std::string error; + + // classic index loop because we need the index + for (size_t i = 0, size = reqarray.size(); i < size; ++i) + { + const auto& reqentry{ reqarray[i] }; + std::string name; + LLSD args; + if (reqentry.isString()) + { + name = reqentry.asString(); + args = argsarray[i]; + } + else if (reqentry.isArray() && reqentry.size() == 2 && reqentry[0].isString()) + { + name = reqentry[0].asString(); + args = reqentry[1]; + } + else + { + // reqentry isn't in either of the documented forms + error = stringize(*this, ": ", getDispatchKey(), '[', i, "] ", + reqentry, " unsupported"); + break; + } + + // reqentry is one of the valid forms, got name and args + try + { + // With this form, capture return value even if undefined + results.append((*this)(name, args)); + } + catch (const DispatchError& err) + { + // append hint as to which requentry produced the error + error = stringize(err.what(), " (", getDispatchKey(), '[', i, ']'); + break; + } + } + + LLSD result; + // was there an error? + if (! error.empty()) { - if (exception) + if (! event.has(mReplyKey)) { - // Caller asked for an exception on error. Oblige. - throw; + // can't send reply, throw + sCallFail(error); + } + else + { + // reply key present + result["error"] = error; } - // Caller does NOT want an exception: return (error message, undefined) - return { err.what(), LLSD() }; } + + // wrap the results array as response map "data" key, as promised + if (results.isDefined()) + { + result["data"] = results; + } + + reply(result, event); } void LLDispatchListener::reply(const LLSD& reply, const LLSD& request) const diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index a348a6660f..5ab860b6dd 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -341,6 +341,8 @@ public: /// Retrieve the LLSD key we use for one-arg operator() method std::string getDispatchKey() const { return mKey; } + /// Retrieve the LLSD key we use for non-map arguments + std::string getArgsKey() const { return mArgskey; } /// description of this instance's leaf class and description friend std::ostream& operator<<(std::ostream&, const LLEventDispatcher&); @@ -363,6 +365,8 @@ private: void addFail(const std::string& name, const std::string& classname) const; LLSD try_call(const std::string& key, const std::string& name, const LLSD& event) const; + +protected: // raise specified EXCEPTION with specified stringize(ARGS) template void callFail(ARGS&&... args) const; @@ -370,6 +374,7 @@ private: static void sCallFail(ARGS&&... args); +private: std::string mDesc, mKey, mArgskey; DispatchMap mDispatch; @@ -521,12 +526,12 @@ LLEventDispatcher::make_invoker(Method f, const InstanceGetter& getter) /** * Bundle an LLEventPump and a listener with an LLEventDispatcher. A class * that contains (or derives from) LLDispatchListener need only specify the - * LLEventPump name and dispatch key, and add() its methods. Incoming events - * will automatically be dispatched. + * LLEventPump name and dispatch key, and add() its methods. Each incoming + * event ("request") will automatically be dispatched. * - * If the incoming event contains a "reply" key specifying the LLSD::String - * name of an LLEventPump to which to respond, LLDispatchListener will attempt - * to send a response to that LLEventPump. + * If the request contains a "reply" key specifying the LLSD::String name of + * an LLEventPump to which to respond, LLDispatchListener will attempt to send + * a response to that LLEventPump. * * If some error occurs (e.g. nonexistent callable name, wrong params) and * "reply" is present, LLDispatchListener will send a response map to the @@ -542,14 +547,70 @@ LLEventDispatcher::make_invoker(Method f, const InstanceGetter& getter) * * If the target callable returns a type convertible to LLSD (and, if it * directly returns LLSD, the return value isDefined()), and if a "reply" key - * is present in the incoming event, LLDispatchListener will post the returned - * value to the "reply" LLEventPump. If the returned value is an LLSD map, it - * will merge the echoed "reqid" key into the map and send that. Otherwise, it - * will send an LLSD map containing "reqid" and a "data" key whose value is - * the value returned by the target callable. + * is present in the request, LLDispatchListener will post the returned value + * to the "reply" LLEventPump. If the returned value is an LLSD map, it will + * merge the echoed "reqid" key into the map and send that. Otherwise, it will + * send an LLSD map containing "reqid" and a "data" key whose value is the + * value returned by the target callable. * * (It is inadvisable for a target callable to return an LLSD map containing - * keys "reqid" or "error", as that will confuse the invoker.) + * keys "data", "reqid" or "error", as that will confuse the invoker.) + * + * Normally the request will specify the value of the dispatch key as an + * LLSD::String naming the target callable. Alternatively, several such calls + * may be "batched" as described below. + * + * If the value of the dispatch key is itself an LLSD map (a "request map"), + * each map key must name a target callable, and the value of that key must + * contain the parameters to pass to that callable. If a "reply" key is + * present in the request, the response map will contain a key for each of the + * keys in the request map. The value of every such key is the value returned + * by the target callable. + * + * (Avoid naming any target callable in the LLDispatchListener "data", "reqid" + * or "error" to avoid confusion.) + * + * Since LLDispatchListener calls the target callables specified by a request + * map in arbitrary order, this form assumes that the batched operations are + * independent of each other. LLDispatchListener will attempt every call, even + * if some attempts produce errors. If any keys in the request map produce + * errors, LLDispatchListener builds a composite error message string + * collecting the relevant messages. The corresponding keys will be missing + * from the response map. As in the single-callable case, absent a "reply" key + * in the request, this error message will be thrown as a DispatchError. With + * a "reply" key, it will be returned as the value of the "error" key. This + * form can indicate partial success: some request keys might have + * return-value keys in the response, others might have message text in the + * "error" key. + * + * If a specific call sequence is required, the value of the dispatch key may + * instead be an LLSD array (a "request array"). Each entry in the request + * array ("request entry") names a target callable, to be called in + * array-index sequence. Arguments for that callable may be specified in + * either of two ways. + * + * The request entry may itself be a two-element array, whose [0] is an + * LLSD::String naming the target callable and whose [1] contains the + * arguments to pass to that callable. + * + * Alternatively, the request entry may be an LLSD::String naming the target + * callable, in which case the request must contain an arguments key (optional + * third constructor argument) whose value is an array matching the request + * array. The arguments for the request entry's target callable are found at + * the same index in the arguments key array. + * + * If a "reply" key is present in the request, the response map will contain a + * "data" key whose value is an array. Each entry in that response array will + * contain the result from the corresponding request entry. + * + * This form assumes that any of the batched operations might depend on the + * success of a previous operation in the same batch. The @emph first error + * encountered will terminate the sequence. The error message might either be + * thrown as DispatchError or, given a "reply" key, returned as the "error" + * key in the response map. This form can indicate partial success: the first + * few request entries might have return-value entries in the "data" response + * array, along with an "error" key whose value is the error message that + * stopped the sequence. */ // Instead of containing an LLEventStream, LLDispatchListener derives from it. // This allows an LLEventPumps::PumpFactory to return a pointer to an @@ -568,13 +629,9 @@ public: private: bool process(const LLSD& event) const; - // Pass empty name to call LLEventDispatcher::operator()(const LLSD&), - // non-empty name to call operator()(const std::string&, const LLSD&). - // Returns (empty string, return value) on successful call. - // Returns (error message, undefined) if error and 'exception' is false. - // Throws DispatchError if error and 'exception' is true. - std::pair call(const std::string& name, const LLSD& event, - bool exception) const; + void call_one(const LLSD& name, const LLSD& event) const; + void call_map(const LLSD& reqmap, const LLSD& event) const; + void call_array(const LLSD& reqarray, const LLSD& event) const; void reply(const LLSD& reply, const LLSD& request) const; LLTempBoundListener mBoundListener; -- cgit v1.2.3 From 98793b8d44b32604033bd1b280f37023c86055bb Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 13 Jan 2023 21:53:18 -0500 Subject: DRTVWR-558: Make DispatchResult methods use their arguments. Fix lleventdispatcher_test.cpp's test class DispatchResult::strfunc(), intfunc(), mapfunc() and arrayfunc() to return values derived from (not identical to) their arguments, so we can reuse these functions for further testing of passing arguments to a named callable. Adjust existing tests accordingly. (cherry picked from commit 07e09a8daea008d28b97399920db60a147cf75c0) --- indra/llcommon/tests/lleventdispatcher_test.cpp | 37 +++++++++++++++---------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index 179fab9fad..0254d23a0b 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -1351,12 +1351,18 @@ namespace tut add("arrayfunc", "return array LLSD", &DR::arrayfunc, [this](){ return this; }); } - std::string strfunc(const LLSD&) const { return "a string"; } + std::string strfunc(const std::string& str) const { return "got " + str; } void voidfunc() const {} LLSD emptyfunc() const { return {}; } - int intfunc(const LLSD&) const { return 17; } - LLSD mapfunc(const LLSD&) const { return llsd::map("key", "value"); } - LLSD arrayfunc(const LLSD&) const { return llsd::array("a", "b", "c"); } + int intfunc(int i) const { return -i; } + LLSD mapfunc(int i, const std::string& str) const + { + return llsd::map("i", intfunc(i), "str", strfunc(str)); + } + LLSD arrayfunc(int i, const std::string& str) const + { + return llsd::array(intfunc(i), strfunc(str)); + } }; template<> template<> @@ -1364,8 +1370,8 @@ namespace tut { set_test_name("string result"); DispatchResult service; - LLSD result{ service("strfunc", "ignored") }; - ensure_equals("strfunc() mismatch", result.asString(), "a string"); + LLSD result{ service("strfunc", "a string") }; + ensure_equals("strfunc() mismatch", result.asString(), "got a string"); } template<> template<> @@ -1382,7 +1388,7 @@ namespace tut { set_test_name("Integer result"); DispatchResult service; - LLSD result{ service("intfunc", "ignored") }; + LLSD result{ service("intfunc", -17) }; ensure_equals("intfunc() mismatch", result.asInteger(), 17); } @@ -1391,8 +1397,8 @@ namespace tut { set_test_name("map LLSD result"); DispatchResult service; - LLSD result{ service("mapfunc", "ignored") }; - ensure_equals("mapfunc() mismatch", result, llsd::map("key", "value")); + LLSD result{ service("mapfunc", llsd::array(-12, "value")) }; + ensure_equals("mapfunc() mismatch", result, llsd::map("i", 12, "str", "got value")); } template<> template<> @@ -1400,8 +1406,8 @@ namespace tut { set_test_name("array LLSD result"); DispatchResult service; - LLSD result{ service("arrayfunc", "ignored") }; - ensure_equals("arrayfunc() mismatch", result, llsd::array("a", "b", "c")); + LLSD result{ service("arrayfunc", llsd::array(-8, "word")) }; + ensure_equals("arrayfunc() mismatch", result, llsd::array(8, "got word")); } template<> template<> @@ -1453,12 +1459,12 @@ namespace tut LLCaptureListener result; service.post(llsd::map( "op", "strfunc", - "args", llsd::array(LLSD()), + "args", llsd::array("a string"), "reqid", 17, "reply", result.getName())); LLSD reply{ result.get() }; ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17); - ensure_equals("bad reply from strfunc", reply["data"].asString(), "a string"); + ensure_equals("bad reply from strfunc", reply["data"].asString(), "got a string"); } template<> template<> @@ -1469,11 +1475,12 @@ namespace tut LLCaptureListener result; service.post(llsd::map( "op", "mapfunc", - "args", llsd::array(LLSD()), + "args", llsd::array(-7, "value"), "reqid", 17, "reply", result.getName())); LLSD reply{ result.get() }; ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17); - ensure_equals("bad reply from mapfunc", reply["key"], "value"); + ensure_equals("bad i from mapfunc", reply["i"].asInteger(), 7); + ensure_equals("bad str from mapfunc", reply["str"], "got value"); } } // namespace tut -- cgit v1.2.3 From b36deb79c2e99bfa600b5895c277ffb78c61957f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 13 Jan 2023 23:07:24 -0500 Subject: DRTVWR-558: Add tests for batched LLDispatchListener operations. Specifically, add tests for: - successful map batch - map batch with some errors and a reply pump - map batch with some errors and no reply - successful array batch - array batch with some errors and a reply pump - array batch with some errors and no reply (cherry picked from commit 078f0f5c9fb5075a8ad01cac417e1d7ee2b6a919) --- indra/llcommon/tests/lleventdispatcher_test.cpp | 175 ++++++++++++++++++++++++ 1 file changed, 175 insertions(+) diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index 0254d23a0b..8e84a9e038 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -1483,4 +1483,179 @@ namespace tut ensure_equals("bad i from mapfunc", reply["i"].asInteger(), 7); ensure_equals("bad str from mapfunc", reply["str"], "got value"); } + + template<> template<> + void object::test<33>() + { + set_test_name("batched map success"); + DispatchResult service; + LLCaptureListener result; + service.post(llsd::map( + "op", llsd::map( + "strfunc", "some string", + "intfunc", 2, + "voidfunc", LLSD(), + "arrayfunc", llsd::array(-5, "other string")), + "reqid", 17, + "reply", result.getName())); + LLSD reply{ result.get() }; + ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17); + reply.erase("reqid"); + ensure_equals( + "bad map batch", + reply, + llsd::map( + "strfunc", "got some string", + "intfunc", -2, + "voidfunc", LLSD(), + "arrayfunc", llsd::array(5, "got other string"))); + } + + template<> template<> + void object::test<34>() + { + set_test_name("batched map error"); + DispatchResult service; + LLCaptureListener result; + service.post(llsd::map( + "op", llsd::map( + "badfunc", 34, // ! + "strfunc", "some string", + "intfunc", 2, + "missing", LLSD(), // ! + "voidfunc", LLSD(), + "arrayfunc", llsd::array(-5, "other string")), + "reqid", 17, + "reply", result.getName())); + LLSD reply{ result.get() }; + ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17); + reply.erase("reqid"); + auto error{ reply["error"].asString() }; + reply.erase("error"); + ensure_has(error, "badfunc"); + ensure_has(error, "missing"); + ensure_equals( + "bad partial batch", + reply, + llsd::map( + "strfunc", "got some string", + "intfunc", -2, + "voidfunc", LLSD(), + "arrayfunc", llsd::array(5, "got other string"))); + } + + template<> template<> + void object::test<35>() + { + set_test_name("batched map exception"); + DispatchResult service; + auto error = tut::call_exc( + [&service]() + { + service.post(llsd::map( + "op", llsd::map( + "badfunc", 34, // ! + "strfunc", "some string", + "intfunc", 2, + "missing", LLSD(), // ! + "voidfunc", LLSD(), + "arrayfunc", llsd::array(-5, "other string")), + "reqid", 17)); + // no "reply" + }, + "badfunc"); + ensure_has(error, "missing"); + } + + template<> template<> + void object::test<36>() + { + set_test_name("batched array success"); + DispatchResult service; + LLCaptureListener result; + service.post(llsd::map( + "op", llsd::array( + llsd::array("strfunc", "some string"), + llsd::array("intfunc", 2), + "arrayfunc", + "voidfunc"), + "args", llsd::array( + LLSD(), + LLSD(), + llsd::array(-5, "other string")), + // args array deliberately short, since the default + // [3] is undefined, which should work for voidfunc + "reqid", 17, + "reply", result.getName())); + LLSD reply{ result.get() }; + ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17); + reply.erase("reqid"); + ensure_equals( + "bad array batch", + reply, + llsd::map( + "data", llsd::array( + "got some string", + -2, + llsd::array(5, "got other string"), + LLSD()))); + } + + template<> template<> + void object::test<37>() + { + set_test_name("batched array error"); + DispatchResult service; + LLCaptureListener result; + service.post(llsd::map( + "op", llsd::array( + llsd::array("strfunc", "some string"), + llsd::array("intfunc", 2, "whoops"), // bad form + "arrayfunc", + "voidfunc"), + "args", llsd::array( + LLSD(), + LLSD(), + llsd::array(-5, "other string")), + // args array deliberately short, since the default + // [3] is undefined, which should work for voidfunc + "reqid", 17, + "reply", result.getName())); + LLSD reply{ result.get() }; + ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17); + reply.erase("reqid"); + auto error{ reply["error"] }; + reply.erase("error"); + ensure_has(error, "[1]"); + ensure_has(error, "unsupported"); + ensure_equals("bad array batch", reply, + llsd::map("data", llsd::array("got some string"))); + } + + template<> template<> + void object::test<38>() + { + set_test_name("batched array exception"); + DispatchResult service; + auto error = tut::call_exc( + [&service]() + { + service.post(llsd::map( + "op", llsd::array( + llsd::array("strfunc", "some string"), + llsd::array("intfunc", 2, "whoops"), // bad form + "arrayfunc", + "voidfunc"), + "args", llsd::array( + LLSD(), + LLSD(), + llsd::array(-5, "other string")), + // args array deliberately short, since the default + // [3] is undefined, which should work for voidfunc + "reqid", 17)); + // no "reply" + }, + "[1]"); + ensure_has(error, "unsupported"); + } } // namespace tut -- cgit v1.2.3 From 2c894ecb25de044f5cb9c408c5264e5234b73983 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 20 Jan 2023 22:34:31 -0500 Subject: DRTVWR-558: Extend LLEventDispatcher::add() overloads. Add LL::always_return(), which takes a callable and variadic arguments. It calls the callable with those arguments and, if the returned type is convertible to T, converts it and returns it. Otherwise it returns T(). always_return() is generalized from, and supersedes, LLEventDispatcher::ReturnLLSD. Add LL::function_arity, which extends boost::function_types::function_arity by reporting results for both std::function and boost::function. Use for LL::apply(function, LLSD array) as well as for LLEventDispatcher. Make LLEventDispatcher::add() overloads uniformly distinguish between a callable (whether non-static member function or otherwise) that accepts a single LLSD parameter, versus any other signature. Accepting exactly one LLSD parameter signals that the callable will accept the composite arguments LLSD blob, instead of asking LLEventDispatcher to unpack the arguments blob into individual arguments. Support add(subclass method) overloads for arbitrary-parameters methods as well as for (const LLSD&) methods. Update tests accordingly: we need no longer pass the boilerplate lambda instance getter that binds and returns 'this'. Extract to the two LLEventDispatcher::make_invoker() overloads the LL::apply() logic formerly found in ReturnLLSD. Change lleventdispatcher_test.cpp tests from boost::bind(), which accepts variadic arguments (even though it only passes a fixed set to the target callable), to fixed-signature lambdas. This is because the revamped add() overloads care about signature. Add a test for a non-static method that accepts (const LLSD&), in other words the composite arguments LLSD blob, and likewise returns LLSD. (cherry picked from commit 95b787f7d7226ee9de79dfc9816f33c8bf199aad) --- indra/llcommon/CMakeLists.txt | 2 + indra/llcommon/always_return.h | 124 +++++++++ indra/llcommon/function_types.h | 49 ++++ indra/llcommon/lleventdispatcher.cpp | 7 +- indra/llcommon/lleventdispatcher.h | 326 ++++++++++++++++++------ indra/llcommon/llsdutil.h | 4 +- indra/llcommon/tests/lleventdispatcher_test.cpp | 62 +++-- 7 files changed, 460 insertions(+), 114 deletions(-) create mode 100644 indra/llcommon/always_return.h create mode 100644 indra/llcommon/function_types.h diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 33e8301e12..64751926d0 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -117,11 +117,13 @@ set(llcommon_SOURCE_FILES set(llcommon_HEADER_FILES CMakeLists.txt + always_return.h apply.h chrono.h classic_callback.h ctype_workaround.h fix_macros.h + function_types.h indra_constants.h lazyeventapi.h linden_common.h diff --git a/indra/llcommon/always_return.h b/indra/llcommon/always_return.h new file mode 100644 index 0000000000..6b9f1fdeaf --- /dev/null +++ b/indra/llcommon/always_return.h @@ -0,0 +1,124 @@ +/** + * @file always_return.h + * @author Nat Goodspeed + * @date 2023-01-20 + * @brief Call specified callable with arbitrary arguments, but always return + * specified type. + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Copyright (c) 2023, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_ALWAYS_RETURN_H) +#define LL_ALWAYS_RETURN_H + +#include // std::enable_if, std::is_convertible + +namespace LL +{ + +#if __cpp_lib_is_invocable >= 201703L // C++17 + template + using invoke_result = std::invoke_result; +#else // C++14 + template + using invoke_result = std::result_of; +#endif // C++14 + + /** + * AlwaysReturn()(some_function, some_args...) calls + * some_function(some_args...). It is guaranteed to return a value of type + * T, regardless of the return type of some_function(). If some_function() + * returns a type convertible to T, it will convert and return that value. + * Otherwise (notably if some_function() is void), AlwaysReturn returns + * T(). + * + * When some_function() returns a type not convertible to T, if + * you want AlwaysReturn to return some T value other than + * default-constructed T(), pass that value to AlwaysReturn's constructor. + */ + template + class AlwaysReturn + { + public: + /// pass explicit default value if other than default-constructed type + AlwaysReturn(const DESIRED& dft=DESIRED()): mDefault(dft) {} + + // callable returns a type not convertible to DESIRED, return default + template ::type, + DESIRED + >::value, + bool + >::type=true> + DESIRED operator()(CALLABLE&& callable, ARGS&&... args) + { + // discard whatever callable(args) returns + std::forward(callable)(std::forward(args)...); + return mDefault; + } + + // callable returns a type convertible to DESIRED + template ::type, + DESIRED + >::value, + bool + >::type=true> + DESIRED operator()(CALLABLE&& callable, ARGS&&... args) + { + return { std::forward(callable)(std::forward(args)...) }; + } + + private: + DESIRED mDefault; + }; + + /** + * always_return(some_function, some_args...) calls + * some_function(some_args...). It is guaranteed to return a value of type + * T, regardless of the return type of some_function(). If some_function() + * returns a type convertible to T, it will convert and return that value. + * Otherwise (notably if some_function() is void), always_return() returns + * T(). + */ + template + DESIRED always_return(CALLABLE&& callable, ARGS&&... args) + { + return AlwaysReturn()(std::forward(callable), + std::forward(args)...); + } + + /** + * make_always_return(some_function) returns a callable which, when + * called with appropriate some_function() arguments, always returns a + * value of type T, regardless of the return type of some_function(). If + * some_function() returns a type convertible to T, the returned callable + * will convert and return that value. Otherwise (notably if + * some_function() is void), the returned callable returns T(). + * + * When some_function() returns a type not convertible to T, if + * you want the returned callable to return some T value other than + * default-constructed T(), pass that value to make_always_return() as its + * optional second argument. + */ + template + auto make_always_return(CALLABLE&& callable, const DESIRED& dft=DESIRED()) + { + return + [dft, callable = std::forward(callable)] + (auto&&... args) + { + return AlwaysReturn(dft)(callable, + std::forward(args)...); + }; + } + +} // namespace LL + +#endif /* ! defined(LL_ALWAYS_RETURN_H) */ diff --git a/indra/llcommon/function_types.h b/indra/llcommon/function_types.h new file mode 100644 index 0000000000..3f42f6d640 --- /dev/null +++ b/indra/llcommon/function_types.h @@ -0,0 +1,49 @@ +/** + * @file function_types.h + * @author Nat Goodspeed + * @date 2023-01-20 + * @brief Extend boost::function_types to examine boost::function and + * std::function + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Copyright (c) 2023, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_FUNCTION_TYPES_H) +#define LL_FUNCTION_TYPES_H + +#include +#include +#include + +namespace LL +{ + + template + struct function_arity_impl + { + static constexpr auto value = boost::function_types::function_arity::value; + }; + + template + struct function_arity_impl> + { + static constexpr auto value = function_arity_impl::value; + }; + + template + struct function_arity_impl> + { + static constexpr auto value = function_arity_impl::value; + }; + + template + struct function_arity + { + static constexpr auto value = function_arity_impl::type>::value; + }; + +} // namespace LL + +#endif /* ! defined(LL_FUNCTION_TYPES_H) */ diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index d10cf16b88..caff854753 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -384,8 +384,7 @@ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchE (desc, ": bad request: ", mismatch); } // Event syntax looks good, go for it! - mFunc(event); - return {}; + return mFunc(event); } LLSD addMetadata(LLSD meta) const override @@ -603,8 +602,8 @@ void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name, } /// Register a callable by name -void LLEventDispatcher::add(const std::string& name, const std::string& desc, - const Callable& callable, const LLSD& required) +void LLEventDispatcher::addLLSD(const std::string& name, const std::string& desc, + const Callable& callable, const LLSD& required) { mDispatch.emplace(name, new LLSDDispatchEntry(desc, callable, required)); } diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index 5ab860b6dd..789a59459c 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -32,17 +32,18 @@ #if ! defined(LL_LLEVENTDISPATCHER_H) #define LL_LLEVENTDISPATCHER_H -#include #include #include -#include -#include +#include // until C++17, when we get std::is_invocable +#include #include // std::function #include // std::unique_ptr #include #include #include #include // std::pair +#include "always_return.h" +#include "function_types.h" // LL::function_arity #include "llevents.h" #include "llptrto.h" #include "llsdutil.h" @@ -78,7 +79,7 @@ public: //@{ /// Accept any C++ callable with the right signature - typedef std::function Callable; + typedef std::function Callable; /** * Register a @a callable by @a name. The passed @a callable accepts a @@ -90,19 +91,25 @@ public: void add(const std::string& name, const std::string& desc, const Callable& callable, - const LLSD& required=LLSD()); + const LLSD& required=LLSD()) + { + addLLSD(name, desc, callable, required); + } - /** - * The case of a free function (or static method) accepting(const LLSD&) - * could also be intercepted by the arbitrary-args overload below. Ensure - * that it's directed to the Callable overload above instead. - */ + template ::value + >::type> void add(const std::string& name, const std::string& desc, - void (*f)(const LLSD&), + CALLABLE&& callable, const LLSD& required=LLSD()) { - add(name, desc, Callable(f), required); + addLLSD( + name, + desc, + Callable(LL::make_always_return(std::forward(callable))), + required); } /** @@ -111,6 +118,27 @@ public: * specifying a std::bind() expression. The passed @a method * accepts a single LLSD value, presumably containing other parameters. */ + template + void add(const std::string& name, + const std::string& desc, + R (CLASS::*method)(const LLSD&), + const LLSD& required=LLSD()) + { + addMethod(name, desc, method, required); + } + + /// Overload for both const and non-const methods. The passed @a method + /// accepts a single LLSD value, presumably containing other parameters. + template + void add(const std::string& name, + const std::string& desc, + R (CLASS::*method)(const LLSD&) const, + const LLSD& required=LLSD()) + { + addMethod(name, desc, method, required); + } + + // because the compiler can't match a method returning void to the above template void add(const std::string& name, const std::string& desc, @@ -131,6 +159,128 @@ public: addMethod(name, desc, method, required); } + // non-const nullary method + template + void add(const std::string& name, + const std::string& desc, + R (CLASS::*method)()) + { + addVMethod(name, desc, method); + } + + // const nullary method + template + void add(const std::string& name, + const std::string& desc, + R (CLASS::*method)() const) + { + addVMethod(name, desc, method); + } + + // non-const nullary method returning void + template + void add(const std::string& name, + const std::string& desc, + void (CLASS::*method)()) + { + addVMethod(name, desc, method); + } + + // const nullary method returning void + template + void add(const std::string& name, + const std::string& desc, + void (CLASS::*method)() const) + { + addVMethod(name, desc, method); + } + + // non-const unary method (but method accepting LLSD should use the other add()) + // enable_if usage per https://stackoverflow.com/a/39913395/5533635 + template ::type, LLSD>::value + >::type> + void add(const std::string& name, + const std::string& desc, + R (CLASS::*method)(ARG)) + { + addVMethod(name, desc, method); + } + + // const unary method (but method accepting LLSD should use the other add()) + template ::type, LLSD>::value + >::type> + void add(const std::string& name, + const std::string& desc, + R (CLASS::*method)(ARG) const) + { + addVMethod(name, desc, method); + } + + // non-const unary method returning void + // enable_if usage per https://stackoverflow.com/a/39913395/5533635 + template ::type, LLSD>::value + >::type> + void add(const std::string& name, + const std::string& desc, + void (CLASS::*method)(ARG)) + { + addVMethod(name, desc, method); + } + + // const unary method returning void + template ::type, LLSD>::value + >::type> + void add(const std::string& name, + const std::string& desc, + void (CLASS::*method)(ARG) const) + { + addVMethod(name, desc, method); + } + + // non-const binary (or more) method + template + void add(const std::string& name, + const std::string& desc, + R (CLASS::*method)(ARG0, ARG1, ARGS...)) + { + addVMethod(name, desc, method); + } + + // const binary (or more) method + template + void add(const std::string& name, + const std::string& desc, + R (CLASS::*method)(ARG0, ARG1, ARGS...) const) + { + addVMethod(name, desc, method); + } + + // non-const binary (or more) method returning void + template + void add(const std::string& name, + const std::string& desc, + void (CLASS::*method)(ARG0, ARG1, ARGS...)) + { + addVMethod(name, desc, method); + } + + // const binary (or more) method returning void + template + void add(const std::string& name, + const std::string& desc, + void (CLASS::*method)(ARG0, ARG1, ARGS...) const) + { + addVMethod(name, desc, method); + } + //@} /// @name Register functions with arbitrary param lists @@ -143,12 +293,16 @@ public: * When calling this name, pass an LLSD::Array. Each entry in turn will be * converted to the corresponding parameter type using LLSDParam. */ - // enable_if usage per https://stackoverflow.com/a/39913395/5533635 - template::value + template () >::type> - void add(const std::string& name, const std::string& desc, Function f); + void add(const std::string& name, + const std::string& desc, + CALLABLE&& f) + { + addV(name, desc, f); + } /** * Register a nonstatic class method with arbitrary parameters. @@ -156,11 +310,7 @@ public: * To cover cases such as a method on an LLSingleton we don't yet want to * instantiate, instead of directly storing an instance pointer, accept a * nullary callable returning a pointer/reference to the desired class - * instance. If you already have an instance in hand, - * boost::lambda::var(instance) or boost::lambda::constant(instance_ptr) - * produce suitable callables. - * - * TODO: variant accepting a method of the containing class, no getter. + * instance. * * When calling this name, pass an LLSD::Array. Each entry in turn will be * converted to the corresponding parameter type using LLSDParam. @@ -186,7 +336,8 @@ public: */ template::value + boost::function_types::is_nonmember_callable_builtin::value && + ! boost::hof::is_invocable::value >::type> void add(const std::string& name, const std::string& desc, Function f, const LLSD& params, const LLSD& defaults=LLSD()); @@ -348,6 +499,11 @@ public: friend std::ostream& operator<<(std::ostream&, const LLEventDispatcher&); private: + void addLLSD(const std::string& name, + const std::string& desc, + const Callable& callable, + const LLSD& required); + template void addMethod(const std::string& name, const std::string& desc, const METHOD& method, const LLSD& required) @@ -359,9 +515,40 @@ private: } else { - add(name, desc, std::bind(method, downcast, std::placeholders::_1), required); + add(name, + desc, + Callable(LL::make_always_return( + [downcast, method] + (const LLSD& args) + { + return (downcast->*method)(args); + })), + required); } } + + template + void addVMethod(const std::string& name, const std::string& desc, + const METHOD& method) + { + CLASS* downcast = dynamic_cast(this); + if (! downcast) + { + addFail(name, typeid(CLASS).name()); + } + else + { + // add() arbitrary method plus InstanceGetter, where the + // InstanceGetter in this case returns 'this'. We don't need to + // worry about binding 'this' because, once this LLEventDispatcher + // is destroyed, the DispatchEntry goes away too. + add(name, desc, method, [downcast](){ return downcast; }); + } + } + + template + void addV(const std::string& name, const std::string& desc, Function f); + void addFail(const std::string& name, const std::string& classname) const; LLSD try_call(const std::string& key, const std::string& name, const LLSD& event) const; @@ -405,21 +592,19 @@ private: const invoker_function& invoker, const LLSD& params, const LLSD& defaults); - template - struct ReturnLLSD; }; /***************************************************************************** * LLEventDispatcher template implementation details *****************************************************************************/ -template -void LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f) +template +void LLEventDispatcher::addV(const std::string& name, const std::string& desc, Function f) { // Construct an invoker_function, a callable accepting const LLSD&. // Add to DispatchMap an ArrayParamsDispatchEntry that will handle the // caller's LLSD::Array. addArrayParamsDispatchEntry(name, desc, make_invoker(f), - boost::function_types::function_arity::value); + LL::function_arity::value); } template @@ -429,7 +614,7 @@ void LLEventDispatcher::add(const std::string& name, const std::string& desc, Me // Subtract 1 from the compile-time arity because the getter takes care of // the first parameter. We only need (arity - 1) additional arguments. addArrayParamsDispatchEntry(name, desc, make_invoker(f, getter), - boost::function_types::function_arity::value - 1); + LL::function_arity::value - 1); } template @@ -448,64 +633,23 @@ void LLEventDispatcher::add(const std::string& name, const std::string& desc, Me addMapParamsDispatchEntry(name, desc, make_invoker(f, getter), params, defaults); } -// general case, when f() has a non-void return type -template -struct LLEventDispatcher::ReturnLLSD -{ - template - LLSD operator()(Function f, const LLSD& args) - { - return { LL::apply(f, args) }; - } - - template - LLSD operator()(Method f, const InstanceGetter& getter, const LLSD& args) - { - constexpr auto arity = boost::function_types::function_arity< - typename std::remove_reference::type>::value - 1; - - // Use bind_front() to bind the method to (a pointer to) the object - // returned by getter(). It's okay to capture and bind a pointer - // because this bind_front() object will last only as long as this - // operator() call. - return { LL::apply_n(LL::bind_front(f, LL::get_ptr(getter())), args) }; - } -}; - -// specialize for void return type -template <> -struct LLEventDispatcher::ReturnLLSD -{ - template - LLSD operator()(Function f, const LLSD& args) - { - LL::apply(f, args); - return {}; - } - - template - LLSD operator()(Method f, const InstanceGetter& getter, const LLSD& args) - { - constexpr auto arity = boost::function_types::function_arity< - typename std::remove_reference::type>::value - 1; - - // Use bind_front() to bind the method to (a pointer to) the object - // returned by getter(). It's okay to capture and bind a pointer - // because this bind_front() object will last only as long as this - // operator() call. - LL::apply_n(LL::bind_front(f, LL::get_ptr(getter())), args); - return {}; - } -}; - template LLEventDispatcher::invoker_function LLEventDispatcher::make_invoker(Function f) { + // Return an invoker_function that accepts (const LLSD& args). return [f](const LLSD& args) { - return ReturnLLSD::type>() - (f, args); + // When called, call always_return, directing it to call + // f(expanded args). always_return guarantees we'll get an LLSD + // value back, even if it's undefined because 'f' doesn't return a + // type convertible to LLSD. + return LL::always_return( + [f, args] + () + { + return LL::apply(f, args); + }); }; } @@ -513,10 +657,24 @@ template LLEventDispatcher::invoker_function LLEventDispatcher::make_invoker(Method f, const InstanceGetter& getter) { + // function_arity includes its implicit 'this' pointer + constexpr auto arity = LL::function_arity< + typename std::remove_reference::type>::value - 1; + return [f, getter](const LLSD& args) { - return ReturnLLSD::type>() - (f, getter, args); + // always_return() immediately calls the lambda we pass, and + // returns LLSD whether our passed lambda returns void or non-void. + return LL::always_return( + [f, getter, args] + () + { + // Use bind_front() to bind the method to (a pointer to) the object + // returned by getter(). It's okay to capture and bind a pointer + // because this bind_front() object will last only as long as this + // lambda call. + return LL::apply_n(LL::bind_front(f, LL::get_ptr(getter())), args); + }); }; } diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h index baf4400768..a6fd2fdac2 100644 --- a/indra/llcommon/llsdutil.h +++ b/indra/llcommon/llsdutil.h @@ -30,9 +30,9 @@ #define LL_LLSDUTIL_H #include "apply.h" // LL::invoke() +#include "function_types.h" // LL::function_arity #include "llsd.h" #include -#include #include #include @@ -628,7 +628,7 @@ template auto apply(CALLABLE&& func, const LLSD& args) { // infer arity from the definition of func - constexpr auto arity = boost::function_types::function_arity< + constexpr auto arity = function_arity< typename std::remove_reference::type>::value; // now that we have a compile-time arity, apply_n() works return apply_n(std::forward(func), args); diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index 8e84a9e038..2a3644e2e1 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -423,9 +423,9 @@ namespace tut work.add(name, desc, &Dispatcher::cmethod1, required); // Non-subclass method with/out required params addf("method1", "method1", &v); - work.add(name, desc, boost::bind(&Vars::method1, boost::ref(v), _1)); + work.add(name, desc, [this](const LLSD& args){ return v.method1(args); }); addf("method1_req", "method1", &v); - work.add(name, desc, boost::bind(&Vars::method1, boost::ref(v), _1), required); + work.add(name, desc, [this](const LLSD& args){ return v.method1(args); }, required); /*--------------- Arbitrary params, array style ----------------*/ @@ -609,6 +609,7 @@ namespace tut void addf(const std::string& n, const std::string& d, Vars* v) { + debug("addf('", n, "', '", d, "')"); // This method is to capture in our own DescMap the name and // description of every registered function, for metadata query // testing. @@ -1339,22 +1340,25 @@ namespace tut DispatchResult(): LLDispatchListener("results", "op") { - // As of 2022-12-22, LLEventDispatcher's shorthand add() methods - // for pointer-to-method of same instance only support methods - // with signature void(const LLSD&). The generic add(pointer-to- - // method) requires an instance getter. - add("strfunc", "return string", &DR::strfunc, [this](){ return this; }); - add("voidfunc", "void function", &DR::voidfunc, [this](){ return this; }); - add("emptyfunc", "return empty LLSD", &DR::emptyfunc, [this](){ return this; }); - add("intfunc", "return Integer LLSD", &DR::intfunc, [this](){ return this; }); - add("mapfunc", "return map LLSD", &DR::mapfunc, [this](){ return this; }); - add("arrayfunc", "return array LLSD", &DR::arrayfunc, [this](){ return this; }); + add("strfunc", "return string", &DR::strfunc); + add("voidfunc", "void function", &DR::voidfunc); + add("emptyfunc", "return empty LLSD", &DR::emptyfunc); + add("intfunc", "return Integer LLSD", &DR::intfunc); + add("llsdfunc", "return passed LLSD", &DR::llsdfunc); + add("mapfunc", "return map LLSD", &DR::mapfunc); + add("arrayfunc", "return array LLSD", &DR::arrayfunc); } std::string strfunc(const std::string& str) const { return "got " + str; } void voidfunc() const {} LLSD emptyfunc() const { return {}; } int intfunc(int i) const { return -i; } + LLSD llsdfunc(const LLSD& event) const + { + LLSD result{ event }; + result["with"] = "string"; + return result; + } LLSD mapfunc(int i, const std::string& str) const { return llsd::map("i", intfunc(i), "str", strfunc(str)); @@ -1394,6 +1398,16 @@ namespace tut template<> template<> void object::test<26>() + { + set_test_name("LLSD echo"); + DispatchResult service; + LLSD result{ service("llsdfunc", llsd::map("op", "llsdfunc", "reqid", 17)) }; + ensure_equals("llsdfunc() mismatch", result, + llsd::map("op", "llsdfunc", "reqid", 17, "with", "string")); + } + + template<> template<> + void object::test<27>() { set_test_name("map LLSD result"); DispatchResult service; @@ -1402,7 +1416,7 @@ namespace tut } template<> template<> - void object::test<27>() + void object::test<28>() { set_test_name("array LLSD result"); DispatchResult service; @@ -1411,7 +1425,7 @@ namespace tut } template<> template<> - void object::test<28>() + void object::test<29>() { set_test_name("listener error, no reply"); DispatchResult service; @@ -1422,7 +1436,7 @@ namespace tut } template<> template<> - void object::test<29>() + void object::test<30>() { set_test_name("listener error with reply"); DispatchResult service; @@ -1435,7 +1449,7 @@ namespace tut } template<> template<> - void object::test<30>() + void object::test<31>() { set_test_name("listener call to void function"); DispatchResult service; @@ -1452,7 +1466,7 @@ namespace tut } template<> template<> - void object::test<31>() + void object::test<32>() { set_test_name("listener call to string function"); DispatchResult service; @@ -1468,7 +1482,7 @@ namespace tut } template<> template<> - void object::test<32>() + void object::test<33>() { set_test_name("listener call to map function"); DispatchResult service; @@ -1485,7 +1499,7 @@ namespace tut } template<> template<> - void object::test<33>() + void object::test<34>() { set_test_name("batched map success"); DispatchResult service; @@ -1512,7 +1526,7 @@ namespace tut } template<> template<> - void object::test<34>() + void object::test<35>() { set_test_name("batched map error"); DispatchResult service; @@ -1545,7 +1559,7 @@ namespace tut } template<> template<> - void object::test<35>() + void object::test<36>() { set_test_name("batched map exception"); DispatchResult service; @@ -1568,7 +1582,7 @@ namespace tut } template<> template<> - void object::test<36>() + void object::test<37>() { set_test_name("batched array success"); DispatchResult service; @@ -1602,7 +1616,7 @@ namespace tut } template<> template<> - void object::test<37>() + void object::test<38>() { set_test_name("batched array error"); DispatchResult service; @@ -1633,7 +1647,7 @@ namespace tut } template<> template<> - void object::test<38>() + void object::test<39>() { set_test_name("batched array exception"); DispatchResult service; -- cgit v1.2.3 From c747ff0925fb85147a96745bb55e66e7e8004fd8 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 21 Jan 2023 11:13:04 -0500 Subject: DRTVWR-558: Enrich LLEventDispatcher::callFail() with current call. Now an LLEventAPI subclass method can call callFail(...) to report an error, and the error will be annotated with the leaf class name, the instance name and the way the method was reached. The enriched error message will be logged and either sent back to the invoker or propagated as an exception, depending on the invocation tactic. In other words, a business method can use callFail() to Do The Right Thing according to the LLEventDispatcher contract. Introduce a nested SetState RAII class to set and clear transient state. SetState's constructor accepts variadic stringize() arguments. The resulting message is passed to LLEventDispatcher::setState(), which requires a SetState reference because ONLY SetState should call setState(): state data really is intended to be transient. SetState guarantees it will be cleared every time it's set. setState() respects previously-set transient state. If a call from an inner function finds that transient state was already set by some ancestor, it ignores the call and informs the caller by returning false. This lets a given SetState instance recognize whether it is responsible for clearing the current transient state. operator<<(std::ostream&, const LLEventDispatcher&) now appends getState() to the data reported by streaming *this. Non-static LLEventDispatcher::callFail() already prepends *this to the reported error message. Transient state is managed by a fiber_specific_ptr, since different threads and even different fibers within a thread might be concurrently performing different operations on the same LLEventDispatcher. Introduce a back pointer to the parent LLEventDispatcher in DispatchEntry. Populate it with a new constructor parameter, propagated through every subclass constructor. Hoist ParamsDispatchEntry::callFail() up into its DispatchEntry base class. Make it call non-static LLEventDispatcher:: callFail(), which prepends the reported error with instance and transient state info. Use DispatchEntry::callFail() in LLSDDispatchEntry::call(), instead of redundantly calling LLEventDispatcher::callFail(). Similarly, introduce an LLEventDispatcher back pointer into LLSDArgsMapper for use by its own callFail() method. The above should (!) eliminate the need to replicate LLEventDispatcher instance info into every helper object's descriptive strings. In particular, since the previous info was stored in each object by its constructor, it couldn't report associated transient information about how the subject callable was actually reached. Traversing a back pointer to the live LLEventDispatcher instance gets us the most current state. Make the internal three-argument LLEventDispatcher::try_call() method, which implements each of the operator()() and public try_call() methods, use SetState to append "[name]" (for explicit operator()(name, event) calls) or "[key=name]" (for implicit operator()(event) calls) to streamed *this. In the new LLDispatchListener request array and request map operations, use SetState to indicate the current entry in the array or map, overriding the lower-level state set by three-argument LLEventDispatcher::try_call(). (cherry picked from commit 2f8d7d20f43ab411ea0fe8b756cb696954acfb3e) --- indra/llcommon/lleventdispatcher.cpp | 118 ++++++++++++++++++++++------------- indra/llcommon/lleventdispatcher.h | 54 +++++++++++++++- 2 files changed, 129 insertions(+), 43 deletions(-) diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index caff854753..0079b9ce36 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -103,7 +103,8 @@ class LL_COMMON_API LLEventDispatcher::LLSDArgsMapper public: /// Accept description of function: function name, param names, param /// default values - LLSDArgsMapper(const std::string& function, const LLSD& names, const LLSD& defaults); + LLSDArgsMapper(LLEventDispatcher* parent, const std::string& function, + const LLSD& names, const LLSD& defaults); /// Given arguments map, return LLSD::Array of parameter values, or /// trigger error. @@ -114,6 +115,9 @@ private: template void callFail(ARGS&&... args) const; + // store a plain dumb back-pointer because we don't have to manage the + // parent LLEventDispatcher's lifespan + LLEventDispatcher* _parent; // The function-name string is purely descriptive. We want error messages // to be able to indicate which function's LLSDArgsMapper has the problem. std::string _function; @@ -132,9 +136,11 @@ private: FilledVector _has_dft; }; -LLEventDispatcher::LLSDArgsMapper::LLSDArgsMapper(const std::string& function, +LLEventDispatcher::LLSDArgsMapper::LLSDArgsMapper(LLEventDispatcher* parent, + const std::string& function, const LLSD& names, const LLSD& defaults): + _parent(parent), _function(function), _names(names), _has_dft(names.size()) @@ -334,7 +340,7 @@ std::string LLEventDispatcher::LLSDArgsMapper::formatlist(const LLSD& list) template void LLEventDispatcher::LLSDArgsMapper::callFail(ARGS&&... args) const { - LLEventDispatcher::sCallFail + _parent->callFail (_function, std::forward(args)...); } @@ -356,7 +362,8 @@ LLEventDispatcher::~LLEventDispatcher() { } -LLEventDispatcher::DispatchEntry::DispatchEntry(const std::string& desc): +LLEventDispatcher::DispatchEntry::DispatchEntry(LLEventDispatcher* parent, const std::string& desc): + mParent(parent), mDesc(desc) {} @@ -365,8 +372,9 @@ LLEventDispatcher::DispatchEntry::DispatchEntry(const std::string& desc): */ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchEntry { - LLSDDispatchEntry(const std::string& desc, const Callable& func, const LLSD& required): - DispatchEntry(desc), + LLSDDispatchEntry(LLEventDispatcher* parent, const std::string& desc, + const Callable& func, const LLSD& required): + DispatchEntry(parent, desc), mFunc(func), mRequired(required) {} @@ -380,8 +388,7 @@ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchE std::string mismatch(llsd_matches(mRequired, event)); if (! mismatch.empty()) { - LLEventDispatcher::sCallFail - (desc, ": bad request: ", mismatch); + return callFail(desc, ": bad request: ", mismatch); } // Event syntax looks good, go for it! return mFunc(event); @@ -400,9 +407,9 @@ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchE */ struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::DispatchEntry { - ParamsDispatchEntry(const std::string& name, const std::string& desc, - const invoker_function& func): - DispatchEntry(desc), + ParamsDispatchEntry(LLEventDispatcher* parent, const std::string& name, + const std::string& desc, const invoker_function& func): + DispatchEntry(parent, desc), mName(name), mInvoker(func) {} @@ -422,14 +429,6 @@ struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::Dispatc return callFail(err.what()); } } - - template - LLSD callFail(ARGS&&... args) const - { - LLEventDispatcher::sCallFail(mName, ": ", std::forward(args)...); - // pacify the compiler - return {}; - } }; /** @@ -438,9 +437,10 @@ struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::Dispatc */ struct LLEventDispatcher::ArrayParamsDispatchEntry: public LLEventDispatcher::ParamsDispatchEntry { - ArrayParamsDispatchEntry(const std::string& name, const std::string& desc, - const invoker_function& func, LLSD::Integer arity): - ParamsDispatchEntry(name, desc, func), + ArrayParamsDispatchEntry(LLEventDispatcher* parent, const std::string& name, + const std::string& desc, const invoker_function& func, + LLSD::Integer arity): + ParamsDispatchEntry(parent, name, desc, func), mArity(arity) {} @@ -503,11 +503,11 @@ struct LLEventDispatcher::ArrayParamsDispatchEntry: public LLEventDispatcher::Pa */ struct LLEventDispatcher::MapParamsDispatchEntry: public LLEventDispatcher::ParamsDispatchEntry { - MapParamsDispatchEntry(const std::string& name, const std::string& desc, - const invoker_function& func, + MapParamsDispatchEntry(LLEventDispatcher* parent, const std::string& name, + const std::string& desc, const invoker_function& func, const LLSD& params, const LLSD& defaults): - ParamsDispatchEntry(name, desc, func), - mMapper(name, params, defaults), + ParamsDispatchEntry(parent, name, desc, func), + mMapper(parent, name, params, defaults), mRequired(LLSD::emptyMap()) { // Build the set of all param keys, then delete the ones that are @@ -581,11 +581,9 @@ void LLEventDispatcher::addArrayParamsDispatchEntry(const std::string& name, const invoker_function& invoker, LLSD::Integer arity) { - // The first parameter to ArrayParamsDispatchEntry is solely for error - // messages. Identify our instance and this entry. mDispatch.emplace( name, - new ArrayParamsDispatchEntry(stringize(*this, '[', name, ']'), desc, invoker, arity)); + new ArrayParamsDispatchEntry(this, "", desc, invoker, arity)); } void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name, @@ -597,15 +595,14 @@ void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name, // Pass instance info as well as this entry name for error messages. mDispatch.emplace( name, - new MapParamsDispatchEntry(stringize(*this, '[', name, ']'), - desc, invoker, params, defaults)); + new MapParamsDispatchEntry(this, "", desc, invoker, params, defaults)); } /// Register a callable by name void LLEventDispatcher::addLLSD(const std::string& name, const std::string& desc, const Callable& callable, const LLSD& required) { - mDispatch.emplace(name, new LLSDDispatchEntry(desc, callable, required)); + mDispatch.emplace(name, new LLSDDispatchEntry(this, desc, callable, required)); } /// Unregister a callable @@ -682,7 +679,7 @@ LLSD LLEventDispatcher::try_call(const std::string& key, const std::string& name DispatchMap::const_iterator found = mDispatch.find(name); if (found == mDispatch.end()) { - // Here we were passed a valid name, but there's no registered + // Here we were passed a non-empty name, but there's no registered // callable with that name. This is the one case in which we throw // DispatchMissing instead of the generic DispatchError. // Distinguish the public method by which our caller reached here: @@ -699,8 +696,10 @@ LLSD LLEventDispatcher::try_call(const std::string& key, const std::string& name } // Found the name, so it's plausible to even attempt the call. - return found->second->call(stringize(*this, " calling ", std::quoted(name)), - event, (! key.empty()), mArgskey); + const char* delim = (key.empty()? "" : "="); + // append either "[key=name]" or just "[name]" + SetState transient(this, '[', key, delim, name, ']'); + return found->second->call("", event, (! key.empty()), mArgskey); } template @@ -735,7 +734,34 @@ LLSD LLEventDispatcher::getMetadata(const std::string& name) const std::ostream& operator<<(std::ostream& out, const LLEventDispatcher& self) { // If we're a subclass of LLEventDispatcher, e.g. LLEventAPI, report that. - return out << LLError::Log::classname(self) << '(' << self.mDesc << ')'; + // Also report whatever transient state is active. + return out << LLError::Log::classname(self) << '(' << self.mDesc << ')' + << self.getState(); +} + +std::string LLEventDispatcher::getState() const +{ + // default value of fiber_specific_ptr is nullptr, and ~SetState() reverts + // to that; infer empty string + if (! mState.get()) + return {}; + else + return *mState; +} + +bool LLEventDispatcher::setState(SetState&, const std::string& state) const +{ + // If SetState is instantiated at multiple levels of function call, ignore + // the lower-level call because the outer call presumably provides more + // context. + if (mState.get()) + return false; + + // Pass us empty string (a la ~SetState()) to reset to nullptr, else take + // a heap copy of the passed state string so we can delete it on + // subsequent reset(). + mState.reset(state.empty()? nullptr : new std::string(state)); + return true; } /***************************************************************************** @@ -802,6 +828,8 @@ void LLDispatchListener::call_map(const LLSD& reqmap, const LLSD& event) const { // LLSD map containing returned values LLSD result; + // cache dispatch key + std::string key{ getDispatchKey() }; // collect any error messages here std::ostringstream errors; const char* delim = ""; @@ -812,6 +840,9 @@ void LLDispatchListener::call_map(const LLSD& reqmap, const LLSD& event) const const LLSD& args{ pair.second }; try { + // in case of errors, tell user the dispatch key, the fact that + // we're processing a request map and the current key in that map + SetState(this, '[', key, '[', name, "]]"); // With this form, capture return value even if undefined: // presence of the key in the response map can be used to detect // which request keys succeeded. @@ -819,10 +850,8 @@ void LLDispatchListener::call_map(const LLSD& reqmap, const LLSD& event) const } catch (const DispatchError& err) { - // collect message in 'errors', with hint as to which request map - // entry failed - errors << delim << err.what() << " (" << getDispatchKey() - << '[' << name << "])"; + // collect message in 'errors' + errors << delim << err.what(); delim = "\n"; } } @@ -850,6 +879,8 @@ void LLDispatchListener::call_array(const LLSD& reqarray, const LLSD& event) con { // LLSD array containing returned values LLSD results; + // cache the dispatch key + std::string key{ getDispatchKey() }; // arguments array, if present -- const because, if it's shorter than // reqarray, we don't want to grow it const LLSD argsarray{ event[getArgsKey()] }; @@ -883,13 +914,16 @@ void LLDispatchListener::call_array(const LLSD& reqarray, const LLSD& event) con // reqentry is one of the valid forms, got name and args try { + // in case of errors, tell user the dispatch key, the fact that + // we're processing a request array, the current entry in that + // array and the corresponding callable name + SetState(this, '[', key, '[', i, "]=", name, ']'); // With this form, capture return value even if undefined results.append((*this)(name, args)); } catch (const DispatchError& err) { - // append hint as to which requentry produced the error - error = stringize(err.what(), " (", getDispatchKey(), '[', i, ']'); + error = err.what(); break; } } diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index 789a59459c..2c5c62dd50 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -32,6 +32,7 @@ #if ! defined(LL_LLEVENTDISPATCHER_H) #define LL_LLEVENTDISPATCHER_H +#include #include #include #include // until C++17, when we get std::is_invocable @@ -460,14 +461,26 @@ public: private: struct DispatchEntry { - DispatchEntry(const std::string& desc); + DispatchEntry(LLEventDispatcher* parent, const std::string& desc); virtual ~DispatchEntry() {} // suppress MSVC warning, sigh + // store a plain dumb back-pointer because the parent + // LLEventDispatcher manages the lifespan of each DispatchEntry + // subclass instance -- not the other way around + LLEventDispatcher* mParent; std::string mDesc; virtual LLSD call(const std::string& desc, const LLSD& event, bool fromMap, const std::string& argskey) const = 0; virtual LLSD addMetadata(LLSD) const = 0; + + template + LLSD callFail(ARGS&&... args) const + { + mParent->callFail(std::forward(args)...); + // pacify the compiler + return {}; + } }; typedef std::map > DispatchMap; @@ -561,9 +574,48 @@ protected: static void sCallFail(ARGS&&... args); + // Manage transient state, e.g. which registered callable we're attempting + // to call, for error reporting + class SetState + { + public: + template + SetState(const LLEventDispatcher* self, ARGS&&... args): + mSelf(self) + { + mSet = mSelf->setState(*this, stringize(std::forward(args)...)); + } + // RAII class: forbid both copy and move + SetState(const SetState&) = delete; + SetState(SetState&&) = delete; + SetState& operator=(const SetState&) = delete; + SetState& operator=(SetState&&) = delete; + virtual ~SetState() + { + // if we're the ones who succeeded in setting state, clear it + if (mSet) + { + mSelf->setState(*this, {}); + } + } + + private: + const LLEventDispatcher* mSelf; + bool mSet; + }; + private: std::string mDesc, mKey, mArgskey; DispatchMap mDispatch; + // transient state: must be fiber_specific since multiple threads and/or + // multiple fibers may be calling concurrently. Make it mutable so we can + // use SetState even within const methods. + mutable boost::fibers::fiber_specific_ptr mState; + + std::string getState() const; + // setState() requires SetState& because only the SetState class should + // call it. Make it const so we can use SetState even within const methods. + bool setState(SetState&, const std::string& state) const; static NameDesc makeNameDesc(const DispatchMap::value_type& item) { -- cgit v1.2.3 From 2eb0ea9593d0e299445d2e1dde711bfe5072542e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 23 Jan 2023 10:51:33 -0500 Subject: DRTVWR-558: Nail down LLDispatchListener exception handling for exceptions other than those thrown by base-class LLEventDispatcher. Explain in LLDispatchListener Doxygen comments that for a request lacking a "reply" key, any exception is allowed to propagate because it's likely to reach the post() call that triggered the exception in the first place. For batch LLDispatchListener operations, catch not only LLEventDispatcher:: DispatchError exceptions but any std::exception, so we can collect them to report to the invoker. "Gotta catch 'em all!" Make LLLeap catch any std::exception thrown by processing a request from the plugin child process, log it and send a reply to the plugin. No plugin should be allowed to crash the viewer. (cherry picked from commit 94e10fd039b79f71ed8d7e10807b6e4eebd1928c) --- indra/llcommon/lleventdispatcher.cpp | 15 ++++++++++----- indra/llcommon/lleventdispatcher.h | 5 ++++- indra/llcommon/llleap.cpp | 27 ++++++++++++++++++++++----- 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index 0079b9ce36..a4c0ed3766 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -848,10 +848,12 @@ void LLDispatchListener::call_map(const LLSD& reqmap, const LLSD& event) const // which request keys succeeded. result[name] = (*this)(name, args); } - catch (const DispatchError& err) + catch (const std::exception& err) { - // collect message in 'errors' - errors << delim << err.what(); + // Catch not only DispatchError, but any C++ exception thrown by + // the target callable. Collect exception name and message in + // 'errors'. + errors << delim << LLError::Log::classname(err) << ": " << err.what(); delim = "\n"; } } @@ -921,9 +923,12 @@ void LLDispatchListener::call_array(const LLSD& reqarray, const LLSD& event) con // With this form, capture return value even if undefined results.append((*this)(name, args)); } - catch (const DispatchError& err) + catch (const std::exception& err) { - error = err.what(); + // Catch not only DispatchError, but any C++ exception thrown by + // the target callable. Report the exception class as well as the + // error string. + error = stringize(LLError::Log::classname(err), ": ", err.what()); break; } } diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index 2c5c62dd50..4ca8a7c735 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -747,7 +747,10 @@ LLEventDispatcher::make_invoker(Method f, const InstanceGetter& getter) * "reply" is present, LLDispatchListener will send a response map to the * specified LLEventPump containing an "error" key whose value is the relevant * error message. If "reply" is not present, the DispatchError exception will - * propagate. + * propagate. Since LLDispatchListener bundles an LLEventStream, which + * attempts the call immediately on receiving the post() call, there's a + * reasonable chance that the exception will highlight the post() call that + * triggered the error. * * If LLDispatchListener successfully calls the target callable, but no * "reply" key is present, any value returned by that callable is discarded. diff --git a/indra/llcommon/llleap.cpp b/indra/llcommon/llleap.cpp index c87c0758fe..abbc4185c6 100644 --- a/indra/llcommon/llleap.cpp +++ b/indra/llcommon/llleap.cpp @@ -327,11 +327,28 @@ public: } else { - // The LLSD object we got from our stream contains the keys we - // need. - LLEventPumps::instance().obtain(data["pump"]).post(data["data"]); - // Block calls to this method; resetting mBlocker unblocks calls - // to the other method. + try + { + // The LLSD object we got from our stream contains the + // keys we need. + LLEventPumps::instance().obtain(data["pump"]).post(data["data"]); + } + catch (const std::exception& err) + { + // No plugin should be allowed to crash the viewer by + // driving an exception -- intentionally or not. + LOG_UNHANDLED_EXCEPTION(stringize("handling request ", data)); + // Whether or not the plugin added a "reply" key to the + // 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(); + sendReply(llsd::map("error", + stringize(LLError::Log::classname(err), ": ", err.what())), + data); + } + // Block calls to this method; resetting mBlocker unblocks + // calls to the other method. mBlocker.reset(new LLEventPump::Blocker(mStdoutDataConnection)); // Go check for any more pending events in the buffer. if (childout.size()) -- cgit v1.2.3 From 7c79d7a7d4d5cb1e39293cdc98fd972be5bd3012 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 24 Jan 2023 13:31:50 -0500 Subject: DRTVWR-558: Fix merge glitch: missing LLEventDispatcher::addFail() (cherry picked from commit 3be250da90dd3d361df713056b881e017684e2b3) --- indra/llcommon/lleventdispatcher.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index a4c0ed3766..5d18d8f6c4 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -605,6 +605,14 @@ void LLEventDispatcher::addLLSD(const std::string& name, const std::string& desc mDispatch.emplace(name, new LLSDDispatchEntry(this, desc, callable, required)); } +void LLEventDispatcher::addFail(const std::string& name, const char* classname) const +{ + LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << ")::add(" << name + << "): " << LLError::Log::demangle(classname) + << " is not a subclass of LLEventDispatcher" + << LL_ENDL; +} + /// Unregister a callable bool LLEventDispatcher::remove(const std::string& name) { -- cgit v1.2.3 From d2738b60e2dfa255e504247f2b5009bc1dc24954 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 24 Jan 2023 13:34:42 -0500 Subject: DRTVWR-558: Fix const-ness glitch in LL::apply(func, tuple) std::get(const tuple) injects const into the type of each returned tuple element. Need to get a non-const ref to the tuple param to get the true type. (cherry picked from commit 6dda39065d3ee231998cb8a2896f94e8a45c9a82) --- indra/llcommon/apply.h | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/indra/llcommon/apply.h b/indra/llcommon/apply.h index 123813bcec..0009dd1f80 100644 --- a/indra/llcommon/apply.h +++ b/indra/llcommon/apply.h @@ -74,7 +74,7 @@ template::type = 0 > auto invoke(Fn&& f, Args&&... args) { - return std::mem_fn(f)(std::forward(args)...); + return std::mem_fn(std::forward(f))(std::forward(args)...); } template auto apply_impl(CALLABLE&& func, const std::tuple& args, std::index_sequence) { + // We accept const std::tuple& so a caller can construct an tuple on the + // fly. But std::get(const tuple) adds a const qualifier to everything + // it extracts. Get a non-const ref to this tuple so we can extract + // without the extraneous const. + auto& non_const_args{ const_cast&>(args) }; + // call func(unpacked args) - return invoke(std::forward(func), std::get(args)...); + return invoke(std::forward(func), + std::forward(std::get(non_const_args))...); } template -- cgit v1.2.3 From 25ee39dcbe7277552d8eee1ef5ad5c367f5763f9 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 24 Jan 2023 13:41:02 -0500 Subject: DRTVWR-558: Fix LLEventDispatcher::addMethod() for LazyEventAPI. addMethod() was using dynamic_cast(this) and testing for nullptr to decide whether the class owning the passed method is, or is not, a subclass of LLEventDispatcher. The trouble is that it doesn't work for the deferred add() calls queued by LazyEventAPI: the dynamic_cast was always returning nullptr. static_cast works better, but that leaves us with the problem we were trying to solve with dynamic_cast: what if the target class really isn't a subclass? Use std::is_base_of to pick which of two addMethod() overloads to invoke, one of which calls addFail(). (cherry picked from commit a4d520aa5d023d80cfeec4f40c3464b54cbcfc5b) --- indra/llcommon/lleventdispatcher.h | 51 ++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index 4ca8a7c735..db67d1b361 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -517,27 +517,40 @@ private: const Callable& callable, const LLSD& required); - template + template ::value, + bool + >::type=true> void addMethod(const std::string& name, const std::string& desc, const METHOD& method, const LLSD& required) { - CLASS* downcast = dynamic_cast(this); - if (! downcast) - { - addFail(name, typeid(CLASS).name()); - } - else - { - add(name, - desc, - Callable(LL::make_always_return( - [downcast, method] - (const LLSD& args) - { - return (downcast->*method)(args); - })), - required); - } + // Why two overloaded addMethod() methods, discriminated with + // std::is_base_of? It might seem simpler to use dynamic_cast and test + // for nullptr. The trouble is that it doesn't work for LazyEventAPI + // deferred registration: we get nullptr even for a method of an + // LLEventAPI subclass. + CLASS* downcast = static_cast(this); + add(name, + desc, + Callable(LL::make_always_return( + [downcast, method] + (const LLSD& args) + { + return (downcast->*method)(args); + })), + required); + } + + template ::value, + bool + >::type=true> + void addMethod(const std::string& name, const std::string& desc, + const METHOD&, const LLSD&) + { + addFail(name, typeid(CLASS).name()); } template @@ -562,7 +575,7 @@ private: template void addV(const std::string& name, const std::string& desc, Function f); - void addFail(const std::string& name, const std::string& classname) const; + void addFail(const std::string& name, const char* classname) const; LLSD try_call(const std::string& key, const std::string& name, const LLSD& event) const; -- cgit v1.2.3 From 3fce3d14d6b8e367f4136efbfe87fcfcb23a4c8e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 7 Mar 2023 17:39:03 -0500 Subject: DRTVWR-558: Avoid extra copy of getMetadata() LLSD map. (cherry picked from commit 2c1253c8ed2a1648317e6edd768b3fda00c56ce2) --- indra/llcommon/lleventdispatcher.cpp | 20 ++++++++------------ indra/llcommon/lleventdispatcher.h | 2 +- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index 5d18d8f6c4..99e2e74376 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -394,10 +394,9 @@ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchE return mFunc(event); } - LLSD addMetadata(LLSD meta) const override + LLSD getMetadata() const override { - meta["required"] = mRequired; - return meta; + return llsd::map("required", mRequired); } }; @@ -485,15 +484,14 @@ struct LLEventDispatcher::ArrayParamsDispatchEntry: public LLEventDispatcher::Pa return ParamsDispatchEntry::call(desc, args, fromMap, argskey); } - LLSD addMetadata(LLSD meta) const override + LLSD getMetadata() const override { LLSD array(LLSD::emptyArray()); // Resize to number of arguments required if (mArity) array[mArity - 1] = LLSD(); llassert_always(array.size() == mArity); - meta["required"] = array; - return meta; + return llsd::map("required", array); } }; @@ -568,11 +566,9 @@ struct LLEventDispatcher::MapParamsDispatchEntry: public LLEventDispatcher::Para return ParamsDispatchEntry::call(desc, mMapper.map(args), fromMap, argskey); } - LLSD addMetadata(LLSD meta) const override + LLSD getMetadata() const override { - meta["required"] = mRequired; - meta["optional"] = mOptional; - return meta; + return llsd::map("required", mRequired, "optional", mOptional); } }; @@ -733,10 +729,10 @@ LLSD LLEventDispatcher::getMetadata(const std::string& name) const { return LLSD(); } - LLSD meta; + LLSD meta{ found->second->getMetadata() }; meta["name"] = name; meta["desc"] = found->second->mDesc; - return found->second->addMetadata(meta); + return meta; } std::ostream& operator<<(std::ostream& out, const LLEventDispatcher& self) diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index db67d1b361..939e3730e1 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -472,7 +472,7 @@ private: virtual LLSD call(const std::string& desc, const LLSD& event, bool fromMap, const std::string& argskey) const = 0; - virtual LLSD addMetadata(LLSD) const = 0; + virtual LLSD getMetadata() const = 0; template LLSD callFail(ARGS&&... args) const -- cgit v1.2.3 From 7d1a3d70373f2988510eccf7dd8f9bd2f6c2694a Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 13 Jul 2023 15:58:37 -0400 Subject: DRTVWR-558: Fix a few lleventdispatcher_test merge glitches. --- indra/llcommon/tests/lleventdispatcher_test.cpp | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index 2a3644e2e1..0f27211d9e 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -754,7 +754,7 @@ namespace tut set_test_name("map-style registration with non-array params"); // Pass "param names" as scalar or as map LLSD attempts(llsd::array(17, LLSDMap("pi", 3.14)("two", 2))); - foreach(LLSD ae, inArray(attempts)) + for (LLSD ae: inArray(attempts)) { std::string threw = catch_what([this, &ae](){ work.add("freena_err", "freena", freena, ae); @@ -829,7 +829,7 @@ namespace tut { set_test_name("query Callables with/out required params"); LLSD names(llsd::array("free1", "Dmethod1", "Dcmethod1", "method1")); - foreach(LLSD nm, inArray(names)) + for (LLSD nm: inArray(names)) { LLSD metadata(getMetadata(nm)); ensure_equals("name mismatch", metadata["name"], nm); @@ -858,7 +858,7 @@ namespace tut (5, llsd::array("freena_array", "smethodna_array", "methodna_array")), llsd::array (5, llsd::array("freenb_array", "smethodnb_array", "methodnb_array")))); - foreach(LLSD ae, inArray(expected)) + for (LLSD ae: inArray(expected)) { LLSD::Integer arity(ae[0].asInteger()); LLSD names(ae[1]); @@ -884,7 +884,7 @@ namespace tut // - (Free function | non-static method), map style, no params (ergo // no defaults) LLSD names(llsd::array("free0_map", "smethod0_map", "method0_map")); - foreach(LLSD nm, inArray(names)) + for (LLSD nm: inArray(names)) { LLSD metadata(getMetadata(nm)); ensure_equals("name mismatch", metadata["name"], nm); @@ -914,7 +914,7 @@ namespace tut llsd::array("smethodnb_map_adft", "smethodnb_map_mdft"), llsd::array("methodna_map_adft", "methodna_map_mdft"), llsd::array("methodnb_map_adft", "methodnb_map_mdft"))); - foreach(LLSD eq, inArray(equivalences)) + for (LLSD eq: inArray(equivalences)) { LLSD adft(eq[0]); LLSD mdft(eq[1]); @@ -1020,10 +1020,6 @@ namespace tut (llsd::array("freenb_map_mdft", "smethodnb_map_mdft", "methodnb_map_mdft"), llsd::array(LLSD::emptyMap(), dft_map_full["b"])))); // required, optional - llsd::array // group - (llsd::array("freenb_map_mdft", "smethodnb_map_mdft", "methodnb_map_mdft"), - llsd::array(LLSD::emptyMap(), dft_map_full["b"])))); // required, optional - for (LLSD grp: inArray(groups)) { // Internal structure of each group in 'groups': @@ -1156,7 +1152,7 @@ namespace tut ("free0_array", "free0_map", "smethod0_array", "smethod0_map", "method0_array", "method0_map")); - foreach(LLSD name, inArray(names)) + for (LLSD name: inArray(names)) { // Look up the Vars instance for this function. Vars* vars(varsfor(name)); @@ -1316,7 +1312,7 @@ namespace tut "freenb_map_mdft", "smethodnb_map_mdft", "methodnb_map_mdft"))); // Treat (full | overfull) (array | map) the same. LLSD argssets(llsd::array(array_full, array_overfull, map_full, map_overfull)); - foreach(const LLSD& args, inArray(argssets)) + for (const LLSD& args: inArray(argssets)) { for (LLSD::String a: ab) { -- cgit v1.2.3 From 3b46c892719ced98cdb3265801cd5f62353b3ced Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 13 Jul 2023 16:01:56 -0400 Subject: DRTVWR-558: Constrain LL::apply()'s use of std::apply(). Once std::apply() becomes available, 'using std::apply;' isn't correct because the more general template tries to handle the apply(function, vector) case that we explicitly implement below. Have to provide apply(function, tuple) and apply(function, array) signatures that can forward to std::apply(). --- indra/llcommon/apply.h | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/indra/llcommon/apply.h b/indra/llcommon/apply.h index 0009dd1f80..cf6161ed50 100644 --- a/indra/llcommon/apply.h +++ b/indra/llcommon/apply.h @@ -93,7 +93,14 @@ auto invoke(Fn&& f, Args&&... args) #if __cpp_lib_apply >= 201603L // C++17 implementation -using std::apply; +// We don't just say 'using std::apply;' because that template is too general: +// it also picks up the apply(function, vector) case, which we want to handle +// below. +template +auto apply(CALLABLE&& func, const std::tuple& args) +{ + return std::apply(std::forward(func), args); +} #else // C++14 @@ -124,6 +131,8 @@ auto apply(CALLABLE&& func, const std::tuple& args) std::index_sequence_for{}); } +#endif // C++14 + // per https://stackoverflow.com/a/57510428/5533635 template auto apply(CALLABLE&& func, const std::array& args) @@ -131,8 +140,6 @@ auto apply(CALLABLE&& func, const std::array& args) return apply(std::forward(func), std::tuple_cat(args)); } -#endif // C++14 - /***************************************************************************** * bind_front() *****************************************************************************/ -- cgit v1.2.3 From 9a3c76757f7f635c0b8e47277881fea195d6b672 Mon Sep 17 00:00:00 2001 From: Roxie Linden Date: Thu, 13 Jul 2023 14:13:24 -0700 Subject: SL-20009 - race condition - calling cards could be created without name This happens when a calling card is created before the name is in the name cache. --- indra/newview/llfriendcard.cpp | 15 +-------------- indra/newview/llviewerinventory.cpp | 25 ++++++++++++++++++++----- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/indra/newview/llfriendcard.cpp b/indra/newview/llfriendcard.cpp index e395da7f1e..97fff033b6 100644 --- a/indra/newview/llfriendcard.cpp +++ b/indra/newview/llfriendcard.cpp @@ -534,20 +534,7 @@ void LLFriendCardsManager::syncFriendsFolder() // Create own calling card if it was not found in Friends/All folder if (!collector.isAgentCallingCardFound()) { - LLAvatarName av_name; - LLAvatarNameCache::get( gAgentID, &av_name ); - - create_inventory_item(gAgentID, - gAgent.getSessionID(), - calling_cards_folder_id, - LLTransactionID::tnull, - av_name.getCompleteName(), - gAgentID.asString(), - LLAssetType::AT_CALLINGCARD, - LLInventoryType::IT_CALLINGCARD, - NO_INV_SUBTYPE, - PERM_MOVE | PERM_TRANSFER, - NULL); + create_inventory_callingcard(gAgentID, calling_cards_folder_id); } // All folders created and updated. diff --git a/indra/newview/llviewerinventory.cpp b/indra/newview/llviewerinventory.cpp index 50252556de..c13acb4158 100644 --- a/indra/newview/llviewerinventory.cpp +++ b/indra/newview/llviewerinventory.cpp @@ -1046,14 +1046,29 @@ void create_inventory_item(const LLUUID& agent_id, const LLUUID& session_id, gAgent.sendReliableMessage(); } +void create_inventory_callingcard_callback(LLPointer cb, + const LLUUID &parent, + const LLUUID &avatar_id, + const LLAvatarName &av_name) +{ + std::string item_desc = avatar_id.asString(); + create_inventory_item(gAgent.getID(), + gAgent.getSessionID(), + parent, + LLTransactionID::tnull, + av_name.getUserName(), + item_desc, + LLAssetType::AT_CALLINGCARD, + LLInventoryType::IT_CALLINGCARD, + NO_INV_SUBTYPE, + PERM_MOVE | PERM_TRANSFER, + cb); +} + void create_inventory_callingcard(const LLUUID& avatar_id, const LLUUID& parent /*= LLUUID::null*/, LLPointer cb/*=NULL*/) { - std::string item_desc = avatar_id.asString(); LLAvatarName av_name; - LLAvatarNameCache::get(avatar_id, &av_name); - create_inventory_item(gAgent.getID(), gAgent.getSessionID(), - parent, LLTransactionID::tnull, av_name.getUserName(), item_desc, LLAssetType::AT_CALLINGCARD, - LLInventoryType::IT_CALLINGCARD, NO_INV_SUBTYPE, PERM_MOVE | PERM_TRANSFER, cb); + LLAvatarNameCache::get(avatar_id, boost::bind(&create_inventory_callingcard_callback, cb, parent, _1, _2)); } void create_inventory_wearable(const LLUUID& agent_id, const LLUUID& session_id, -- cgit v1.2.3 From c70f54095e01490b7d6f4eb58253ab966d49fcbe Mon Sep 17 00:00:00 2001 From: Roxie Linden Date: Fri, 14 Jul 2023 10:34:39 -0700 Subject: Fix some spacing --- indra/newview/llviewerinventory.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/indra/newview/llviewerinventory.cpp b/indra/newview/llviewerinventory.cpp index c13acb4158..15942faa98 100644 --- a/indra/newview/llviewerinventory.cpp +++ b/indra/newview/llviewerinventory.cpp @@ -1053,16 +1053,16 @@ void create_inventory_callingcard_callback(LLPointer cb, { std::string item_desc = avatar_id.asString(); create_inventory_item(gAgent.getID(), - gAgent.getSessionID(), - parent, - LLTransactionID::tnull, - av_name.getUserName(), - item_desc, - LLAssetType::AT_CALLINGCARD, - LLInventoryType::IT_CALLINGCARD, - NO_INV_SUBTYPE, - PERM_MOVE | PERM_TRANSFER, - cb); + gAgent.getSessionID(), + parent, + LLTransactionID::tnull, + av_name.getUserName(), + item_desc, + LLAssetType::AT_CALLINGCARD, + LLInventoryType::IT_CALLINGCARD, + NO_INV_SUBTYPE, + PERM_MOVE | PERM_TRANSFER, + cb); } void create_inventory_callingcard(const LLUUID& avatar_id, const LLUUID& parent /*= LLUUID::null*/, LLPointer cb/*=NULL*/) -- cgit v1.2.3 From d573bc2f926fb6ff473d9b346cdeb4d73e3b0ab3 Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Sat, 15 Jul 2023 14:23:44 +0300 Subject: SL-20011 Restrict 'Empty Trash' if objects are attached --- indra/newview/llinventorybridge.cpp | 3 ++- indra/newview/llvoavatarself.cpp | 21 +++++++++++++++++++++ indra/newview/llvoavatarself.h | 2 ++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp index db347f7096..31b1cb7a23 100644 --- a/indra/newview/llinventorybridge.cpp +++ b/indra/newview/llinventorybridge.cpp @@ -4141,7 +4141,8 @@ void LLFolderBridge::buildContextMenuOptions(U32 flags, menuentry_vec_t& items || is_recent_panel || !trash || trash->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN - || trash->getDescendentCount() == LLViewerInventoryCategory::VERSION_UNKNOWN) + || trash->getDescendentCount() == LLViewerInventoryCategory::VERSION_UNKNOWN + || gAgentAvatarp->hasAttachmentsInTrash()) { disabled_items.push_back(std::string("Empty Trash")); } diff --git a/indra/newview/llvoavatarself.cpp b/indra/newview/llvoavatarself.cpp index 8fc1dcd81f..914376f5d1 100644 --- a/indra/newview/llvoavatarself.cpp +++ b/indra/newview/llvoavatarself.cpp @@ -1247,6 +1247,27 @@ BOOL LLVOAvatarSelf::detachObject(LLViewerObject *viewer_object) return FALSE; } +bool LLVOAvatarSelf::hasAttachmentsInTrash() +{ + const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); + + for (attachment_map_t::const_iterator iter = mAttachmentPoints.begin(); iter != mAttachmentPoints.end(); ++iter) + { + LLViewerJointAttachment *attachment = iter->second; + for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); + attachment_iter != attachment->mAttachedObjects.end(); + ++attachment_iter) + { + LLViewerObject *attached_object = attachment_iter->get(); + if (attached_object && gInventory.isObjectDescendentOf(attached_object->getAttachmentItemID(), trash_id)) + { + return true; + } + } + } + return false; +} + // static BOOL LLVOAvatarSelf::detachAttachmentIntoInventory(const LLUUID &item_id) { diff --git a/indra/newview/llvoavatarself.h b/indra/newview/llvoavatarself.h index 279dbd61a6..6384e2b844 100644 --- a/indra/newview/llvoavatarself.h +++ b/indra/newview/llvoavatarself.h @@ -289,6 +289,8 @@ public: /*virtual*/ BOOL detachObject(LLViewerObject *viewer_object); static BOOL detachAttachmentIntoInventory(const LLUUID& item_id); + bool hasAttachmentsInTrash(); + //-------------------------------------------------------------------- // HUDs //-------------------------------------------------------------------- -- cgit v1.2.3 From 4c89ad558688f6dfa8f7216ad7613ed75823b069 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Fri, 14 Jul 2023 02:00:16 +0300 Subject: SL-19306 A method of displaying user-customized keybindings in user-visible text --- indra/llui/llurlentry.cpp | 121 ++++++++++++++++++++++++++++++++++ indra/llui/llurlentry.h | 33 ++++++++++ indra/llui/llurlregistry.cpp | 8 +++ indra/llui/llurlregistry.h | 6 ++ indra/llwindow/llkeyboard.cpp | 51 ++++++++++++++ indra/llwindow/llkeyboard.h | 9 +++ indra/newview/llappviewer.cpp | 2 + indra/newview/llfloaterpreference.cpp | 42 +++++++++++- indra/newview/llkeyconflict.cpp | 38 +---------- indra/newview/llviewerinput.cpp | 92 ++++++++++++++++++++++---- indra/newview/llviewerinput.h | 17 +++-- 11 files changed, 360 insertions(+), 59 deletions(-) diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index 6a9070634c..77e9edf5e5 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -35,7 +35,9 @@ #include "llavatarnamecache.h" #include "llcachename.h" +#include "llkeyboard.h" #include "llregex.h" +#include "llscrolllistctrl.h" // for LLUrlEntryKeybinding file parsing #include "lltrans.h" #include "lluicolortable.h" #include "message.h" @@ -1609,3 +1611,122 @@ std::string LLUrlEntryIPv6::getUrl(const std::string &string) const { return string; } + + +// +// LLUrlEntryKeybinding Displays currently assigned key +// +LLUrlEntryKeybinding::LLUrlEntryKeybinding() + : LLUrlEntryBase() + , pHandler(NULL) +{ + mPattern = boost::regex(APP_HEADER_REGEX "/keybinding/\\w+(\\?mode=\\w+)?$", + boost::regex::perl | boost::regex::icase); + mMenuName = "menu_url_experience.xml"; + + initLocalization(); +} + +std::string LLUrlEntryKeybinding::getLabel(const std::string& url, const LLUrlLabelCallback& cb) +{ + std::string control = getControlName(url); + + std::map::iterator iter = mLocalizations.find(control); + + std::string keybind; + if (pHandler) + { + keybind = pHandler->getKeyBindingAsString(getMode(url), control); + } + + if (iter != mLocalizations.end()) + { + return iter->second.mLocalization + ": " + keybind; + } + + return control + ": " + keybind; +} + +std::string LLUrlEntryKeybinding::getTooltip(const std::string& url) const +{ + std::string control = getControlName(url); + + std::map::const_iterator iter = mLocalizations.find(control); + if (iter != mLocalizations.end()) + { + return iter->second.mTooltip; + } + return url; +} + +std::string LLUrlEntryKeybinding::getControlName(const std::string& url) const +{ + std::string search = "/keybinding/"; + size_t pos_start = url.find(search); + if (pos_start == std::string::npos) + { + return std::string(); + } + pos_start += search.size(); + + size_t pos_end = url.find("?mode="); + if (pos_end == std::string::npos) + { + pos_end = url.size(); + } + return url.substr(pos_start, pos_end - pos_start); +} + +std::string LLUrlEntryKeybinding::getMode(const std::string& url) const +{ + std::string search = "?mode="; + size_t pos_start = url.find(search); + if (pos_start == std::string::npos) + { + return std::string(); + } + pos_start += search.size(); + return url.substr(pos_start, url.size() - pos_start); +} + +void LLUrlEntryKeybinding::initLocalization() +{ + initLocalizationFromFile("control_table_contents_movement.xml"); + initLocalizationFromFile("control_table_contents_camera.xml"); + initLocalizationFromFile("control_table_contents_editing.xml"); + initLocalizationFromFile("control_table_contents_media.xml"); +} + +void LLUrlEntryKeybinding::initLocalizationFromFile(const std::string& filename) +{ + LLXMLNodePtr xmlNode; + LLScrollListCtrl::Contents contents; + if (!LLUICtrlFactory::getLayeredXMLNode(filename, xmlNode)) + { + LL_WARNS() << "Failed to load " << filename << LL_ENDL; + return; + } + LLXUIParser parser; + parser.readXUI(xmlNode, contents, filename); + + if (!contents.validateBlock()) + { + LL_WARNS() << "Failed to validate " << filename << LL_ENDL; + return; + } + + for (LLInitParam::ParamIterator::const_iterator row_it = contents.rows.begin(); + row_it != contents.rows.end(); + ++row_it) + { + std::string control = row_it->value.getValue().asString(); + if (!control.empty() && control != "menu_separator") + { + mLocalizations[control] = + LLLocalizationData( + row_it->columns.begin()->value.getValue().asString(), + row_it->columns.begin()->tool_tip.getValue() + ); + } + } +} diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h index 63a1506731..5d0f5479f6 100644 --- a/indra/llui/llurlentry.h +++ b/indra/llui/llurlentry.h @@ -550,4 +550,37 @@ public: std::string mHostPath; }; +class LLKeyBindingToStringHandler; + +/// +/// LLUrlEntryKeybinding A way to access keybindings and show currently used one in text. +/// secondlife:///app/keybinding/control_name +class LLUrlEntryKeybinding: public LLUrlEntryBase +{ +public: + LLUrlEntryKeybinding(); + /*virtual*/ std::string getLabel(const std::string& url, const LLUrlLabelCallback& cb); + /*virtual*/ std::string getTooltip(const std::string& url) const; + void setHandler(LLKeyBindingToStringHandler* handler) {pHandler = handler;} +private: + std::string getControlName(const std::string& url) const; + std::string getMode(const std::string& url) const; + void initLocalization(); + void initLocalizationFromFile(const std::string& filename); + + struct LLLocalizationData + { + LLLocalizationData() {} + LLLocalizationData(const std::string& localization, const std::string& tooltip) + : mLocalization(localization) + , mTooltip(tooltip) + {} + std::string mLocalization; + std::string mTooltip; + }; + + std::map mLocalizations; + LLKeyBindingToStringHandler* pHandler; +}; + #endif diff --git a/indra/llui/llurlregistry.cpp b/indra/llui/llurlregistry.cpp index 23f3dca3fb..3bd7321777 100644 --- a/indra/llui/llurlregistry.cpp +++ b/indra/llui/llurlregistry.cpp @@ -73,6 +73,8 @@ LLUrlRegistry::LLUrlRegistry() registerUrl(new LLUrlEntryPlace()); registerUrl(new LLUrlEntryInventory()); registerUrl(new LLUrlEntryExperienceProfile()); + mUrlEntryKeybinding = new LLUrlEntryKeybinding(); + registerUrl(mUrlEntryKeybinding); //LLUrlEntrySL and LLUrlEntrySLLabel have more common pattern, //so it should be registered in the end of list registerUrl(new LLUrlEntrySL()); @@ -307,3 +309,9 @@ bool LLUrlRegistry::isUrl(const LLWString &text) } return false; } + +void LLUrlRegistry::setKeybindingHandler(LLKeyBindingToStringHandler* handler) +{ + LLUrlEntryKeybinding *entry = (LLUrlEntryKeybinding*)mUrlEntryKeybinding; + entry->setHandler(handler); +} diff --git a/indra/llui/llurlregistry.h b/indra/llui/llurlregistry.h index efafe543ab..186447c0be 100644 --- a/indra/llui/llurlregistry.h +++ b/indra/llui/llurlregistry.h @@ -36,6 +36,8 @@ #include #include +class LLKeyBindingToStringHandler; + /// This default callback for findUrl() simply ignores any label updates void LLUrlRegistryNullCallback(const std::string &url, const std::string &label, @@ -88,6 +90,9 @@ public: bool isUrl(const std::string &text); bool isUrl(const LLWString &text); + // Set handler for url registry to be capable of parsing and populating keybindings + void setKeybindingHandler(LLKeyBindingToStringHandler* handler); + private: std::vector mUrlEntry; LLUrlEntryBase* mUrlEntryTrusted; @@ -96,6 +101,7 @@ private: LLUrlEntryBase* mUrlEntryHTTPLabel; LLUrlEntryBase* mUrlEntrySLLabel; LLUrlEntryBase* mUrlEntryNoLink; + LLUrlEntryBase* mUrlEntryKeybinding; }; #endif diff --git a/indra/llwindow/llkeyboard.cpp b/indra/llwindow/llkeyboard.cpp index e65cc7563e..34720ff64e 100644 --- a/indra/llwindow/llkeyboard.cpp +++ b/indra/llwindow/llkeyboard.cpp @@ -366,6 +366,45 @@ std::string LLKeyboard::stringFromKey(KEY key, bool translate) return res; } +//static +std::string LLKeyboard::stringFromMouse(EMouseClickType click, bool translate) +{ + std::string res; + switch (click) + { + case CLICK_LEFT: + res = "LMB"; + break; + case CLICK_MIDDLE: + res = "MMB"; + break; + case CLICK_RIGHT: + res = "RMB"; + break; + case CLICK_BUTTON4: + res = "MB4"; + break; + case CLICK_BUTTON5: + res = "MB5"; + break; + case CLICK_DOUBLELEFT: + res = "Double LMB"; + break; + default: + break; + } + + if (translate && !res.empty()) + { + LLKeyStringTranslatorFunc* trans = gKeyboard->mStringTranslator; + if (trans != NULL) + { + res = trans(res.c_str()); + } + } + return res; +} + //static std::string LLKeyboard::stringFromAccelerator(MASK accel_mask) { @@ -433,6 +472,18 @@ std::string LLKeyboard::stringFromAccelerator( MASK accel_mask, KEY key ) return res; } +//static +std::string LLKeyboard::stringFromAccelerator(MASK accel_mask, EMouseClickType click) +{ + std::string res; + if (CLICK_NONE == click) + { + return res; + } + res.append(stringFromAccelerator(accel_mask)); + res.append(stringFromMouse(click)); + return res; +} //static BOOL LLKeyboard::maskFromString(const std::string& str, MASK *mask) diff --git a/indra/llwindow/llkeyboard.h b/indra/llwindow/llkeyboard.h index fb1ae10f50..dad150e3c1 100644 --- a/indra/llwindow/llkeyboard.h +++ b/indra/llwindow/llkeyboard.h @@ -96,8 +96,10 @@ public: static BOOL maskFromString(const std::string& str, MASK *mask); // False on failure static BOOL keyFromString(const std::string& str, KEY *key); // False on failure static std::string stringFromKey(KEY key, bool translate = true); + static std::string stringFromMouse(EMouseClickType click, bool translate = true); static std::string stringFromAccelerator( MASK accel_mask ); // separated for convinience, returns with "+": "Shift+" or "Shift+Alt+"... static std::string stringFromAccelerator( MASK accel_mask, KEY key ); + static std::string stringFromAccelerator(MASK accel_mask, EMouseClickType click); void setCallbacks(LLWindowCallbacks *cbs) { mCallbacks = cbs; } F32 getKeyElapsedTime( KEY key ); // Returns time in seconds since key was pressed. @@ -130,6 +132,13 @@ protected: static std::map sNamesToKeys; }; +// Interface to get key from assigned command +class LLKeyBindingToStringHandler +{ +public: + virtual std::string getKeyBindingAsString(const std::string& mode, const std::string& control) const = 0; +}; + extern LLKeyboard *gKeyboard; #endif diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 8235e4466c..3d81d4d7ea 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -195,6 +195,7 @@ #include "llhudeffecttrail.h" #include "llvectorperfoptions.h" #include "llslurl.h" +#include "llurlregistry.h" #include "llwatchdog.h" // Included so that constants/settings might be initialized @@ -4447,6 +4448,7 @@ void LLAppViewer::loadKeyBindings() LL_ERRS("InitInfo") << "Unable to open default key bindings from " << key_bindings_file << LL_ENDL; } } + LLUrlRegistry::instance().setKeybindingHandler(&gViewerInput); } void LLAppViewer::purgeCache() diff --git a/indra/newview/llfloaterpreference.cpp b/indra/newview/llfloaterpreference.cpp index 9ea49e935f..e59c2ee2cd 100644 --- a/indra/newview/llfloaterpreference.cpp +++ b/indra/newview/llfloaterpreference.cpp @@ -251,11 +251,49 @@ void fractionFromDecimal(F32 decimal_val, S32& numerator, S32& denominator) } } } -// static -std::string LLFloaterPreference::sSkin = ""; + +// handle secondlife:///app/worldmap/{NAME}/{COORDS} URLs +// Also see LLUrlEntryKeybinding, the value of this command type +// is ability to show up to date value in chat +class LLKeybindingHandler: public LLCommandHandler +{ +public: + // requires trusted browser to trigger + LLKeybindingHandler(): LLCommandHandler("keybinding", UNTRUSTED_CLICK_ONLY) + { + } + + bool handle(const LLSD& params, const LLSD& query_map, + LLMediaCtrl* web) + { + if (params.size() < 1) return false; + + LLFloaterPreference* prefsfloater = dynamic_cast + (LLFloaterReg::showInstance("preferences")); + + if (prefsfloater) + { + // find 'controls' panel and bring it the front + LLTabContainer* tabcontainer = prefsfloater->getChild("pref core"); + LLPanel* panel = prefsfloater->getChild("controls"); + if (tabcontainer && panel) + { + tabcontainer->selectTabPanel(panel); + } + } + + return true; + } +}; +LLKeybindingHandler gKeybindHandler; + + ////////////////////////////////////////////// // LLFloaterPreference +// static +std::string LLFloaterPreference::sSkin = ""; + LLFloaterPreference::LLFloaterPreference(const LLSD& key) : LLFloater(key), mGotPersonalInfo(false), diff --git a/indra/newview/llkeyconflict.cpp b/indra/newview/llkeyconflict.cpp index 60f8aca94c..4a0ee8fd0c 100644 --- a/indra/newview/llkeyconflict.cpp +++ b/indra/newview/llkeyconflict.cpp @@ -74,40 +74,6 @@ std::string string_from_mask(MASK mask) return res; } -std::string string_from_mouse(EMouseClickType click, bool translate) -{ - std::string res; - switch (click) - { - case CLICK_LEFT: - res = "LMB"; - break; - case CLICK_MIDDLE: - res = "MMB"; - break; - case CLICK_RIGHT: - res = "RMB"; - break; - case CLICK_BUTTON4: - res = "MB4"; - break; - case CLICK_BUTTON5: - res = "MB5"; - break; - case CLICK_DOUBLELEFT: - res = "Double LMB"; - break; - default: - break; - } - - if (translate && !res.empty()) - { - res = LLTrans::getString(res); - } - return res; -} - // LLKeyConflictHandler S32 LLKeyConflictHandler::sTemporaryFileUseCount = 0; @@ -270,7 +236,7 @@ std::string LLKeyConflictHandler::getStringFromKeyData(const LLKeyData& keydata) result = LLKeyboard::stringFromAccelerator(keydata.mMask); } - result += string_from_mouse(keydata.mMouse, true); + result += LLKeyboard::stringFromMouse(keydata.mMouse); return result; } @@ -545,7 +511,7 @@ void LLKeyConflictHandler::saveToSettings(bool temporary) { // set() because 'optional', for compatibility purposes // just copy old keys.xml and rename to key_bindings.xml, it should work - binding.mouse.set(string_from_mouse(data.mMouse, false), true); + binding.mouse.set(LLKeyboard::stringFromMouse(data.mMouse, false), true); } binding.command = iter->first; mode.bindings.add(binding); diff --git a/indra/newview/llviewerinput.cpp b/indra/newview/llviewerinput.cpp index 226e0a9a56..3f8e6f57f6 100644 --- a/indra/newview/llviewerinput.cpp +++ b/indra/newview/llviewerinput.cpp @@ -991,34 +991,50 @@ LLViewerInput::LLViewerInput() } } +LLViewerInput::~LLViewerInput() +{ + +} + // static -BOOL LLViewerInput::modeFromString(const std::string& string, S32 *mode) +bool LLViewerInput::modeFromString(const std::string& string, S32 *mode) { - if (string == "FIRST_PERSON") + if (string.empty()) + { + return false; + } + + std::string cmp_string = string; + LLStringUtil::toLower(cmp_string); + if (cmp_string == "first_person") { *mode = MODE_FIRST_PERSON; - return TRUE; + return true; } - else if (string == "THIRD_PERSON") + else if (cmp_string == "third_person") { *mode = MODE_THIRD_PERSON; - return TRUE; + return true; } - else if (string == "EDIT_AVATAR") + else if (cmp_string == "edit_avatar") { *mode = MODE_EDIT_AVATAR; - return TRUE; + return true; } - else if (string == "SITTING") + else if (cmp_string == "sitting") { *mode = MODE_SITTING; - return TRUE; - } - else - { - *mode = MODE_THIRD_PERSON; - return FALSE; + return true; } + + S32 val = atoi(string.c_str()); + if (val >= 0 || val < MODE_COUNT) + { + *mode = val; + return true; + } + + return false; } // static @@ -1222,6 +1238,7 @@ BOOL LLViewerInput::bindKey(const S32 mode, const KEY key, const MASK mask, cons bind.mKey = key; bind.mMask = mask; bind.mFunction = function; + bind.mFunctionName = function_name; if (result->mIsGlobal) { @@ -1303,6 +1320,7 @@ BOOL LLViewerInput::bindMouse(const S32 mode, const EMouseClickType mouse, const bind.mMouse = mouse; bind.mMask = mask; bind.mFunction = function; + bind.mFunctionName = function_name; if (result->mIsGlobal) { @@ -1801,3 +1819,49 @@ bool LLViewerInput::isMouseBindUsed(const EMouseClickType mouse, const MASK mask } return false; } + +std::string LLViewerInput::getKeyBindingAsString(const std::string& mode, const std::string& control) const +{ + S32 keyboard_mode; + if (!modeFromString(mode, &keyboard_mode)) + { + keyboard_mode = getMode(); + } + + std::string res; + bool needs_separator = false; + + // keybindings are sorted from having most mask to no mask (from restrictive to less restrictive), + // but it's visually better to present this data in reverse + std::vector::const_reverse_iterator iter_key = mKeyBindings[keyboard_mode].rbegin(); + while (iter_key != mKeyBindings[keyboard_mode].rend()) + { + if (iter_key->mFunctionName == control) + { + if (needs_separator) + { + res.append(" | "); + } + res.append(LLKeyboard::stringFromAccelerator(iter_key->mMask, iter_key->mKey)); + needs_separator = true; + } + iter_key++; + } + + std::vector::const_reverse_iterator iter_mouse = mMouseBindings[keyboard_mode].rbegin(); + while (iter_mouse != mMouseBindings[keyboard_mode].rend()) + { + if (iter_mouse->mFunctionName == control) + { + if (needs_separator) + { + res.append(" | "); + } + res.append(LLKeyboard::stringFromAccelerator(iter_mouse->mMask, iter_mouse->mMouse)); + needs_separator = true; + } + iter_mouse++; + } + + return res; +} diff --git a/indra/newview/llviewerinput.h b/indra/newview/llviewerinput.h index 52e95e2168..41e289ac1d 100644 --- a/indra/newview/llviewerinput.h +++ b/indra/newview/llviewerinput.h @@ -28,12 +28,13 @@ #define LL_LLVIEWERINPUT_H #include "llkeyboard.h" // For EKeystate -#include "llinitparam.h" const S32 MAX_KEY_BINDINGS = 128; // was 60 const S32 keybindings_xml_version = 1; const std::string script_mouse_handler_name = "script_trigger_lbutton"; +class LLWindow; + class LLNamedFunction { public: @@ -51,6 +52,7 @@ public: MASK mMask; LLKeyFunc mFunction; + std::string mFunctionName; }; class LLMouseBinding @@ -60,6 +62,7 @@ public: MASK mMask; LLKeyFunc mFunction; + std::string mFunctionName; }; @@ -72,11 +75,7 @@ typedef enum e_keyboard_mode MODE_COUNT } EKeyboardMode; -class LLWindow; - -void bind_keyboard_functions(); - -class LLViewerInput +class LLViewerInput : public LLKeyBindingToStringHandler { public: struct KeyBinding : public LLInitParam::Block @@ -107,6 +106,7 @@ public: }; LLViewerInput(); + virtual ~LLViewerInput(); BOOL handleKey(KEY key, MASK mask, BOOL repeated); BOOL handleKeyUp(KEY key, MASK mask); @@ -121,7 +121,7 @@ public: S32 loadBindingsXML(const std::string& filename); // returns number bound, 0 on error EKeyboardMode getMode() const; - static BOOL modeFromString(const std::string& string, S32 *mode); // False on failure + static bool modeFromString(const std::string& string, S32 *mode); // False on failure static BOOL mouseFromString(const std::string& string, EMouseClickType *mode);// False on failure bool scanKey(KEY key, @@ -136,6 +136,9 @@ public: bool isMouseBindUsed(const EMouseClickType mouse, const MASK mask, const S32 mode) const; bool isLMouseHandlingDefault(const S32 mode) const { return mLMouseDefaultHandling[mode]; } + // inherited from LLKeyBindingToStringHandler + virtual std::string getKeyBindingAsString(const std::string& mode, const std::string& control) const override; + private: bool scanKey(const std::vector &binding, S32 binding_count, -- cgit v1.2.3 From c37140dd89051317216bbf7a2cb08198c62535e0 Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Mon, 17 Jul 2023 18:05:10 +0300 Subject: SL-19995 FIXED The context menu is not fully displayed for the navigation bar --- indra/llui/lllineeditor.cpp | 5 +++-- indra/llui/lllineeditor.h | 7 ++++++- indra/newview/lllocationinputctrl.cpp | 4 +++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp index 940cf398c0..60dbfd68c6 100644 --- a/indra/llui/lllineeditor.cpp +++ b/indra/llui/lllineeditor.cpp @@ -164,7 +164,8 @@ LLLineEditor::LLLineEditor(const LLLineEditor::Params& p) mHighlightColor(p.highlight_color()), mPreeditBgColor(p.preedit_bg_color()), mGLFont(p.font), - mContextMenuHandle() + mContextMenuHandle(), + mShowContextMenu(true) { llassert( mMaxLengthBytes > 0 ); @@ -825,7 +826,7 @@ BOOL LLLineEditor::handleMiddleMouseDown(S32 x, S32 y, MASK mask) BOOL LLLineEditor::handleRightMouseDown(S32 x, S32 y, MASK mask) { setFocus(TRUE); - if (!LLUICtrl::handleRightMouseDown(x, y, mask)) + if (!LLUICtrl::handleRightMouseDown(x, y, mask) && getShowContextMenu()) { showContextMenu(x, y); } diff --git a/indra/llui/lllineeditor.h b/indra/llui/lllineeditor.h index ae4e05c065..f983828d2b 100644 --- a/indra/llui/lllineeditor.h +++ b/indra/llui/lllineeditor.h @@ -286,7 +286,10 @@ public: void setBgImage(LLPointer image) { mBgImage = image; } void setBgImageFocused(LLPointer image) { mBgImageFocused = image; } -private: + void setShowContextMenu(bool show) { mShowContextMenu = show; } + bool getShowContextMenu() const { return mShowContextMenu; } + + private: // private helper methods void pasteHelper(bool is_primary); @@ -405,6 +408,8 @@ protected: LLHandle mContextMenuHandle; + bool mShowContextMenu; + private: // Instances that by default point to the statics but can be overidden in XML. LLPointer mBgImage; diff --git a/indra/newview/lllocationinputctrl.cpp b/indra/newview/lllocationinputctrl.cpp index d6f3068610..9fa35e3bd9 100644 --- a/indra/newview/lllocationinputctrl.cpp +++ b/indra/newview/lllocationinputctrl.cpp @@ -391,7 +391,9 @@ LLLocationInputCtrl::LLLocationInputCtrl(const LLLocationInputCtrl::Params& p) LL_WARNS() << "Error loading navigation bar context menu" << LL_ENDL; } - getTextEntry()->setRightMouseUpCallback(boost::bind(&LLLocationInputCtrl::onTextEditorRightClicked,this,_2,_3,_4)); + //don't show default context menu + getTextEntry()->setShowContextMenu(false); + getTextEntry()->setRightMouseDownCallback(boost::bind(&LLLocationInputCtrl::onTextEditorRightClicked, this, _2, _3, _4)); updateWidgetlayout(); // Connecting signal for updating location on "Show Coordinates" setting change. -- cgit v1.2.3 From fbc4c9e31fd80a721767f0bb3c32df5bce7a149a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20N=C3=A6sbye=20Christensen?= Date: Fri, 21 Jul 2023 00:25:07 +0200 Subject: replaces deprecated (since 10.12) reference to NSAnyEventMask with the replacement NSEventMaskAny. Functionality unaltered. --- indra/llplugin/slplugin/slplugin-objc.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/llplugin/slplugin/slplugin-objc.mm b/indra/llplugin/slplugin/slplugin-objc.mm index a5ab1d95c8..68ff196eaf 100644 --- a/indra/llplugin/slplugin/slplugin-objc.mm +++ b/indra/llplugin/slplugin/slplugin-objc.mm @@ -95,7 +95,7 @@ void LLCocoaPlugin::processEvents() { // Some plugins (webkit at least) will want an event loop. This qualifies. NSEvent * event; - event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES]; + event = [NSApp nextEventMatchingMask:NSEventMaskAny untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES]; [NSApp sendEvent: event]; } -- cgit v1.2.3 From a6e84f5e9ed71c93f333733d7d4642e37fb3a50f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20N=C3=A6sbye=20Christensen?= Date: Wed, 19 Jul 2023 19:35:49 +0200 Subject: update DejaVu Fonts to 2.37, including LICENSE --- indra/newview/fonts/DejaVu-license.txt | 92 ++++++++++++++++++++++++- indra/newview/fonts/DejaVuSans-Bold.ttf | Bin 573136 -> 705684 bytes indra/newview/fonts/DejaVuSans-BoldOblique.ttf | Bin 524056 -> 643292 bytes indra/newview/fonts/DejaVuSans-Oblique.ttf | Bin 523804 -> 635416 bytes indra/newview/fonts/DejaVuSans.ttf | Bin 622280 -> 757076 bytes indra/newview/fonts/DejaVuSansMono.ttf | Bin 321524 -> 340712 bytes 6 files changed, 90 insertions(+), 2 deletions(-) diff --git a/indra/newview/fonts/DejaVu-license.txt b/indra/newview/fonts/DejaVu-license.txt index 254e2cc42a..df52c1709b 100644 --- a/indra/newview/fonts/DejaVu-license.txt +++ b/indra/newview/fonts/DejaVu-license.txt @@ -1,6 +1,7 @@ Fonts are (c) Bitstream (see below). DejaVu changes are in public domain. Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below) + Bitstream Vera Fonts Copyright ------------------------------ @@ -46,7 +47,7 @@ Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the Gnome Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot -org. +org. Arev Fonts Copyright ------------------------------ @@ -96,4 +97,91 @@ dealings in this Font Software without prior written authorization from Tavmjong Bah. For further information, contact: tavmjong @ free . fr. -$Id: LICENSE 2133 2007-11-28 02:46:28Z lechimp $ +TeX Gyre DJV Math +----------------- +Fonts are (c) Bitstream (see below). DejaVu changes are in public domain. + +Math extensions done by B. Jackowski, P. Strzelczyk and P. Pianowski +(on behalf of TeX users groups) are in public domain. + +Letters imported from Euler Fraktur from AMSfonts are (c) American +Mathematical Society (see below). +Bitstream Vera Fonts Copyright +Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera +is a trademark of Bitstream, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of the fonts accompanying this license (“Fontsâ€) and associated +documentation +files (the “Font Softwareâ€), to reproduce and distribute the Font Software, +including without limitation the rights to use, copy, merge, publish, +distribute, +and/or sell copies of the Font Software, and to permit persons to whom +the Font Software is furnished to do so, subject to the following +conditions: + +The above copyright and trademark notices and this permission notice +shall be +included in all copies of one or more of the Font Software typefaces. + +The Font Software may be modified, altered, or added to, and in particular +the designs of glyphs or characters in the Fonts may be modified and +additional +glyphs or characters may be added to the Fonts, only if the fonts are +renamed +to names not containing either the words “Bitstream†or the word “Veraâ€. + +This License becomes null and void to the extent applicable to Fonts or +Font Software +that has been modified and is distributed under the “Bitstream Vera†+names. + +The Font Software may be sold as part of a larger software package but +no copy +of one or more of the Font Software typefaces may be sold by itself. + +THE FONT SOFTWARE IS PROVIDED “AS ISâ€, WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, +TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME +FOUNDATION +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, +SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN +ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR +INABILITY TO USE +THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. +Except as contained in this notice, the names of GNOME, the GNOME +Foundation, +and Bitstream Inc., shall not be used in advertising or otherwise to promote +the sale, use or other dealings in this Font Software without prior written +authorization from the GNOME Foundation or Bitstream Inc., respectively. +For further information, contact: fonts at gnome dot org. + +AMSFonts (v. 2.2) copyright + +The PostScript Type 1 implementation of the AMSFonts produced by and +previously distributed by Blue Sky Research and Y&Y, Inc. are now freely +available for general use. This has been accomplished through the +cooperation +of a consortium of scientific publishers with Blue Sky Research and Y&Y. +Members of this consortium include: + +Elsevier Science IBM Corporation Society for Industrial and Applied +Mathematics (SIAM) Springer-Verlag American Mathematical Society (AMS) + +In order to assure the authenticity of these fonts, copyright will be +held by +the American Mathematical Society. This is not meant to restrict in any way +the legitimate use of the fonts, such as (but not limited to) electronic +distribution of documents containing these fonts, inclusion of these fonts +into other public domain or commercial font collections or computer +applications, use of the outline data to create derivative fonts and/or +faces, etc. However, the AMS does require that the AMS copyright notice be +removed from any derivative versions of the fonts which have been altered in +any way. In addition, to ensure the fidelity of TeX documents using Computer +Modern fonts, Professor Donald Knuth, creator of the Computer Modern faces, +has requested that any alterations which yield different font metrics be +given a different name. + +$Id$ diff --git a/indra/newview/fonts/DejaVuSans-Bold.ttf b/indra/newview/fonts/DejaVuSans-Bold.ttf index ec1a2ebaf2..6d65fa7dc4 100644 Binary files a/indra/newview/fonts/DejaVuSans-Bold.ttf and b/indra/newview/fonts/DejaVuSans-Bold.ttf differ diff --git a/indra/newview/fonts/DejaVuSans-BoldOblique.ttf b/indra/newview/fonts/DejaVuSans-BoldOblique.ttf index 1a5576460d..753f2d80b1 100644 Binary files a/indra/newview/fonts/DejaVuSans-BoldOblique.ttf and b/indra/newview/fonts/DejaVuSans-BoldOblique.ttf differ diff --git a/indra/newview/fonts/DejaVuSans-Oblique.ttf b/indra/newview/fonts/DejaVuSans-Oblique.ttf index becc549927..999bac7714 100644 Binary files a/indra/newview/fonts/DejaVuSans-Oblique.ttf and b/indra/newview/fonts/DejaVuSans-Oblique.ttf differ diff --git a/indra/newview/fonts/DejaVuSans.ttf b/indra/newview/fonts/DejaVuSans.ttf index c1b19d8705..e5f7eecce4 100644 Binary files a/indra/newview/fonts/DejaVuSans.ttf and b/indra/newview/fonts/DejaVuSans.ttf differ diff --git a/indra/newview/fonts/DejaVuSansMono.ttf b/indra/newview/fonts/DejaVuSansMono.ttf index 6bc854ddae..f5786022f1 100644 Binary files a/indra/newview/fonts/DejaVuSansMono.ttf and b/indra/newview/fonts/DejaVuSansMono.ttf differ -- cgit v1.2.3 From 4a19e601eadb8d635c65118c5b65e4d24b9bbaee Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Mon, 24 Jul 2023 20:50:24 +0300 Subject: SL-20054 Update contributions.txt --- doc/contributions.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/contributions.txt b/doc/contributions.txt index e764e89e35..2c587a8a96 100755 --- a/doc/contributions.txt +++ b/doc/contributions.txt @@ -924,6 +924,8 @@ LSL Scientist Lamorna Proctor Lares Carter Larry Pixel +Lars Næsbye Christensen + SL-20054 Laurent Bechir Leal Choche Lenae Munz -- cgit v1.2.3 From 37a6b6411c0ad86fecd1bf71f7874c2d74a5a121 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 25 Jul 2023 00:13:27 +0300 Subject: SL-18058 Updated profile picture not shown in Conversation floater --- indra/newview/llavatariconctrl.cpp | 5 +++++ indra/newview/llpanelprofile.cpp | 44 ++++++++++++++++++++++++++++---------- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/indra/newview/llavatariconctrl.cpp b/indra/newview/llavatariconctrl.cpp index c131dc641b..44bf698caa 100644 --- a/indra/newview/llavatariconctrl.cpp +++ b/indra/newview/llavatariconctrl.cpp @@ -247,6 +247,11 @@ void LLAvatarIconCtrl::setValue(const LLSD& value) app->addObserver(mAvatarId, this); app->sendAvatarPropertiesRequest(mAvatarId); } + else if (gAgentID == mAvatarId) + { + // Always track any changes to our own icon id + app->addObserver(mAvatarId, this); + } } } else diff --git a/indra/newview/llpanelprofile.cpp b/indra/newview/llpanelprofile.cpp index 8ac1efe8e7..643d0f4df9 100644 --- a/indra/newview/llpanelprofile.cpp +++ b/indra/newview/llpanelprofile.cpp @@ -276,9 +276,9 @@ void request_avatar_properties_coro(std::string cap_url, LLUUID agent_id) //TODO: changes take two minutes to propagate! // Add some storage that holds updated data for two minutes // for new instances to reuse the data -// Profile data is only relevant to won avatar, but notes -// are for everybody -void put_avatar_properties_coro(std::string cap_url, LLUUID agent_id, LLSD data) +// Profile data is only relevant to own avatar, but notes +// are for everybody (no onger an issue?) +void put_avatar_properties_coro(std::string cap_url, LLUUID agent_id, LLSD data, std::function callback) { LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t @@ -299,10 +299,16 @@ void put_avatar_properties_coro(std::string cap_url, LLUUID agent_id, LLSD data) if (!status) { LL_WARNS("AvatarProperties") << "Failed to put agent information " << data << " for id " << agent_id << LL_ENDL; - return; + } + else + { + LL_DEBUGS("AvatarProperties") << "Agent id: " << agent_id << " Data: " << data << " Result: " << httpResults << LL_ENDL; } - LL_DEBUGS("AvatarProperties") << "Agent id: " << agent_id << " Data: " << data << " Result: " << httpResults << LL_ENDL; + if (callback) + { + callback(status); + } } LLUUID post_profile_image(std::string cap_url, const LLSD &first_data, std::string path_to_image, LLHandle *handle) @@ -447,6 +453,13 @@ void post_profile_image_coro(std::string cap_url, EProfileImageType type, std::s } } + if (type == PROFILE_IMAGE_SL && result.notNull()) + { + LLAvatarIconIDCache::getInstance()->add(gAgentID, result); + // Should trigger callbacks in icon controls + LLAvatarPropertiesProcessor::getInstance()->sendAvatarPropertiesRequest(gAgentID); + } + // Cleanup LLFile::remove(path_to_image); delete handle; @@ -1823,7 +1836,7 @@ void LLPanelProfileSecondLife::onShowInSearchCallback() LLSD data; data["allow_publish"] = mAllowPublish; LLCoros::instance().launch("putAgentUserInfoCoro", - boost::bind(put_avatar_properties_coro, cap_url, getAvatarId(), data)); + boost::bind(put_avatar_properties_coro, cap_url, getAvatarId(), data, nullptr)); } else { @@ -1838,7 +1851,7 @@ void LLPanelProfileSecondLife::onSaveDescriptionChanges() if (!cap_url.empty()) { LLCoros::instance().launch("putAgentUserInfoCoro", - boost::bind(put_avatar_properties_coro, cap_url, getAvatarId(), LLSD().with("sl_about_text", mDescriptionText))); + boost::bind(put_avatar_properties_coro, cap_url, getAvatarId(), LLSD().with("sl_about_text", mDescriptionText), nullptr)); } else { @@ -1999,10 +2012,19 @@ void LLPanelProfileSecondLife::onCommitProfileImage(const LLUUID& id) std::string cap_url = gAgent.getRegionCapability(PROFILE_PROPERTIES_CAP); if (!cap_url.empty()) { + std::function callback = [id](bool result) + { + if (result) + { + LLAvatarIconIDCache::getInstance()->add(gAgentID, id); + // Should trigger callbacks in icon controls + LLAvatarPropertiesProcessor::getInstance()->sendAvatarPropertiesRequest(gAgentID); + } + }; LLSD params; params["sl_image_id"] = id; LLCoros::instance().launch("putAgentUserInfoCoro", - boost::bind(put_avatar_properties_coro, cap_url, getAvatarId(), params)); + boost::bind(put_avatar_properties_coro, cap_url, getAvatarId(), params, callback)); mImageId = id; if (mImageId == LLUUID::null) @@ -2353,7 +2375,7 @@ void LLPanelProfileFirstLife::onCommitPhoto(const LLUUID& id) LLSD params; params["fl_image_id"] = id; LLCoros::instance().launch("putAgentUserInfoCoro", - boost::bind(put_avatar_properties_coro, cap_url, getAvatarId(), params)); + boost::bind(put_avatar_properties_coro, cap_url, getAvatarId(), params, nullptr)); mImageId = id; if (mImageId.notNull()) @@ -2397,7 +2419,7 @@ void LLPanelProfileFirstLife::onSaveDescriptionChanges() if (!cap_url.empty()) { LLCoros::instance().launch("putAgentUserInfoCoro", - boost::bind(put_avatar_properties_coro, cap_url, getAvatarId(), LLSD().with("fl_about_text", mCurrentDescription))); + boost::bind(put_avatar_properties_coro, cap_url, getAvatarId(), LLSD().with("fl_about_text", mCurrentDescription), nullptr)); } else { @@ -2540,7 +2562,7 @@ void LLPanelProfileNotes::onSaveNotesChanges() if (!cap_url.empty()) { LLCoros::instance().launch("putAgentUserInfoCoro", - boost::bind(put_avatar_properties_coro, cap_url, getAvatarId(), LLSD().with("notes", mCurrentNotes))); + boost::bind(put_avatar_properties_coro, cap_url, getAvatarId(), LLSD().with("notes", mCurrentNotes), nullptr)); } else { -- cgit v1.2.3 From 0a014776b3361cf6b7126f50f9e7d2f2b2eb04a5 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 24 Jul 2023 17:08:51 -0400 Subject: DRTVWR-587: Pacify VS 2022 specifically. --- indra/llcommon/lleventdispatcher.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index 939e3730e1..6c7338efb9 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -478,8 +478,10 @@ private: LLSD callFail(ARGS&&... args) const { mParent->callFail(std::forward(args)...); +#if _MSC_VER < 1930 // pre VS 2022 // pacify the compiler return {}; +#endif // pre VS 2022 } }; typedef std::map > DispatchMap; -- cgit v1.2.3 From 24f9b1f6dae69a642f8446e9bba4c2586f076594 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 26 Jul 2023 00:59:09 +0300 Subject: SL-20049 Don't show selection beam when attempting to drag avatar --- indra/newview/llvoavatarself.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/indra/newview/llvoavatarself.cpp b/indra/newview/llvoavatarself.cpp index 914376f5d1..b03d32d291 100644 --- a/indra/newview/llvoavatarself.cpp +++ b/indra/newview/llvoavatarself.cpp @@ -960,7 +960,7 @@ void LLVOAvatarSelf::updateRegion(LLViewerRegion *regionp) } //-------------------------------------------------------------------- -// draw tractor beam when editing objects +// draw tractor (selection) beam when editing objects //-------------------------------------------------------------------- //virtual void LLVOAvatarSelf::idleUpdateTractorBeam() @@ -2821,12 +2821,14 @@ BOOL LLVOAvatarSelf::needsRenderBeam() LLTool *tool = LLToolMgr::getInstance()->getCurrentTool(); BOOL is_touching_or_grabbing = (tool == LLToolGrab::getInstance() && LLToolGrab::getInstance()->isEditing()); - if (LLToolGrab::getInstance()->getEditingObject() && - LLToolGrab::getInstance()->getEditingObject()->isAttachment()) - { - // don't render selection beam on hud objects - is_touching_or_grabbing = FALSE; - } + LLViewerObject* objp = LLToolGrab::getInstance()->getEditingObject(); + if (objp // might need to be "!objp ||" instead of "objp &&". + && (objp->isAttachment() || objp->isAvatar())) + { + // don't render grab tool's selection beam on hud objects, + // attachments or avatars + is_touching_or_grabbing = FALSE; + } return is_touching_or_grabbing || (getAttachmentState() & AGENT_STATE_EDITING && LLSelectMgr::getInstance()->shouldShowSelection()); } -- cgit v1.2.3 From df880791b81c5d0ebe9b65011107e867ad042ac2 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Wed, 26 Jul 2023 17:53:45 +0200 Subject: SL-18619 Eyes not rendering in Shape floater thumbnails --- indra/llappearance/llavatarappearance.cpp | 1 - indra/newview/lldrawpoolavatar.cpp | 12 ++++++------ indra/newview/llvoavatar.cpp | 5 ----- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/indra/llappearance/llavatarappearance.cpp b/indra/llappearance/llavatarappearance.cpp index 7946a3e705..18b03c1f89 100644 --- a/indra/llappearance/llavatarappearance.cpp +++ b/indra/llappearance/llavatarappearance.cpp @@ -1050,7 +1050,6 @@ BOOL LLAvatarAppearance::loadSkeletonNode () mRoot->addChild(mMeshLOD[MESH_ID_UPPER_BODY]); mRoot->addChild(mMeshLOD[MESH_ID_LOWER_BODY]); mRoot->addChild(mMeshLOD[MESH_ID_SKIRT]); - mRoot->addChild(mMeshLOD[MESH_ID_HEAD]); LLAvatarJoint *skull = (LLAvatarJoint*)mRoot->findJoint("mSkull"); if (skull) diff --git a/indra/newview/lldrawpoolavatar.cpp b/indra/newview/lldrawpoolavatar.cpp index 4ffa903cca..3160f693b2 100644 --- a/indra/newview/lldrawpoolavatar.cpp +++ b/indra/newview/lldrawpoolavatar.cpp @@ -183,8 +183,8 @@ void LLDrawPoolAvatar::beginDeferredPass(S32 pass) is_deferred_render = true; if (LLPipeline::sImpostorRender) - { //impostor pass does not have rigid or impostor rendering - pass += 2; + { //impostor pass does not have impostor rendering + ++pass; } switch (pass) @@ -210,7 +210,7 @@ void LLDrawPoolAvatar::endDeferredPass(S32 pass) if (LLPipeline::sImpostorRender) { - pass += 2; + ++pass; } switch (pass) @@ -431,7 +431,7 @@ void LLDrawPoolAvatar::render(S32 pass) LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; if (LLPipeline::sImpostorRender) { - renderAvatars(NULL, pass+2); + renderAvatars(NULL, ++pass); return; } @@ -446,7 +446,7 @@ void LLDrawPoolAvatar::beginRenderPass(S32 pass) if (LLPipeline::sImpostorRender) { //impostor render does not have impostors or rigid rendering - pass += 2; + ++pass; } switch (pass) @@ -474,7 +474,7 @@ void LLDrawPoolAvatar::endRenderPass(S32 pass) if (LLPipeline::sImpostorRender) { - pass += 2; + ++pass; } switch (pass) diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index 305c489cc8..e77530ecbd 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -5230,11 +5230,6 @@ U32 LLVOAvatar::renderRigid() { return 0; } - - if (!mIsBuilt) - { - return 0; - } bool should_alpha_mask = shouldAlphaMask(); LLGLState test(GL_ALPHA_TEST, should_alpha_mask); -- cgit v1.2.3 From 861cf0a5d0590eac2ce6486d94ff3d121f8f775b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 26 Jul 2023 12:36:09 -0400 Subject: DRTVWR-587: Move constexpr arity into lambda that uses it. VC doesn't recognize that a constexpr name doesn't need to be bound into a lambda. However, since it's knowable at compile time, it can be deduced within the innermost lambda. (cherry picked from commit 37c3daff1a565eaafee691dfb57702b6b8f024d6) --- indra/llcommon/lleventdispatcher.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index 6c7338efb9..b4a610bf2d 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -724,10 +724,6 @@ template LLEventDispatcher::invoker_function LLEventDispatcher::make_invoker(Method f, const InstanceGetter& getter) { - // function_arity includes its implicit 'this' pointer - constexpr auto arity = LL::function_arity< - typename std::remove_reference::type>::value - 1; - return [f, getter](const LLSD& args) { // always_return() immediately calls the lambda we pass, and @@ -736,6 +732,10 @@ LLEventDispatcher::make_invoker(Method f, const InstanceGetter& getter) [f, getter, args] () { + // function_arity includes its implicit 'this' pointer + constexpr auto arity = LL::function_arity< + typename std::remove_reference::type>::value - 1; + // Use bind_front() to bind the method to (a pointer to) the object // returned by getter(). It's okay to capture and bind a pointer // because this bind_front() object will last only as long as this -- cgit v1.2.3 From 35e8d44e17bb53b01ca8c73d3302d08220f5373c Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 27 Jul 2023 08:52:10 -0400 Subject: DRTVWR-587: Tweak LazyEventAPIBase::add() to mollify clang. Both the previous version and this compile and run successfully with Xcode 14.3.1, but our older TeamCity compiler chokes -- so we must iterate remotely, sigh. --- indra/llcommon/lazyeventapi.h | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/indra/llcommon/lazyeventapi.h b/indra/llcommon/lazyeventapi.h index a815b119f0..e2dec8f35b 100644 --- a/indra/llcommon/lazyeventapi.h +++ b/indra/llcommon/lazyeventapi.h @@ -71,25 +71,22 @@ namespace LL mOperations.push_back(std::make_pair(name, desc)); // Use connect_extended() so the lambda is passed its own // connection. + // We can't bind an unexpanded parameter pack into a lambda -- - // shame really. Instead, capture it as a std::tuple and then, in - // the lambda, use apply() to convert back to function args. + // shame really. Instead, capture all our args as a std::tuple and + // then, in the lambda, use apply() to pass to add_trampoline(). mParams.init.connect_extended( - [name, desc, rest = std::make_tuple(std::forward(rest)...)] + [args = std::make_tuple(name, desc, std::forward(rest)...)] (const boost::signals2::connection& conn, LLEventAPI* instance) { // we only need this connection once conn.disconnect(); - // Our add() method distinguishes name and desc because we - // capture them separately. But now, because apply() - // expects a tuple specifying ALL the arguments, expand to - // a tuple including add_trampoline() arguments: instance, - // name, desc, rest. + // apply() expects a tuple specifying ALL the arguments, + // so prepend instance. // apply() can't accept a template per se; it needs a // particular specialization. apply(&LazyEventAPIBase::add_trampoline, - std::tuple_cat(std::make_tuple(instance, name, desc), - rest)); + std::tuple_cat(std::make_tuple(instance), args)); }); } -- cgit v1.2.3 From c406fa7ae97441d1d6e0ea6727c42c8f978fabed Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 27 Jul 2023 09:49:54 -0400 Subject: DRTVWR-587: Try again to address older clang difficulties. --- indra/llcommon/lazyeventapi.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/indra/llcommon/lazyeventapi.h b/indra/llcommon/lazyeventapi.h index e2dec8f35b..8bedb6fe18 100644 --- a/indra/llcommon/lazyeventapi.h +++ b/indra/llcommon/lazyeventapi.h @@ -83,10 +83,11 @@ namespace LL conn.disconnect(); // apply() expects a tuple specifying ALL the arguments, // so prepend instance. + std::tuple full_args{ std::tuple_cat(std::make_tuple(instance), args) }; // apply() can't accept a template per se; it needs a // particular specialization. apply(&LazyEventAPIBase::add_trampoline, - std::tuple_cat(std::make_tuple(instance), args)); + full_args); }); } -- cgit v1.2.3 From edab599edaf67d37a3a2e954371128676a8cf9cc Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 27 Jul 2023 10:31:55 -0400 Subject: DRTVWR-587: Revert "Try again to address older clang difficulties." That wasn't the issue. This is a compiler bug: https://github.com/llvm/llvm-project/issues/41999 https://stackoverflow.com/q/57080425 https://bugs.llvm.org/show_bug.cgi?id=42654 This reverts commit c406fa7ae97441d1d6e0ea6727c42c8f978fabed. --- indra/llcommon/lazyeventapi.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/indra/llcommon/lazyeventapi.h b/indra/llcommon/lazyeventapi.h index 8bedb6fe18..e2dec8f35b 100644 --- a/indra/llcommon/lazyeventapi.h +++ b/indra/llcommon/lazyeventapi.h @@ -83,11 +83,10 @@ namespace LL conn.disconnect(); // apply() expects a tuple specifying ALL the arguments, // so prepend instance. - std::tuple full_args{ std::tuple_cat(std::make_tuple(instance), args) }; // apply() can't accept a template per se; it needs a // particular specialization. apply(&LazyEventAPIBase::add_trampoline, - full_args); + std::tuple_cat(std::make_tuple(instance), args)); }); } -- cgit v1.2.3 From 5000f8bd123a87ffbc321a7e3fed61221c9a4da1 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 27 Jul 2023 10:50:46 -0400 Subject: DRTVWR-587: Try to work around clang bug. --- indra/llcommon/lazyeventapi.h | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/indra/llcommon/lazyeventapi.h b/indra/llcommon/lazyeventapi.h index e2dec8f35b..cc566e35af 100644 --- a/indra/llcommon/lazyeventapi.h +++ b/indra/llcommon/lazyeventapi.h @@ -72,21 +72,24 @@ namespace LL // Use connect_extended() so the lambda is passed its own // connection. + // apply() can't accept a template per se; it needs a particular + // specialization. Specialize out here to work around a clang bug: + // https://github.com/llvm/llvm-project/issues/41999 + auto func{ &LazyEventAPIBase::add_trampoline + }; + // We can't bind an unexpanded parameter pack into a lambda -- // shame really. Instead, capture all our args as a std::tuple and // then, in the lambda, use apply() to pass to add_trampoline(). mParams.init.connect_extended( - [args = std::make_tuple(name, desc, std::forward(rest)...)] + [func, args = std::make_tuple(name, desc, std::forward(rest)...)] (const boost::signals2::connection& conn, LLEventAPI* instance) { // we only need this connection once conn.disconnect(); // apply() expects a tuple specifying ALL the arguments, // so prepend instance. - // apply() can't accept a template per se; it needs a - // particular specialization. - apply(&LazyEventAPIBase::add_trampoline, - std::tuple_cat(std::make_tuple(instance), args)); + apply(func, std::tuple_cat(std::make_tuple(instance), args)); }); } -- cgit v1.2.3 From 2159da95e0acc15daa7ab18bc17f1d5f60be71cc Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 27 Jul 2023 11:11:53 -0400 Subject: DRTVWR-587: Try harder to work around clang bug. --- indra/llcommon/lazyeventapi.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/indra/llcommon/lazyeventapi.h b/indra/llcommon/lazyeventapi.h index cc566e35af..e36831270b 100644 --- a/indra/llcommon/lazyeventapi.h +++ b/indra/llcommon/lazyeventapi.h @@ -77,12 +77,13 @@ namespace LL // https://github.com/llvm/llvm-project/issues/41999 auto func{ &LazyEventAPIBase::add_trampoline }; - // We can't bind an unexpanded parameter pack into a lambda -- // shame really. Instead, capture all our args as a std::tuple and // then, in the lambda, use apply() to pass to add_trampoline(). + auto args{ std::make_tuple(name, desc, std::forward(rest)...) }; + mParams.init.connect_extended( - [func, args = std::make_tuple(name, desc, std::forward(rest)...)] + [func, args] (const boost::signals2::connection& conn, LLEventAPI* instance) { // we only need this connection once -- cgit v1.2.3 From 1cebf95f13aecf6050cde296bce6696ba791378b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 27 Jul 2023 12:21:05 -0400 Subject: DRTVWR-587: Disable LazyEventAPI tests on TeamCity Macs. There's a limit to how much time it's worth trying to work around a compiler bug that's already been fixed in newer Xcode. --- indra/llcommon/CMakeLists.txt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index e02f69126e..0df113e2c8 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -298,7 +298,13 @@ if (LL_TESTS) LL_ADD_INTEGRATION_TEST(bitpack "" "${test_libs}") LL_ADD_INTEGRATION_TEST(classic_callback "" "${test_libs}") LL_ADD_INTEGRATION_TEST(commonmisc "" "${test_libs}") - LL_ADD_INTEGRATION_TEST(lazyeventapi "" "${test_libs}") + if (WINDOWS) + # As of 2023-07-27, lazyeventapi.h triggers a bug in older clang, + # unfortunately the version we run on our TeamCity Mac build agent. As we + # move forward, either with an updated TC agent or GitHub builds, remove + # this 'if'. + LL_ADD_INTEGRATION_TEST(lazyeventapi "" "${test_libs}") + endif () LL_ADD_INTEGRATION_TEST(llbase64 "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llcond "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lldate "" "${test_libs}") -- cgit v1.2.3 From 7fcb2bdb05ca1cd6846e6c1a8227a54b2cf0dcc2 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 27 Jul 2023 16:29:41 -0400 Subject: DRTVWR-587: Make LLSDParam simplify type when delegating. LLSDParam is the generic case, when we need to pass LLSDParam adapters to some set of function parameters whose types we don't specifically know. Its templated conversion operator notices the actual parameter type T and delegates conversion to the specific LLSDParam specialization. But when T has picked up references, e.g. somewhere along the way in the LL::apply() machinery, the compiler might not choose the desired conversion because we don't have a sufficiently specific LLSDParam specialization. LLSDParam can address that by using std::decay_t when delegating to the specific LLSDParam specialization. This removes references, const and volatile. --- indra/llcommon/llsdutil.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h index a6fd2fdac2..cd68f938ea 100644 --- a/indra/llcommon/llsdutil.h +++ b/indra/llcommon/llsdutil.h @@ -358,7 +358,7 @@ public: /// otherwise, instantiate a more specific LLSDParam to convert; that /// preserves the existing customization mechanism template - operator T() const { return LLSDParam(value_); } + operator T() const { return LLSDParam>(value_); } }; /** -- cgit v1.2.3 From 6a77d333d3eb876ccd64324c09cf63c0989164ca Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 27 Jul 2023 21:09:50 -0400 Subject: DRTVWR-587: Skip some tests that only fail with older Visual Studio --- indra/llcommon/tests/lleventdispatcher_test.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index 0f27211d9e..58469313e9 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -1200,6 +1200,9 @@ namespace tut void object::test<20>() { set_test_name("call array-style functions with right-size arrays"); +#if defined(_MSC_VER) && _MSC_VER <= 1933 + skip("This test fails on VS older than VS2022 ver 17.4"); +#endif std::vector binary; for (size_t h(0x01), i(0); i < 5; h+= 0x22, ++i) { @@ -1238,6 +1241,9 @@ namespace tut void object::test<21>() { set_test_name("verify that passing LLSD() to const char* sends NULL"); +#if defined(_MSC_VER) && _MSC_VER <= 1933 + skip("This test fails on VS older than VS2022 ver 17.4"); +#endif ensure_equals("Vars::cp init", v.cp, ""); work("methodna_map_mdft", LLSDMap("cp", LLSD())); @@ -1251,6 +1257,9 @@ namespace tut template<> template<> void object::test<22>() { +#if defined(_MSC_VER) && _MSC_VER <= 1933 + skip("This test fails on VS older than VS2022 ver 17.4"); +#endif set_test_name("call map-style functions with (full | oversized) (arrays | maps)"); const char binary[] = "\x99\x88\x77\x66\x55"; LLSD array_full(LLSDMap -- cgit v1.2.3 From f6505f5a40e4d4227083283562474b91d20767f4 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 27 Jul 2023 09:56:46 -0400 Subject: SL-20031: Update viewer-manager to 3.0-5cfc07c. This version eliminates references to the Windows wmic command, calling the WMI API instead. --- autobuild.xml | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/autobuild.xml b/autobuild.xml index 9785884a40..67963c97b6 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -1,6 +1,5 @@ - - + installables SDL @@ -2701,24 +2700,42 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive hash - 8b091b1f13348eedadf66d7d81cb6bc1 + c3edd45dadeb2afca56c1574bb996d5a4355044d + hash_algorithm + sha1 url - https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/116621/1003286/viewer_manager-3.0.580913-darwin64-580913.tar.bz2 + https://github.com/secondlife/viewer-manager/releases/download/v3.0-5cfc07c/viewer_manager-3.0-5cfc07c-darwin64-5cfc07c.tar.zst name darwin64 - windows + linux64 archive hash - 647e86470e02509b1cf89829d08dfd46 + 73d32a7271bb3a1b8c469286f07a407311997bbc + hash_algorithm + sha1 url - https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/116623/1003293/viewer_manager-3.0.580913-windows-580913.tar.bz2 + https://github.com/secondlife/viewer-manager/releases/download/v3.0-5cfc07c/viewer_manager-3.0-5cfc07c-linux64-5cfc07c.tar.zst name - windows + linux64 + + windows64 + + archive + + hash + 69f4e51d2346ca0574cb0bd477f79e384ebbc2e0 + hash_algorithm + sha1 + url + https://github.com/secondlife/viewer-manager/releases/download/v3.0-5cfc07c/viewer_manager-3.0-5cfc07c-windows64-5cfc07c.tar.zst + + name + windows64 source @@ -2726,7 +2743,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors source_type hg version - 3.0.580913 + 3.0-5cfc07c vlc-bin @@ -3400,5 +3417,4 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors autobuild version 1.3 - - + -- cgit v1.2.3 From 1f615e78b8fe490e844d5d04932302d499126cdf Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 27 Jul 2023 12:49:55 -0400 Subject: SL-20031: Update to viewer-manager 3.0-bd3aec2. This one includes the fix for SL-19756. --- autobuild.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/autobuild.xml b/autobuild.xml index 67963c97b6..1ceaed1b98 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -2700,11 +2700,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive hash - c3edd45dadeb2afca56c1574bb996d5a4355044d + 6ba629ff34c4b14a1f851de707bc35041df8b6a9 hash_algorithm sha1 url - https://github.com/secondlife/viewer-manager/releases/download/v3.0-5cfc07c/viewer_manager-3.0-5cfc07c-darwin64-5cfc07c.tar.zst + https://github.com/secondlife/viewer-manager/releases/download/v3.0-bd3aec2/viewer_manager-3.0-bd3aec2-darwin64-bd3aec2.tar.zst name darwin64 @@ -2714,11 +2714,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive hash - 73d32a7271bb3a1b8c469286f07a407311997bbc + 7e086a28db17b0c086a5460e9d62f0c6584560b3 hash_algorithm sha1 url - https://github.com/secondlife/viewer-manager/releases/download/v3.0-5cfc07c/viewer_manager-3.0-5cfc07c-linux64-5cfc07c.tar.zst + https://github.com/secondlife/viewer-manager/releases/download/v3.0-bd3aec2/viewer_manager-3.0-bd3aec2-linux64-bd3aec2.tar.zst name linux64 @@ -2728,11 +2728,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive hash - 69f4e51d2346ca0574cb0bd477f79e384ebbc2e0 + 1eab994c0c1df5b2c057878a4071a88320cec978 hash_algorithm sha1 url - https://github.com/secondlife/viewer-manager/releases/download/v3.0-5cfc07c/viewer_manager-3.0-5cfc07c-windows64-5cfc07c.tar.zst + https://github.com/secondlife/viewer-manager/releases/download/v3.0-bd3aec2/viewer_manager-3.0-bd3aec2-windows64-bd3aec2.tar.zst name windows64 @@ -2743,7 +2743,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors source_type hg version - 3.0-5cfc07c + 3.0-bd3aec2 vlc-bin -- cgit v1.2.3 From bfc9772d61cadc88b3fdf6f553c60e73c79e83ed Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 25 Jul 2023 11:18:12 -0400 Subject: DRTVWR-587: Use [[noreturn]] attribute on callFail() methods that unconditionally return. This eliminates the problem of pacifying a compiler that expects a return statement vs. a compiler that detects that callFail() unconditionally throws. Thanks, Ansariel. --- indra/llcommon/lleventdispatcher.cpp | 16 ++++++++-------- indra/llcommon/lleventdispatcher.h | 10 +++------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index 99e2e74376..da96de18f7 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -113,7 +113,7 @@ public: private: static std::string formatlist(const LLSD&); template - void callFail(ARGS&&... args) const; + [[noreturn]] void callFail(ARGS&&... args) const; // store a plain dumb back-pointer because we don't have to manage the // parent LLEventDispatcher's lifespan @@ -338,7 +338,7 @@ std::string LLEventDispatcher::LLSDArgsMapper::formatlist(const LLSD& list) } template -void LLEventDispatcher::LLSDArgsMapper::callFail(ARGS&&... args) const +[[noreturn]] void LLEventDispatcher::LLSDArgsMapper::callFail(ARGS&&... args) const { _parent->callFail (_function, std::forward(args)...); @@ -388,7 +388,7 @@ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchE std::string mismatch(llsd_matches(mRequired, event)); if (! mismatch.empty()) { - return callFail(desc, ": bad request: ", mismatch); + callFail(desc, ": bad request: ", mismatch); } // Event syntax looks good, go for it! return mFunc(event); @@ -425,7 +425,7 @@ struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::Dispatc catch (const LL::apply_error& err) { // could hit runtime errors with LL::apply() - return callFail(err.what()); + callFail(err.what()); } } }; @@ -472,11 +472,11 @@ struct LLEventDispatcher::ArrayParamsDispatchEntry: public LLEventDispatcher::Pa // array, we must have an argskey. if (argskey.empty()) { - return callFail("LLEventDispatcher has no args key"); + callFail("LLEventDispatcher has no args key"); } if ((! event.has(argskey))) { - return callFail("missing required key ", std::quoted(argskey)); + callFail("missing required key ", std::quoted(argskey)); } args = event[argskey]; } @@ -708,7 +708,7 @@ LLSD LLEventDispatcher::try_call(const std::string& key, const std::string& name template //static -void LLEventDispatcher::sCallFail(ARGS&&... args) +[[noreturn]] void LLEventDispatcher::sCallFail(ARGS&&... args) { auto error = stringize(std::forward(args)...); LL_WARNS("LLEventDispatcher") << error << LL_ENDL; @@ -716,7 +716,7 @@ void LLEventDispatcher::sCallFail(ARGS&&... args) } template -void LLEventDispatcher::callFail(ARGS&&... args) const +[[noreturn]] void LLEventDispatcher::callFail(ARGS&&... args) const { // Describe this instance in addition to the error itself. sCallFail(*this, ": ", std::forward(args)...); diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index b4a610bf2d..a82bc7a69b 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -475,13 +475,9 @@ private: virtual LLSD getMetadata() const = 0; template - LLSD callFail(ARGS&&... args) const + [[noreturn]] void callFail(ARGS&&... args) const { mParent->callFail(std::forward(args)...); -#if _MSC_VER < 1930 // pre VS 2022 - // pacify the compiler - return {}; -#endif // pre VS 2022 } }; typedef std::map > DispatchMap; @@ -584,9 +580,9 @@ private: protected: // raise specified EXCEPTION with specified stringize(ARGS) template - void callFail(ARGS&&... args) const; + [[noreturn]] void callFail(ARGS&&... args) const; template - static + [[noreturn]] static void sCallFail(ARGS&&... args); // Manage transient state, e.g. which registered callable we're attempting -- cgit v1.2.3 From 2531144643bfb9208d829cfb8ff0d594b9e4aaee Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Thu, 27 Jul 2023 11:14:15 +0200 Subject: SL-18399 'DisableCameraConstraints' debug setting is not working on RC --- indra/newview/llagentcamera.cpp | 179 ++++++++++++++++++++-------------------- 1 file changed, 91 insertions(+), 88 deletions(-) diff --git a/indra/newview/llagentcamera.cpp b/indra/newview/llagentcamera.cpp index 77131efd75..382187cd37 100644 --- a/indra/newview/llagentcamera.cpp +++ b/indra/newview/llagentcamera.cpp @@ -100,6 +100,12 @@ const F32 GROUND_TO_AIR_CAMERA_TRANSITION_START_TIME = 0.5f; const F32 OBJECT_EXTENTS_PADDING = 0.5f; +static bool isDisableCameraConstraints() +{ + static LLCachedControl sDisableCameraConstraints(gSavedSettings, "DisableCameraConstraints", false); + return sDisableCameraConstraints; +} + // The agent instance. LLAgentCamera gAgentCamera; @@ -565,9 +571,9 @@ BOOL LLAgentCamera::calcCameraMinDistance(F32 &obj_min_distance) { BOOL soft_limit = FALSE; // is the bounding box to be treated literally (volumes) or as an approximation (avatars) - if (!mFocusObject || mFocusObject->isDead() || + if (!mFocusObject || mFocusObject->isDead() || mFocusObject->isMesh() || - gSavedSettings.getBOOL("DisableCameraConstraints")) + isDisableCameraConstraints()) { obj_min_distance = 0.f; return TRUE; @@ -737,39 +743,44 @@ F32 LLAgentCamera::getCameraZoomFraction(bool get_third_person) // already [0,1] return mHUDTargetZoom; } - else if (get_third_person || (mFocusOnAvatar && cameraThirdPerson())) + + if (isDisableCameraConstraints()) + { + return mCameraZoomFraction; + } + + if (get_third_person || (mFocusOnAvatar && cameraThirdPerson())) { return clamp_rescale(mCameraZoomFraction, MIN_ZOOM_FRACTION, MAX_ZOOM_FRACTION, 1.f, 0.f); } - else if (cameraCustomizeAvatar()) + + if (cameraCustomizeAvatar()) { F32 distance = (F32)mCameraFocusOffsetTarget.magVec(); return clamp_rescale(distance, APPEARANCE_MIN_ZOOM, APPEARANCE_MAX_ZOOM, 1.f, 0.f ); } - else - { - F32 min_zoom; - F32 max_zoom = getCameraMaxZoomDistance(); - F32 distance = (F32)mCameraFocusOffsetTarget.magVec(); - if (mFocusObject.notNull()) + F32 min_zoom; + F32 max_zoom = getCameraMaxZoomDistance(); + + F32 distance = (F32)mCameraFocusOffsetTarget.magVec(); + if (mFocusObject.notNull()) + { + if (mFocusObject->isAvatar()) { - if (mFocusObject->isAvatar()) - { - min_zoom = AVATAR_MIN_ZOOM; - } - else - { - min_zoom = OBJECT_MIN_ZOOM; - } + min_zoom = AVATAR_MIN_ZOOM; } else { - min_zoom = LAND_MIN_ZOOM; + min_zoom = OBJECT_MIN_ZOOM; } - - return clamp_rescale(distance, min_zoom, max_zoom, 1.f, 0.f); } + else + { + min_zoom = LAND_MIN_ZOOM; + } + + return clamp_rescale(distance, min_zoom, max_zoom, 1.f, 0.f); } void LLAgentCamera::setCameraZoomFraction(F32 fraction) @@ -782,6 +793,10 @@ void LLAgentCamera::setCameraZoomFraction(F32 fraction) { mHUDTargetZoom = fraction; } + else if (isDisableCameraConstraints()) + { + mCameraZoomFraction = fraction; + } else if (mFocusOnAvatar && cameraThirdPerson()) { mCameraZoomFraction = rescale(fraction, 0.f, 1.f, MAX_ZOOM_FRACTION, MIN_ZOOM_FRACTION); @@ -816,6 +831,7 @@ void LLAgentCamera::setCameraZoomFraction(F32 fraction) camera_offset_dir.normalize(); mCameraFocusOffsetTarget = camera_offset_dir * rescale(fraction, 0.f, 1.f, max_zoom, min_zoom); } + startCameraAnimation(); } @@ -920,49 +936,40 @@ void LLAgentCamera::cameraZoomIn(const F32 fraction) return; } - - LLVector3d camera_offset_unit(mCameraFocusOffsetTarget); - F32 min_zoom = LAND_MIN_ZOOM; + LLVector3d camera_offset_unit(mCameraFocusOffsetTarget); F32 current_distance = (F32)camera_offset_unit.normalize(); F32 new_distance = current_distance * fraction; - // Don't move through focus point - if (mFocusObject) + // Unless camera is unlocked + if (!isDisableCameraConstraints()) { - LLVector3 camera_offset_dir((F32)camera_offset_unit.mdV[VX], (F32)camera_offset_unit.mdV[VY], (F32)camera_offset_unit.mdV[VZ]); + F32 min_zoom = LAND_MIN_ZOOM; - if (mFocusObject->isAvatar()) - { - calcCameraMinDistance(min_zoom); - } - else + // Don't move through focus point + if (mFocusObject) { - min_zoom = OBJECT_MIN_ZOOM; - } - } + LLVector3 camera_offset_dir((F32)camera_offset_unit.mdV[VX], (F32)camera_offset_unit.mdV[VY], (F32)camera_offset_unit.mdV[VZ]); - new_distance = llmax(new_distance, min_zoom); - - F32 max_distance = getCameraMaxZoomDistance(); + if (mFocusObject->isAvatar()) + { + calcCameraMinDistance(min_zoom); + } + else + { + min_zoom = OBJECT_MIN_ZOOM; + } + } - max_distance = llmin(max_distance, current_distance * 4.f); //Scaled max relative to current distance. MAINT-3154 + new_distance = llmax(new_distance, min_zoom); - if (new_distance > max_distance) - { - new_distance = max_distance; + F32 max_distance = getCameraMaxZoomDistance(); + max_distance = llmin(max_distance, current_distance * 4.f); //Scaled max relative to current distance. MAINT-3154 + new_distance = llmin(new_distance, max_distance); - /* - // Unless camera is unlocked - if (!LLViewerCamera::sDisableCameraConstraints) + if (cameraCustomizeAvatar()) { - return; + new_distance = llclamp(new_distance, APPEARANCE_MIN_ZOOM, APPEARANCE_MAX_ZOOM); } - */ - } - - if(cameraCustomizeAvatar()) - { - new_distance = llclamp( new_distance, APPEARANCE_MIN_ZOOM, APPEARANCE_MAX_ZOOM ); } mCameraFocusOffsetTarget = new_distance * camera_offset_unit; @@ -985,53 +992,52 @@ void LLAgentCamera::cameraOrbitIn(const F32 meters) changeCameraToMouselook(FALSE); } - mCameraZoomFraction = llclamp(mCameraZoomFraction, MIN_ZOOM_FRACTION, MAX_ZOOM_FRACTION); + if (!isDisableCameraConstraints()) + { + mCameraZoomFraction = llclamp(mCameraZoomFraction, MIN_ZOOM_FRACTION, MAX_ZOOM_FRACTION); + } } else { LLVector3d camera_offset_unit(mCameraFocusOffsetTarget); F32 current_distance = (F32)camera_offset_unit.normalize(); F32 new_distance = current_distance - meters; - F32 min_zoom = LAND_MIN_ZOOM; - - // Don't move through focus point - if (mFocusObject.notNull()) + + // Unless camera is unlocked + if (!isDisableCameraConstraints()) { - if (mFocusObject->isAvatar()) - { - min_zoom = AVATAR_MIN_ZOOM; - } - else + F32 min_zoom = LAND_MIN_ZOOM; + + // Don't move through focus point + if (mFocusObject.notNull()) { - min_zoom = OBJECT_MIN_ZOOM; + if (mFocusObject->isAvatar()) + { + min_zoom = AVATAR_MIN_ZOOM; + } + else + { + min_zoom = OBJECT_MIN_ZOOM; + } } - } - new_distance = llmax(new_distance, min_zoom); + new_distance = llmax(new_distance, min_zoom); - F32 max_distance = getCameraMaxZoomDistance(); + F32 max_distance = getCameraMaxZoomDistance(); + new_distance = llmin(new_distance, max_distance); - if (new_distance > max_distance) - { - // Unless camera is unlocked - if (!gSavedSettings.getBOOL("DisableCameraConstraints")) + if (CAMERA_MODE_CUSTOMIZE_AVATAR == getCameraMode()) { - return; + new_distance = llclamp(new_distance, APPEARANCE_MIN_ZOOM, APPEARANCE_MAX_ZOOM); } } - if( CAMERA_MODE_CUSTOMIZE_AVATAR == getCameraMode() ) - { - new_distance = llclamp( new_distance, APPEARANCE_MIN_ZOOM, APPEARANCE_MAX_ZOOM ); - } - // Compute new camera offset mCameraFocusOffsetTarget = new_distance * camera_offset_unit; cameraZoomIn(1.f); } } - //----------------------------------------------------------------------------- // cameraPanIn() //----------------------------------------------------------------------------- @@ -1823,7 +1829,8 @@ LLVector3d LLAgentCamera::calcCameraPositionTargetGlobal(BOOL *hit_limit) local_camera_offset = gAgent.getFrameAgent().rotateToAbsolute( local_camera_offset ); } - if (!mCameraCollidePlane.isExactlyZero() && (!isAgentAvatarValid() || !gAgentAvatarp->isSitting())) + if (!isDisableCameraConstraints() && !mCameraCollidePlane.isExactlyZero() && + (!isAgentAvatarValid() || !gAgentAvatarp->isSitting())) { LLVector3 plane_normal; plane_normal.setVec(mCameraCollidePlane.mV); @@ -1942,7 +1949,7 @@ LLVector3d LLAgentCamera::calcCameraPositionTargetGlobal(BOOL *hit_limit) camera_position_global = focusPosGlobal + mCameraFocusOffset; } - if (!gSavedSettings.getBOOL("DisableCameraConstraints") && !gAgent.isGodlike()) + if (!isDisableCameraConstraints() && !gAgent.isGodlike()) { LLViewerRegion* regionp = LLWorld::getInstance()->getRegionFromPosGlobal(camera_position_global); bool constrain = true; @@ -2106,17 +2113,13 @@ F32 LLAgentCamera::getCameraMinOffGround() { return 0.f; } - else + + if (isDisableCameraConstraints()) { - if (gSavedSettings.getBOOL("DisableCameraConstraints")) - { - return -1000.f; - } - else - { - return 0.5f; - } + return -1000.f; } + + return 0.5f; } -- cgit v1.2.3 From ef9cb58a9ea6e37f5ca8c61c258560b2ee72c8d8 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 1 Aug 2023 01:07:51 +0300 Subject: SL-18623 LLAvatarRenderInfoAccountant coroutine crash For unknown reason allocations of these coroutines often crash on client machines. 1. Limit quantity of coros running in parallel by reducing retries and wait time 2. Print out more diagnostic info --- indra/llcommon/llcoros.cpp | 1 + indra/newview/llavatarrenderinfoaccountant.cpp | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp index 70d8dfc8b9..fe2adf051b 100644 --- a/indra/llcommon/llcoros.cpp +++ b/indra/llcommon/llcoros.cpp @@ -282,6 +282,7 @@ std::string LLCoros::launch(const std::string& prefix, const callable_t& callabl catch (std::bad_alloc&) { // Out of memory on stack allocation? + printActiveCoroutines(); LL_ERRS("LLCoros") << "Bad memory allocation in LLCoros::launch(" << prefix << ")!" << LL_ENDL; } diff --git a/indra/newview/llavatarrenderinfoaccountant.cpp b/indra/newview/llavatarrenderinfoaccountant.cpp index 293c9d60a1..a6c9a41fa4 100644 --- a/indra/newview/llavatarrenderinfoaccountant.cpp +++ b/indra/newview/llavatarrenderinfoaccountant.cpp @@ -79,8 +79,14 @@ void LLAvatarRenderInfoAccountant::avatarRenderInfoGetCoro(std::string url, U64 LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("AvatarRenderInfoAccountant", httpPolicy)); LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); - LLSD result = httpAdapter->getAndSuspend(httpRequest, url); + // Going to request each 15 seconds either way, so don't wait + // too long and don't repeat + httpOpts->setRetries(0); + httpOpts->setTimeout(SECS_BETWEEN_REGION_REQUEST); + + LLSD result = httpAdapter->getAndSuspend(httpRequest, url, httpOpts); LLWorld *world_inst = LLWorld::getInstance(); if (!world_inst) @@ -190,6 +196,11 @@ void LLAvatarRenderInfoAccountant::avatarRenderInfoReportCoro(std::string url, U LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("AvatarRenderInfoAccountant", httpPolicy)); LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); + + // Going to request each 60+ seconds, timeout is 30s. + // Don't repeat too often, will be sending newer data soon + httpOpts->setRetries(1); LLWorld *world_inst = LLWorld::getInstance(); if (!world_inst) @@ -256,7 +267,7 @@ void LLAvatarRenderInfoAccountant::avatarRenderInfoReportCoro(std::string url, U regionp = NULL; world_inst = NULL; - LLSD result = httpAdapter->postAndSuspend(httpRequest, url, report); + LLSD result = httpAdapter->postAndSuspend(httpRequest, url, report, httpOpts); world_inst = LLWorld::getInstance(); if (!world_inst) -- cgit v1.2.3 From 8ed2fb94641393b391425987a108057753729d41 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Tue, 1 Aug 2023 00:53:23 +0200 Subject: SL-19528 Remove PERMISSION_DEBIT warning from experience that is Grid and Privileged --- indra/newview/llviewermessage.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index e7456f77bb..ce29238667 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -5815,6 +5815,22 @@ void process_script_question(LLMessageSystem *msg, void **user_data) args["OBJECTNAME"] = object_name; args["NAME"] = clean_owner_name; S32 known_questions = 0; + + // SL-19346, SL-19528 - No DEBIT warning for GRID & PRIVILEGED + if (experienceid.notNull()) + { + const LLSD& experience = LLExperienceCache::instance().get(experienceid); + if (!experience.isUndefined()) + { + S32 properties = experience[LLExperienceCache::PROPERTIES].asInteger(); + if ((properties | LLExperienceCache::PROPERTY_GRID) && + (properties | LLExperienceCache::PROPERTY_PRIVILEGED)) + { + questions ^= SCRIPT_PERMISSIONS[SCRIPT_PERMISSION_DEBIT].permbit; + } + } + } + bool has_not_only_debit = questions ^ SCRIPT_PERMISSIONS[SCRIPT_PERMISSION_DEBIT].permbit; // check the received permission flags against each permission BOOST_FOREACH(script_perm_t script_perm, SCRIPT_PERMISSIONS) -- cgit v1.2.3 From c704a5fb629da056e8aa6e3c69befaaad04cfa9f Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Wed, 2 Aug 2023 14:58:57 +0300 Subject: DRTVWR-582 Update llca to build 202305160024.0 --- autobuild.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/autobuild.xml b/autobuild.xml index 1ceaed1b98..dff1944e63 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -1628,18 +1628,18 @@ archive hash - d6e7ab8483c348f223fd24028e27a52f + 3a45d167f60ed26dc1f8467b93ec64676cc7fe34 hash_algorithm - md5 + sha1 url - https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/93933/844890/llca-202202010217.567974-common-567974.tar.bz2 + https://github.com/secondlife/llca/releases/download/v202305121625.0-efdf149/llca-202305160024.0-common-efdf149.tar.zst name common version - 202202010217.567974 + 202305160024.0 llphysicsextensions_source -- cgit v1.2.3 From ab000b4dce5c7fdad2607ece2547a01967dd529c Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Thu, 3 Aug 2023 01:13:29 +0300 Subject: SL-16671 Add "Bellisseria" to the viewer dictionary --- autobuild.xml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/autobuild.xml b/autobuild.xml index dff1944e63..38d1ec1e94 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -461,16 +461,18 @@ archive hash - d778c6a3475bc35ee8b9615dfc38b4a9 + 8dd8a7e799362d5c45f2385805f3e857aca4edff + hash_algorithm + sha1 url - http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/55025/511964/dictionaries-1.538984-common-538984.tar.bz2 + https://github.com/secondlife/3p-dictionaries/releases/download/v1.0.2-dev2.d98581c/dictionaries-1.d98581c-common-d98581c.tar.zst name common version - 1.538984 + 1.d98581c dullahan -- cgit v1.2.3 From bbb1f32cfcb91d36a765770935a7b378d5a9d7f9 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Thu, 3 Aug 2023 22:35:00 +0300 Subject: SL-20121 Fixed the crash in LLViewerInput::getKeyBindingAsString() --- indra/newview/llviewerinput.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/llviewerinput.cpp b/indra/newview/llviewerinput.cpp index 3f8e6f57f6..aac0cba92e 100644 --- a/indra/newview/llviewerinput.cpp +++ b/indra/newview/llviewerinput.cpp @@ -1028,7 +1028,7 @@ bool LLViewerInput::modeFromString(const std::string& string, S32 *mode) } S32 val = atoi(string.c_str()); - if (val >= 0 || val < MODE_COUNT) + if (val >= 0 && val < MODE_COUNT) { *mode = val; return true; -- cgit v1.2.3 From 06989cb1451d18f137714a375856244c500ddf3c Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Mon, 14 Aug 2023 23:25:40 +0200 Subject: SL-19528 Remove PERMISSION_DEBIT warning (revert recent change) --- indra/newview/llviewermessage.cpp | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index ce29238667..e7456f77bb 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -5815,22 +5815,6 @@ void process_script_question(LLMessageSystem *msg, void **user_data) args["OBJECTNAME"] = object_name; args["NAME"] = clean_owner_name; S32 known_questions = 0; - - // SL-19346, SL-19528 - No DEBIT warning for GRID & PRIVILEGED - if (experienceid.notNull()) - { - const LLSD& experience = LLExperienceCache::instance().get(experienceid); - if (!experience.isUndefined()) - { - S32 properties = experience[LLExperienceCache::PROPERTIES].asInteger(); - if ((properties | LLExperienceCache::PROPERTY_GRID) && - (properties | LLExperienceCache::PROPERTY_PRIVILEGED)) - { - questions ^= SCRIPT_PERMISSIONS[SCRIPT_PERMISSION_DEBIT].permbit; - } - } - } - bool has_not_only_debit = questions ^ SCRIPT_PERMISSIONS[SCRIPT_PERMISSION_DEBIT].permbit; // check the received permission flags against each permission BOOST_FOREACH(script_perm_t script_perm, SCRIPT_PERMISSIONS) -- cgit v1.2.3 From f40b85c4f495b9079991c41a26b76d397a6168ae Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 23 Aug 2023 19:21:30 +0300 Subject: SL-20184 Crash when opening Marketplace Listings Window Looks like a bit of code from Inventory Extensions viewer leaked into release. This was supposed to prevent 'folder does not exist' spam as Inventory Extensions does not create folders this way, instead it blocked folder creation. --- indra/newview/llinventorymodel.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp index b4727de77f..cd0ce88920 100644 --- a/indra/newview/llinventorymodel.cpp +++ b/indra/newview/llinventorymodel.cpp @@ -596,9 +596,7 @@ const LLUUID LLInventoryModel::findCategoryUUIDForTypeInRoot( if(rv.isNull() && root_id.notNull() - && create_folder - && preferred_type != LLFolderType::FT_MARKETPLACE_LISTINGS - && preferred_type != LLFolderType::FT_OUTBOX) + && create_folder) { if (isInventoryUsable()) -- cgit v1.2.3 From 2d27a6ac31009fd5ad828b6d769393f1cbfd3694 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Thu, 24 Aug 2023 00:12:24 +0300 Subject: DRTVWR-587 Post-merge build fix --- indra/newview/llfloaterpreference.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/llfloaterpreference.cpp b/indra/newview/llfloaterpreference.cpp index e59c2ee2cd..47a78c049a 100644 --- a/indra/newview/llfloaterpreference.cpp +++ b/indra/newview/llfloaterpreference.cpp @@ -264,7 +264,7 @@ public: } bool handle(const LLSD& params, const LLSD& query_map, - LLMediaCtrl* web) + const std::string& grid, LLMediaCtrl* web) { if (params.size() < 1) return false; -- cgit v1.2.3 From dc384d6046be1d66237c563d18c9bc3462a5b723 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Mon, 28 Aug 2023 20:21:11 +0300 Subject: SL-20130 Update viewer-manager to build 3.0-83fb46a --- autobuild.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/autobuild.xml b/autobuild.xml index 38d1ec1e94..761d98b3a8 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -2702,11 +2702,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive hash - 6ba629ff34c4b14a1f851de707bc35041df8b6a9 + 6db9317ed29ccb00c2f75758f4aa2d4cd8784f46 hash_algorithm sha1 url - https://github.com/secondlife/viewer-manager/releases/download/v3.0-bd3aec2/viewer_manager-3.0-bd3aec2-darwin64-bd3aec2.tar.zst + https://github.com/secondlife/viewer-manager/releases/download/v3.0-83fb46a/viewer_manager-3.0-83fb46a-darwin64-83fb46a.tar.zst name darwin64 @@ -2716,11 +2716,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive hash - 7e086a28db17b0c086a5460e9d62f0c6584560b3 + 426d3b30681b747056027ca21136ae56b188cd47 hash_algorithm sha1 url - https://github.com/secondlife/viewer-manager/releases/download/v3.0-bd3aec2/viewer_manager-3.0-bd3aec2-linux64-bd3aec2.tar.zst + https://github.com/secondlife/viewer-manager/releases/download/v3.0-83fb46a/viewer_manager-3.0-83fb46a-linux64-83fb46a.tar.zst name linux64 @@ -2745,7 +2745,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors source_type hg version - 3.0-bd3aec2 + 3.0-83fb46a vlc-bin -- cgit v1.2.3 From 147def0fdb333ca41054c714be9c207f1397e4a8 Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Mon, 28 Aug 2023 20:40:30 +0300 Subject: SL-20130 Update viewer-manager (win64) to build 3.0-83fb46a --- autobuild.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/autobuild.xml b/autobuild.xml index 761d98b3a8..161f4c149d 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -2730,11 +2730,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive hash - 1eab994c0c1df5b2c057878a4071a88320cec978 + fe8e9ee00f53f11a7417a7bcf1db069e25dace5d hash_algorithm sha1 url - https://github.com/secondlife/viewer-manager/releases/download/v3.0-bd3aec2/viewer_manager-3.0-bd3aec2-windows64-bd3aec2.tar.zst + https://github.com/secondlife/viewer-manager/releases/download/v3.0-83fb46a/viewer_manager-3.0-83fb46a-windows64-83fb46a.tar.zst name windows64 -- cgit v1.2.3 From 3d2da2b2c09fc637c2eaccac1607e3480bede145 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Mon, 28 Aug 2023 21:48:32 +0300 Subject: SL-20214 Crash at LLControlAVBridge::updateSpatialExtents --- indra/newview/llvovolume.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp index 4ddba872f1..269c5666cc 100644 --- a/indra/newview/llvovolume.cpp +++ b/indra/newview/llvovolume.cpp @@ -5114,8 +5114,6 @@ void LLControlAVBridge::updateSpatialExtents() { LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE - LLControlAvatar* controlAvatar = getVObj()->getControlAvatar(); - LLSpatialGroup* root = (LLSpatialGroup*)mOctree->getListener(0); bool rootWasDirty = root->isDirty(); @@ -5126,7 +5124,11 @@ void LLControlAVBridge::updateSpatialExtents() // disappear when root goes off-screen" // // Expand extents to include Control Avatar placed outside of the bounds - if (controlAvatar && (rootWasDirty || controlAvatar->mPlaying)) + LLControlAvatar* controlAvatar = getVObj() ? getVObj()->getControlAvatar() : NULL; + if (controlAvatar + && controlAvatar->mDrawable + && controlAvatar->mDrawable->getEntry() + && (rootWasDirty || controlAvatar->mPlaying)) { root->expandExtents(controlAvatar->mDrawable->getSpatialExtents(), *mDrawable->getXform()); } -- cgit v1.2.3 From 35c0f1a7697731ed481fe41807e3f04eb5ec5045 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 29 Aug 2023 01:57:44 +0300 Subject: SL-20205 Clipping of label "Water" --- indra/llui/lltextbase.cpp | 8 +++++++- .../default/xui/en/panel_region_environment.xml | 20 ++++++++++---------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 8732a7ce45..e0697cb454 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -1550,7 +1550,13 @@ S32 LLTextBase::getLeftOffset(S32 width) case LLFontGL::HCENTER: return mHPad + llmax(0, (mVisibleTextRect.getWidth() - width - mHPad) / 2); case LLFontGL::RIGHT: - return mVisibleTextRect.getWidth() - width; + { + // Font's rendering rounds string size, if value gets rounded + // down last symbol might not have enough space to render, + // compensate by adding an extra pixel as padding + const S32 right_padding = 1; + return llmax(mHPad, mVisibleTextRect.getWidth() - width - right_padding); + } default: return mHPad; } diff --git a/indra/newview/skins/default/xui/en/panel_region_environment.xml b/indra/newview/skins/default/xui/en/panel_region_environment.xml index edf1e1efd4..0b3639f779 100644 --- a/indra/newview/skins/default/xui/en/panel_region_environment.xml +++ b/indra/newview/skins/default/xui/en/panel_region_environment.xml @@ -259,7 +259,7 @@ follows="left|top" layout="topleft" height="24" - width="52" + width="53" left_delta="2" top_pad="1" halign="right" @@ -271,7 +271,7 @@ follows="left|top" enabled="false" top_delta="3" - left_pad="21" + left_pad="20" height="20" layout="topleft" name="edt_invname_alt1" @@ -305,7 +305,7 @@ follows="left|top" layout="topleft" height="24" - width="52" + width="53" left_delta="2" top_pad="1" halign="right" @@ -317,7 +317,7 @@ follows="left|top" enabled="false" top_delta="3" - left_pad="21" + left_pad="20" height="20" layout="topleft" name="edt_invname_alt2" @@ -351,7 +351,7 @@ follows="left|top" layout="topleft" height="25" - width="52" + width="53" left_delta="2" top_pad="1" halign="right" @@ -363,7 +363,7 @@ follows="left|top" enabled="false" top_delta="3" - left_pad="21" + left_pad="20" height="20" layout="topleft" name="edt_invname_alt3" @@ -460,7 +460,7 @@ follows="left|top" layout="topleft" height="12" - width="52" + width="53" left_delta="2" top_pad="2" halign="right" @@ -477,7 +477,7 @@ mouse_opaque="false" visible="true" top_delta="-3" - left_pad="2"/> + left_pad="1"/> + left_pad="1"/> Date: Sat, 26 Aug 2023 21:04:05 +0200 Subject: Fix builds using OpenAL --- indra/llaudio/llaudioengine_openal.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/llaudio/llaudioengine_openal.h b/indra/llaudio/llaudioengine_openal.h index a3cab97cd2..562c96c794 100644 --- a/indra/llaudio/llaudioengine_openal.h +++ b/indra/llaudio/llaudioengine_openal.h @@ -42,6 +42,7 @@ class LLAudioEngine_OpenAL : public LLAudioEngine virtual bool init(void *user_data, const std::string &app_title); virtual std::string getDriverName(bool verbose); + virtual LLStreamingAudioInterface* createDefaultStreamingAudioImpl() const { return nullptr; } virtual void allocateListener(); virtual void shutdown(); @@ -56,7 +57,6 @@ class LLAudioEngine_OpenAL : public LLAudioEngine /*virtual*/ void updateWind(LLVector3 direction, F32 camera_altitude); private: - void * windDSP(void *newbuffer, int length); typedef S16 WIND_SAMPLE_T; LLWindGen *mWindGen; S16 *mWindBuf; -- cgit v1.2.3 From fa29f4f0b1f0680ada3b95e67c0728a2f6a529b1 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 29 Aug 2023 20:44:27 +0300 Subject: SL-20224 Update contributions Fix builds using OpenAL --- doc/contributions.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/contributions.txt b/doc/contributions.txt index 3ff18ced7f..a3ccba8445 100755 --- a/doc/contributions.txt +++ b/doc/contributions.txt @@ -238,6 +238,7 @@ Ansariel Hiller SL-15398 SL-18432 SL-4126 + SL-20224 Aralara Rajal Arare Chantilly CHUIBUG-191 -- cgit v1.2.3 From 512d450fe31eee5772339c27333e0d56f4bfbd45 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Mon, 28 Aug 2023 18:47:20 +0200 Subject: Fix CMake CMP0148 deprecation warning introduced with CMake 3.27.4 --- indra/cmake/Python.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/cmake/Python.cmake b/indra/cmake/Python.cmake index f9259f6c2b..2167fb7864 100644 --- a/indra/cmake/Python.cmake +++ b/indra/cmake/Python.cmake @@ -40,7 +40,7 @@ elseif (WINDOWS) ${regpaths} ${pymaybe} ) - include(FindPythonInterp) + find_package(Python3 COMPONENTS Interpreter) else() find_program(python python3) -- cgit v1.2.3 From 25388312cf28f8b30934ac3885783a96a3b2ed69 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Tue, 5 Sep 2023 13:19:10 +0200 Subject: SL-20206 Underwater visuals problematic when camera is swung below Z=0 --- indra/newview/llagentcamera.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/indra/newview/llagentcamera.cpp b/indra/newview/llagentcamera.cpp index 098ff8fea9..6e8784b726 100644 --- a/indra/newview/llagentcamera.cpp +++ b/indra/newview/llagentcamera.cpp @@ -1996,15 +1996,21 @@ LLVector3d LLAgentCamera::calcCameraPositionTargetGlobal(BOOL *hit_limit) // Don't let camera go underground F32 camera_min_off_ground = getCameraMinOffGround(); - camera_land_height = LLWorld::getInstance()->resolveLandHeightGlobal(camera_position_global); - - if (camera_position_global.mdV[VZ] < camera_land_height + camera_min_off_ground) + F32 minZ = llmax(F_ALMOST_ZERO, camera_land_height + camera_min_off_ground); + if (camera_position_global.mdV[VZ] < minZ) { - camera_position_global.mdV[VZ] = camera_land_height + camera_min_off_ground; + camera_position_global.mdV[VZ] = minZ; isConstrained = TRUE; } + // Don't let camera go abovesky + F32 maxZ = LLWorld::getInstance()->getRegionMaxHeight() * 0.25 - F_ALMOST_ZERO; + if (camera_position_global.mdV[VZ] > maxZ) + { + camera_position_global.mdV[VZ] = maxZ; + isConstrained = TRUE; + } if (hit_limit) { -- cgit v1.2.3 From 7efe86cdde1fd8821d73e330c52145cdb786999b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 5 Sep 2023 16:42:48 -0400 Subject: DRTVWR-587: Simplify Python.cmake by omitting find_package(Python3). Elsewhere in CMake land, we reference PYTHONINTERP_FOUND and PYTHON_EXECUTABLE, both of which are explicitly set by Python.cmake. We don't seem to need the find_package(Python3 COMPONENTS Interpreter) call. Given that we take some pains to be careful about which Windows Python interpreter we find, this eliminates a wildcard. --- indra/cmake/Python.cmake | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/indra/cmake/Python.cmake b/indra/cmake/Python.cmake index 2167fb7864..676cf31f2f 100644 --- a/indra/cmake/Python.cmake +++ b/indra/cmake/Python.cmake @@ -40,18 +40,15 @@ elseif (WINDOWS) ${regpaths} ${pymaybe} ) - find_package(Python3 COMPONENTS Interpreter) else() find_program(python python3) - - if (python) - set(PYTHONINTERP_FOUND ON) - endif (python) endif (DEFINED ENV{PYTHON}) -if (NOT python) +if (python) + set(PYTHONINTERP_FOUND ON) +else() message(FATAL_ERROR "No Python interpreter found") -endif (NOT python) +endif (python) set(PYTHON_EXECUTABLE "${python}" CACHE FILEPATH "Python interpreter for builds") mark_as_advanced(PYTHON_EXECUTABLE) -- cgit v1.2.3 From 1aa27d4d13602baaf568d6269f4842881a78bdbc Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Thu, 7 Sep 2023 23:06:00 +0300 Subject: SL-20273 IM, Offer Teleport, Map and Pay SLurls from external browser were blocked --- indra/newview/llfloaterworldmap.cpp | 53 ++++++++++++++++++++++++----- indra/newview/llgroupactions.cpp | 3 +- indra/newview/llpanelprofile.cpp | 3 +- indra/newview/llpanelprofileclassifieds.cpp | 3 +- indra/newview/llpanelprofilepicks.cpp | 3 +- indra/newview/llviewerfloaterreg.cpp | 6 +++- indra/newview/llviewerinventory.cpp | 26 ++++++++++++-- 7 files changed, 81 insertions(+), 16 deletions(-) diff --git a/indra/newview/llfloaterworldmap.cpp b/indra/newview/llfloaterworldmap.cpp index 8f3ec8af05..c8559fc9d3 100755 --- a/indra/newview/llfloaterworldmap.cpp +++ b/indra/newview/llfloaterworldmap.cpp @@ -121,10 +121,27 @@ static const F32 ZOOM_MAX = 128.f; class LLWorldMapHandler : public LLCommandHandler { public: - // requires trusted browser to trigger - LLWorldMapHandler() : LLCommandHandler("worldmap", UNTRUSTED_CLICK_ONLY ) { } - - bool handle(const LLSD& params, + LLWorldMapHandler() : LLCommandHandler("worldmap", UNTRUSTED_THROTTLE) + { + } + + virtual bool canHandleUntrusted( + const LLSD& params, + const LLSD& query_map, + LLMediaCtrl* web, + const std::string& nav_type) + { + if (nav_type == NAV_TYPE_CLICKED + || nav_type == NAV_TYPE_EXTERNAL) + { + // NAV_TYPE_EXTERNAL will be throttled + return true; + } + + return false; + } + + bool handle(const LLSD& params, const LLSD& query_map, const std::string& grid, LLMediaCtrl* web) @@ -160,12 +177,32 @@ LLWorldMapHandler gWorldMapHandler; class LLMapTrackAvatarHandler : public LLCommandHandler { public: - // requires trusted browser to trigger - LLMapTrackAvatarHandler() : LLCommandHandler("maptrackavatar", UNTRUSTED_CLICK_ONLY) + LLMapTrackAvatarHandler() : LLCommandHandler("maptrackavatar", UNTRUSTED_THROTTLE) { } - - bool handle(const LLSD& params, + + virtual bool canHandleUntrusted( + const LLSD& params, + const LLSD& query_map, + LLMediaCtrl* web, + const std::string& nav_type) + { + if (params.size() < 1) + { + return true; // don't block, will fail later + } + + if (nav_type == NAV_TYPE_CLICKED + || nav_type == NAV_TYPE_EXTERNAL) + { + // NAV_TYPE_EXTERNAL will be throttled + return true; + } + + return false; + } + + bool handle(const LLSD& params, const LLSD& query_map, const std::string& grid, LLMediaCtrl* web) diff --git a/indra/newview/llgroupactions.cpp b/indra/newview/llgroupactions.cpp index 043316ccca..380e49c320 100644 --- a/indra/newview/llgroupactions.cpp +++ b/indra/newview/llgroupactions.cpp @@ -65,7 +65,8 @@ public: return true; // don't block, will fail later } - if (nav_type == NAV_TYPE_CLICKED) + if (nav_type == NAV_TYPE_CLICKED + || nav_type == NAV_TYPE_EXTERNAL) { return true; } diff --git a/indra/newview/llpanelprofile.cpp b/indra/newview/llpanelprofile.cpp index 0c2ec017b5..4b8d92c7fd 100644 --- a/indra/newview/llpanelprofile.cpp +++ b/indra/newview/llpanelprofile.cpp @@ -511,7 +511,8 @@ public: return true; // don't block, will fail later } - if (nav_type == NAV_TYPE_CLICKED) + if (nav_type == NAV_TYPE_CLICKED + || nav_type == NAV_TYPE_EXTERNAL) { return true; } diff --git a/indra/newview/llpanelprofileclassifieds.cpp b/indra/newview/llpanelprofileclassifieds.cpp index dec6cfd83b..3fbaad4dee 100644 --- a/indra/newview/llpanelprofileclassifieds.cpp +++ b/indra/newview/llpanelprofileclassifieds.cpp @@ -93,7 +93,8 @@ public: return true; // don't block, will fail later } - if (nav_type == NAV_TYPE_CLICKED) + if (nav_type == NAV_TYPE_CLICKED + || nav_type == NAV_TYPE_EXTERNAL) { return true; } diff --git a/indra/newview/llpanelprofilepicks.cpp b/indra/newview/llpanelprofilepicks.cpp index 0535036cb0..ff3f654d0e 100644 --- a/indra/newview/llpanelprofilepicks.cpp +++ b/indra/newview/llpanelprofilepicks.cpp @@ -74,7 +74,8 @@ public: return true; // don't block, will fail later } - if (nav_type == NAV_TYPE_CLICKED) + if (nav_type == NAV_TYPE_CLICKED + || nav_type == NAV_TYPE_EXTERNAL) { return true; } diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index 0f2fe1e1cd..e7a799c754 100644 --- a/indra/newview/llviewerfloaterreg.cpp +++ b/indra/newview/llviewerfloaterreg.cpp @@ -192,7 +192,11 @@ public: std::string fl_name = params[0].asString(); - if (nav_type == NAV_TYPE_CLICKED) + // External browsers explicitly ask user about opening links + // so treat "external" same as "clicked" in this case, + // despite it being treated as untrusted. + if (nav_type == NAV_TYPE_CLICKED + || nav_type == NAV_TYPE_EXTERNAL) { const std::list blacklist_clicked = { "camera_presets", diff --git a/indra/newview/llviewerinventory.cpp b/indra/newview/llviewerinventory.cpp index 33d162ed29..2d33cf6a7f 100644 --- a/indra/newview/llviewerinventory.cpp +++ b/indra/newview/llviewerinventory.cpp @@ -231,9 +231,29 @@ LLLocalizedInventoryItemsDictionary::LLLocalizedInventoryItemsDictionary() class LLInventoryHandler : public LLCommandHandler { public: - // requires trusted browser to trigger - LLInventoryHandler() : LLCommandHandler("inventory", UNTRUSTED_CLICK_ONLY) { } - + LLInventoryHandler() : LLCommandHandler("inventory", UNTRUSTED_THROTTLE) { } + + virtual bool canHandleUntrusted( + const LLSD& params, + const LLSD& query_map, + LLMediaCtrl* web, + const std::string& nav_type) + { + if (params.size() < 1) + { + return true; // don't block, will fail later + } + + if (nav_type == NAV_TYPE_CLICKED + || nav_type == NAV_TYPE_EXTERNAL) + { + // NAV_TYPE_EXTERNAL will be throttled + return true; + } + + return false; + } + bool handle(const LLSD& params, const LLSD& query_map, const std::string& grid, -- cgit v1.2.3 From 566ace0d5694922638806d36b331479e37cb8f2b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 8 Sep 2023 14:37:09 -0400 Subject: DRTVWR-587: Adopt Ansariel's Python.cmake Windows simplification. --- indra/cmake/Python.cmake | 38 +++++--------------------------------- 1 file changed, 5 insertions(+), 33 deletions(-) diff --git a/indra/cmake/Python.cmake b/indra/cmake/Python.cmake index 676cf31f2f..316f49ba59 100644 --- a/indra/cmake/Python.cmake +++ b/indra/cmake/Python.cmake @@ -7,39 +7,11 @@ if (DEFINED ENV{PYTHON}) set(python "$ENV{PYTHON}") set(PYTHONINTERP_FOUND ON) elseif (WINDOWS) - # On Windows, explicitly avoid Cygwin Python. - - # if the user has their own version of Python installed, prefer that - foreach(hive HKEY_CURRENT_USER HKEY_LOCAL_MACHINE) - # prefer more recent Python versions to older ones, if multiple versions - # are installed - foreach(pyver 3.11 3.10 3.9 3.8 3.7) - list(APPEND regpaths "[${hive}\\SOFTWARE\\Python\\PythonCore\\${pyver}\\InstallPath]") - endforeach() - endforeach() - - # TODO: This logic has the disadvantage that if you have multiple versions - # of Python installed, the selected path won't necessarily be the newest - - # e.g. this GLOB will prefer Python310 to Python311. But since pymaybe is - # checked AFTER the registry entries, this will only surface as a problem if - # no installed Python appears in the registry. - file(GLOB pymaybe - "$ENV{PROGRAMFILES}/Python*" -## "$ENV{PROGRAMFILES(X86)}/Python*" - # The Windows environment variable is in fact as shown above, but CMake - # disallows querying an environment variable containing parentheses - - # thanks, Windows. Fudge by just appending " (x86)" to $PROGRAMFILES and - # hoping for the best. - "$ENV{PROGRAMFILES} (x86)/Python*" - "c:/Python*") - - find_program(python - NAMES python3.exe python.exe - NO_DEFAULT_PATH # added so that cmake does not find cygwin python - PATHS - ${regpaths} - ${pymaybe} - ) + if (DEFINED ENV{VIRTUAL_ENV}) + set(Python3_FIND_VIRTUALENV "ONLY") + endif() + find_package(Python3 COMPONENTS Interpreter) + set(python ${Python3_EXECUTABLE}) else() find_program(python python3) endif (DEFINED ENV{PYTHON}) -- cgit v1.2.3 From 51f904e4a7d7e5d04be09991f859bd1b26f0d7ae Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Mon, 25 Sep 2023 12:29:51 +0200 Subject: SL-20244 On-screen animesh characters that start pelvis offset animations disappear when root goes off-screen --- indra/newview/lldrawable.cpp | 13 ------------- indra/newview/llspatialpartition.cpp | 37 ++++++++++++++++++++++++++++++++++++ indra/newview/llspatialpartition.h | 5 ++++- indra/newview/llvovolume.cpp | 24 ----------------------- 4 files changed, 41 insertions(+), 38 deletions(-) diff --git a/indra/newview/lldrawable.cpp b/indra/newview/lldrawable.cpp index bb4174d3b6..d8be4c3bd5 100644 --- a/indra/newview/lldrawable.cpp +++ b/indra/newview/lldrawable.cpp @@ -763,19 +763,6 @@ void LLDrawable::movePartition() if (part) { part->move(this, getSpatialGroup()); - - // SL-18251 "On-screen animesh characters using pelvis offset animations - // disappear when root goes off-screen" - // - // Update extents of the root node when Control Avatar changes it's bounds - if (mRenderType == LLPipeline::RENDER_TYPE_CONTROL_AV && isRoot()) - { - LLControlAvatar* controlAvatar = dynamic_cast(getVObj().get()); - if (controlAvatar && controlAvatar->mControlAVBridge) - { - ((LLSpatialGroup*)controlAvatar->mControlAVBridge->mOctree->getListener(0))->setState(LLViewerOctreeGroup::DIRTY); - } - } } } diff --git a/indra/newview/llspatialpartition.cpp b/indra/newview/llspatialpartition.cpp index 17c834326c..ae97ce604a 100644 --- a/indra/newview/llspatialpartition.cpp +++ b/indra/newview/llspatialpartition.cpp @@ -847,6 +847,43 @@ void LLSpatialGroup::handleChildAddition(const OctreeNode* parent, OctreeNode* c assert_states_valid(this); } +//virtual +void LLSpatialGroup::rebound() +{ + if (!isDirty()) + return; + + super::rebound(); + + if (mSpatialPartition->mDrawableType == LLPipeline::RENDER_TYPE_CONTROL_AV) + { + llassert(mSpatialPartition->mPartitionType == LLViewerRegion::PARTITION_CONTROL_AV); + + LLSpatialBridge* bridge = getSpatialPartition()->asBridge(); + if (bridge && + bridge->mDrawable && + bridge->mDrawable->getVObj() && + bridge->mDrawable->getVObj()->isRoot()) + { + LLControlAvatar* controlAvatar = bridge->mDrawable->getVObj()->getControlAvatar(); + if (controlAvatar && + controlAvatar->mDrawable && + controlAvatar->mControlAVBridge) + { + llassert(controlAvatar->mControlAVBridge->mOctree); + + LLSpatialGroup* root = (LLSpatialGroup*)controlAvatar->mControlAVBridge->mOctree->getListener(0); + if (this == root) + { + const LLVector4a* addingExtents = controlAvatar->mDrawable->getSpatialExtents(); + const LLXformMatrix* currentTransform = bridge->mDrawable->getXform(); + expandExtents(addingExtents, *currentTransform); + } + } + } + } +} + void LLSpatialGroup::destroyGL(bool keep_occlusion) { setState(LLSpatialGroup::GEOM_DIRTY | LLSpatialGroup::IMAGE_DIRTY); diff --git a/indra/newview/llspatialpartition.h b/indra/newview/llspatialpartition.h index cdb591083c..b5ac867b3e 100644 --- a/indra/newview/llspatialpartition.h +++ b/indra/newview/llspatialpartition.h @@ -205,6 +205,7 @@ public: LL_ALIGN_PREFIX(64) class LLSpatialGroup : public LLOcclusionCullingGroup { + using super = LLOcclusionCullingGroup; friend class LLSpatialPartition; friend class LLOctreeStateCheck; public: @@ -322,6 +323,9 @@ public: virtual void handleDestruction(const TreeNode* node); virtual void handleChildAddition(const OctreeNode* parent, OctreeNode* child); + // LLViewerOctreeGroup + virtual void rebound(); + public: LL_ALIGN_16(LLVector4a mViewAngle); LL_ALIGN_16(LLVector4a mLastUpdateViewAngle); @@ -703,7 +707,6 @@ class LLControlAVBridge : public LLVolumeBridge using super = LLVolumeBridge; public: LLControlAVBridge(LLDrawable* drawablep, LLViewerRegion* regionp); - virtual void updateSpatialExtents(); }; class LLHUDBridge : public LLVolumeBridge diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp index 269c5666cc..c7054102fd 100644 --- a/indra/newview/llvovolume.cpp +++ b/indra/newview/llvovolume.cpp @@ -5110,30 +5110,6 @@ LLControlAVBridge::LLControlAVBridge(LLDrawable* drawablep, LLViewerRegion* regi mPartitionType = LLViewerRegion::PARTITION_CONTROL_AV; } -void LLControlAVBridge::updateSpatialExtents() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE - - LLSpatialGroup* root = (LLSpatialGroup*)mOctree->getListener(0); - - bool rootWasDirty = root->isDirty(); - - super::updateSpatialExtents(); // root becomes non-dirty here - - // SL-18251 "On-screen animesh characters using pelvis offset animations - // disappear when root goes off-screen" - // - // Expand extents to include Control Avatar placed outside of the bounds - LLControlAvatar* controlAvatar = getVObj() ? getVObj()->getControlAvatar() : NULL; - if (controlAvatar - && controlAvatar->mDrawable - && controlAvatar->mDrawable->getEntry() - && (rootWasDirty || controlAvatar->mPlaying)) - { - root->expandExtents(controlAvatar->mDrawable->getSpatialExtents(), *mDrawable->getXform()); - } -} - bool can_batch_texture(LLFace* facep) { if (facep->getTextureEntry()->getBumpmap()) -- cgit v1.2.3 From aee8f570e14684e6143a18541ba0bc9a5060fa98 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Fri, 29 Sep 2023 21:27:37 +0300 Subject: SL-20368 Request outfits using 'links' instead of getting items one by one --- indra/newview/llappearancemgr.cpp | 32 ++++++++++++++--- indra/newview/llappearancemgr.h | 2 +- indra/newview/llinventoryobserver.cpp | 65 +++++++++++++++++------------------ 3 files changed, 60 insertions(+), 39 deletions(-) diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp index 8010b84c20..667ae999bb 100644 --- a/indra/newview/llappearancemgr.cpp +++ b/indra/newview/llappearancemgr.cpp @@ -2776,9 +2776,33 @@ void LLAppearanceMgr::wearInventoryCategory(LLInventoryCategory* category, bool else { selfStartPhase("wear_inventory_category_fetch"); - callAfterCategoryFetch(category->getUUID(),boost::bind(&LLAppearanceMgr::wearCategoryFinal, - &LLAppearanceMgr::instance(), - category->getUUID(), copy, append)); + if (AISAPI::isAvailable() && category->getPreferredType() == LLFolderType::FT_OUTFIT) + { + // for reliability just fetch it whole, linked items included + LLViewerInventoryCategory* cat = (LLViewerInventoryCategory*)category; + LLUUID cat_id = category->getUUID(); + cat->setFetching(LLViewerInventoryCategory::FETCH_RECURSIVE); + AISAPI::FetchCategoryLinks(cat_id, + [cat_id, copy, append](const LLUUID& id) + { + LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id); + if (cat) + { + cat->setFetching(LLViewerInventoryCategory::FETCH_NONE); + } + if (id.isNull()) + { + LL_WARNS() << "failed to fetch category, attempting to wear as is " << cat_id << LL_ENDL; + } + LLAppearanceMgr::instance().wearCategoryFinal(cat_id, copy, append); + }); + } + else + { + callAfterCategoryFetch(category->getUUID(), boost::bind(&LLAppearanceMgr::wearCategoryFinal, + &LLAppearanceMgr::instance(), + category->getUUID(), copy, append)); + } } } @@ -2787,7 +2811,7 @@ S32 LLAppearanceMgr::getActiveCopyOperations() const return LLCallAfterInventoryCopyMgr::getInstanceCount(); } -void LLAppearanceMgr::wearCategoryFinal(LLUUID& cat_id, bool copy_items, bool append) +void LLAppearanceMgr::wearCategoryFinal(const LLUUID& cat_id, bool copy_items, bool append) { LL_INFOS("Avatar") << self_av_string() << "starting" << LL_ENDL; diff --git a/indra/newview/llappearancemgr.h b/indra/newview/llappearancemgr.h index 43839e47a6..da29ceee3a 100644 --- a/indra/newview/llappearancemgr.h +++ b/indra/newview/llappearancemgr.h @@ -58,7 +58,7 @@ public: void updateCOF(const LLUUID& category, bool append = false); void wearInventoryCategory(LLInventoryCategory* category, bool copy, bool append); void wearInventoryCategoryOnAvatar(LLInventoryCategory* category, bool append); - void wearCategoryFinal(LLUUID& cat_id, bool copy_items, bool append); + void wearCategoryFinal(const LLUUID& cat_id, bool copy_items, bool append); void wearOutfitByName(const std::string& name); void changeOutfit(bool proceed, const LLUUID& category, bool append); void replaceCurrentOutfit(const LLUUID& new_outfit); diff --git a/indra/newview/llinventoryobserver.cpp b/indra/newview/llinventoryobserver.cpp index 281a8bc789..b6c2e954a4 100644 --- a/indra/newview/llinventoryobserver.cpp +++ b/indra/newview/llinventoryobserver.cpp @@ -337,52 +337,49 @@ void LLInventoryFetchItemsObserver::startFetch() { for (requests_by_folders_t::value_type &folder : requests) { - if (folder.second.size() > MAX_INDIVIDUAL_ITEM_REQUESTS) + LLViewerInventoryCategory* cat = gInventory.getCategory(folder.first); + if (cat) { - // requesting one by one will take a while - // do whole folder - LLInventoryModelBackgroundFetch::getInstance()->scheduleFolderFetch(folder.first, true); - } - else - { - LLViewerInventoryCategory* cat = gInventory.getCategory(folder.first); - if (cat) + if (cat->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN) { - if (cat->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN) - { - // start fetching whole folder since it's not ready either way - cat->fetch(); - } - else if (cat->getViewerDescendentCount() <= folder.second.size() - || cat->getDescendentCount() <= folder.second.size()) - { - // Start fetching whole folder since we need all items - LLInventoryModelBackgroundFetch::getInstance()->scheduleFolderFetch(folder.first, true); + // start fetching whole folder since it's not ready either way + cat->fetch(); + } + else if (folder.second.size() > MAX_INDIVIDUAL_ITEM_REQUESTS) + { + // requesting one by one will take a while + // do whole folder + LLInventoryModelBackgroundFetch::getInstance()->scheduleFolderFetch(folder.first, true); + } + else if (cat->getViewerDescendentCount() <= folder.second.size() + || cat->getDescendentCount() <= folder.second.size()) + { + // Start fetching whole folder since we need all items + LLInventoryModelBackgroundFetch::getInstance()->scheduleFolderFetch(folder.first, true); - } - else - { - // get items one by one - for (LLUUID &item_id : folder.second) - { - LLInventoryModelBackgroundFetch::getInstance()->scheduleItemFetch(item_id); - } - } } else { - // Isn't supposed to happen? We should have all folders - // and if item exists, folder is supposed to exist as well. - llassert(false); - LL_WARNS("Inventory") << "Missing folder: " << folder.first << " fetching items individually" << LL_ENDL; - // get items one by one - for (LLUUID &item_id : folder.second) + for (LLUUID& item_id : folder.second) { LLInventoryModelBackgroundFetch::getInstance()->scheduleItemFetch(item_id); } } } + else + { + // Isn't supposed to happen? We should have all folders + // and if item exists, folder is supposed to exist as well. + llassert(false); + LL_WARNS("Inventory") << "Missing folder: " << folder.first << " fetching items individually" << LL_ENDL; + + // get items one by one + for (LLUUID& item_id : folder.second) + { + LLInventoryModelBackgroundFetch::getInstance()->scheduleItemFetch(item_id); + } + } } } else -- cgit v1.2.3 From 55ebcf3fd469d65d15b956b2e0996f0f2257b885 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Sat, 30 Sep 2023 00:56:21 +0300 Subject: SL-20347 Fix structure of localized panel_main_inventory --- .../skins/default/xui/da/panel_main_inventory.xml | 29 ++++++-------- .../skins/default/xui/de/panel_main_inventory.xml | 43 ++++++++++---------- .../skins/default/xui/en/panel_main_inventory.xml | 6 ++- .../skins/default/xui/es/panel_main_inventory.xml | 43 ++++++++++---------- .../skins/default/xui/fr/panel_main_inventory.xml | 43 ++++++++++---------- .../skins/default/xui/it/panel_main_inventory.xml | 43 ++++++++++---------- .../skins/default/xui/ja/panel_main_inventory.xml | 46 ++++++++++------------ .../skins/default/xui/pl/panel_main_inventory.xml | 29 ++++++-------- .../skins/default/xui/pt/panel_main_inventory.xml | 43 ++++++++++---------- .../skins/default/xui/ru/panel_main_inventory.xml | 43 ++++++++++---------- .../skins/default/xui/tr/panel_main_inventory.xml | 43 ++++++++++---------- .../skins/default/xui/zh/panel_main_inventory.xml | 43 ++++++++++---------- 12 files changed, 211 insertions(+), 243 deletions(-) diff --git a/indra/newview/skins/default/xui/da/panel_main_inventory.xml b/indra/newview/skins/default/xui/da/panel_main_inventory.xml index d6406939c1..37a17f4bd6 100644 --- a/indra/newview/skins/default/xui/da/panel_main_inventory.xml +++ b/indra/newview/skins/default/xui/da/panel_main_inventory.xml @@ -9,20 +9,17 @@ Genstande: - - - - - - - -