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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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 (limited to 'indra') 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 (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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 8d7cde22c31a59d0503230334b1d68e858364c9a Mon Sep 17 00:00:00 2001 From: Signal Linden Date: Tue, 11 Oct 2022 15:10:04 -0700 Subject: Replace llbase with llsd module --- indra/llcommon/tests/llleap_test.cpp | 2 +- indra/llcommon/tests/llsdserialize_test.cpp | 2 +- indra/llcorehttp/tests/test_llcorehttp_peer.py | 3 +-- indra/llmessage/tests/test_llsdmessage_peer.py | 3 +-- indra/newview/viewer_manifest.py | 2 +- 5 files changed, 5 insertions(+), 7 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index 7ee36a9ea6..60005fc6a9 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -109,7 +109,7 @@ namespace tut "import os\n" "import sys\n" "\n" - "from llbase import llsd\n" + "import llsd\n" "\n" "class ProtocolError(Exception):\n" " def __init__(self, msg, data):\n" diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index c246f5ee56..be7ec12094 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -1706,7 +1706,7 @@ namespace tut // scanner. import_llsd("import os.path\n" "import sys\n" - "from llbase import llsd\n") + "import llsd\n") {} ~TestPythonCompatible() {} diff --git a/indra/llcorehttp/tests/test_llcorehttp_peer.py b/indra/llcorehttp/tests/test_llcorehttp_peer.py index 778de90962..b9992538ba 100755 --- a/indra/llcorehttp/tests/test_llcorehttp_peer.py +++ b/indra/llcorehttp/tests/test_llcorehttp_peer.py @@ -38,8 +38,7 @@ from io import StringIO from http.server import HTTPServer, BaseHTTPRequestHandler -from llbase.fastest_elementtree import parse as xml_parse -from llbase import llsd +import llsd # we're in llcorehttp/tests ; testrunner.py is found in llmessage/tests sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, diff --git a/indra/llmessage/tests/test_llsdmessage_peer.py b/indra/llmessage/tests/test_llsdmessage_peer.py index 5ba0749e31..ff8f40a144 100755 --- a/indra/llmessage/tests/test_llsdmessage_peer.py +++ b/indra/llmessage/tests/test_llsdmessage_peer.py @@ -33,8 +33,7 @@ import os import sys from http.server import HTTPServer, BaseHTTPRequestHandler -from llbase.fastest_elementtree import parse as xml_parse -from llbase import llsd +import llsd from testrunner import freeport, run, debug, VERBOSE import time diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 3327ecfb56..30b0d23a93 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -49,7 +49,7 @@ viewer_dir = os.path.dirname(__file__) # indra.util.llmanifest under their system Python! sys.path.insert(0, os.path.join(viewer_dir, os.pardir, "lib", "python")) from indra.util.llmanifest import LLManifest, main, path_ancestors, CHANNEL_VENDOR_BASE, RELEASE_CHANNEL, ManifestError, MissingError -from llbase import llsd +import llsd class ViewerManifest(LLManifest): def is_packaging_viewer(self): -- cgit v1.2.3 From 6112e92b61c334d3c0656bacdea92690cea1a76b Mon Sep 17 00:00:00 2001 From: Signal Linden Date: Wed, 12 Oct 2022 11:49:47 -0700 Subject: Upload installer, build Release, use large runner --- .../installers/windows/installer_template.nsi | 1 - indra/newview/viewer_manifest.py | 34 ++++++---------------- 2 files changed, 9 insertions(+), 26 deletions(-) (limited to 'indra') diff --git a/indra/newview/installers/windows/installer_template.nsi b/indra/newview/installers/windows/installer_template.nsi index 7513908cb4..f7908b059c 100644 --- a/indra/newview/installers/windows/installer_template.nsi +++ b/indra/newview/installers/windows/installer_template.nsi @@ -26,7 +26,6 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Compiler flags ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -Unicode true SetOverwrite on # Overwrite files SetCompress auto # Compress if saves space SetCompressor /solid lzma # Compress whole installer as one block diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 30b0d23a93..eec8e84a2d 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -28,7 +28,6 @@ $/LicenseInfo$ """ import errno import glob -import itertools import json import os import os.path @@ -36,12 +35,9 @@ import plistlib import random import re import shutil -import stat import subprocess import sys -import tarfile import time -import zipfile viewer_dir = os.path.dirname(__file__) # Add indra/lib/python to our path so we don't have to muck with PYTHONPATH. @@ -789,27 +785,15 @@ class WindowsManifest(ViewerManifest): # Check two paths, one for Program Files, and one for Program Files (x86). # Yay 64bit windows. - for ProgramFiles in 'ProgramFiles', 'ProgramFiles(x86)': - NSIS_path = os.path.expandvars(r'${%s}\NSIS\makensis.exe' % ProgramFiles) - if os.path.exists(NSIS_path): - break - installer_created=False - nsis_attempts=3 - nsis_retry_wait=15 - for attempt in range(nsis_attempts): - try: - self.run_command([NSIS_path, '/V2', self.dst_path_of(tempfile)]) - except ManifestError as err: - if attempt+1 < nsis_attempts: - print("nsis failed, waiting %d seconds before retrying" % nsis_retry_wait, file=sys.stderr) - time.sleep(nsis_retry_wait) - nsis_retry_wait*=2 - else: - # NSIS worked! Done! - break - else: - print("Maximum nsis attempts exceeded; giving up", file=sys.stderr) - raise + nsis_path = "makensis.exe" + for program_files in '${programfiles}', '${programfiles(x86)}': + for nesis_path in 'NSIS', 'NSIS\\Unicode': + possible_path = os.path.expandvars(f"{program_files}\\{nesis_path}\\makensis.exe") + if os.path.exists(possible_path): + nsis_path = possible_path + break + + self.run_command([possible_path, '/V2', self.dst_path_of(tempfile)]) self.sign(installer_file) self.created_path(self.dst_path_of(installer_file)) -- cgit v1.2.3 From efb987f595d6749d42e3148bf0e071f5bafce450 Mon Sep 17 00:00:00 2001 From: Signal Linden Date: Wed, 12 Oct 2022 12:59:12 -0700 Subject: Run on windows-2022-large --- indra/cmake/00-Common.cmake | 9 --------- 1 file changed, 9 deletions(-) (limited to 'indra') diff --git a/indra/cmake/00-Common.cmake b/indra/cmake/00-Common.cmake index c848d00710..bf5a107c73 100644 --- a/indra/cmake/00-Common.cmake +++ b/indra/cmake/00-Common.cmake @@ -55,15 +55,6 @@ if (WINDOWS) # http://www.cmake.org/pipermail/cmake/2009-September/032143.html string(REPLACE "/Zm1000" " " CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) - # Without PreferredToolArchitecture=x64, as of 2020-06-26 the 32-bit - # compiler on our TeamCity build hosts has started running out of virtual - # memory for the precompiled header file. - # CP changed to only append the flag for 32bit builds - on 64bit builds, - # locally at least, the build output is spammed with 1000s of 'D9002' - # warnings about this switch being ignored. - if( ADDRESS_SIZE EQUAL 32 ) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /p:PreferredToolArchitecture=x64") - endif() # zlib has assembly-language object files incompatible with SAFESEH add_link_options(/LARGEADDRESSAWARE /SAFESEH:NO -- 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(-) (limited to 'indra') 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 53c89d9723b816d06516ae59d7e0f0d12e477ec9 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 2 Jun 2023 16:30:28 -0400 Subject: SL-18837: Don't try to copy long, specific libnghttp2.14.19.0.dylib. The package doesn't include that any more. --- indra/cmake/Copy3rdPartyLibs.cmake | 1 - 1 file changed, 1 deletion(-) (limited to 'indra') diff --git a/indra/cmake/Copy3rdPartyLibs.cmake b/indra/cmake/Copy3rdPartyLibs.cmake index d43cc30706..e4d06d6ba2 100644 --- a/indra/cmake/Copy3rdPartyLibs.cmake +++ b/indra/cmake/Copy3rdPartyLibs.cmake @@ -172,7 +172,6 @@ elseif(DARWIN) libndofdev.dylib libnghttp2.dylib libnghttp2.14.dylib - libnghttp2.14.19.0.dylib liburiparser.dylib liburiparser.1.dylib liburiparser.1.0.27.dylib -- cgit v1.2.3 From 6d2d0c8ee59eed641937b19cf19a1ff08762cbfa Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 2 Jun 2023 17:27:57 -0400 Subject: SL-18837: Boost.Bind _1, _2 placeholders are no longer global. This was a longstanding complaint: that Boost shouldn't dump the (somewhat mysterious) _1, _2 et al. names into the global namespace. Recent Boost has fixed that, requiring 'using namespace boost::placeholders;' if you want to use them unqualified. --- indra/llinventory/llsettingsbase.cpp | 4 ++++ indra/llinventory/llsettingssky.cpp | 4 ++++ indra/llinventory/llsettingswater.cpp | 4 ++++ 3 files changed, 12 insertions(+) (limited to 'indra') diff --git a/indra/llinventory/llsettingsbase.cpp b/indra/llinventory/llsettingsbase.cpp index 936b166409..ba338dbbee 100644 --- a/indra/llinventory/llsettingsbase.cpp +++ b/indra/llinventory/llsettingsbase.cpp @@ -31,6 +31,10 @@ #include #include "llsdserialize.h" +#include + +// allow unqualified _1, _2 et al. to mean boost::bind placeholders +using namespace boost::placeholders; //========================================================================= namespace diff --git a/indra/llinventory/llsettingssky.cpp b/indra/llinventory/llsettingssky.cpp index a129f0a6f0..0244b2585e 100644 --- a/indra/llinventory/llsettingssky.cpp +++ b/indra/llinventory/llsettingssky.cpp @@ -31,6 +31,10 @@ #include "lltrace.h" #include "llfasttimer.h" #include "v3colorutil.h" +#include + +// allow unqualified _1, _2 et al. to mean boost::bind placeholders +using namespace boost::placeholders; //========================================================================= namespace diff --git a/indra/llinventory/llsettingswater.cpp b/indra/llinventory/llsettingswater.cpp index d732032a6c..bc53a46255 100644 --- a/indra/llinventory/llsettingswater.cpp +++ b/indra/llinventory/llsettingswater.cpp @@ -32,6 +32,10 @@ #include "llfasttimer.h" #include "v3colorutil.h" #include "indra_constants.h" +#include + +// allow unqualified _1, _2 et al. to mean boost::bind placeholders +using namespace boost::placeholders; const std::string LLSettingsWater::SETTING_BLUR_MULTIPLIER("blur_multiplier"); const std::string LLSettingsWater::SETTING_FOG_COLOR("water_fog_color"); -- cgit v1.2.3 From 42c69448da85b06caf9edabb4dd3f5f4ae926ea7 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 2 Jun 2023 17:32:31 -0400 Subject: SL-18837: Don't include a version-specific Boost.Regex header. Let the umbrella header make that decision. --- indra/newview/lllogchat.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/newview/lllogchat.cpp b/indra/newview/lllogchat.cpp index ba82ff0b0f..8c03292361 100644 --- a/indra/newview/lllogchat.cpp +++ b/indra/newview/lllogchat.cpp @@ -41,7 +41,7 @@ #include #include -#include +#include #include #if LL_MSVC -- cgit v1.2.3 From a4a68c4f5e2e411ba636d25e6b07129ca5de2bb9 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 5 Jun 2023 10:39:34 -0400 Subject: SL-18837: We no longer build Windows apr-iconv -- don't reference. With VS 2022 on Windows GitHub Actions runners, we can't build apr_suite at all with the upstream .sln / .vcxproj files, so we had to switch to "experimental" CMake support. However there's no CMakeLists.txt file for apr-iconv, so the Windows package omits that library. --- indra/cmake/APR.cmake | 2 -- indra/cmake/Copy3rdPartyLibs.cmake | 1 - 2 files changed, 3 deletions(-) (limited to 'indra') diff --git a/indra/cmake/APR.cmake b/indra/cmake/APR.cmake index 8a0939c92c..21139319c3 100644 --- a/indra/cmake/APR.cmake +++ b/indra/cmake/APR.cmake @@ -16,7 +16,6 @@ if (WINDOWS) endif (LLCOMMON_LINK_SHARED) target_link_libraries( ll::apr INTERFACE ${ARCH_PREBUILT_DIRS_RELEASE}/${APR_selector}apr-1.lib - ${ARCH_PREBUILT_DIRS_RELEASE}/${APR_selector}apriconv-1.lib ${ARCH_PREBUILT_DIRS_RELEASE}/${APR_selector}aprutil-1.lib ) elseif (DARWIN) @@ -37,7 +36,6 @@ else (WINDOWS) target_link_libraries( ll::apr INTERFACE apr-1 aprutil-1 - iconv uuid rt ) diff --git a/indra/cmake/Copy3rdPartyLibs.cmake b/indra/cmake/Copy3rdPartyLibs.cmake index e4d06d6ba2..a3db02372d 100644 --- a/indra/cmake/Copy3rdPartyLibs.cmake +++ b/indra/cmake/Copy3rdPartyLibs.cmake @@ -57,7 +57,6 @@ if(WINDOWS) openjp2.dll libapr-1.dll libaprutil-1.dll - libapriconv-1.dll nghttp2.dll libhunspell.dll uriparser.dll -- cgit v1.2.3 From 524f3b2af08c4e2a856a70d4505391c6f56d76df Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 5 Jun 2023 10:43:16 -0400 Subject: SL-18837: #include in several sources that need it. It seems we're no longer implicitly inheriting from some other [set of] header file[s]. Where we use std::array, bring it in explicitly. --- indra/llaudio/llaudioengine.h | 1 + indra/newview/llenvironment.h | 2 ++ indra/newview/llimview.cpp | 2 ++ 3 files changed, 5 insertions(+) (limited to 'indra') diff --git a/indra/llaudio/llaudioengine.h b/indra/llaudio/llaudioengine.h index 0fe8b3d756..cf694cfaf4 100755 --- a/indra/llaudio/llaudioengine.h +++ b/indra/llaudio/llaudioengine.h @@ -30,6 +30,7 @@ #include #include +#include #include "v3math.h" #include "v3dmath.h" diff --git a/indra/newview/llenvironment.h b/indra/newview/llenvironment.h index 64fd170e43..1c8a68ae02 100644 --- a/indra/newview/llenvironment.h +++ b/indra/newview/llenvironment.h @@ -42,6 +42,8 @@ #include +#include + //------------------------------------------------------------------------- class LLViewerCamera; class LLParcel; diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index 6880cf2171..61a01d7418 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -72,6 +72,8 @@ #include "llcorehttputil.h" #include "lluiusage.h" +#include + const static std::string ADHOC_NAME_SUFFIX(" Conference"); const static std::string NEARBY_P2P_BY_OTHER("nearby_P2P_by_other"); -- cgit v1.2.3 From 045342ba29aae186e13c711bd4dd84377d4a7e43 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 5 Jun 2023 11:28:41 -0400 Subject: SL-18837: Bump the granularity of WorkQueue timing tests. On a low-powered GitHub Mac runner, the system doesn't wake up as soon as it should, and we get spurious "too late" errors. Try a bigger time increment. --- indra/llcommon/tests/workqueue_test.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/llcommon/tests/workqueue_test.cpp b/indra/llcommon/tests/workqueue_test.cpp index 1d73f7aa0d..7655a7aa1f 100644 --- a/indra/llcommon/tests/workqueue_test.cpp +++ b/indra/llcommon/tests/workqueue_test.cpp @@ -83,7 +83,11 @@ namespace tut // signal the work item that it can quit; consider LLOneShotCond. LLCond data; auto start = WorkQueue::TimePoint::clock::now(); - auto interval = 100ms; + // 2s seems like a long time to wait, since it directly impacts the + // duration of this test program. Unfortunately GitHub's Mac runners + // are pretty wimpy, and we're getting spurious "too late" errors just + // because the thread doesn't wake up as soon as we want. + auto interval = 2s; queue.postEvery( interval, [&data, count = 0] -- cgit v1.2.3 From b5e1484c00880bfd8001cd1418f3eb96fc38b89e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 5 Jun 2023 12:22:28 -0400 Subject: SL-18837: Windows APR 1.7.2 requires MS rpcrt4.dll. --- indra/cmake/Linking.cmake | 1 + 1 file changed, 1 insertion(+) (limited to 'indra') diff --git a/indra/cmake/Linking.cmake b/indra/cmake/Linking.cmake index 4a501f420b..1ce21c11f9 100644 --- a/indra/cmake/Linking.cmake +++ b/indra/cmake/Linking.cmake @@ -62,6 +62,7 @@ elseif (WINDOWS) user32 ole32 dbghelp + rpcrt4.lib legacy_stdio_definitions ) else() -- cgit v1.2.3 From 19e9e8cf419c18b527bc19c5da1ab2cdf040dd0a Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 5 Jun 2023 12:32:10 -0400 Subject: SL-18837: Try to silence cascade of Boost.Bind warning messages. --- indra/cmake/00-Common.cmake | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'indra') diff --git a/indra/cmake/00-Common.cmake b/indra/cmake/00-Common.cmake index bf5a107c73..dc9ccd356a 100644 --- a/indra/cmake/00-Common.cmake +++ b/indra/cmake/00-Common.cmake @@ -26,6 +26,11 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} $ENV{LL_BUILD}") # Portable compilation flags. add_compile_definitions( ADDRESS_SIZE=${ADDRESS_SIZE}) +# Because older versions of Boost.Bind dumped placeholders _1, _2 et al. into +# the global namespace, Boost now requires either BOOST_BIND_NO_PLACEHOLDERS +# to avoid that or BOOST_BIND_GLOBAL_PLACEHOLDERS to state that we require it +# -- which we do. Without one or the other, we get a ton of Boost warnings. +add_compile_definitions(BOOST_BIND_GLOBAL_PLACEHOLDERS) # Configure crash reporting set(RELEASE_CRASH_REPORTING OFF CACHE BOOL "Enable use of crash reporting in release builds") -- cgit v1.2.3 From ddc6d219585251ea6246f56bfa5aba6b3e2fd4f0 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 5 Jun 2023 21:47:52 -0400 Subject: SL-18837: Followup to 19e9e8c: global Boost.Bind placeholders do not need 'using' directive, given BOOST_BIND_GLOBAL_PLACEHOLDERS. --- indra/llinventory/llsettingsbase.cpp | 3 --- indra/llinventory/llsettingssky.cpp | 3 --- indra/llinventory/llsettingswater.cpp | 3 --- 3 files changed, 9 deletions(-) (limited to 'indra') diff --git a/indra/llinventory/llsettingsbase.cpp b/indra/llinventory/llsettingsbase.cpp index ba338dbbee..6ea93e045d 100644 --- a/indra/llinventory/llsettingsbase.cpp +++ b/indra/llinventory/llsettingsbase.cpp @@ -33,9 +33,6 @@ #include "llsdserialize.h" #include -// allow unqualified _1, _2 et al. to mean boost::bind placeholders -using namespace boost::placeholders; - //========================================================================= namespace { diff --git a/indra/llinventory/llsettingssky.cpp b/indra/llinventory/llsettingssky.cpp index 0244b2585e..51fca76518 100644 --- a/indra/llinventory/llsettingssky.cpp +++ b/indra/llinventory/llsettingssky.cpp @@ -33,9 +33,6 @@ #include "v3colorutil.h" #include -// allow unqualified _1, _2 et al. to mean boost::bind placeholders -using namespace boost::placeholders; - //========================================================================= namespace { diff --git a/indra/llinventory/llsettingswater.cpp b/indra/llinventory/llsettingswater.cpp index bc53a46255..89156000b0 100644 --- a/indra/llinventory/llsettingswater.cpp +++ b/indra/llinventory/llsettingswater.cpp @@ -34,9 +34,6 @@ #include "indra_constants.h" #include -// allow unqualified _1, _2 et al. to mean boost::bind placeholders -using namespace boost::placeholders; - const std::string LLSettingsWater::SETTING_BLUR_MULTIPLIER("blur_multiplier"); const std::string LLSettingsWater::SETTING_FOG_COLOR("water_fog_color"); const std::string LLSettingsWater::SETTING_FOG_DENSITY("water_fog_density"); -- cgit v1.2.3 From 6914ff6113f1667308293fe37e1ba4a4dcba850f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 5 Jun 2023 21:51:28 -0400 Subject: SL-18837: Use Boost.Filesystem for NamedTempFile, instead of APR. --- indra/test/namedtempfile.h | 136 +++++++++++---------------------------------- indra/test/test.cpp | 10 ++-- 2 files changed, 38 insertions(+), 108 deletions(-) (limited to 'indra') diff --git a/indra/test/namedtempfile.h b/indra/test/namedtempfile.h index 7d59cad32c..927e2b4fd9 100644 --- a/indra/test/namedtempfile.h +++ b/indra/test/namedtempfile.h @@ -13,15 +13,15 @@ #define LL_NAMEDTEMPFILE_H #include "llerror.h" -#include "llapr.h" -#include "apr_file_io.h" +#include "stringize.h" #include -#include -#include -#include #include +#include +#include +#include #include #include +#include /** * Create a text file with specified content "somewhere in the @@ -31,41 +31,36 @@ class NamedTempFile: public boost::noncopyable { LOG_CLASS(NamedTempFile); public: - NamedTempFile(const std::string& pfx, const std::string& content, apr_pool_t* pool=gAPRPoolp): - mPool(pool) + NamedTempFile(const std::string_view& pfx, + const std::string_view& content, + const std::string_view& sfx=std::string_view("")) { - createFile(pfx, boost::phoenix::placeholders::arg1 << content); - } - - // Disambiguate when passing string literal - NamedTempFile(const std::string& pfx, const char* content, apr_pool_t* pool=gAPRPoolp): - mPool(pool) - { - createFile(pfx, boost::phoenix::placeholders::arg1 << content); + createFile(pfx, [&content](std::ostream& out){ out << content; }, sfx); } // Function that accepts an ostream ref and (presumably) writes stuff to // it, e.g.: // (boost::phoenix::placeholders::arg1 << "the value is " << 17 << '\n') - typedef boost::function Streamer; + typedef std::function Streamer; - NamedTempFile(const std::string& pfx, const Streamer& func, apr_pool_t* pool=gAPRPoolp): - mPool(pool) + NamedTempFile(const std::string_view& pfx, + const Streamer& func, + const std::string_view& sfx=std::string_view("")) { - createFile(pfx, func); + createFile(pfx, func, sfx); } virtual ~NamedTempFile() { - ll_apr_assert_status(apr_file_remove(mPath.c_str(), mPool)); + boost::filesystem::remove(mPath); } - virtual std::string getName() const { return mPath; } + virtual std::string getName() const { return mPath.native(); } void peep() { std::cout << "File '" << mPath << "' contains:\n"; - std::ifstream reader(mPath.c_str()); + boost::filesystem::ifstream reader(mPath); std::string line; while (std::getline(reader, line)) std::cout << line << '\n'; @@ -73,92 +68,40 @@ public: } protected: - void createFile(const std::string& pfx, const Streamer& func) + void createFile(const std::string_view& pfx, + const Streamer& func, + const std::string_view& sfx) { // Create file in a temporary place. - const char* tempdir = NULL; - ll_apr_assert_status(apr_temp_dir_get(&tempdir, mPool)); - - // Construct a temp filename template in that directory. - char *tempname = NULL; - ll_apr_assert_status(apr_filepath_merge(&tempname, - tempdir, - (pfx + "XXXXXX").c_str(), - 0, - mPool)); - - // Create a temp file from that template. - apr_file_t* fp = NULL; - ll_apr_assert_status(apr_file_mktemp(&fp, - tempname, - APR_CREATE | APR_WRITE | APR_EXCL, - mPool)); - // apr_file_mktemp() alters tempname with the actual name. Not until - // now is it valid to capture as our mPath. - mPath = tempname; + boost::filesystem::path tempname{ boost::filesystem::temp_directory_path() }; + // unique_path() recommended model + tempname += stringize(pfx, "%%%%-%%%%-%%%%-%%%%", sfx); + mPath = boost::filesystem::unique_path(tempname); + boost::filesystem::ofstream out{ mPath }; // Write desired content. - std::ostringstream out; - // Stream stuff to it. func(out); - - std::string data(out.str()); - apr_size_t writelen(data.length()); - ll_apr_assert_status(apr_file_write(fp, data.c_str(), &writelen)); - ll_apr_assert_status(apr_file_close(fp)); - llassert_always(writelen == data.length()); } - std::string mPath; - apr_pool_t* mPool; + boost::filesystem::path mPath; }; /** * Create a NamedTempFile with a specified filename extension. This is useful * when, for instance, you must be able to use the file in a Python import * statement. - * - * A NamedExtTempFile actually has two different names. We retain the original - * no-extension name as a placeholder in the temp directory to ensure - * uniqueness; to that we link the name plus the desired extension. Naturally, - * both must be removed on destruction. */ class NamedExtTempFile: public NamedTempFile { LOG_CLASS(NamedExtTempFile); public: - NamedExtTempFile(const std::string& ext, const std::string& content, apr_pool_t* pool=gAPRPoolp): - NamedTempFile(remove_dot(ext), content, pool), - mLink(mPath + ensure_dot(ext)) - { - linkto(mLink); - } + NamedExtTempFile(const std::string& ext, const std::string_view& content): + NamedTempFile(remove_dot(ext), content, ensure_dot(ext)) + {} - // Disambiguate when passing string literal - NamedExtTempFile(const std::string& ext, const char* content, apr_pool_t* pool=gAPRPoolp): - NamedTempFile(remove_dot(ext), content, pool), - mLink(mPath + ensure_dot(ext)) - { - linkto(mLink); - } - - NamedExtTempFile(const std::string& ext, const Streamer& func, apr_pool_t* pool=gAPRPoolp): - NamedTempFile(remove_dot(ext), func, pool), - mLink(mPath + ensure_dot(ext)) - { - linkto(mLink); - } - - virtual ~NamedExtTempFile() - { - ll_apr_assert_status(apr_file_remove(mLink.c_str(), mPool)); - } - - // Since the caller has gone to the trouble to create the name with the - // extension, that should be the name we return. In this class, mPath is - // just a placeholder to ensure that future createFile() calls won't - // collide. - virtual std::string getName() const { return mLink; } + NamedExtTempFile(const std::string& ext, const Streamer& func): + NamedTempFile(remove_dot(ext), func, ensure_dot(ext)) + {} static std::string ensure_dot(const std::string& ext) { @@ -175,7 +118,7 @@ public: { return ext; } - return std::string(".") + ext; + return "." + ext; } static std::string remove_dot(const std::string& ext) @@ -187,19 +130,6 @@ public: } return ext.substr(found); } - -private: - void linkto(const std::string& path) - { - // This method assumes that since mPath (without extension) is - // guaranteed by apr_file_mktemp() to be unique, then (mPath + any - // extension) is also unique. This is likely, though not guaranteed: - // files could be created in the same temp directory other than by - // this class. - ll_apr_assert_status(apr_file_link(mPath.c_str(), path.c_str())); - } - - std::string mLink; }; #endif /* ! defined(LL_NAMEDTEMPFILE_H) */ diff --git a/indra/test/test.cpp b/indra/test/test.cpp index bb48216b2b..04f32831b7 100644 --- a/indra/test/test.cpp +++ b/indra/test/test.cpp @@ -97,10 +97,10 @@ public: class RecordToTempFile : public LLError::Recorder, public boost::noncopyable { public: - RecordToTempFile(apr_pool_t* pPool) + RecordToTempFile() : LLError::Recorder(), boost::noncopyable(), - mTempFile("log", "", pPool), + mTempFile("log", ""), mFile(mTempFile.getName().c_str()) { } @@ -141,11 +141,11 @@ private: class LLReplayLogReal: public LLReplayLog, public boost::noncopyable { public: - LLReplayLogReal(LLError::ELevel level, apr_pool_t* pool) + LLReplayLogReal(LLError::ELevel level) : LLReplayLog(), boost::noncopyable(), mOldSettings(LLError::saveAndResetSettings()), - mRecorder(new RecordToTempFile(pool)) + mRecorder(new RecordToTempFile()) { LLError::setFatalFunction(wouldHaveCrashed); LLError::setDefaultLevel(level); @@ -624,7 +624,7 @@ int main(int argc, char **argv) if (LOGFAIL && *LOGFAIL) { LLError::ELevel level = LLError::decodeLevel(LOGFAIL); - replayer.reset(new LLReplayLogReal(level, gAPRPoolp)); + replayer.reset(new LLReplayLogReal(level)); } } LLError::setFatalFunction(wouldHaveCrashed); -- cgit v1.2.3 From ca510f6c299335c8db27c65c10a8553801c06023 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 5 Jun 2023 22:08:26 -0400 Subject: SL-18837: Try giving temp Python scripts a .py extension. On GitHub Windows Actions runners, we're getting permissions errors trying to tell the Python interpreter to run a NamedTempFile script. Try using NamedExtTempFile to give each such script a .py extension. --- indra/llcommon/tests/llleap_test.cpp | 222 ++++++++++++++++---------------- indra/llcommon/tests/llprocess_test.cpp | 4 +- 2 files changed, 113 insertions(+), 113 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index 60005fc6a9..99fd073dd2 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -213,9 +213,9 @@ namespace tut void object::test<1>() { set_test_name("multiple LLLeap instances"); - NamedTempFile script("py", - "import time\n" - "time.sleep(1)\n"); + NamedExtTempFile script("py", + "import time\n" + "time.sleep(1)\n"); LLLeapVector instances; instances.push_back(LLLeap::create(get_test_name(), sv(list_of(PYTHON)(script.getName())))->getWeak()); @@ -231,10 +231,10 @@ namespace tut void object::test<2>() { set_test_name("stderr to log"); - NamedTempFile script("py", - "import sys\n" - "sys.stderr.write('''Hello from Python!\n" - "note partial line''')\n"); + NamedExtTempFile script("py", + "import sys\n" + "sys.stderr.write('''Hello from Python!\n" + "note partial line''')\n"); StringVec vcommand{ PYTHON, script.getName() }; CaptureLog log(LLError::LEVEL_INFO); waitfor(LLLeap::create(get_test_name(), vcommand)); @@ -246,8 +246,8 @@ namespace tut void object::test<3>() { set_test_name("bad stdout protocol"); - NamedTempFile script("py", - "print('Hello from Python!')\n"); + NamedExtTempFile script("py", + "print('Hello from Python!')\n"); CaptureLog log(LLError::LEVEL_WARN); waitfor(LLLeap::create(get_test_name(), sv(list_of(PYTHON)(script.getName())))); @@ -259,10 +259,10 @@ namespace tut void object::test<4>() { set_test_name("leftover stdout"); - NamedTempFile script("py", - "import sys\n" - // note lack of newline - "sys.stdout.write('Hello from Python!')\n"); + NamedExtTempFile script("py", + "import sys\n" + // note lack of newline + "sys.stdout.write('Hello from Python!')\n"); CaptureLog log(LLError::LEVEL_WARN); waitfor(LLLeap::create(get_test_name(), sv(list_of(PYTHON)(script.getName())))); @@ -274,9 +274,9 @@ namespace tut void object::test<5>() { set_test_name("bad stdout len prefix"); - NamedTempFile script("py", - "import sys\n" - "sys.stdout.write('5a2:something')\n"); + NamedExtTempFile script("py", + "import sys\n" + "sys.stdout.write('5a2:something')\n"); CaptureLog log(LLError::LEVEL_WARN); waitfor(LLLeap::create(get_test_name(), sv(list_of(PYTHON)(script.getName())))); @@ -381,16 +381,16 @@ namespace tut set_test_name("round trip"); AckAPI api; Result result; - NamedTempFile script("py", - boost::phoenix::placeholders::arg1 << - "from " << reader_module << " import *\n" - // make a request on our little API - "request(pump='" << api.getName() << "', data={})\n" - // wait for its response - "resp = get()\n" - "result = '' if resp == dict(pump=replypump(), data='ack')\\\n" - " else 'bad: ' + str(resp)\n" - "send(pump='" << result.getName() << "', data=result)\n"); + NamedExtTempFile script("py", + boost::phoenix::placeholders::arg1 << + "from " << reader_module << " import *\n" + // make a request on our little API + "request(pump='" << api.getName() << "', data={})\n" + // wait for its response + "resp = get()\n" + "result = '' if resp == dict(pump=replypump(), data='ack')\\\n" + " else 'bad: ' + str(resp)\n" + "send(pump='" << result.getName() << "', data=result)\n"); waitfor(LLLeap::create(get_test_name(), sv(list_of(PYTHON)(script.getName())))); result.ensure(); } @@ -419,37 +419,37 @@ namespace tut // iterations etc. in OS pipes and the LLLeap/LLProcess implementation. ReqIDAPI api; Result result; - NamedTempFile script("py", - boost::phoenix::placeholders::arg1 << - "import sys\n" - "from " << reader_module << " import *\n" - // Note that since reader imports llsd, this - // 'import *' gets us llsd too. - "sample = llsd.format_notation(dict(pump='" << - api.getName() << "', data=dict(reqid=999999, reply=replypump())))\n" - // The whole packet has length prefix too: "len:data" - "samplen = len(str(len(sample))) + 1 + len(sample)\n" - // guess how many messages it will take to - // accumulate BUFFERED_LENGTH - "count = int(" << BUFFERED_LENGTH << "/samplen)\n" - "print('Sending %s requests' % count, file=sys.stderr)\n" - "for i in range(count):\n" - " request('" << api.getName() << "', dict(reqid=i))\n" - // The assumption in this specific test that - // replies will arrive in the same order as - // requests is ONLY valid because the API we're - // invoking sends replies instantly. If the API - // had to wait for some external event before - // sending its reply, replies could arrive in - // arbitrary order, and we'd have to tick them - // off from a set. - "result = ''\n" - "for i in range(count):\n" - " resp = get()\n" - " if resp['data']['reqid'] != i:\n" - " result = 'expected reqid=%s in %s' % (i, resp)\n" - " break\n" - "send(pump='" << result.getName() << "', data=result)\n"); + NamedExtTempFile script("py", + boost::phoenix::placeholders::arg1 << + "import sys\n" + "from " << reader_module << " import *\n" + // Note that since reader imports llsd, this + // 'import *' gets us llsd too. + "sample = llsd.format_notation(dict(pump='" << + api.getName() << "', data=dict(reqid=999999, reply=replypump())))\n" + // The whole packet has length prefix too: "len:data" + "samplen = len(str(len(sample))) + 1 + len(sample)\n" + // guess how many messages it will take to + // accumulate BUFFERED_LENGTH + "count = int(" << BUFFERED_LENGTH << "/samplen)\n" + "print('Sending %s requests' % count, file=sys.stderr)\n" + "for i in range(count):\n" + " request('" << api.getName() << "', dict(reqid=i))\n" + // The assumption in this specific test that + // replies will arrive in the same order as + // requests is ONLY valid because the API we're + // invoking sends replies instantly. If the API + // had to wait for some external event before + // sending its reply, replies could arrive in + // arbitrary order, and we'd have to tick them + // off from a set. + "result = ''\n" + "for i in range(count):\n" + " resp = get()\n" + " if resp['data']['reqid'] != i:\n" + " result = 'expected reqid=%s in %s' % (i, resp)\n" + " break\n" + "send(pump='" << result.getName() << "', data=result)\n"); waitfor(LLLeap::create(get_test_name(), sv(list_of(PYTHON)(script.getName()))), 300); // needs more realtime than most tests result.ensure(); @@ -462,60 +462,60 @@ namespace tut { ReqIDAPI api; Result result; - NamedTempFile script("py", - boost::phoenix::placeholders::arg1 << - "import sys\n" - "from " << reader_module << " import *\n" - // Generate a very large string value. - "desired = int(sys.argv[1])\n" - // 7 chars per item: 6 digits, 1 comma - "count = int((desired - 50)/7)\n" - "large = ''.join('%06d,' % i for i in range(count))\n" - // Pass 'large' as reqid because we know the API - // will echo reqid, and we want to receive it back. - "request('" << api.getName() << "', dict(reqid=large))\n" - "try:\n" - " resp = get()\n" - "except ParseError as e:\n" - " # try to find where e.data diverges from expectation\n" - // Normally we'd expect a 'pump' key in there, - // too, with value replypump(). But Python - // serializes keys in a different order than C++, - // so incoming data start with 'data'. - // Truthfully, though, if we get as far as 'pump' - // before we find a difference, something's very - // strange. - " expect = llsd.format_notation(dict(data=dict(reqid=large)))\n" - " chunk = 40\n" - " for offset in range(0, max(len(e.data), len(expect)), chunk):\n" - " if e.data[offset:offset+chunk] != \\\n" - " expect[offset:offset+chunk]:\n" - " print('Offset %06d: expect %r,\\n'\\\n" - " ' get %r' %\\\n" - " (offset,\n" - " expect[offset:offset+chunk],\n" - " e.data[offset:offset+chunk]),\n" - " file=sys.stderr)\n" - " break\n" - " else:\n" - " print('incoming data matches expect?!', file=sys.stderr)\n" - " send('" << result.getName() << "', '%s: %s' % (e.__class__.__name__, e))\n" - " sys.exit(1)\n" - "\n" - "echoed = resp['data']['reqid']\n" - "if echoed == large:\n" - " send('" << result.getName() << "', '')\n" - " sys.exit(0)\n" - // Here we know echoed did NOT match; try to find where - "for i in range(count):\n" - " start = 7*i\n" - " end = 7*(i+1)\n" - " if end > len(echoed)\\\n" - " or echoed[start:end] != large[start:end]:\n" - " send('" << result.getName() << "',\n" - " 'at offset %s, expected %r but got %r' %\n" - " (start, large[start:end], echoed[start:end]))\n" - "sys.exit(1)\n"); + NamedExtTempFile script("py", + boost::phoenix::placeholders::arg1 << + "import sys\n" + "from " << reader_module << " import *\n" + // Generate a very large string value. + "desired = int(sys.argv[1])\n" + // 7 chars per item: 6 digits, 1 comma + "count = int((desired - 50)/7)\n" + "large = ''.join('%06d,' % i for i in range(count))\n" + // Pass 'large' as reqid because we know the API + // will echo reqid, and we want to receive it back. + "request('" << api.getName() << "', dict(reqid=large))\n" + "try:\n" + " resp = get()\n" + "except ParseError as e:\n" + " # try to find where e.data diverges from expectation\n" + // Normally we'd expect a 'pump' key in there, + // too, with value replypump(). But Python + // serializes keys in a different order than C++, + // so incoming data start with 'data'. + // Truthfully, though, if we get as far as 'pump' + // before we find a difference, something's very + // strange. + " expect = llsd.format_notation(dict(data=dict(reqid=large)))\n" + " chunk = 40\n" + " for offset in range(0, max(len(e.data), len(expect)), chunk):\n" + " if e.data[offset:offset+chunk] != \\\n" + " expect[offset:offset+chunk]:\n" + " print('Offset %06d: expect %r,\\n'\\\n" + " ' get %r' %\\\n" + " (offset,\n" + " expect[offset:offset+chunk],\n" + " e.data[offset:offset+chunk]),\n" + " file=sys.stderr)\n" + " break\n" + " else:\n" + " print('incoming data matches expect?!', file=sys.stderr)\n" + " send('" << result.getName() << "', '%s: %s' % (e.__class__.__name__, e))\n" + " sys.exit(1)\n" + "\n" + "echoed = resp['data']['reqid']\n" + "if echoed == large:\n" + " send('" << result.getName() << "', '')\n" + " sys.exit(0)\n" + // Here we know echoed did NOT match; try to find where + "for i in range(count):\n" + " start = 7*i\n" + " end = 7*(i+1)\n" + " if end > len(echoed)\\\n" + " or echoed[start:end] != large[start:end]:\n" + " send('" << result.getName() << "',\n" + " 'at offset %s, expected %r but got %r' %\n" + " (start, large[start:end], echoed[start:end]))\n" + "sys.exit(1)\n"); waitfor(LLLeap::create(test_name, sv(list_of (PYTHON) diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 81449b4a42..4adb8d872a 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -191,7 +191,7 @@ struct PythonProcessLauncher LLProcess::Params mParams; LLProcessPtr mPy; std::string mDesc; - NamedTempFile mScript; + NamedExtTempFile mScript; }; /// convenience function for PythonProcessLauncher::run() @@ -355,7 +355,7 @@ namespace tut set_test_name("raw APR nonblocking I/O"); // Create a script file in a temporary place. - NamedTempFile script("py", + NamedExtTempFile script("py", "from __future__ import print_function" EOL "import sys" EOL "import time" EOL -- cgit v1.2.3 From aab769e9d7a9b11256572f8adb69d586d9ff8d3d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 5 Jun 2023 23:24:34 -0400 Subject: SL-18837: Make NamedTempFile revert to boost::function from std::function, since some consumers still use (e.g.) boost::phoenix::placeholders::arg1 to generate an inline callable. --- indra/test/namedtempfile.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'indra') diff --git a/indra/test/namedtempfile.h b/indra/test/namedtempfile.h index 927e2b4fd9..df7495fc13 100644 --- a/indra/test/namedtempfile.h +++ b/indra/test/namedtempfile.h @@ -15,10 +15,10 @@ #include "llerror.h" #include "stringize.h" #include -#include +#include #include #include -#include +#include #include #include #include @@ -41,7 +41,7 @@ public: // Function that accepts an ostream ref and (presumably) writes stuff to // it, e.g.: // (boost::phoenix::placeholders::arg1 << "the value is " << 17 << '\n') - typedef std::function Streamer; + typedef boost::function Streamer; NamedTempFile(const std::string_view& pfx, const Streamer& func, -- cgit v1.2.3 From 26ca3e14d623e4094dde76ad88e3da2a209483b5 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 5 Jun 2023 23:46:43 -0400 Subject: SL-18837: NamedTempFile must still disambiguate string literals. --- indra/test/namedtempfile.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'indra') diff --git a/indra/test/namedtempfile.h b/indra/test/namedtempfile.h index df7495fc13..acfc048b7a 100644 --- a/indra/test/namedtempfile.h +++ b/indra/test/namedtempfile.h @@ -38,6 +38,15 @@ public: createFile(pfx, [&content](std::ostream& out){ out << content; }, sfx); } + // Disambiguate when passing string literal -- unclear why a string + // literal should be ambiguous wrt std::string_view and Streamer + NamedTempFile(const std::string_view& pfx, + const char* content, + const std::string_view& sfx=std::string_view("")) + { + createFile(pfx, [&content](std::ostream& out){ out << content; }, sfx); + } + // Function that accepts an ostream ref and (presumably) writes stuff to // it, e.g.: // (boost::phoenix::placeholders::arg1 << "the value is " << 17 << '\n') @@ -99,6 +108,11 @@ public: NamedTempFile(remove_dot(ext), content, ensure_dot(ext)) {} + // Disambiguate when passing string literal + NamedExtTempFile(const std::string& ext, const char* content): + NamedTempFile(remove_dot(ext), content, ensure_dot(ext)) + {} + NamedExtTempFile(const std::string& ext, const Streamer& func): NamedTempFile(remove_dot(ext), func, ensure_dot(ext)) {} -- cgit v1.2.3 From 6516c9d07db42beba5ba9c0c41a33925794a249c Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 6 Jun 2023 07:44:42 -0400 Subject: SL-18837: NamedTempFile back to std::function, use boost::phoenix << It seems the problem addressed by aab769e wasn't some synergy between Boost.Phoenix and Boost.Function, but rather the lack of a Phoenix header file introducing operator<<(). --- indra/llcommon/tests/llleap_test.cpp | 1 + indra/llcommon/tests/llsdserialize_test.cpp | 1 + indra/test/namedtempfile.h | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index 99fd073dd2..0c91db3e24 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -19,6 +19,7 @@ // external library headers #include #include +#include // operator<<() // other Linden headers #include "../test/lltut.h" #include "../test/namedtempfile.h" diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index d7c11c5021..a0b8519508 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -50,6 +50,7 @@ typedef U32 uint32_t; #include "boost/bind.hpp" #include "boost/phoenix/bind/bind_function.hpp" #include "boost/phoenix/core/argument.hpp" +#include using namespace boost::phoenix; #include "../llsd.h" diff --git a/indra/test/namedtempfile.h b/indra/test/namedtempfile.h index acfc048b7a..84b62a0945 100644 --- a/indra/test/namedtempfile.h +++ b/indra/test/namedtempfile.h @@ -15,10 +15,10 @@ #include "llerror.h" #include "stringize.h" #include -#include #include #include #include +#include #include #include #include @@ -50,7 +50,7 @@ public: // Function that accepts an ostream ref and (presumably) writes stuff to // it, e.g.: // (boost::phoenix::placeholders::arg1 << "the value is " << 17 << '\n') - typedef boost::function Streamer; + typedef std::function Streamer; NamedTempFile(const std::string_view& pfx, const Streamer& func, -- cgit v1.2.3 From 1db7ac7139adf505be12308fd7ba2920f5beecde Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 6 Jun 2023 10:02:57 -0400 Subject: SL-18837: Make NamedTempFile name template compatible with Python. The recommended template uses hyphens; change to underscores to be valid Python temp module names. --- indra/test/namedtempfile.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/test/namedtempfile.h b/indra/test/namedtempfile.h index 84b62a0945..8cd6c990dc 100644 --- a/indra/test/namedtempfile.h +++ b/indra/test/namedtempfile.h @@ -83,8 +83,9 @@ protected: { // Create file in a temporary place. boost::filesystem::path tempname{ boost::filesystem::temp_directory_path() }; - // unique_path() recommended model - tempname += stringize(pfx, "%%%%-%%%%-%%%%-%%%%", sfx); + // unique_path() recommended template, but with underscores instead of + // hyphens: some use cases involve temporary Python scripts + tempname += stringize(pfx, "%%%%_%%%%_%%%%_%%%%", sfx); mPath = boost::filesystem::unique_path(tempname); boost::filesystem::ofstream out{ mPath }; -- cgit v1.2.3 From c4366378b61cacb9d87eb2917302fa17e9207491 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 6 Jun 2023 10:04:26 -0400 Subject: SL-18837: Ditch Boost.Phoenix implicit lambda syntax. It's cool to be able to write 'arg1 << "stuff" << var ...;' for a lambda accepting a std::ostream reference, but cascading compile errors mean it's no longer worth trying to make that work -- given actual C++ lambdas. Also clean up a lingering BOOST_FOREACH() and a boost::bind() while at it. --- indra/llcommon/tests/llleap_test.cpp | 18 ++++++++---------- indra/llcommon/tests/llsdserialize_test.cpp | 21 +++++++-------------- 2 files changed, 15 insertions(+), 24 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index 0c91db3e24..0fc741d9e1 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -18,8 +18,6 @@ #include // external library headers #include -#include -#include // operator<<() // other Linden headers #include "../test/lltut.h" #include "../test/namedtempfile.h" @@ -105,7 +103,7 @@ namespace tut llleap_data(): reader(".py", // This logic is adapted from vita.viewerclient.receiveEvent() - boost::phoenix::placeholders::arg1 << + [](std::ostream& out){ out << "import re\n" "import os\n" "import sys\n" @@ -189,7 +187,7 @@ namespace tut "def request(pump, data):\n" " # we expect 'data' is a dict\n" " data['reply'] = _reply\n" - " send(pump, data)\n"), + " send(pump, data)\n";}), // Get the actual pathname of the NamedExtTempFile and trim off // the ".py" extension. (We could cache reader.getName() in a // separate member variable, but I happen to know getName() just @@ -383,7 +381,7 @@ namespace tut AckAPI api; Result result; NamedExtTempFile script("py", - boost::phoenix::placeholders::arg1 << + [&](std::ostream& out){ out << "from " << reader_module << " import *\n" // make a request on our little API "request(pump='" << api.getName() << "', data={})\n" @@ -391,7 +389,7 @@ namespace tut "resp = get()\n" "result = '' if resp == dict(pump=replypump(), data='ack')\\\n" " else 'bad: ' + str(resp)\n" - "send(pump='" << result.getName() << "', data=result)\n"); + "send(pump='" << result.getName() << "', data=result)\n";}); waitfor(LLLeap::create(get_test_name(), sv(list_of(PYTHON)(script.getName())))); result.ensure(); } @@ -421,7 +419,7 @@ namespace tut ReqIDAPI api; Result result; NamedExtTempFile script("py", - boost::phoenix::placeholders::arg1 << + [&](std::ostream& out){ out << "import sys\n" "from " << reader_module << " import *\n" // Note that since reader imports llsd, this @@ -450,7 +448,7 @@ namespace tut " if resp['data']['reqid'] != i:\n" " result = 'expected reqid=%s in %s' % (i, resp)\n" " break\n" - "send(pump='" << result.getName() << "', data=result)\n"); + "send(pump='" << result.getName() << "', data=result)\n";}); waitfor(LLLeap::create(get_test_name(), sv(list_of(PYTHON)(script.getName()))), 300); // needs more realtime than most tests result.ensure(); @@ -464,7 +462,7 @@ namespace tut ReqIDAPI api; Result result; NamedExtTempFile script("py", - boost::phoenix::placeholders::arg1 << + [&](std::ostream& out){ out << "import sys\n" "from " << reader_module << " import *\n" // Generate a very large string value. @@ -516,7 +514,7 @@ namespace tut " send('" << result.getName() << "',\n" " 'at offset %s, expected %r but got %r' %\n" " (start, large[start:end], echoed[start:end]))\n" - "sys.exit(1)\n"); + "sys.exit(1)\n";}); waitfor(LLLeap::create(test_name, sv(list_of (PYTHON) diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index a0b8519508..08bf7fbc51 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -45,13 +45,6 @@ typedef U32 uint32_t; #endif #include "boost/range.hpp" -#include "boost/foreach.hpp" -#include "boost/function.hpp" -#include "boost/bind.hpp" -#include "boost/phoenix/bind/bind_function.hpp" -#include "boost/phoenix/core/argument.hpp" -#include -using namespace boost::phoenix; #include "../llsd.h" #include "../llsdserialize.h" @@ -1802,7 +1795,7 @@ namespace tut // helper for test<3> static void writeLLSDArray(std::ostream& out, const LLSD& array) { - BOOST_FOREACH(LLSD item, llsd::inArray(array)) + for (const LLSD& item: llsd::inArray(array)) { LLSDSerialize::toNotation(item, out); // It's important to separate with newlines because Python's llsd @@ -1842,21 +1835,21 @@ namespace tut // Create an llsdXXXXXX file containing 'data' serialized to // notation. NamedTempFile file("llsd", - // NamedTempFile's boost::function constructor + // NamedTempFile's function constructor // takes a callable. To this callable it passes the // std::ostream with which it's writing the // NamedTempFile. - boost::bind(writeLLSDArray, _1, cdata)); + [&](std::ostream& out){ writeLLSDArray(out, cdata); }); python("read C++ notation", - placeholders::arg1 << + [&](std::ostream& out){ out << import_llsd << "def parse_each(iterable):\n" " for item in iterable:\n" " yield llsd.parse(item)\n" << pydata << // Don't forget raw-string syntax for Windows pathnames. - "verify(parse_each(open(r'" << file.getName() << "', 'rb')))\n"); + "verify(parse_each(open(r'" << file.getName() << "', 'rb')))\n";}); } template<> template<> @@ -1870,7 +1863,7 @@ namespace tut NamedTempFile file("llsd", ""); python("write Python notation", - placeholders::arg1 << + [&](std::ostream& out){ out << import_llsd << "DATA = [\n" " 17,\n" @@ -1884,7 +1877,7 @@ namespace tut // N.B. Using 'print' implicitly adds newlines. "with open(r'" << file.getName() << "', 'w') as f:\n" " for item in DATA:\n" - " print(llsd.format_notation(item).decode(), file=f)\n"); + " print(llsd.format_notation(item).decode(), file=f)\n";}); std::ifstream inf(file.getName().c_str()); LLSD item; -- cgit v1.2.3 From e63c571d1336e3c521e1fc3a5e27bb77fc667790 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 6 Jun 2023 13:34:01 -0400 Subject: SL-18837: On Windows, NamedTempFile must convert from wstring --- indra/test/namedtempfile.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/test/namedtempfile.h b/indra/test/namedtempfile.h index 8cd6c990dc..da4fec97c8 100644 --- a/indra/test/namedtempfile.h +++ b/indra/test/namedtempfile.h @@ -13,6 +13,7 @@ #define LL_NAMEDTEMPFILE_H #include "llerror.h" +#include "llstring.h" #include "stringize.h" #include #include @@ -64,7 +65,8 @@ public: boost::filesystem::remove(mPath); } - virtual std::string getName() const { return mPath.native(); } + // On Windows, path::native() returns a wstring + std::string getName() const { return ll_convert(mPath.native()); } void peep() { -- cgit v1.2.3 From 004150a5305d0df06c52a51a0df3ac26dd4a63cd Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 6 Jun 2023 16:27:43 -0400 Subject: SL-18837: Concat path part with / rather than +=. Using concatenation appends the intended filename to the parent directory name, instead of putting the filename in the parent directory. --- indra/test/namedtempfile.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'indra') diff --git a/indra/test/namedtempfile.h b/indra/test/namedtempfile.h index da4fec97c8..c215c50f3f 100644 --- a/indra/test/namedtempfile.h +++ b/indra/test/namedtempfile.h @@ -84,10 +84,12 @@ protected: const std::string_view& sfx) { // Create file in a temporary place. - boost::filesystem::path tempname{ boost::filesystem::temp_directory_path() }; - // unique_path() recommended template, but with underscores instead of - // hyphens: some use cases involve temporary Python scripts - tempname += stringize(pfx, "%%%%_%%%%_%%%%_%%%%", sfx); + boost::filesystem::path tempname{ + boost::filesystem::temp_directory_path() / + // unique_path() recommended template, but with underscores + // instead of hyphens: some use cases involve temporary Python + // scripts + stringize(pfx, "%%%%_%%%%_%%%%_%%%%", sfx) }; mPath = boost::filesystem::unique_path(tempname); boost::filesystem::ofstream out{ mPath }; -- cgit v1.2.3 From f57de07f73871bc7be6c338ea18893a494d104eb Mon Sep 17 00:00:00 2001 From: Brad Linden Date: Wed, 7 Jun 2023 13:46:29 -0700 Subject: Attempt to port some build.yaml improvements from DRTVWR-559 over to actions branch --- indra/cmake/Variables.cmake | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) (limited to 'indra') diff --git a/indra/cmake/Variables.cmake b/indra/cmake/Variables.cmake index e6285ab48b..778384f87c 100644 --- a/indra/cmake/Variables.cmake +++ b/indra/cmake/Variables.cmake @@ -173,13 +173,17 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") set(CMAKE_XCODE_ATTRIBUTE_GCC_OPTIMIZATION_LEVEL "${CMAKE_MATCH_1}") message(STATUS "CMAKE_XCODE_ATTRIBUTE_GCC_OPTIMIZATION_LEVEL = '${CMAKE_XCODE_ATTRIBUTE_GCC_OPTIMIZATION_LEVEL}'") - string(REGEX MATCHALL "[^ ]+" LL_BUILD_LIST "$ENV{LL_BUILD}") - list(FIND LL_BUILD_LIST "-iwithsysroot" sysroot_idx) - if ("${sysroot_idx}" LESS 0) - message(FATAL_ERROR "Environment variable LL_BUILD must contain '-iwithsysroot'") - endif () - math(EXPR sysroot_idx "${sysroot_idx} + 1") - list(GET LL_BUILD_LIST "${sysroot_idx}" CMAKE_OSX_SYSROOT) + # allow disabling this check by setting LL_SKIP_REQUIRE_SYSROOT either ON as cmake cache var or non-empty as environment var + set(LL_SKIP_REQUIRE_SYSROOT OFF CACHE BOOL "Skip requirement to set toolchain sysroot ahead of time. Not skipped by default for consistency, but skipping can be useful for selecting alternative xcode versions side by side") + if("$ENV{LL_SKIP_REQUIRE_SYSROOT}" STREQUAL "" AND NOT ${LL_SKIP_REQUIRE_SYSROOT}) + string(REGEX MATCHALL "[^ ]+" LL_BUILD_LIST "$ENV{LL_BUILD}") + list(FIND LL_BUILD_LIST "-iwithsysroot" sysroot_idx) + if ("${sysroot_idx}" LESS 0) + message(FATAL_ERROR "Environment variable LL_BUILD must contain '-iwithsysroot'") + endif () + math(EXPR sysroot_idx "${sysroot_idx} + 1") + list(GET LL_BUILD_LIST "${sysroot_idx}" CMAKE_OSX_SYSROOT) + endif() message(STATUS "CMAKE_OSX_SYSROOT = '${CMAKE_OSX_SYSROOT}'") set(CMAKE_XCODE_ATTRIBUTE_GCC_VERSION "com.apple.compilers.llvm.clang.1_0") -- cgit v1.2.3 From 3c63cc9c55b1193f2876e7523f0b6766f5a8c5ac Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 28 Jun 2023 18:36:51 -0400 Subject: SL-18837: Make LLVersionInfo::getBuild() S64 for GitHub run IDs. --- indra/newview/llappviewer.cpp | 4 ++-- indra/newview/llpanellogin.cpp | 4 ++-- indra/newview/lltranslate.cpp | 4 ++-- indra/newview/llversioninfo.cpp | 2 +- indra/newview/llversioninfo.h | 2 +- indra/newview/llweb.cpp | 2 +- indra/newview/llxmlrpctransaction.cpp | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) (limited to 'indra') diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 8235e4466c..99ace834c2 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -3552,7 +3552,7 @@ void LLAppViewer::writeSystemInfo() gDebugInfo["ClientInfo"]["MajorVersion"] = LLVersionInfo::instance().getMajor(); gDebugInfo["ClientInfo"]["MinorVersion"] = LLVersionInfo::instance().getMinor(); gDebugInfo["ClientInfo"]["PatchVersion"] = LLVersionInfo::instance().getPatch(); - gDebugInfo["ClientInfo"]["BuildVersion"] = LLVersionInfo::instance().getBuild(); + gDebugInfo["ClientInfo"]["BuildVersion"] = std::to_string(LLVersionInfo::instance().getBuild()); gDebugInfo["ClientInfo"]["AddressSize"] = LLVersionInfo::instance().getAddressSize(); gDebugInfo["CAFilename"] = gDirUtilp->getCAFile(); @@ -5669,7 +5669,7 @@ void LLAppViewer::handleLoginComplete() gDebugInfo["ClientInfo"]["MajorVersion"] = LLVersionInfo::instance().getMajor(); gDebugInfo["ClientInfo"]["MinorVersion"] = LLVersionInfo::instance().getMinor(); gDebugInfo["ClientInfo"]["PatchVersion"] = LLVersionInfo::instance().getPatch(); - gDebugInfo["ClientInfo"]["BuildVersion"] = LLVersionInfo::instance().getBuild(); + gDebugInfo["ClientInfo"]["BuildVersion"] = std::to_string(LLVersionInfo::instance().getBuild()); LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); if ( parcel && parcel->getMusicURL()[0]) diff --git a/indra/newview/llpanellogin.cpp b/indra/newview/llpanellogin.cpp index b14fdbf38e..c61c176530 100644 --- a/indra/newview/llpanellogin.cpp +++ b/indra/newview/llpanellogin.cpp @@ -300,7 +300,7 @@ LLPanelLogin::LLPanelLogin(const LLRect &rect, setDefaultBtn(def_btn); std::string channel = LLVersionInfo::instance().getChannel(); - std::string version = llformat("%s (%d)", + std::string version = llformat("%s (%ld)", LLVersionInfo::instance().getShortVersion().c_str(), LLVersionInfo::instance().getBuild()); @@ -894,7 +894,7 @@ void LLPanelLogin::loadLoginPage() } // Channel and Version - params["version"] = llformat("%s (%d)", + params["version"] = llformat("%s (%ld)", LLVersionInfo::instance().getShortVersion().c_str(), LLVersionInfo::instance().getBuild()); params["channel"] = LLVersionInfo::instance().getChannel(); diff --git a/indra/newview/lltranslate.cpp b/indra/newview/lltranslate.cpp index 34d3ed8bb1..d7cb12826f 100644 --- a/indra/newview/lltranslate.cpp +++ b/indra/newview/lltranslate.cpp @@ -133,7 +133,7 @@ void LLTranslationAPIHandler::verifyKeyCoro(LLTranslate::EService service, std:: LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); - std::string user_agent = llformat("%s %d.%d.%d (%d)", + std::string user_agent = llformat("%s %d.%d.%d (%ld)", LLVersionInfo::instance().getChannel().c_str(), LLVersionInfo::instance().getMajor(), LLVersionInfo::instance().getMinor(), @@ -177,7 +177,7 @@ void LLTranslationAPIHandler::translateMessageCoro(LanguagePair_t fromTo, std::s LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); - std::string user_agent = llformat("%s %d.%d.%d (%d)", + std::string user_agent = llformat("%s %d.%d.%d (%ld)", LLVersionInfo::instance().getChannel().c_str(), LLVersionInfo::instance().getMajor(), LLVersionInfo::instance().getMinor(), diff --git a/indra/newview/llversioninfo.cpp b/indra/newview/llversioninfo.cpp index 376a7fce76..62bfa24e29 100644 --- a/indra/newview/llversioninfo.cpp +++ b/indra/newview/llversioninfo.cpp @@ -91,7 +91,7 @@ S32 LLVersionInfo::getPatch() return LL_VIEWER_VERSION_PATCH; } -S32 LLVersionInfo::getBuild() +S64 LLVersionInfo::getBuild() { return LL_VIEWER_VERSION_BUILD; } diff --git a/indra/newview/llversioninfo.h b/indra/newview/llversioninfo.h index 02ff0c094a..122bd5c47a 100644 --- a/indra/newview/llversioninfo.h +++ b/indra/newview/llversioninfo.h @@ -61,7 +61,7 @@ public: S32 getPatch(); /// return the build number as an integer - S32 getBuild(); + S64 getBuild(); /// return the full viewer version as a string like "2.0.0.200030" std::string getVersion(); diff --git a/indra/newview/llweb.cpp b/indra/newview/llweb.cpp index c4d873dd22..9afe332025 100644 --- a/indra/newview/llweb.cpp +++ b/indra/newview/llweb.cpp @@ -160,7 +160,7 @@ std::string LLWeb::expandURLSubstitutions(const std::string &url, substitution["VERSION_MAJOR"] = LLVersionInfo::instance().getMajor(); substitution["VERSION_MINOR"] = LLVersionInfo::instance().getMinor(); substitution["VERSION_PATCH"] = LLVersionInfo::instance().getPatch(); - substitution["VERSION_BUILD"] = LLVersionInfo::instance().getBuild(); + substitution["VERSION_BUILD"] = std::to_string(LLVersionInfo::instance().getBuild()); substitution["CHANNEL"] = LLVersionInfo::instance().getChannel(); substitution["GRID"] = LLGridManager::getInstance()->getGridId(); substitution["GRID_LOWERCASE"] = utf8str_tolower(LLGridManager::getInstance()->getGridId()); diff --git a/indra/newview/llxmlrpctransaction.cpp b/indra/newview/llxmlrpctransaction.cpp index 8d178dbbdc..b851b7ad5c 100644 --- a/indra/newview/llxmlrpctransaction.cpp +++ b/indra/newview/llxmlrpctransaction.cpp @@ -384,7 +384,7 @@ void LLXMLRPCTransaction::Impl::init(XMLRPC_REQUEST request, bool useGzip, const httpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, HTTP_CONTENT_TEXT_XML); - std::string user_agent = llformat("%s %d.%d.%d (%d)", + std::string user_agent = llformat("%s %d.%d.%d (%ld)", LLVersionInfo::instance().getChannel().c_str(), LLVersionInfo::instance().getMajor(), LLVersionInfo::instance().getMinor(), -- 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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 f54c1215676f26480d88b4588bb0eeb9c05f50d9 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 7 Jul 2023 10:06:02 -0400 Subject: SL-18837: Try putting generated Python scripts in RUNNER_TEMP dir. The claim is that the Windows Python interpreter is integrated somehow with the OS such that a command line that tries to run Python with a script that "looks suspicious" (i.e. in a system temp directory) fails with "Access denied" without even loading the interpreter. At least that theory would explain the "Access denied" errors we've been getting trying to run Python scripts generated into the system temp directory by our integration tests. Our hope is that generating such scripts into the GitHub RUNNER_TEMP directory will work better. As this test is specific to Windows, don't even bother running Mac builds. --- indra/test/namedtempfile.h | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) (limited to 'indra') diff --git a/indra/test/namedtempfile.h b/indra/test/namedtempfile.h index c215c50f3f..4343361f41 100644 --- a/indra/test/namedtempfile.h +++ b/indra/test/namedtempfile.h @@ -84,12 +84,19 @@ protected: const std::string_view& sfx) { // Create file in a temporary place. + // This variable is set by GitHub actions and is the recommended place + // to put temp files belonging to an actions job. + const char* RUNNER_TEMP = getenv("RUNNER_TEMP"); + boost::filesystem::path tempdir{ + // if RUNNER_TEMP is set and not empty + (RUNNER_TEMP && *RUNNER_TEMP)? + boost::filesystem::path(RUNNER_TEMP) : // use RUNNER_TEMP if available + boost::filesystem::temp_directory_path()}; // else canonical temp dir boost::filesystem::path tempname{ - boost::filesystem::temp_directory_path() / - // unique_path() recommended template, but with underscores - // instead of hyphens: some use cases involve temporary Python - // scripts - stringize(pfx, "%%%%_%%%%_%%%%_%%%%", sfx) }; + // use filename template recommended by unique_path() doc, but + // with underscores instead of hyphens: some use cases involve + // temporary Python scripts + tempdir / stringize(pfx, "%%%%_%%%%_%%%%_%%%%", sfx) }; mPath = boost::filesystem::unique_path(tempname); boost::filesystem::ofstream out{ mPath }; -- 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(-) (limited to 'indra') 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 e933ace53b24b732d4111169e3c5964a8591a29e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 7 Jul 2023 14:07:12 -0400 Subject: SL-18837: Try to bypass Windows perm problem with Python indirection. --- indra/llcommon/tests/llprocess_test.cpp | 41 ++++++++++++++++++------------ indra/llcommon/tests/test_python_script.py | 20 +++++++++++++++ indra/test/namedtempfile.h | 22 +++++++++------- 3 files changed, 58 insertions(+), 25 deletions(-) create mode 100644 indra/llcommon/tests/test_python_script.py (limited to 'indra') diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 4adb8d872a..af99e97d66 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -124,6 +124,17 @@ void waitfor(LLProcess::handle h, const std::string& desc, int timeout=60) i < timeout); } +namespace { + +// find test helper, a sibling of this file +// nat 2023-07-07: we're currently using Boost 1.81, but +// path::replace_filename() (which is exactly what we need here) doesn't +// arrive until Boost 1.82. +auto test_python_script{ + (boost::filesystem::path(__FILE__).remove_filename() / "test_python_script.py").string() }; + +} + /** * Construct an LLProcess to run a Python script. */ @@ -145,6 +156,7 @@ struct PythonProcessLauncher mParams.desc = desc + " script"; mParams.executable = PYTHON; + mParams.args.add(test_python_script); mParams.args.add(mScript.getName()); } @@ -214,30 +226,26 @@ static std::string python_out(const std::string& desc, const CONTENT& script) class NamedTempDir: public boost::noncopyable { public: - // Use python() function to create a temp directory: I've found - // nothing in either Boost.Filesystem or APR quite like Python's - // tempfile.mkdtemp(). - // Special extra bonus: on Mac, mkdtemp() reports a pathname - // starting with /var/folders/something, whereas that's really a - // symlink to /private/var/folders/something. Have to use - // realpath() to compare properly. NamedTempDir(): - mPath(python_out("mkdtemp()", - "from __future__ import with_statement\n" - "import os.path, sys, tempfile\n" - "with open(sys.argv[1], 'w') as f:\n" - " f.write(os.path.normcase(os.path.normpath(os.path.realpath(tempfile.mkdtemp()))))\n")) - {} + mPath(NamedTempFile::temp_path()), + mCreated(boost::filesystem::create_directories(mPath)) + { + mPath = boost::filesystem::canonical(mPath); + } ~NamedTempDir() { - aprchk(apr_dir_remove(mPath.c_str(), gAPRPoolp)); + if (mCreated) + { + boost::filesystem::remove_all(mPath); + } } - std::string getName() const { return mPath; } + std::string getName() const { return mPath.string(); } private: - std::string mPath; + boost::filesystem::path mPath; + bool mCreated; }; /***************************************************************************** @@ -390,6 +398,7 @@ namespace tut // Have to have a named copy of this std::string so its c_str() value // will persist. std::string scriptname(script.getName()); + argv.push_back(test_python_script.c_str()); argv.push_back(scriptname.c_str()); argv.push_back(NULL); diff --git a/indra/llcommon/tests/test_python_script.py b/indra/llcommon/tests/test_python_script.py new file mode 100644 index 0000000000..c0c8661aa9 --- /dev/null +++ b/indra/llcommon/tests/test_python_script.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +"""\ +@file test_python_script.py +@author Nat Goodspeed +@date 2023-07-07 +@brief Work around a problem running Python within integration tests on GitHub + Windows runners. + +$LicenseInfo:firstyear=2023&license=viewerlgpl$ +Copyright (c) 2023, Linden Research, Inc. +$/LicenseInfo$ +""" + +import os +import sys + +# use pop() so that if the referenced script in turn looks at sys.argv, it +# will see its arguments rather than its own filename +_script = sys.argv.pop(1) +exec(open(_script).read()) diff --git a/indra/test/namedtempfile.h b/indra/test/namedtempfile.h index 4343361f41..525a35000d 100644 --- a/indra/test/namedtempfile.h +++ b/indra/test/namedtempfile.h @@ -65,8 +65,7 @@ public: boost::filesystem::remove(mPath); } - // On Windows, path::native() returns a wstring - std::string getName() const { return ll_convert(mPath.native()); } + std::string getName() const { return mPath.string(); } void peep() { @@ -78,12 +77,9 @@ public: std::cout << "---\n"; } -protected: - void createFile(const std::string_view& pfx, - const Streamer& func, - const std::string_view& sfx) + static boost::filesystem::path temp_path(const std::string_view& pfx="", + const std::string_view& sfx="") { - // Create file in a temporary place. // This variable is set by GitHub actions and is the recommended place // to put temp files belonging to an actions job. const char* RUNNER_TEMP = getenv("RUNNER_TEMP"); @@ -97,9 +93,17 @@ protected: // with underscores instead of hyphens: some use cases involve // temporary Python scripts tempdir / stringize(pfx, "%%%%_%%%%_%%%%_%%%%", sfx) }; - mPath = boost::filesystem::unique_path(tempname); - boost::filesystem::ofstream out{ mPath }; + return boost::filesystem::unique_path(tempname); + } +protected: + void createFile(const std::string_view& pfx, + const Streamer& func, + const std::string_view& sfx) + { + // Create file in a temporary place. + mPath = temp_path(pfx, sfx); + boost::filesystem::ofstream out{ mPath }; // Write desired content. func(out); } -- cgit v1.2.3 From c4b5d089dad5680a0dd12b2d386b692318eb5c58 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 7 Jul 2023 16:57:20 -0400 Subject: SL-18837: Partially revert e933ace, keeping useful tweaks. Introducing indirection via test_python_script.py did NOT address the "Access is denied" errors on GitHub Windows runners. --- indra/llcommon/tests/llleap_test.cpp | 25 +++++++++---------------- indra/llcommon/tests/llprocess_test.cpp | 13 ------------- indra/llcommon/tests/test_python_script.py | 20 -------------------- 3 files changed, 9 insertions(+), 49 deletions(-) delete mode 100644 indra/llcommon/tests/test_python_script.py (limited to 'indra') diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index 0fc741d9e1..e9edd165df 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -17,7 +17,6 @@ // std headers #include // external library headers -#include // other Linden headers #include "../test/lltut.h" #include "../test/namedtempfile.h" @@ -29,10 +28,6 @@ #include "stringize.h" #include "StringVec.h" -using boost::assign::list_of; - -StringVec sv(const StringVec& listof) { return listof; } - #if defined(LL_WINDOWS) #define sleep(secs) _sleep((secs) * 1000) @@ -217,9 +212,9 @@ namespace tut "time.sleep(1)\n"); LLLeapVector instances; instances.push_back(LLLeap::create(get_test_name(), - sv(list_of(PYTHON)(script.getName())))->getWeak()); + StringVec{PYTHON, script.getName()})->getWeak()); instances.push_back(LLLeap::create(get_test_name(), - sv(list_of(PYTHON)(script.getName())))->getWeak()); + StringVec{PYTHON, script.getName()})->getWeak()); // In this case we're simply establishing that two LLLeap instances // can coexist without throwing exceptions or bombing in any other // way. Wait for them to terminate. @@ -249,7 +244,7 @@ namespace tut "print('Hello from Python!')\n"); CaptureLog log(LLError::LEVEL_WARN); waitfor(LLLeap::create(get_test_name(), - sv(list_of(PYTHON)(script.getName())))); + StringVec{PYTHON, script.getName()})); ensure_contains("error log line", log.messageWith("invalid protocol"), "Hello from Python!"); } @@ -264,7 +259,7 @@ namespace tut "sys.stdout.write('Hello from Python!')\n"); CaptureLog log(LLError::LEVEL_WARN); waitfor(LLLeap::create(get_test_name(), - sv(list_of(PYTHON)(script.getName())))); + StringVec{PYTHON, script.getName()})); ensure_contains("error log line", log.messageWith("Discarding"), "Hello from Python!"); } @@ -278,7 +273,7 @@ namespace tut "sys.stdout.write('5a2:something')\n"); CaptureLog log(LLError::LEVEL_WARN); waitfor(LLLeap::create(get_test_name(), - sv(list_of(PYTHON)(script.getName())))); + StringVec{PYTHON, script.getName()})); ensure_contains("error log line", log.messageWith("invalid protocol"), "5a2:"); } @@ -390,7 +385,8 @@ namespace tut "result = '' if resp == dict(pump=replypump(), data='ack')\\\n" " else 'bad: ' + str(resp)\n" "send(pump='" << result.getName() << "', data=result)\n";}); - waitfor(LLLeap::create(get_test_name(), sv(list_of(PYTHON)(script.getName())))); + waitfor(LLLeap::create(get_test_name(), + StringVec{PYTHON, script.getName()})); result.ensure(); } @@ -449,7 +445,7 @@ namespace tut " result = 'expected reqid=%s in %s' % (i, resp)\n" " break\n" "send(pump='" << result.getName() << "', data=result)\n";}); - waitfor(LLLeap::create(get_test_name(), sv(list_of(PYTHON)(script.getName()))), + waitfor(LLLeap::create(get_test_name(), StringVec{PYTHON, script.getName()}), 300); // needs more realtime than most tests result.ensure(); } @@ -516,10 +512,7 @@ namespace tut " (start, large[start:end], echoed[start:end]))\n" "sys.exit(1)\n";}); waitfor(LLLeap::create(test_name, - sv(list_of - (PYTHON) - (script.getName()) - (stringize(size)))), + StringVec{PYTHON, script.getName(), stringize(size)}), 180); // try a longer timeout result.ensure(); } diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index af99e97d66..c7d1a2c86a 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -124,17 +124,6 @@ void waitfor(LLProcess::handle h, const std::string& desc, int timeout=60) i < timeout); } -namespace { - -// find test helper, a sibling of this file -// nat 2023-07-07: we're currently using Boost 1.81, but -// path::replace_filename() (which is exactly what we need here) doesn't -// arrive until Boost 1.82. -auto test_python_script{ - (boost::filesystem::path(__FILE__).remove_filename() / "test_python_script.py").string() }; - -} - /** * Construct an LLProcess to run a Python script. */ @@ -156,7 +145,6 @@ struct PythonProcessLauncher mParams.desc = desc + " script"; mParams.executable = PYTHON; - mParams.args.add(test_python_script); mParams.args.add(mScript.getName()); } @@ -398,7 +386,6 @@ namespace tut // Have to have a named copy of this std::string so its c_str() value // will persist. std::string scriptname(script.getName()); - argv.push_back(test_python_script.c_str()); argv.push_back(scriptname.c_str()); argv.push_back(NULL); diff --git a/indra/llcommon/tests/test_python_script.py b/indra/llcommon/tests/test_python_script.py deleted file mode 100644 index c0c8661aa9..0000000000 --- a/indra/llcommon/tests/test_python_script.py +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env python3 -"""\ -@file test_python_script.py -@author Nat Goodspeed -@date 2023-07-07 -@brief Work around a problem running Python within integration tests on GitHub - Windows runners. - -$LicenseInfo:firstyear=2023&license=viewerlgpl$ -Copyright (c) 2023, Linden Research, Inc. -$/LicenseInfo$ -""" - -import os -import sys - -# use pop() so that if the referenced script in turn looks at sys.argv, it -# will see its arguments rather than its own filename -_script = sys.argv.pop(1) -exec(open(_script).read()) -- cgit v1.2.3 From 1fc8758458c99b3a41965e33b3c62613c83e403a Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 7 Jul 2023 17:31:50 -0400 Subject: SL-18837: Coax APR to log LLProcess launch attempts; show log file. --- indra/llcommon/tests/llprocess_test.cpp | 34 +++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index c7d1a2c86a..fbddc7f909 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -30,6 +30,7 @@ #include "../test/namedtempfile.h" #include "../test/catch_and_store_what_in.h" #include "stringize.h" +#include "lldir.h" #include "llsdutil.h" #include "llevents.h" #include "llstring.h" @@ -151,8 +152,37 @@ struct PythonProcessLauncher /// Launch Python script; verify that it launched void launch() { - mPy = LLProcess::create(mParams); - tut::ensure(STRINGIZE("Couldn't launch " << mDesc << " script"), bool(mPy)); + std::string logpath{ gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "apr.log") }; +#if LL_WINDOWS + _putenv_s("APR_LOG", logpath.c_str()); +#else + setenv("APR_LOG", logpath.c_str(), 1); +#endif + try + { + mPy = LLProcess::create(mParams); + tut::ensure(STRINGIZE("Couldn't launch " << mDesc << " script"), bool(mPy)); + } + catch (const tut::failure& err); + { + std::ifstream inf(logpath.c_str()); + if (! inf.is_open()) + { + LL_WARNS() << "Couldn't open '" << logpath << "'" << LL_ENDL; + } + else + { + LL_WARNS() << "==============================" << LL_ENDL; + LL_WARNS() << "From '" << logpath << "':" << LL_ENDL; + std::string line; + while (std::getline(line, inf)) + { + LL_WARNS() << line << LL_ENDL; + } + LL_WARNS() << "==============================" << LL_ENDL; + } + throw; + } } /// Run Python script and wait for it to complete. -- cgit v1.2.3 From 8f81e1fa87123ff6255e9ee82e68c414efe05cdd Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 7 Jul 2023 17:47:57 -0400 Subject: SL-18837: Fix "lldir.h" #include --- indra/llcommon/tests/llprocess_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index fbddc7f909..9ee7890c7c 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -30,7 +30,7 @@ #include "../test/namedtempfile.h" #include "../test/catch_and_store_what_in.h" #include "stringize.h" -#include "lldir.h" +#include "../llfilesystem/lldir.h" #include "llsdutil.h" #include "llevents.h" #include "llstring.h" -- cgit v1.2.3 From 8aa3a0a7ed8cf3e3fedb2c98d6ea336fdd45e296 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 7 Jul 2023 19:48:02 -0400 Subject: SL-18837: Fix spurious semi --- indra/llcommon/tests/llprocess_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 9ee7890c7c..fb5cf12cb2 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -163,7 +163,7 @@ struct PythonProcessLauncher mPy = LLProcess::create(mParams); tut::ensure(STRINGIZE("Couldn't launch " << mDesc << " script"), bool(mPy)); } - catch (const tut::failure& err); + catch (const tut::failure& err) { std::ifstream inf(logpath.c_str()); if (! inf.is_open()) -- cgit v1.2.3 From 09c5b01997e1d34e799a8a0ee3571bd181f9a665 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 7 Jul 2023 20:02:33 -0400 Subject: SL-18837: Hook in LLDir to allow reading APR log file. --- indra/llcommon/CMakeLists.txt | 2 +- indra/llcommon/tests/llprocess_test.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 54020a4231..7412514e6b 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -308,7 +308,7 @@ if (LL_TESTS) LL_ADD_INTEGRATION_TEST(llleap "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llmainthreadtask "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llpounceable "" "${test_libs}") - LL_ADD_INTEGRATION_TEST(llprocess "" "${test_libs}") + LL_ADD_INTEGRATION_TEST(llprocess "" "${test_libs};llfilesystem") LL_ADD_INTEGRATION_TEST(llprocessor "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llprocinfo "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llrand "" "${test_libs}") diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index fb5cf12cb2..c6091bfeb1 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -175,7 +175,7 @@ struct PythonProcessLauncher LL_WARNS() << "==============================" << LL_ENDL; LL_WARNS() << "From '" << logpath << "':" << LL_ENDL; std::string line; - while (std::getline(line, inf)) + while (std::getline(inf, line)) { LL_WARNS() << line << LL_ENDL; } -- cgit v1.2.3 From 908fb3fed6b858da4dc2b1c840b849e30ade2046 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 7 Jul 2023 20:54:34 -0400 Subject: SL-18837: Ditch unreferenced name of caught exception --- indra/llcommon/tests/llprocess_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index c6091bfeb1..827837d62a 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -163,7 +163,7 @@ struct PythonProcessLauncher mPy = LLProcess::create(mParams); tut::ensure(STRINGIZE("Couldn't launch " << mDesc << " script"), bool(mPy)); } - catch (const tut::failure& err) + catch (const tut::failure&) { std::ifstream inf(logpath.c_str()); if (! inf.is_open()) -- cgit v1.2.3 From f37d2c307617302f2ed5dfead7e280da54a7d3e4 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 8 Jul 2023 09:04:33 -0400 Subject: SL-18837: Don't use LLDir, use NamedTempFile::temp_path. Remove llcommon circular dependency on llfilesystem, which doesn't work for this case anyway. --- indra/llcommon/CMakeLists.txt | 2 +- indra/llcommon/tests/llprocess_test.cpp | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 7412514e6b..54020a4231 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -308,7 +308,7 @@ if (LL_TESTS) LL_ADD_INTEGRATION_TEST(llleap "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llmainthreadtask "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llpounceable "" "${test_libs}") - LL_ADD_INTEGRATION_TEST(llprocess "" "${test_libs};llfilesystem") + LL_ADD_INTEGRATION_TEST(llprocess "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llprocessor "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llprocinfo "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llrand "" "${test_libs}") diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 827837d62a..6fcc6fd8aa 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -30,7 +30,6 @@ #include "../test/namedtempfile.h" #include "../test/catch_and_store_what_in.h" #include "stringize.h" -#include "../llfilesystem/lldir.h" #include "llsdutil.h" #include "llevents.h" #include "llstring.h" @@ -152,11 +151,9 @@ struct PythonProcessLauncher /// Launch Python script; verify that it launched void launch() { - std::string logpath{ gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "apr.log") }; + std::string logpath{ NamedTempFile::temp_path("apr", ".log").string() }; #if LL_WINDOWS _putenv_s("APR_LOG", logpath.c_str()); -#else - setenv("APR_LOG", logpath.c_str(), 1); #endif try { -- cgit v1.2.3 From 1ec6c744048a2905b0f2bf83f035a8fb8798dbdf Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 8 Jul 2023 11:08:16 -0400 Subject: SL-18837: Set APR_LOG once for the whole job instead of a new value for each LLProcess::create() invocation. Since the internal apr_log() function only looks at APR_LOG once per process, the first test (which succeeded, hence no log file dump) left the log file open with that same original pathname. Resetting the APR_LOG environment variable for subsequent runs only made the new code in llprocess_test look for files that were never created. --- indra/llcommon/tests/llprocess_test.cpp | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 6fcc6fd8aa..a01ec84547 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -151,10 +151,6 @@ struct PythonProcessLauncher /// Launch Python script; verify that it launched void launch() { - std::string logpath{ NamedTempFile::temp_path("apr", ".log").string() }; -#if LL_WINDOWS - _putenv_s("APR_LOG", logpath.c_str()); -#endif try { mPy = LLProcess::create(mParams); @@ -162,21 +158,25 @@ struct PythonProcessLauncher } catch (const tut::failure&) { - std::ifstream inf(logpath.c_str()); - if (! inf.is_open()) - { - LL_WARNS() << "Couldn't open '" << logpath << "'" << LL_ENDL; - } - else + const char* APR_LOG = getenv("APR_LOG"); + if (APR_LOG && *APR_LOG) { - LL_WARNS() << "==============================" << LL_ENDL; - LL_WARNS() << "From '" << logpath << "':" << LL_ENDL; - std::string line; - while (std::getline(inf, line)) + std::ifstream inf(APR_LOG); + if (! inf.is_open()) { - LL_WARNS() << line << LL_ENDL; + LL_WARNS() << "Couldn't open '" << APR_LOG << "'" << LL_ENDL; + } + else + { + LL_WARNS() << "==============================" << LL_ENDL; + LL_WARNS() << "From '" << APR_LOG << "':" << LL_ENDL; + std::string line; + while (std::getline(inf, line)) + { + LL_WARNS() << line << LL_ENDL; + } + LL_WARNS() << "==============================" << LL_ENDL; } - LL_WARNS() << "==============================" << LL_ENDL; } throw; } -- cgit v1.2.3 From 7dc6211ad5ea83685a35c6fff740278343aa8b9d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 8 Jul 2023 14:08:16 -0400 Subject: SL-18837: Force llprocess_test and llleap_test to use just 'python'. On GitHub Windows runners, trying to make build.yaml set PYTHON=python in the environment doesn't work: integration tests still fail with "Access is denied" because they're still trying to execute the interpreter's full pathname. Instead, make llprocess_test and llleap_test detect the case of GitHub Windows and override the environment variable PYTHON with a baked-in string constant "python". --- indra/llcommon/tests/llleap_test.cpp | 11 ++++++++++- indra/llcommon/tests/llprocess_test.cpp | 13 ++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index e9edd165df..01515ecebf 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -193,11 +193,20 @@ namespace tut reader.getName().substr(0, reader.getName().length()-3))), PYTHON(LLStringUtil::getenv("PYTHON")) { +#if LL_WINDOWS + // Weirdly, on GitHub Windows runners, plain 'python' works much + // better than a full pathname. + const char* RUNNER_TEMP = getenv("RUNNER_TEMP"); + if (RUNNER_TEMP && *RUNNER_TEMP) + { + PYTHON = "python"; + } +#endif ensure("Set PYTHON to interpreter pathname", !PYTHON.empty()); } NamedExtTempFile reader; const std::string reader_module; - const std::string PYTHON; + std::string PYTHON; }; typedef test_group llleap_group; typedef llleap_group::object object; diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index a01ec84547..3ba3a8aab3 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -141,6 +141,15 @@ struct PythonProcessLauncher mScript("py", script) { auto PYTHON(LLStringUtil::getenv("PYTHON")); +#if LL_WINDOWS + // Weirdly, on GitHub Windows runners, plain 'python' works much better + // than a full pathname. + const char* RUNNER_TEMP = getenv("RUNNER_TEMP"); + if (RUNNER_TEMP && *RUNNER_TEMP) + { + PYTHON = "python"; + } +#endif tut::ensure("Set $PYTHON to the Python interpreter", !PYTHON.empty()); mParams.desc = desc + " script"; @@ -1013,7 +1022,9 @@ namespace tut set_test_name("get*Pipe() validation"); PythonProcessLauncher py(get_test_name(), "from __future__ import print_function\n" - "print('this output is expected')\n"); + "import sys\n" + "print('this output is expected')\n" + "print('run by', sys.executable)\n"); py.mParams.files.add(LLProcess::FileParam("pipe")); // pipe for stdin py.mParams.files.add(LLProcess::FileParam()); // inherit stdout py.mParams.files.add(LLProcess::FileParam("pipe")); // pipe for stderr -- cgit v1.2.3 From 31ccef8a666da54312a55663a7ac03061c4903be Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 10 Jul 2023 14:35:41 -0400 Subject: SL-18837: Revert "Force llprocess_test and llleap_test to use just 'python'." Turns out that the pathname of the Python executable wasn't the issue. This reverts commit 7dc6211ad5ea83685a35c6fff740278343aa8b9d. --- indra/llcommon/tests/llleap_test.cpp | 11 +---------- indra/llcommon/tests/llprocess_test.cpp | 13 +------------ 2 files changed, 2 insertions(+), 22 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index 01515ecebf..e9edd165df 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -193,20 +193,11 @@ namespace tut reader.getName().substr(0, reader.getName().length()-3))), PYTHON(LLStringUtil::getenv("PYTHON")) { -#if LL_WINDOWS - // Weirdly, on GitHub Windows runners, plain 'python' works much - // better than a full pathname. - const char* RUNNER_TEMP = getenv("RUNNER_TEMP"); - if (RUNNER_TEMP && *RUNNER_TEMP) - { - PYTHON = "python"; - } -#endif ensure("Set PYTHON to interpreter pathname", !PYTHON.empty()); } NamedExtTempFile reader; const std::string reader_module; - std::string PYTHON; + const std::string PYTHON; }; typedef test_group llleap_group; typedef llleap_group::object object; diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 3ba3a8aab3..a01ec84547 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -141,15 +141,6 @@ struct PythonProcessLauncher mScript("py", script) { auto PYTHON(LLStringUtil::getenv("PYTHON")); -#if LL_WINDOWS - // Weirdly, on GitHub Windows runners, plain 'python' works much better - // than a full pathname. - const char* RUNNER_TEMP = getenv("RUNNER_TEMP"); - if (RUNNER_TEMP && *RUNNER_TEMP) - { - PYTHON = "python"; - } -#endif tut::ensure("Set $PYTHON to the Python interpreter", !PYTHON.empty()); mParams.desc = desc + " script"; @@ -1022,9 +1013,7 @@ namespace tut set_test_name("get*Pipe() validation"); PythonProcessLauncher py(get_test_name(), "from __future__ import print_function\n" - "import sys\n" - "print('this output is expected')\n" - "print('run by', sys.executable)\n"); + "print('this output is expected')\n"); py.mParams.files.add(LLProcess::FileParam("pipe")); // pipe for stdin py.mParams.files.add(LLProcess::FileParam()); // inherit stdout py.mParams.files.add(LLProcess::FileParam("pipe")); // pipe for stderr -- cgit v1.2.3 From d8292a629149c2cfdda6ae9df4e87aa117153c21 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 10 Jul 2023 14:46:14 -0400 Subject: SL-18837: Disable APR_LOG for now, but leave notes for the future. --- indra/llcommon/tests/llprocess_test.cpp | 3 +++ 1 file changed, 3 insertions(+) (limited to 'indra') diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index a01ec84547..9ca664c80c 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -158,6 +158,9 @@ struct PythonProcessLauncher } catch (const tut::failure&) { + // On Windows, if APR_LOG is set, our version of APR's + // apr_create_proc() logs to the specified file. If this test + // failed, try to report that log. const char* APR_LOG = getenv("APR_LOG"); if (APR_LOG && *APR_LOG) { -- cgit v1.2.3 From c77737b925e3687e47d3a1dce1b7e8b481302741 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 10 Jul 2023 15:26:21 -0400 Subject: SL-18837: Windows failures in setWorkingDirectory(): C: vs. c: (sigh) Normalize the case of the name of the temp directory for string comparison. --- indra/llcommon/tests/llprocess_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 9ca664c80c..c1cb2af7fe 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -591,7 +591,7 @@ namespace tut " f.write(os.path.normcase(os.path.normpath(os.getcwd())))\n"); // Before running, call setWorkingDirectory() py.mParams.cwd = tempdir.getName(); - ensure_equals("os.getcwd()", py.run_read(), tempdir.getName()); + ensure_equals("os.getcwd()", py.run_read(), utf8str_tolower(tempdir.getName())); } template<> template<> -- 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(-) (limited to 'indra') 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(+) (limited to 'indra') 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 (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(+) (limited to 'indra') 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(+) (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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 (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(+) (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(+) (limited to 'indra') 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 (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(+) (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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 54f9ca5404c05a4031c1c12caf24b88048704cbd Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 17 Jul 2023 15:41:26 -0400 Subject: SL-18837: Merge branch 'actions' into actions-build-sh --- indra/llcommon/llprocessor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/llcommon/llprocessor.cpp b/indra/llcommon/llprocessor.cpp index 4a1a81f083..28f8bc2b93 100644 --- a/indra/llcommon/llprocessor.cpp +++ b/indra/llcommon/llprocessor.cpp @@ -746,7 +746,7 @@ private: __cpuid(0x1, eax, ebx, ecx, edx); if(feature_infos[0] != (S32)edx) { - LL_ERRS() << "machdep.cpu.feature_bits doesn't match expected cpuid result!" << LL_ENDL; + LL_WARNS() << "machdep.cpu.feature_bits doesn't match expected cpuid result!" << LL_ENDL; } #endif // LL_RELEASE_FOR_DOWNLOAD -- cgit v1.2.3 From 167ac704c8387c531630949860e33fb5a59789a8 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 17 Jul 2023 16:20:59 -0400 Subject: SL-18837: Clean up some redundancy in llrand.cpp. --- indra/llcommon/llrand.cpp | 96 +++++++++++++++++++++++++++-------------------- 1 file changed, 55 insertions(+), 41 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/llrand.cpp b/indra/llcommon/llrand.cpp index cb28a8f5c3..4e4345f37a 100644 --- a/indra/llcommon/llrand.cpp +++ b/indra/llcommon/llrand.cpp @@ -58,14 +58,18 @@ * to restore uniform distribution. */ +template +inline REAL ll_internal_random(); + // *NOTE: The system rand implementation is probably not correct. #define LL_USE_SYSTEM_RAND 0 +/***************************************************************************** +* The LL_USE_SYSTEM_RAND implementation has been disabled since June 2008. +*****************************************************************************/ #if LL_USE_SYSTEM_RAND #include -#endif -#if LL_USE_SYSTEM_RAND class LLSeedRand { public: @@ -79,25 +83,25 @@ public: } }; static LLSeedRand sRandomSeeder; -inline F64 ll_internal_random_double() + +template <> +inline F64 ll_internal_random() { #if LL_WINDOWS - return (F64)rand() / (F64)RAND_MAX; + return (F64)rand() / (F64)(RAND_MAX+1); #else return drand48(); #endif } -inline F32 ll_internal_random_float() -{ -#if LL_WINDOWS - return (F32)rand() / (F32)RAND_MAX; -#else - return (F32)drand48(); -#endif -} -#else + +/***************************************************************************** +* This is the implementation we've been using. +*****************************************************************************/ +#else // LL_USE_SYSTEM_RAND static LLRandLagFib2281 gRandomGenerator(LLUUID::getRandomSeed()); -inline F64 ll_internal_random_double() + +template <> +inline F64 ll_internal_random() { // *HACK: Through experimentation, we have found that dual core // CPUs (or at least multi-threaded processes) seem to @@ -107,16 +111,40 @@ inline F64 ll_internal_random_double() if(!((rv >= 0.0) && (rv < 1.0))) return fmod(rv, 1.0); return rv; } +#endif // LL_USE_SYSTEM_RAND + +/***************************************************************************** +* Functions common to both implementations +*****************************************************************************/ +template <> +inline F32 ll_internal_random() +{ + return F32(ll_internal_random()); +} + +/*------------------------------ F64 aliases -------------------------------*/ +inline F64 ll_internal_random_double() +{ + return ll_internal_random(); +} + +F64 ll_drand() +{ + return ll_internal_random_double(); +} +/*------------------------------ F32 aliases -------------------------------*/ inline F32 ll_internal_random_float() { - // The clamping rules are described above. - F32 rv = (F32)gRandomGenerator(); - if(!((rv >= 0.0f) && (rv < 1.0f))) return fmod(rv, 1.f); - return rv; + return ll_internal_random(); } -#endif +F32 ll_frand() +{ + return ll_internal_random_float(); +} + +/*-------------------------- clamped random range --------------------------*/ S32 ll_rand() { return ll_rand(RAND_MAX); @@ -130,42 +158,28 @@ S32 ll_rand(S32 val) return rv; } -F32 ll_frand() -{ - return ll_internal_random_float(); -} - -F32 ll_frand(F32 val) +template +REAL ll_grand(REAL val) { // The clamping rules are described above. - F32 rv = ll_internal_random_float() * val; + REAL rv = ll_internal_random() * val; if(val > 0) { - if(rv >= val) return 0.0f; + if(rv >= val) return REAL(); } else { - if(rv <= val) return 0.0f; + if(rv <= val) return REAL(); } return rv; } -F64 ll_drand() +F32 ll_frand(F32 val) { - return ll_internal_random_double(); + return ll_grand(val); } F64 ll_drand(F64 val) { - // The clamping rules are described above. - F64 rv = ll_internal_random_double() * val; - if(val > 0) - { - if(rv >= val) return 0.0; - } - else - { - if(rv <= val) return 0.0; - } - return rv; + return ll_grand(val); } -- cgit v1.2.3 From 4b158580e5654615d2a5510267bf76392c9666fa Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 17 Jul 2023 16:47:50 -0400 Subject: SL-18837: Lowercasing pathname for string compare is Windows-only. --- indra/llcommon/tests/llprocess_test.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index c1cb2af7fe..b6b297b8d7 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -591,7 +591,13 @@ namespace tut " f.write(os.path.normcase(os.path.normpath(os.getcwd())))\n"); // Before running, call setWorkingDirectory() py.mParams.cwd = tempdir.getName(); - ensure_equals("os.getcwd()", py.run_read(), utf8str_tolower(tempdir.getName())); + std::string expected{ tempdir.getName() }; +#if LL_WINDOWS + // SIGH, don't get tripped up by "C:" != "c:" -- + // but on the Mac, using tolower() fails because "/users" != "/Users"! + expected = utf8str_tolower(expected); +#endif + ensure_equals("os.getcwd()", py.run_read(), expected); } template<> template<> -- cgit v1.2.3 From 14d0b514af4e70956ecc2fbf6fe4c2e745e144a8 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 18 Jul 2023 09:45:00 -0400 Subject: SL-18837: Ditch inactive llrand.cpp LL_USE_SYSTEM_RAND code. LL_USE_SYSTEM_RAND has been disabled since June 2008; that code only clutters the implementation we actually use. --- indra/llcommon/llrand.cpp | 46 +++------------------------------------------- 1 file changed, 3 insertions(+), 43 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/llrand.cpp b/indra/llcommon/llrand.cpp index 4e4345f37a..33afc50cf7 100644 --- a/indra/llcommon/llrand.cpp +++ b/indra/llcommon/llrand.cpp @@ -58,48 +58,12 @@ * to restore uniform distribution. */ +static LLRandLagFib2281 gRandomGenerator(LLUUID::getRandomSeed()); + +// no default implementation, only specific F64 and F32 specializations template inline REAL ll_internal_random(); -// *NOTE: The system rand implementation is probably not correct. -#define LL_USE_SYSTEM_RAND 0 - -/***************************************************************************** -* The LL_USE_SYSTEM_RAND implementation has been disabled since June 2008. -*****************************************************************************/ -#if LL_USE_SYSTEM_RAND -#include - -class LLSeedRand -{ -public: - LLSeedRand() - { -#if LL_WINDOWS - srand(LLUUID::getRandomSeed()); -#else - srand48(LLUUID::getRandomSeed()); -#endif - } -}; -static LLSeedRand sRandomSeeder; - -template <> -inline F64 ll_internal_random() -{ -#if LL_WINDOWS - return (F64)rand() / (F64)(RAND_MAX+1); -#else - return drand48(); -#endif -} - -/***************************************************************************** -* This is the implementation we've been using. -*****************************************************************************/ -#else // LL_USE_SYSTEM_RAND -static LLRandLagFib2281 gRandomGenerator(LLUUID::getRandomSeed()); - template <> inline F64 ll_internal_random() { @@ -111,11 +75,7 @@ inline F64 ll_internal_random() if(!((rv >= 0.0) && (rv < 1.0))) return fmod(rv, 1.0); return rv; } -#endif // LL_USE_SYSTEM_RAND -/***************************************************************************** -* Functions common to both implementations -*****************************************************************************/ template <> inline F32 ll_internal_random() { -- cgit v1.2.3 From ecb938c95b66ff58840aae64fd5fb791c55ec080 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 18 Jul 2023 13:38:05 -0400 Subject: SL-18837: Try waiting a couple seconds before hdiutil detach to try to avoid "Resource busy" errors from hdiutil. --- indra/newview/viewer_manifest.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'indra') diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 4660991de6..1e43485b9c 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -1329,6 +1329,10 @@ class DarwinManifest(ViewerManifest): self.run_command([self.src_path_of("installers/darwin/apple-notarize.sh"), app_in_dmg]) finally: + # Empirically, on GitHub we've hit errors like: + # hdiutil: couldn't eject "disk10" - Resource busy + # Try waiting a bit to see if that improves reliability. + time.sleep(2) # Unmount the image even if exceptions from any of the above self.run_command(['hdiutil', 'detach', '-force', devfile]) -- 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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 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(-) (limited to 'indra') 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(+) (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(+) (limited to 'indra') 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 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(+) (limited to 'indra') 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 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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: Tue, 29 Aug 2023 11:12:32 -0400 Subject: SL-18837: On Windows, LLLeap partial final line test failed. Add DEBUG log output to try to diagnose. --- indra/llcommon/llleap.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'indra') diff --git a/indra/llcommon/llleap.cpp b/indra/llcommon/llleap.cpp index c87c0758fe..060e96557d 100644 --- a/indra/llcommon/llleap.cpp +++ b/indra/llcommon/llleap.cpp @@ -376,6 +376,17 @@ public: // Read all remaining bytes and log. LL_INFOS("LLLeap") << mDesc << ": " << rest << LL_ENDL; } + /*--------------------------- diagnostic ---------------------------*/ + else if (data["eof"].asBoolean()) + { + LL_DEBUGS("LLLeap") << mDesc << " ended, no partial line" << LL_ENDL; + } + else + { + LL_DEBUGS("LLLeap") << mDesc << " (still running, " << childerr.size() + << " bytes pending)" << LL_ENDL; + } + /*------------------------- end diagnostic -------------------------*/ return false; } -- cgit v1.2.3 From fd73b6e5cf6341d606628646b73a0d05223b74bc Mon Sep 17 00:00:00 2001 From: Ansariel 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(-) (limited to 'indra') 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 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(-) (limited to 'indra') 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 e8cd5205e89993df357410c245f99ebb7703958d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 31 Aug 2023 15:53:56 -0400 Subject: SL-19243: Try to run Windows BugSplat uploads as a separate GH job. Upload a new Windows-exe artifact containing just the executable (needed by BugSplat) separately from the artifact containing the whole NSIS installer. This requires a new viewer_exe step output set by viewer_manifest.py. Define viewer_channel and viewer_version as build job outputs. Set viewer_channel in build.yaml when tag is interpreted. Set viewer_version in build.sh at the point when it would have posted viewer_version.txt to codeticket. Add a post-windows-symbols job dependent on the build job that engages secondlife/viewer-post-bugsplat-windows, which in turn engages secondlife/post-bugsplat-windows. We keep the actual upload code in a separate repo in case we need to modify that code before rerunning to resolve upload errors. If we kept the upload code in the viewer repo itself, rerunning the upload with modifications would necessarily require rerunning the viewer build, which would defeat the purpose of SL-19243. Because of that new upload job in build.yaml, skip Windows symbol uploads in build.sh. Use a simple (platform name) artifact name for metadata because of flatten_files.py's filename collision resolution. Use hyphens, not spaces, in remaining artifact names: apparently download-artifact doesn't much like artifacts with spaces in their names. Only run the release job when in fact there's a tag. Without that, we get errors. We need not create flatten_files.py's output directory beforehand because it will do that implicitly. --- indra/newview/viewer_manifest.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'indra') diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 1e43485b9c..6c4f8cb2d4 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -480,6 +480,12 @@ class WindowsManifest(ViewerManifest): if self.is_packaging_viewer(): # Find secondlife-bin.exe in the 'configuration' dir, then rename it to the result of final_exe. self.path(src='%s/secondlife-bin.exe' % self.args['configuration'], dst=self.final_exe()) + # emit that as one of the GitHub step outputs + GITHUB_OUTPUT = os.getenv('GITHUB_OUTPUT') + if GITHUB_OUTPUT: + exepath = os.path.join(self.get_dst_prefix(), self.final_exe()) + with open(GITHUB_OUTPUT, 'a') as outf: + print(f'viewer_exe={exepath}', file=outf) with self.prefix(src=os.path.join(pkgdir, "VMP")): # include the compiled launcher scripts so that it gets included in the file_list -- cgit v1.2.3 From 6a219d147d0761fce1fd75dc75f98b4e5283feb7 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 15 Aug 2023 15:32:35 -0400 Subject: SL-18837: Enlarge default coroutine stack size. A test executable on a GitHub Windows runner failed with C00000FD, which reports stack overflow. (cherry picked from commit aab7b4ba3812e5876b1205285bcfd8cff96bcac9) --- indra/llcommon/llcoros.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp index 70d8dfc8b9..cfaf3415e7 100644 --- a/indra/llcommon/llcoros.cpp +++ b/indra/llcommon/llcoros.cpp @@ -123,11 +123,7 @@ LLCoros::LLCoros(): // Previously we used // boost::context::guarded_stack_allocator::default_stacksize(); // empirically this is insufficient. -#if ADDRESS_SIZE == 64 - mStackSize(512*1024), -#else - mStackSize(256*1024), -#endif + mStackSize(768*1024), // mCurrent does NOT own the current CoroData instance -- it simply // points to it. So initialize it with a no-op deleter. mCurrent{ [](CoroData*){} } -- cgit v1.2.3 From c8aa205fab621c6b88393b06268e0026eeb07a23 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 31 Aug 2023 22:35:49 -0400 Subject: SL-19243: Try to robustify GH Mac volume detach Use a retry loop very like the code-signing retry loop. --- indra/newview/viewer_manifest.py | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) (limited to 'indra') diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 6c4f8cb2d4..aa28632e8a 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -1288,6 +1288,7 @@ class DarwinManifest(ViewerManifest): 'Keychains', 'viewer.keychain') self.run_command(['security', 'unlock-keychain', '-p', keychain_pwd, viewer_keychain]) + sign_retries=3 sign_retry_wait=15 resources = app_in_dmg + "/Contents/Resources/" plain_sign = glob.glob(resources + "llplugin/*.dylib") @@ -1296,9 +1297,10 @@ class DarwinManifest(ViewerManifest): resources + "SLPlugin.app/Contents/MacOS/SLPlugin", app_in_dmg, ] - for attempt in range(3): + for attempt in range(sign_retries): if attempt: # second or subsequent iteration - print("codesign failed, waiting {:d} seconds before retrying".format(sign_retry_wait), + print(f"codesign attempt {attempt+1} failed, " + f"waiting {sign_retry_wait:d} seconds before retrying", file=sys.stderr) time.sleep(sign_retry_wait) sign_retry_wait*=2 @@ -1329,18 +1331,37 @@ class DarwinManifest(ViewerManifest): # 'err' goes out of scope sign_failed = err else: - print("Maximum codesign attempts exceeded; giving up", file=sys.stderr) + print(f"{sign_retries} codesign attempts failed; giving up", + file=sys.stderr) raise sign_failed self.run_command(['spctl', '-a', '-texec', '-vvvv', app_in_dmg]) - self.run_command([self.src_path_of("installers/darwin/apple-notarize.sh"), app_in_dmg]) + self.run_command([self.src_path_of("installers/darwin/apple-notarize.sh"), + app_in_dmg]) finally: + # Unmount the image even if exceptions from any of the above + detach_retries = 3 + detach_retry_wait = 2 # Empirically, on GitHub we've hit errors like: # hdiutil: couldn't eject "disk10" - Resource busy - # Try waiting a bit to see if that improves reliability. - time.sleep(2) - # Unmount the image even if exceptions from any of the above - self.run_command(['hdiutil', 'detach', '-force', devfile]) + for attempt in range(detach_retries): + if attempt: # second or subsequent iteration + print(f'detach failed, waiting {detach_retry_wait} seconds before retrying', + file=sys.stderr) + # Try waiting a bit to see if that improves reliability. + time.sleep(detach_retry_wait) + detach_retry_wait *= 2 + + try: + self.run_command(['hdiutil', 'detach', '-force', devfile]) + # if no exception, the detach worked + break + except ManifestError as err: + detach_failed = err + else: + print(f'{detach_retries} detach attempts failed', file=sys.stderr) + ## can we carry on anyway?? + ## raise detach_failed print("Converting temp disk image to final disk image") self.run_command(['hdiutil', 'convert', sparsename, '-format', 'UDZO', -- cgit v1.2.3 From 00d8d4e9cdff7b5b584e7bd4f30c46902263c033 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 1 Sep 2023 12:16:36 -0400 Subject: SL-19243: Upload Mac .app as another build artifact. --- indra/newview/viewer_manifest.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) (limited to 'indra') diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index aa28632e8a..d07ec22496 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -406,6 +406,15 @@ class ViewerManifest(LLManifest): return os.path.relpath(abspath(path), abspath(base)) + def set_github_output_path(self, variable, path): + self.set_github_output(variable, os.path.join(self.get_dst_prefix(), path)) + + def set_github_output(self, variable, value): + GITHUB_OUTPUT = os.getenv('GITHUB_OUTPUT') + if GITHUB_OUTPUT: + with open(GITHUB_OUTPUT, 'a') as outf: + print('='.join((variable, value)), file=outf) + class WindowsManifest(ViewerManifest): # We want the platform, per se, for every Windows build to be 'win'. The @@ -481,11 +490,7 @@ class WindowsManifest(ViewerManifest): # Find secondlife-bin.exe in the 'configuration' dir, then rename it to the result of final_exe. self.path(src='%s/secondlife-bin.exe' % self.args['configuration'], dst=self.final_exe()) # emit that as one of the GitHub step outputs - GITHUB_OUTPUT = os.getenv('GITHUB_OUTPUT') - if GITHUB_OUTPUT: - exepath = os.path.join(self.get_dst_prefix(), self.final_exe()) - with open(GITHUB_OUTPUT, 'a') as outf: - print(f'viewer_exe={exepath}', file=outf) + self.set_github_output_path('viewer_exe', self.final_exe()) with self.prefix(src=os.path.join(pkgdir, "VMP")): # include the compiled launcher scripts so that it gets included in the file_list @@ -848,6 +853,7 @@ class DarwinManifest(ViewerManifest): def construct(self): # copy over the build result (this is a no-op if run within the xcode script) self.path(os.path.join(self.args['configuration'], self.channel()+".app"), dst="") + self.set_github_output_path('viewer_exe', self.channel() + ".app") pkgdir = os.path.join(self.args['build'], os.pardir, 'packages') relpkgdir = os.path.join(pkgdir, "lib", "release") -- cgit v1.2.3 From b13c1f77c7004ab991eb38637ccde01bc4f13b6f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 2 Sep 2023 09:44:24 -0400 Subject: SL-19242: Emphasize to upload-artifact that our .app is a directory --- indra/newview/viewer_manifest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index d07ec22496..1f67324aaa 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -853,7 +853,7 @@ class DarwinManifest(ViewerManifest): def construct(self): # copy over the build result (this is a no-op if run within the xcode script) self.path(os.path.join(self.args['configuration'], self.channel()+".app"), dst="") - self.set_github_output_path('viewer_exe', self.channel() + ".app") + self.set_github_output_path('viewer_exe', self.channel() + ".app/") pkgdir = os.path.join(self.args['build'], os.pardir, 'packages') relpkgdir = os.path.join(pkgdir, "lib", "release") -- cgit v1.2.3 From 3eea556c28f86d1c1334879ff4d7dfc36201485e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 2 Sep 2023 11:02:23 -0400 Subject: SL-19243: Post xcarchive.zip instead of separate symbols tarball. On Mac, in the CMake USE_BUGSPLAT logic, we created both xcarchive.zip (which is what BugSplat wants to see) and secondlife-symbols-darwin -64.tar.bz2 (which we don't think is used for anything). The tarball was posted to codeticket -- but why? If the point is to manually re-upload to BugSplat in case of failure, we'll do better saving xcarchive.zip to codeticket. For SL-19243, posting xcarchive.zip directly supports the goal of breaking out the upload to BugSplat as a separate step. Anyway, since xcarchive.zip is a superset of the tarball, the tarball can be recreated from the zip file, whereas the zip file can't be recreated from the tarball without opening the .dmg installer and extracting the viewer executable. If the xcarchive.zip file exists (that is, on Mac), post that to codeticket or GitHub, as applicable, instead of the tarball. In fact, in the USE_BUGSPLAT case, don't even bother creating the tarball since we're going to ignore it. Make the new build.sh logic that insists on BUGSPLAT_USER and BUGSPLAT_PASS conditional on BUGSPLAT_DB. --- indra/newview/CMakeLists.txt | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) (limited to 'indra') diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index dbd1f1b4ac..f23e4fa849 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -2141,20 +2141,6 @@ if (PACKAGE AND (RELEASE_CRASH_REPORTING OR NON_RELEASE_CRASH_REPORTING) AND VIE ) add_custom_target(dsym_generate DEPENDS "${VIEWER_APP_DSYM}") add_dependencies(dsym_generate ${VIEWER_BINARY_NAME}) - add_custom_command(OUTPUT "${VIEWER_SYMBOL_FILE}" - # See above comments about "tar ...j" - COMMAND "tar" - ARGS - "cjf" - "${VIEWER_SYMBOL_FILE}" - "-C" - "${VIEWER_APP_DSYM}/.." - "${product}.dSYM" - DEPENDS "${VIEWER_APP_DSYM}" - COMMENT "Packing dSYM into ${VIEWER_SYMBOL_FILE}" - ) - add_custom_target(dsym_tarball DEPENDS "${VIEWER_SYMBOL_FILE}") - add_dependencies(dsym_tarball dsym_generate) add_custom_command(OUTPUT "${VIEWER_APP_XCARCHIVE}" COMMAND "zip" ARGS @@ -2172,16 +2158,15 @@ if (PACKAGE AND (RELEASE_CRASH_REPORTING OR NON_RELEASE_CRASH_REPORTING) AND VIE add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/dsym.stamp" COMMAND rm -rf "${VIEWER_APP_DSYM}" COMMAND touch "${CMAKE_CURRENT_BINARY_DIR}/dsym.stamp" - DEPENDS "${VIEWER_SYMBOL_FILE}" "${VIEWER_APP_XCARCHIVE}" + DEPENDS "${VIEWER_APP_XCARCHIVE}" COMMENT "Cleaning up dSYM" ) add_custom_target(generate_symbols DEPENDS "${VIEWER_APP_DSYM}" - "${VIEWER_SYMBOL_FILE}" "${VIEWER_APP_XCARCHIVE}" "${CMAKE_CURRENT_BINARY_DIR}/dsym.stamp" ) - add_dependencies(generate_symbols dsym_tarball dsym_xcarchive) + add_dependencies(generate_symbols dsym_xcarchive) endif (DARWIN) if (LINUX) # TBD -- cgit v1.2.3 From 2eda2eb21b720646ec8acc16d47551226f1c059d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 2 Sep 2023 12:17:06 -0400 Subject: SL-19242: Fix duplicated 'Second Life Mumble.app' path component in the path passed as the macOS viewer_exe GitHub output. --- indra/newview/viewer_manifest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 1f67324aaa..f6282743bb 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -853,7 +853,8 @@ class DarwinManifest(ViewerManifest): def construct(self): # copy over the build result (this is a no-op if run within the xcode script) self.path(os.path.join(self.args['configuration'], self.channel()+".app"), dst="") - self.set_github_output_path('viewer_exe', self.channel() + ".app/") + # capture the entire destination app bundle + self.set_github_output_path('viewer_exe', '') pkgdir = os.path.join(self.args['build'], os.pardir, 'packages') relpkgdir = os.path.join(pkgdir, "lib", "release") -- 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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 db71f834bc4b6898d8b38b484eb953924d42a5db Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 7 Sep 2023 11:44:53 -0400 Subject: SL-18837: Without USE_BUGSPLAT, no target generate_symbols. --- indra/newview/CMakeLists.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'indra') diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index f23e4fa849..68f96e5669 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -2171,10 +2171,9 @@ if (PACKAGE AND (RELEASE_CRASH_REPORTING OR NON_RELEASE_CRASH_REPORTING) AND VIE if (LINUX) # TBD endif (LINUX) - endif (USE_BUGSPLAT) - # for both Bugsplat and Breakpad - add_dependencies(llpackage generate_symbols) + add_dependencies(llpackage generate_symbols) + endif (USE_BUGSPLAT) endif () if (LL_TESTS) -- cgit v1.2.3 From 95aa00f7427b7d19ab502862b3018012c1bf1904 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 7 Sep 2023 13:40:46 -0400 Subject: SL-18837: Fix minor merge glitch. --- indra/llcommon/tests/llsdserialize_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index 2de7df6f36..76e9ecc293 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -2081,7 +2081,7 @@ namespace tut " for item in DATA:\n" " serialized = llsd." << pyformatter << "(item)\n" " f.write(lenformat.pack(len(serialized)))\n" - " f.write(serialized)\n"); + " f.write(serialized)\n";}); std::ifstream inf(file.getName().c_str()); LLSD item; -- 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(-) (limited to 'indra') 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 a45c9f68c3e2600f48b25cc5cc74ef47cd83005b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 8 Sep 2023 08:58:32 -0400 Subject: SL-18837: Add debugging output to llsdserialize_test.cpp. --- indra/llcommon/tests/llsdserialize_test.cpp | 90 ++++++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index 76e9ecc293..ca63e74c6c 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -55,7 +55,9 @@ typedef U32 uint32_t; #include "../test/lltut.h" #include "../test/namedtempfile.h" #include "stringize.h" +#include #include +#include typedef std::function FormatterFunction; typedef std::function ParserFunction; @@ -65,6 +67,81 @@ std::vector string_to_vector(const std::string& str) return std::vector(str.begin(), str.end()); } +// Format a given byte string as 2-digit hex values, no separators +// Usage: std::cout << hexdump(somestring) << ... +class hexdump +{ +public: + hexdump(const char* data, size_t len): + hexdump(reinterpret_cast(data), len) + {} + + hexdump(const U8* data, size_t len): + mData(data, data + len) + {} + + hexdump(const hexdump&) = delete; + hexdump& operator=(const hexdump&) = delete; + + friend std::ostream& operator<<(std::ostream& out, const hexdump& self) + { + auto oldfmt{ out.flags() }; + auto oldfill{ out.fill() }; + out.setf(std::ios_base::hex, std::ios_base::basefield); + out.fill('0'); + for (auto c : self.mData) + { + out << std::setw(2) << unsigned(c); + } + out.setf(oldfmt, std::ios_base::basefield); + out.fill(oldfill); + return out; + } + +private: + std::vector mData; +}; + +// Format a given byte string as a mix of printable characters and, for each +// non-printable character, "\xnn" +// Usage: std::cout << hexmix(somestring) << ... +class hexmix +{ +public: + hexmix(const char* data, size_t len): + mData(data, len) + {} + + hexmix(const hexmix&) = delete; + hexmix& operator=(const hexmix&) = delete; + + friend std::ostream& operator<<(std::ostream& out, const hexmix& self) + { + auto oldfmt{ out.flags() }; + auto oldfill{ out.fill() }; + out.setf(std::ios_base::hex, std::ios_base::basefield); + out.fill('0'); + for (auto c : self.mData) + { + // std::isprint() must be passed an unsigned char! + if (std::isprint(static_cast(c))) + { + out << c; + } + else + { + out << "\\x" << std::setw(2) << unsigned(c); + } + } + out.setf(oldfmt, std::ios_base::basefield); + out.fill(oldfill); + return out; + } + +private: + std::string mData; +}; + namespace tut { struct sd_xml_data @@ -1909,7 +1986,14 @@ namespace tut auto buffstr{ buffer.str() }; int bufflen{ static_cast(buffstr.length()) }; out.write(reinterpret_cast(&bufflen), sizeof(bufflen)); + LL_DEBUGS("topy") << "Wrote length: " + << hexdump(reinterpret_cast(&bufflen), + sizeof(bufflen)) + << LL_ENDL; out.write(buffstr.c_str(), buffstr.length()); + LL_DEBUGS("topy") << "Wrote data: " + << hexmix(buffstr.c_str(), buffstr.length()) + << LL_ENDL; } } @@ -1938,8 +2022,8 @@ namespace tut " else:\n" " raise AssertionError('Too many data items')\n"; - // Create an llsdXXXXXX file containing 'data' serialized to - // notation. + // Create an llsdXXXXXX file containing 'data' serialized per + // FormatterFunction. NamedTempFile file("llsd", // NamedTempFile's function constructor // takes a callable. To this callable it passes the @@ -1958,11 +2042,13 @@ namespace tut "lenformat = struct.Struct('i')\n" "def parse_each(inf):\n" " for rawlen in iter(partial(inf.read, lenformat.size), b''):\n" + " print('Read length:', ''.join(('%02x' % b) for b in rawlen))\n" " len = lenformat.unpack(rawlen)[0]\n" // Since llsd.parse() has no max_bytes argument, instead of // passing the input stream directly to parse(), read the item // into a distinct bytes object and parse that. " data = inf.read(len)\n" + " print('Read data: ', repr(data))\n" " try:\n" " frombytes = llsd.parse(data)\n" " except llsd.LLSDParseError as err:\n" -- cgit v1.2.3 From eb8458587537e06df23447db56b9910a0d4e451e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 8 Sep 2023 09:50:38 -0400 Subject: SL-18837: NamedTempFile must be binary mode on Windows. --- indra/test/namedtempfile.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/test/namedtempfile.h b/indra/test/namedtempfile.h index 525a35000d..3a994ae798 100644 --- a/indra/test/namedtempfile.h +++ b/indra/test/namedtempfile.h @@ -70,7 +70,7 @@ public: void peep() { std::cout << "File '" << mPath << "' contains:\n"; - boost::filesystem::ifstream reader(mPath); + boost::filesystem::ifstream reader(mPath, std::ios::binary); std::string line; while (std::getline(reader, line)) std::cout << line << '\n'; @@ -103,7 +103,7 @@ protected: { // Create file in a temporary place. mPath = temp_path(pfx, sfx); - boost::filesystem::ofstream out{ mPath }; + boost::filesystem::ofstream out{ mPath, std::ios::binary }; // Write desired content. func(out); } -- cgit v1.2.3 From 6cb906c44908a304af26e3ea95de88ff34ef46f7 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 8 Sep 2023 10:35:10 -0400 Subject: SL-18837: Add OpenAL::createDefaultStreamingAudioImpl(). LLAudioEngine added a new abstract virtual method that wasn't yet implemented for LLStreamingAudio_OpenAL. --- indra/llaudio/llaudioengine_openal.cpp | 5 +++++ indra/llaudio/llaudioengine_openal.h | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/llaudio/llaudioengine_openal.cpp b/indra/llaudio/llaudioengine_openal.cpp index 0a79614424..e6c62caae9 100644 --- a/indra/llaudio/llaudioengine_openal.cpp +++ b/indra/llaudio/llaudioengine_openal.cpp @@ -119,6 +119,11 @@ std::string LLAudioEngine_OpenAL::getDriverName(bool verbose) return version.str(); } +LLStreamingAudioInterface *LLAudioEngine_OpenAL::createDefaultStreamingAudioImpl() const +{ + return new LLStreamingAudio_OpenAL(); +} + // virtual void LLAudioEngine_OpenAL::allocateListener() { diff --git a/indra/llaudio/llaudioengine_openal.h b/indra/llaudio/llaudioengine_openal.h index a3cab97cd2..cd55261813 100644 --- a/indra/llaudio/llaudioengine_openal.h +++ b/indra/llaudio/llaudioengine_openal.h @@ -40,8 +40,9 @@ class LLAudioEngine_OpenAL : public LLAudioEngine LLAudioEngine_OpenAL(); virtual ~LLAudioEngine_OpenAL(); - virtual bool init(void *user_data, const std::string &app_title); - virtual std::string getDriverName(bool verbose); + virtual bool init(void *user_data, const std::string &app_title); + virtual std::string getDriverName(bool verbose); + virtual LLStreamingAudioInterface* createDefaultStreamingAudioImpl() const; virtual void allocateListener(); virtual void shutdown(); -- cgit v1.2.3 From 46bd102e80178abb094b5dac6fe9c476e044eaed Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 8 Sep 2023 11:00:16 -0400 Subject: SL-18837: Typo for LLAudioEngine_OpenAL --- indra/llaudio/llaudioengine_openal.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/llaudio/llaudioengine_openal.cpp b/indra/llaudio/llaudioengine_openal.cpp index e6c62caae9..d2a66c8c0f 100644 --- a/indra/llaudio/llaudioengine_openal.cpp +++ b/indra/llaudio/llaudioengine_openal.cpp @@ -121,7 +121,7 @@ std::string LLAudioEngine_OpenAL::getDriverName(bool verbose) LLStreamingAudioInterface *LLAudioEngine_OpenAL::createDefaultStreamingAudioImpl() const { - return new LLStreamingAudio_OpenAL(); + return new LLAudioEngine_OpenAL(); } // virtual -- cgit v1.2.3 From 7e655d8c828829bd45ca0ea8c47e3ce5619b23c1 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 8 Sep 2023 12:40:37 -0400 Subject: SL-18837: Revert "Typo for LLAudioEngine_OpenAL" This reverts commit 46bd102e80178abb094b5dac6fe9c476e044eaed. --- indra/llaudio/llaudioengine_openal.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/llaudio/llaudioengine_openal.cpp b/indra/llaudio/llaudioengine_openal.cpp index d2a66c8c0f..e6c62caae9 100644 --- a/indra/llaudio/llaudioengine_openal.cpp +++ b/indra/llaudio/llaudioengine_openal.cpp @@ -121,7 +121,7 @@ std::string LLAudioEngine_OpenAL::getDriverName(bool verbose) LLStreamingAudioInterface *LLAudioEngine_OpenAL::createDefaultStreamingAudioImpl() const { - return new LLAudioEngine_OpenAL(); + return new LLStreamingAudio_OpenAL(); } // virtual -- cgit v1.2.3 From 44181628d587f9c87cae9cdbfd57de69078000d2 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 8 Sep 2023 12:41:04 -0400 Subject: SL-18837: Revert "Add OpenAL::createDefaultStreamingAudioImpl()." This reverts commit 6cb906c44908a304af26e3ea95de88ff34ef46f7. --- indra/llaudio/llaudioengine_openal.cpp | 5 ----- indra/llaudio/llaudioengine_openal.h | 5 ++--- 2 files changed, 2 insertions(+), 8 deletions(-) (limited to 'indra') diff --git a/indra/llaudio/llaudioengine_openal.cpp b/indra/llaudio/llaudioengine_openal.cpp index e6c62caae9..0a79614424 100644 --- a/indra/llaudio/llaudioengine_openal.cpp +++ b/indra/llaudio/llaudioengine_openal.cpp @@ -119,11 +119,6 @@ std::string LLAudioEngine_OpenAL::getDriverName(bool verbose) return version.str(); } -LLStreamingAudioInterface *LLAudioEngine_OpenAL::createDefaultStreamingAudioImpl() const -{ - return new LLStreamingAudio_OpenAL(); -} - // virtual void LLAudioEngine_OpenAL::allocateListener() { diff --git a/indra/llaudio/llaudioengine_openal.h b/indra/llaudio/llaudioengine_openal.h index cd55261813..a3cab97cd2 100644 --- a/indra/llaudio/llaudioengine_openal.h +++ b/indra/llaudio/llaudioengine_openal.h @@ -40,9 +40,8 @@ class LLAudioEngine_OpenAL : public LLAudioEngine LLAudioEngine_OpenAL(); virtual ~LLAudioEngine_OpenAL(); - virtual bool init(void *user_data, const std::string &app_title); - virtual std::string getDriverName(bool verbose); - virtual LLStreamingAudioInterface* createDefaultStreamingAudioImpl() const; + virtual bool init(void *user_data, const std::string &app_title); + virtual std::string getDriverName(bool verbose); virtual void allocateListener(); virtual void shutdown(); -- cgit v1.2.3 From d459b3a1ca317c6927dffacda3aac3b580c0dfb1 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Sat, 26 Aug 2023 21:04:05 +0200 Subject: Fix builds using OpenAL (cherry picked from commit fd73b6e5cf6341d606628646b73a0d05223b74bc) --- indra/llaudio/llaudioengine_openal.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra') 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 c7546ea65e55143ff3d2d82d8c289bbac7fffe0f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 8 Sep 2023 14:14:09 -0400 Subject: SL-18837: Make llsdserialize_test debug output conditional. Move hexdump() and hexmix() stream formatters to new hexdump.h for potential use by other tests. In toPythonUsing() helper function, add a temp file to receive Python script debug output, and direct debug output to that file. On test failure, dump the contents of that file to the log. Give NamedTempFile::peep() an optional target std::ostream; refactor implementation as peep_via() that accepts a callable to process each text line. Add operator<<() to stream the contents of a NamedTempFile object to ostream -- but don't use that with LL_DEBUGS(), as it flattens the file contents into a single log line. Instead add peep_log(), which streams each individual text line to LL_DEBUGS(). --- indra/llcommon/tests/llsdserialize_test.cpp | 202 +++++++++++----------------- indra/test/hexdump.h | 97 +++++++++++++ indra/test/namedtempfile.h | 25 +++- 3 files changed, 193 insertions(+), 131 deletions(-) create mode 100644 indra/test/hexdump.h (limited to 'indra') diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index ca63e74c6c..ac40125f75 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -52,12 +52,12 @@ typedef U32 uint32_t; #include "llformat.h" #include "llmemorystream.h" +#include "../test/hexdump.h" #include "../test/lltut.h" #include "../test/namedtempfile.h" #include "stringize.h" -#include +#include "StringVec.h" #include -#include typedef std::function FormatterFunction; typedef std::function ParserFunction; @@ -67,81 +67,6 @@ std::vector string_to_vector(const std::string& str) return std::vector(str.begin(), str.end()); } -// Format a given byte string as 2-digit hex values, no separators -// Usage: std::cout << hexdump(somestring) << ... -class hexdump -{ -public: - hexdump(const char* data, size_t len): - hexdump(reinterpret_cast(data), len) - {} - - hexdump(const U8* data, size_t len): - mData(data, data + len) - {} - - hexdump(const hexdump&) = delete; - hexdump& operator=(const hexdump&) = delete; - - friend std::ostream& operator<<(std::ostream& out, const hexdump& self) - { - auto oldfmt{ out.flags() }; - auto oldfill{ out.fill() }; - out.setf(std::ios_base::hex, std::ios_base::basefield); - out.fill('0'); - for (auto c : self.mData) - { - out << std::setw(2) << unsigned(c); - } - out.setf(oldfmt, std::ios_base::basefield); - out.fill(oldfill); - return out; - } - -private: - std::vector mData; -}; - -// Format a given byte string as a mix of printable characters and, for each -// non-printable character, "\xnn" -// Usage: std::cout << hexmix(somestring) << ... -class hexmix -{ -public: - hexmix(const char* data, size_t len): - mData(data, len) - {} - - hexmix(const hexmix&) = delete; - hexmix& operator=(const hexmix&) = delete; - - friend std::ostream& operator<<(std::ostream& out, const hexmix& self) - { - auto oldfmt{ out.flags() }; - auto oldfill{ out.fill() }; - out.setf(std::ios_base::hex, std::ios_base::basefield); - out.fill('0'); - for (auto c : self.mData) - { - // std::isprint() must be passed an unsigned char! - if (std::isprint(static_cast(c))) - { - out << c; - } - else - { - out << "\\x" << std::setw(2) << unsigned(c); - } - } - out.setf(oldfmt, std::ios_base::basefield); - out.fill(oldfill); - return out; - } - -private: - std::string mData; -}; - namespace tut { struct sd_xml_data @@ -1868,16 +1793,12 @@ namespace tut // helper for TestPythonCompatible static std::string import_llsd("import os.path\n" "import sys\n" - "try:\n" - // new freestanding llsd package - " import llsd\n" - "except ImportError:\n" - // older llbase.llsd module - " from llbase import llsd\n"); + "import llsd\n"); // helper for TestPythonCompatible - template - void python(const std::string& desc, const CONTENT& script, int expect=0) + template + void python_expect(const std::string& desc, const CONTENT& script, int expect=0, + ARGS&&... args) { auto PYTHON(LLStringUtil::getenv("PYTHON")); ensure("Set $PYTHON to the Python interpreter", !PYTHON.empty()); @@ -1888,7 +1809,8 @@ namespace tut std::string q("\""); std::string qPYTHON(q + PYTHON + q); std::string qscript(q + scriptfile.getName() + q); - int rc = _spawnl(_P_WAIT, PYTHON.c_str(), qPYTHON.c_str(), qscript.c_str(), NULL); + int rc = _spawnl(_P_WAIT, PYTHON.c_str(), qPYTHON.c_str(), qscript.c_str(), + std::forward(args)..., NULL); if (rc == -1) { char buffer[256]; @@ -1904,6 +1826,10 @@ namespace tut LLProcess::Params params; params.executable = PYTHON; params.args.add(scriptfile.getName()); + for (const std::string& arg : StringVec{ std::forward(args)... }) + { + params.args.add(arg); + } LLProcessPtr py(LLProcess::create(params)); ensure(STRINGIZE("Couldn't launch " << desc << " script"), bool(py)); // Implementing timeout would mean messing with alarm() and @@ -1938,6 +1864,14 @@ namespace tut #endif } + // helper for TestPythonCompatible + template + void python(const std::string& desc, const CONTENT& script, ARGS&&... args) + { + // plain python() expects rc 0 + python_expect(desc, script, 0, std::forward(args)...); + } + struct TestPythonCompatible { TestPythonCompatible() {} @@ -1952,10 +1886,10 @@ namespace tut void TestPythonCompatibleObject::test<1>() { set_test_name("verify python()"); - python("hello", - "import sys\n" - "sys.exit(17)\n", - 17); // expect nonzero rc + python_expect("hello", + "import sys\n" + "sys.exit(17)\n", + 17); // expect nonzero rc } template<> template<> @@ -1986,14 +1920,14 @@ namespace tut auto buffstr{ buffer.str() }; int bufflen{ static_cast(buffstr.length()) }; out.write(reinterpret_cast(&bufflen), sizeof(bufflen)); - LL_DEBUGS("topy") << "Wrote length: " - << hexdump(reinterpret_cast(&bufflen), - sizeof(bufflen)) - << LL_ENDL; + LL_DEBUGS() << "Wrote length: " + << hexdump(reinterpret_cast(&bufflen), + sizeof(bufflen)) + << LL_ENDL; out.write(buffstr.c_str(), buffstr.length()); - LL_DEBUGS("topy") << "Wrote data: " - << hexmix(buffstr.c_str(), buffstr.length()) - << LL_ENDL; + LL_DEBUGS() << "Wrote data: " + << hexmix(buffstr.c_str(), buffstr.length()) + << LL_ENDL; } } @@ -2033,36 +1967,50 @@ namespace tut (std::ostream& out) { writeLLSDArray(serialize, out, cdata); }); - python("read C++ " + desc, - [&](std::ostream& out){ out << - import_llsd << - "from functools import partial\n" - "import io\n" - "import struct\n" - "lenformat = struct.Struct('i')\n" - "def parse_each(inf):\n" - " for rawlen in iter(partial(inf.read, lenformat.size), b''):\n" - " print('Read length:', ''.join(('%02x' % b) for b in rawlen))\n" - " len = lenformat.unpack(rawlen)[0]\n" - // Since llsd.parse() has no max_bytes argument, instead of - // passing the input stream directly to parse(), read the item - // into a distinct bytes object and parse that. - " data = inf.read(len)\n" - " print('Read data: ', repr(data))\n" - " try:\n" - " frombytes = llsd.parse(data)\n" - " except llsd.LLSDParseError as err:\n" - " print(f'*** {err}')\n" - " print(f'Bad content:\\n{data!r}')\n" - " raise\n" - // Also try parsing from a distinct stream. - " stream = io.BytesIO(data)\n" - " fromstream = llsd.parse(stream)\n" - " assert frombytes == fromstream\n" - " yield frombytes\n" - << pydata << - // Don't forget raw-string syntax for Windows pathnames. - "verify(parse_each(open(r'" << file.getName() << "', 'rb')))\n";}); + // 'debug' starts empty because it's intended as an output file + NamedTempFile debug("debug", ""); + + try + { + python("read C++ " + desc, + [&](std::ostream& out){ out << + import_llsd << + "from functools import partial\n" + "import io\n" + "import struct\n" + "lenformat = struct.Struct('i')\n" + "def parse_each(inf):\n" + " for rawlen in iter(partial(inf.read, lenformat.size), b''):\n" + " print('Read length:', ''.join(('%02x' % b) for b in rawlen),\n" + " file=debug)\n" + " len = lenformat.unpack(rawlen)[0]\n" + // Since llsd.parse() has no max_bytes argument, instead of + // passing the input stream directly to parse(), read the item + // into a distinct bytes object and parse that. + " data = inf.read(len)\n" + " print('Read data: ', repr(data), file=debug)\n" + " try:\n" + " frombytes = llsd.parse(data)\n" + " except llsd.LLSDParseError as err:\n" + " print(f'*** {err}')\n" + " print(f'Bad content:\\n{data!r}')\n" + " raise\n" + // Also try parsing from a distinct stream. + " stream = io.BytesIO(data)\n" + " fromstream = llsd.parse(stream)\n" + " assert frombytes == fromstream\n" + " yield frombytes\n" + << pydata << + // Don't forget raw-string syntax for Windows pathnames. + "debug = open(r'" << debug.getName() << "', 'w')\n" + "verify(parse_each(open(r'" << file.getName() << "', 'rb')))\n";}); + } + catch (const failure&) + { + LL_DEBUGS() << "Script debug output:" << LL_ENDL; + debug.peep_log(); + throw; + } } template<> template<> diff --git a/indra/test/hexdump.h b/indra/test/hexdump.h new file mode 100644 index 0000000000..dd7cbaaa3c --- /dev/null +++ b/indra/test/hexdump.h @@ -0,0 +1,97 @@ +/** + * @file hexdump.h + * @author Nat Goodspeed + * @date 2023-09-08 + * @brief Provide hexdump() and hexmix() ostream formatters + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Copyright (c) 2023, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_HEXDUMP_H) +#define LL_HEXDUMP_H + +#include +#include +#include +#include + +// Format a given byte string as 2-digit hex values, no separators +// Usage: std::cout << hexdump(somestring) << ... +class hexdump +{ +public: + hexdump(const std::string_view& data): + hexdump(data.data(), data.length()) + {} + + hexdump(const char* data, size_t len): + hexdump(reinterpret_cast(data), len) + {} + + hexdump(const unsigned char* data, size_t len): + mData(data, data + len) + {} + + friend std::ostream& operator<<(std::ostream& out, const hexdump& self) + { + auto oldfmt{ out.flags() }; + auto oldfill{ out.fill() }; + out.setf(std::ios_base::hex, std::ios_base::basefield); + out.fill('0'); + for (auto c : self.mData) + { + out << std::setw(2) << unsigned(c); + } + out.setf(oldfmt, std::ios_base::basefield); + out.fill(oldfill); + return out; + } + +private: + std::vector mData; +}; + +// Format a given byte string as a mix of printable characters and, for each +// non-printable character, "\xnn" +// Usage: std::cout << hexmix(somestring) << ... +class hexmix +{ +public: + hexmix(const std::string_view& data): + mData(data) + {} + + hexmix(const char* data, size_t len): + mData(data, len) + {} + + friend std::ostream& operator<<(std::ostream& out, const hexmix& self) + { + auto oldfmt{ out.flags() }; + auto oldfill{ out.fill() }; + out.setf(std::ios_base::hex, std::ios_base::basefield); + out.fill('0'); + for (auto c : self.mData) + { + // std::isprint() must be passed an unsigned char! + if (std::isprint(static_cast(c))) + { + out << c; + } + else + { + out << "\\x" << std::setw(2) << unsigned(c); + } + } + out.setf(oldfmt, std::ios_base::basefield); + out.fill(oldfill); + return out; + } + +private: + std::string mData; +}; + +#endif /* ! defined(LL_HEXDUMP_H) */ diff --git a/indra/test/namedtempfile.h b/indra/test/namedtempfile.h index 3a994ae798..ad14cebbd1 100644 --- a/indra/test/namedtempfile.h +++ b/indra/test/namedtempfile.h @@ -67,14 +67,31 @@ public: std::string getName() const { return mPath.string(); } - void peep() + template + void peep_via(CALLABLE&& callable) const { - std::cout << "File '" << mPath << "' contains:\n"; + std::forward(callable)(stringize("File '", mPath, "' contains:")); boost::filesystem::ifstream reader(mPath, std::ios::binary); std::string line; while (std::getline(reader, line)) - std::cout << line << '\n'; - std::cout << "---\n"; + std::forward(callable)(line); + std::forward(callable)("---"); + } + + void peep_log() const + { + peep_via([](const std::string& line){ LL_DEBUGS() << line << LL_ENDL; }); + } + + void peep(std::ostream& out=std::cout) const + { + peep_via([&out](const std::string& line){ out << line << '\n'; }); + } + + friend std::ostream& operator<<(std::ostream& out, const NamedTempFile& self) + { + self.peep(out); + return out; } static boost::filesystem::path temp_path(const std::string_view& pfx="", -- 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(-) (limited to 'indra') 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 2d04cc14d3805df982d51d96d2e3d180f5ef0b34 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 8 Sep 2023 17:01:46 -0400 Subject: SL-19242: Post -app artifact, not -exe, with entire install image. Previously we posted Windows-exe, macOS-exe artifacts that were a little inconsistent: Windows-exe contained just the Windows executable, whereas macOS-exe contained the whole .app tree (but without the .app directory). Change to post Windows-app, macOS-app artifacts that each contain the whole viewer install image, including the top-level application name directory. This is what we'll need to codesign and notarize. --- indra/newview/viewer_manifest.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'indra') diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index f6282743bb..679a3441b9 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -489,8 +489,11 @@ class WindowsManifest(ViewerManifest): if self.is_packaging_viewer(): # Find secondlife-bin.exe in the 'configuration' dir, then rename it to the result of final_exe. self.path(src='%s/secondlife-bin.exe' % self.args['configuration'], dst=self.final_exe()) - # emit that as one of the GitHub step outputs - self.set_github_output_path('viewer_exe', self.final_exe()) + # Emit the whole app image as one of the GitHub step outputs. The + # current get_dst_prefix() is the top-level contents of the app + # directory -- so hop outward to the directory containing the app + # name. + self.set_github_output_path('viewer_app', os.pardir) with self.prefix(src=os.path.join(pkgdir, "VMP")): # include the compiled launcher scripts so that it gets included in the file_list @@ -853,8 +856,9 @@ class DarwinManifest(ViewerManifest): def construct(self): # copy over the build result (this is a no-op if run within the xcode script) self.path(os.path.join(self.args['configuration'], self.channel()+".app"), dst="") - # capture the entire destination app bundle - self.set_github_output_path('viewer_exe', '') + # capture the entire destination app bundle, including the containing + # .app directory + self.set_github_output_path('viewer_app', os.pardir) pkgdir = os.path.join(self.args['build'], os.pardir, 'packages') relpkgdir = os.path.join(pkgdir, "lib", "release") -- cgit v1.2.3 From ee82cd046f9bedc6a3eac96afc90f4413786669f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 8 Sep 2023 19:25:47 -0400 Subject: SL-19242: Resolve '..' in viwer_app path before trying to upload. --- indra/newview/viewer_manifest.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'indra') diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 679a3441b9..e93a3db6d6 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -492,8 +492,11 @@ class WindowsManifest(ViewerManifest): # Emit the whole app image as one of the GitHub step outputs. The # current get_dst_prefix() is the top-level contents of the app # directory -- so hop outward to the directory containing the app - # name. - self.set_github_output_path('viewer_app', os.pardir) + # name. But upload_artifact refuses to deal with '..', so resolve + # the path before setting viewer_app. + self.set_github_output_path( + 'viewer_app', + os.path.abspath(os.path.join(self.get_dst_prefix(), os.pardir))) with self.prefix(src=os.path.join(pkgdir, "VMP")): # include the compiled launcher scripts so that it gets included in the file_list @@ -858,7 +861,9 @@ class DarwinManifest(ViewerManifest): self.path(os.path.join(self.args['configuration'], self.channel()+".app"), dst="") # capture the entire destination app bundle, including the containing # .app directory - self.set_github_output_path('viewer_app', os.pardir) + self.set_github_output_path( + 'viewer_app', + os.path.abspath(os.path.join(self.get_dst_prefix(), os.pardir))) pkgdir = os.path.join(self.args['build'], os.pardir, 'packages') relpkgdir = os.path.join(pkgdir, "lib", "release") -- cgit v1.2.3 From dc0ebc96a4556b99b515364465b23a64e84f0899 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 9 Sep 2023 09:36:03 -0400 Subject: SL-19242: Try passing upload-artifact a normalized relative path. --- indra/newview/viewer_manifest.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) (limited to 'indra') diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index e93a3db6d6..dd9ca569ce 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -407,7 +407,8 @@ class ViewerManifest(LLManifest): return os.path.relpath(abspath(path), abspath(base)) def set_github_output_path(self, variable, path): - self.set_github_output(variable, os.path.join(self.get_dst_prefix(), path)) + self.set_github_output(variable, + os.path.normpath(os.path.join(self.get_dst_prefix(), path))) def set_github_output(self, variable, value): GITHUB_OUTPUT = os.getenv('GITHUB_OUTPUT') @@ -492,11 +493,8 @@ class WindowsManifest(ViewerManifest): # Emit the whole app image as one of the GitHub step outputs. The # current get_dst_prefix() is the top-level contents of the app # directory -- so hop outward to the directory containing the app - # name. But upload_artifact refuses to deal with '..', so resolve - # the path before setting viewer_app. - self.set_github_output_path( - 'viewer_app', - os.path.abspath(os.path.join(self.get_dst_prefix(), os.pardir))) + # name. + self.set_github_output_path('viewer_app', os.pardir) with self.prefix(src=os.path.join(pkgdir, "VMP")): # include the compiled launcher scripts so that it gets included in the file_list @@ -861,9 +859,7 @@ class DarwinManifest(ViewerManifest): self.path(os.path.join(self.args['configuration'], self.channel()+".app"), dst="") # capture the entire destination app bundle, including the containing # .app directory - self.set_github_output_path( - 'viewer_app', - os.path.abspath(os.path.join(self.get_dst_prefix(), os.pardir))) + self.set_github_output_path('viewer_app', os.pardir) pkgdir = os.path.join(self.args['build'], os.pardir, 'packages') relpkgdir = os.path.join(pkgdir, "lib", "release") -- cgit v1.2.3 From 29300a1fd356b7355ecfb56951e7d7ad0553ef15 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 11 Sep 2023 10:07:51 -0400 Subject: SL-19242: Try harder to post artifacts containing exactly app image. In a Windows build tree, we don't actually have an app-named top directory, so don't package its containing directory -- just the app dir itself, e.g. "newview/Release". In a Mac build tree, though we do have "Second Life Mumble.app", its parent directory also contains other large stuff. Try posting a temp directory containing a symlink to the .app. Ditch the "!*.bat" exclusion: the presence of a second path (even an exclusion) changes how upload-artifact nests its contents. --- indra/newview/viewer_manifest.py | 46 +++++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 10 deletions(-) (limited to 'indra') diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index dd9ca569ce..4084c1d9a4 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -37,6 +37,7 @@ import re import shutil import subprocess import sys +import tempfile import time viewer_dir = os.path.dirname(__file__) @@ -490,11 +491,8 @@ class WindowsManifest(ViewerManifest): if self.is_packaging_viewer(): # Find secondlife-bin.exe in the 'configuration' dir, then rename it to the result of final_exe. self.path(src='%s/secondlife-bin.exe' % self.args['configuration'], dst=self.final_exe()) - # Emit the whole app image as one of the GitHub step outputs. The - # current get_dst_prefix() is the top-level contents of the app - # directory -- so hop outward to the directory containing the app - # name. - self.set_github_output_path('viewer_app', os.pardir) + # Emit the whole app image as one of the GitHub step outputs. + self.set_github_output_path('viewer_app', '') with self.prefix(src=os.path.join(pkgdir, "VMP")): # include the compiled launcher scripts so that it gets included in the file_list @@ -855,11 +853,39 @@ class DarwinManifest(ViewerManifest): return bool(set(["package", "unpacked"]).intersection(self.args['actions'])) def construct(self): - # copy over the build result (this is a no-op if run within the xcode script) - self.path(os.path.join(self.args['configuration'], self.channel()+".app"), dst="") - # capture the entire destination app bundle, including the containing - # .app directory - self.set_github_output_path('viewer_app', os.pardir) + # copy over the build result (this is a no-op if run within the xcode + # script) + appname = self.channel() + ".app" + self.path(os.path.join(self.args['configuration'], appname), dst="") + RUNNER_TEMP = os.getenv('RUNNER_TEMP') + # When running as a GitHub Action job, RUNNER_TEMP is the recommended + # temp directory. If we're not running on GitHub, don't create this + # temp directory or this symlink: we don't clean them up, trusting + # that the runner is itself transient. On a dev machine, that would + # result in temp-directory clutter. + if RUNNER_TEMP: + # We want an artifact containing the "Second Life Mumble.app" + # directory, which in turn contains the whole app bundle. + # Unfortunately, the directory that contains the .app directory + # also contains other stuff, notably the xcarchive.zip, which is + # itself enormous. Create a temp directory containing only (a link + # to) our .app dir, and specify that as the directory to upload. + wrapdir = tempfile.mkdtemp(dir=RUNNER_TEMP) + applink = os.path.join(wrapdir, appname) + # This link will be used by a different job step, so link to an + # absolute path: we can't guarantee that the other step will have + # the same current directory. + # diagnostic output + parentdir = os.path.abspath(os.path.join(self.get_dst_prefix(), os.pardir)) + for dir in parentdir, os.path.join(parentdir, appname): + print(f'Contents of {dir}:') + for item in os.listdir(dir): + print(f' {item}') + # end diagnostic output + appreal = os.path.abspath(os.path.join(self.get_dst_prefix(), os.pardir, appname)) + print(f"Linking {applink} => {appreal}") + os.symlink(appreal, applink) + self.set_github_output_path('viewer_app', wrapdir) pkgdir = os.path.join(self.args['build'], os.pardir, 'packages') relpkgdir = os.path.join(pkgdir, "lib", "release") -- cgit v1.2.3 From 0107f030c85c1ea122309a21a4d6790fc22e6d10 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 11 Sep 2023 12:53:34 -0400 Subject: SL-19242: Eliminate cruft from Windows app image artifact --- indra/newview/viewer_manifest.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) (limited to 'indra') diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 4084c1d9a4..33d5720320 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -34,6 +34,7 @@ import os.path import plistlib import random import re +import secrets import shutil import subprocess import sys @@ -411,11 +412,18 @@ class ViewerManifest(LLManifest): self.set_github_output(variable, os.path.normpath(os.path.join(self.get_dst_prefix(), path))) - def set_github_output(self, variable, value): + def set_github_output(self, variable, *values): GITHUB_OUTPUT = os.getenv('GITHUB_OUTPUT') - if GITHUB_OUTPUT: + if GITHUB_OUTPUT and values: with open(GITHUB_OUTPUT, 'a') as outf: - print('='.join((variable, value)), file=outf) + if len(values) == 1: + print('='.join((variable, value)), file=outf) + else: + delim = secrets.token_hex(8) + print('<<'.join((variable, delim)), file=outf) + for value in values: + print(value, file=outf) + print(delim, file=outf) class WindowsManifest(ViewerManifest): @@ -492,7 +500,12 @@ class WindowsManifest(ViewerManifest): # Find secondlife-bin.exe in the 'configuration' dir, then rename it to the result of final_exe. self.path(src='%s/secondlife-bin.exe' % self.args['configuration'], dst=self.final_exe()) # Emit the whole app image as one of the GitHub step outputs. - self.set_github_output_path('viewer_app', '') + self.set_github_output('viewer_app', + self.get_dst_prefix(), # whole app directory + '!secondlife-bin.*', # except for this stuff + '!*.bat', + '!*.tar.bz2', + '!*.nsi') with self.prefix(src=os.path.join(pkgdir, "VMP")): # include the compiled launcher scripts so that it gets included in the file_list -- cgit v1.2.3 From 0d0dafc1be14d0c4b8cf7b02ac949182d1eb093c Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 11 Sep 2023 13:33:30 -0400 Subject: SL-19242: Fix minor error in viewer_manifest.set_github_output(). --- indra/newview/viewer_manifest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 33d5720320..a5415bb4bc 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -417,7 +417,7 @@ class ViewerManifest(LLManifest): if GITHUB_OUTPUT and values: with open(GITHUB_OUTPUT, 'a') as outf: if len(values) == 1: - print('='.join((variable, value)), file=outf) + print('='.join((variable, values[0])), file=outf) else: delim = secrets.token_hex(8) print('<<'.join((variable, delim)), file=outf) -- cgit v1.2.3 From db2953e5de12eb5eea347f4069be9c0ab015dcdb Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 11 Sep 2023 15:29:32 -0400 Subject: SL-19242: Have to prefix upload-artifact exclude paths with pathname. --- indra/newview/viewer_manifest.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) (limited to 'indra') diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index a5415bb4bc..1a8973ff84 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -500,12 +500,15 @@ class WindowsManifest(ViewerManifest): # Find secondlife-bin.exe in the 'configuration' dir, then rename it to the result of final_exe. self.path(src='%s/secondlife-bin.exe' % self.args['configuration'], dst=self.final_exe()) # Emit the whole app image as one of the GitHub step outputs. - self.set_github_output('viewer_app', - self.get_dst_prefix(), # whole app directory - '!secondlife-bin.*', # except for this stuff - '!*.bat', - '!*.tar.bz2', - '!*.nsi') + appbase = self.relpath(self.get_dst_prefix(), base=os.getcwd()) + self.set_github_output('viewer_app', appbase, + # except for this stuff + *(('!' + os.path.join(appbase, pattern)) + for pattern in ( + 'secondlife-bin.*', + '*.bat', + '*.tar.bz2', + '*.nsi'))) with self.prefix(src=os.path.join(pkgdir, "VMP")): # include the compiled launcher scripts so that it gets included in the file_list -- cgit v1.2.3 From 62879b6e698a839784e1319e0d555c01ce0b4220 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 11 Sep 2023 18:05:32 -0400 Subject: SL-19242: Adjust Windows relative path base directory. --- indra/newview/viewer_manifest.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 1a8973ff84..8869831635 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -499,8 +499,19 @@ class WindowsManifest(ViewerManifest): if self.is_packaging_viewer(): # Find secondlife-bin.exe in the 'configuration' dir, then rename it to the result of final_exe. self.path(src='%s/secondlife-bin.exe' % self.args['configuration'], dst=self.final_exe()) - # Emit the whole app image as one of the GitHub step outputs. - appbase = self.relpath(self.get_dst_prefix(), base=os.getcwd()) + # Emit the whole app image as one of the GitHub step outputs. When + # we feed upload-artifact multiple absolute pathnames, even just + # for exclusion, it ends up creating several extraneous directory + # levels within the artifact -- so try using only relative paths. + # One problem: as of right now, our current directory os.getcwd() + # is not the same as the initial working directory for this job + # step, meaning paths relative to our os.getcwd() won't work for + # the subsequent upload-artifact step. We're a couple directory + # levels down. Try adjusting for those when specifying the base + # for self.relpath(). + appbase = self.relpath( + self.get_dst_prefix(), + base=os.path.join(os.getcwd(), os.pardir, os.pardir)) self.set_github_output('viewer_app', appbase, # except for this stuff *(('!' + os.path.join(appbase, pattern)) -- cgit v1.2.3 From f31326189c99b18758b3754460d105a0195640c6 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 11 Sep 2023 19:38:55 -0400 Subject: SL-19242: Exclude installer from Windows-app artifact. --- indra/newview/viewer_manifest.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'indra') diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 8869831635..b994b304eb 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -499,9 +499,12 @@ class WindowsManifest(ViewerManifest): if self.is_packaging_viewer(): # Find secondlife-bin.exe in the 'configuration' dir, then rename it to the result of final_exe. self.path(src='%s/secondlife-bin.exe' % self.args['configuration'], dst=self.final_exe()) - # Emit the whole app image as one of the GitHub step outputs. When - # we feed upload-artifact multiple absolute pathnames, even just - # for exclusion, it ends up creating several extraneous directory + # Emit the whole app image as one of the GitHub step outputs. We + # want the whole app -- but NOT the extraneous build products that + # get tossed into the same directory, such as the installer and + # the symbols tarball, so add exclusions. When we feed + # upload-artifact multiple absolute pathnames, even just for + # exclusion, it ends up creating several extraneous directory # levels within the artifact -- so try using only relative paths. # One problem: as of right now, our current directory os.getcwd() # is not the same as the initial working directory for this job @@ -517,6 +520,7 @@ class WindowsManifest(ViewerManifest): *(('!' + os.path.join(appbase, pattern)) for pattern in ( 'secondlife-bin.*', + '*_Setup.exe', '*.bat', '*.tar.bz2', '*.nsi'))) -- cgit v1.2.3 From be5f210e72a12eb31152c8d8c7f28c5d8930ba45 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 13 Sep 2023 11:13:55 -0400 Subject: SL-19242: Don't exclude the prepared .nsi file from Windows-app. Since we need to run NSIS in a separate job step, allow the Windows-app build artifact to include the temporary .nsi file prepared by filling in our template. Also tweak the logic that finds and runs NSIS. --- indra/newview/viewer_manifest.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) (limited to 'indra') diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index b994b304eb..3f07eefbec 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -522,8 +522,7 @@ class WindowsManifest(ViewerManifest): 'secondlife-bin.*', '*_Setup.exe', '*.bat', - '*.tar.bz2', - '*.nsi'))) + '*.tar.bz2'))) with self.prefix(src=os.path.join(pkgdir, "VMP")): # include the compiled launcher scripts so that it gets included in the file_list @@ -831,14 +830,20 @@ class WindowsManifest(ViewerManifest): # Check two paths, one for Program Files, and one for Program Files (x86). # Yay 64bit windows. nsis_path = "makensis.exe" - for program_files in '${programfiles}', '${programfiles(x86)}': - for nesis_path in 'NSIS', 'NSIS\\Unicode': - possible_path = os.path.expandvars(f"{program_files}\\{nesis_path}\\makensis.exe") - if os.path.exists(possible_path): - nsis_path = possible_path - break - - self.run_command([possible_path, '/V2', self.dst_path_of(tempfile)]) + try: + for program_files in os.getenv('programfiles'), os.getenv('programfiles(x86)'): + if program_files: + for subpath in 'NSIS', r'NSIS\Unicode': + possible_path = os.path.join(program_files, subpath, nsis_path) + if os.path.isfile(possible_path): + nsis_path = possible_path + # don't just break: we need to exit multiple + # levels of 'for' loop + raise StopIteration() + except StopIteration: + pass + + self.run_command([nsis_path, '/V2', self.dst_path_of(tempfile)]) self.sign(installer_file) self.created_path(self.dst_path_of(installer_file)) -- cgit v1.2.3 From b1c93e6a50cb98de154bf6cabd0d7734248c1423 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 13 Sep 2023 13:17:38 -0400 Subject: SL-19242: Add NSIS language files etc. to Windows-app artifact. --- indra/newview/viewer_manifest.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 3f07eefbec..527aa25d05 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -805,12 +805,22 @@ class WindowsManifest(ViewerManifest): engage_registry="SetRegView 32" program_files="" + # Dump the installers/windows directory into the raw app image tree + # because NSIS needs those files. But don't use path() because we + # don't want them installed with the viewer - they're only for use by + # the installer itself. + shutil.copytree(os.path.join(self.get_src_prefix(), 'installers', 'windows'), + os.path.join(self.get_dst_prefix(), 'installers', 'windows')) + tempfile = "secondlife_setup_tmp.nsi" # the following replaces strings in the nsi template # it also does python-style % substitution self.replace_in("installers/windows/installer_template.nsi", tempfile, { "%%VERSION%%":version_vars, - "%%SOURCE%%":self.get_src_prefix(), + # The template references "%%SOURCE%%\installers\windows\...". + # Now that we've copied that directory into the app image + # tree, we can just replace %%SOURCE%% with '.'. + "%%SOURCE%%":'.', "%%INST_VARS%%":inst_vars_template % substitution_strings, "%%INSTALL_FILES%%":self.nsi_file_commands(True), "%%PROGRAMFILES%%":program_files, -- cgit v1.2.3 From baa0faa7d126e07b05d3f57d56a5440beb1c20b7 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 13 Sep 2023 15:10:22 -0400 Subject: SL-19242: Don't die if Windows app image installers/windows exists. --- indra/newview/viewer_manifest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 527aa25d05..40e6a8fa7c 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -810,7 +810,8 @@ class WindowsManifest(ViewerManifest): # don't want them installed with the viewer - they're only for use by # the installer itself. shutil.copytree(os.path.join(self.get_src_prefix(), 'installers', 'windows'), - os.path.join(self.get_dst_prefix(), 'installers', 'windows')) + os.path.join(self.get_dst_prefix(), 'installers', 'windows'), + dirs_exist_ok=True) tempfile = "secondlife_setup_tmp.nsi" # the following replaces strings in the nsi template -- cgit v1.2.3 From 62898143bff7627eeea860cd6fdf7ae865d85a60 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 13 Sep 2023 17:59:09 -0400 Subject: SL-19242: Write relative pathnames into NSIS input file. --- indra/newview/viewer_manifest.py | 53 ++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 29 deletions(-) (limited to 'indra') diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 40e6a8fa7c..fb22d29183 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -28,6 +28,7 @@ $/LicenseInfo$ """ import errno import glob +import itertools import json import os import os.path @@ -718,46 +719,38 @@ class WindowsManifest(ViewerManifest): self.package_file = "copied_deps" def nsi_file_commands(self, install=True): - def wpath(path): - if path.endswith('/') or path.endswith(os.path.sep): - path = path[:-1] - path = path.replace('/', '\\') - return path - - result = "" + result = [] dest_files = [pair[1] for pair in self.file_list if pair[0] and os.path.isfile(pair[1])] # sort deepest hierarchy first dest_files.sort(key=lambda f: (f.count(os.path.sep), f), reverse=True) out_path = None for pkg_file in dest_files: - rel_file = os.path.normpath(pkg_file.replace(self.get_dst_prefix()+os.path.sep,'')) - installed_dir = wpath(os.path.join('$INSTDIR', os.path.dirname(rel_file))) - pkg_file = wpath(os.path.normpath(pkg_file)) - if installed_dir != out_path: - if install: - out_path = installed_dir - result += 'SetOutPath ' + out_path + '\n' + pkg_file = os.path.normpath(pkg_file) + rel_file = self.relpath(pkg_file) + installed_dir = os.path.join('$INSTDIR', os.path.dirname(rel_file)) + if install and installed_dir != out_path: + out_path = installed_dir + # emit SetOutPath every time it changes + result.append('SetOutPath ' + out_path) if install: - result += 'File ' + pkg_file + '\n' + result.append('File ' + rel_file) else: - result += 'Delete ' + wpath(os.path.join('$INSTDIR', rel_file)) + '\n' + # Note that '$INSTDIR' is purely textual here: we write + # exactly that into the .nsi file for NSIS to interpret. + result.append('Delete ' + os.path.join('$INSTDIR', rel_file)) # at the end of a delete, just rmdir all the directories if not install: - deleted_file_dirs = [os.path.dirname(pair[1].replace(self.get_dst_prefix()+os.path.sep,'')) for pair in self.file_list] - # find all ancestors so that we don't skip any dirs that happened to have no non-dir children - deleted_dirs = [] - for d in deleted_file_dirs: - deleted_dirs.extend(path_ancestors(d)) + deleted_file_dirs = [os.path.dirname(self.relpath(f)) for f in dest_files] + # find all ancestors so that we don't skip any dirs that happened + # to have no non-dir children + deleted_dirs = set(itertools.chain.from_iterable(path_ancestors(d) + for d in deleted_file_dirs)) # sort deepest hierarchy first - deleted_dirs.sort(key=lambda f: (f.count(os.path.sep), f), reverse=True) - prev = None - for d in deleted_dirs: - if d != prev: # skip duplicates - result += 'RMDir ' + wpath(os.path.join('$INSTDIR', os.path.normpath(d))) + '\n' - prev = d + for d in sorted(deleted_dirs, key=lambda f: (f.count(os.path.sep), f), reverse=True): + result.append('RMDir ' + os.path.join('$INSTDIR', os.path.normpath(d))) - return result + return '\n'.join(result) def package_finish(self): # a standard map of strings for replacing in the templates @@ -854,7 +847,9 @@ class WindowsManifest(ViewerManifest): except StopIteration: pass - self.run_command([nsis_path, '/V2', self.dst_path_of(tempfile)]) + # Because we've written relative pathnames into tempfile, run nsis + # with their base directory as current. + self.run_command([nsis_path, '/V2', tempfile], cwd=self.get_dst_prefix()) self.sign(installer_file) self.created_path(self.dst_path_of(installer_file)) -- cgit v1.2.3 From 6c6f6f402383baaa53cb14f31813654a6c6c0279 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 13 Sep 2023 19:29:45 -0400 Subject: SL-19242: Pass arbitrary subprocess kwds through run_command(). That is, make LLManifest.run_command() accept and forward subprocess keyword arguments. --- indra/lib/python/indra/util/llmanifest.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'indra') diff --git a/indra/lib/python/indra/util/llmanifest.py b/indra/lib/python/indra/util/llmanifest.py index 820f356dae..bcb9d884c3 100755 --- a/indra/lib/python/indra/util/llmanifest.py +++ b/indra/lib/python/indra/util/llmanifest.py @@ -38,6 +38,7 @@ import itertools import operator import os import re +import shlex import shutil import subprocess import sys @@ -531,15 +532,15 @@ class LLManifest(object, metaclass=LLManifestRegistry): self.cmakedirs(path) return path - def run_command(self, command): + def run_command(self, command, **kwds): """ Runs an external command. Raises ManifestError exception if the command returns a nonzero status. """ - print("Running command:", command) + print("Running command:", shlex.join(command)) sys.stdout.flush() try: - subprocess.check_call(command) + subprocess.check_call(command, **kwds) except subprocess.CalledProcessError as err: raise ManifestError( "Command %s returned non-zero status (%s)" % (command, err.returncode) ) -- cgit v1.2.3 From 2670cde77b3f75a34f537fb935464c73e9e2814a Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 13 Sep 2023 20:12:43 -0400 Subject: SL-19242: On NSIS error, dump the generated .nsi file. --- indra/newview/viewer_manifest.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index fb22d29183..62e64b1d44 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -849,7 +849,15 @@ class WindowsManifest(ViewerManifest): # Because we've written relative pathnames into tempfile, run nsis # with their base directory as current. - self.run_command([nsis_path, '/V2', tempfile], cwd=self.get_dst_prefix()) + try: + self.run_command([nsis_path, '/V2', tempfile], cwd=self.get_dst_prefix()) + except ManifestError as err: + print(f' {tempfile} '.center(72, '=')) + with open(self.dst_path_of(tempfile)) as nsi: + for line in nsi: + print(line) + print(72 * '=') + raise self.sign(installer_file) self.created_path(self.dst_path_of(installer_file)) -- cgit v1.2.3 From 2a031fa11cb5a5abca41d9eef5fdfb0b7bb54301 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 13 Sep 2023 21:17:34 -0400 Subject: SL-19242: Ensure NSIS file paths don't end with backslash. If they do, NSIS takes it as line continuation. --- indra/newview/viewer_manifest.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) (limited to 'indra') diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 62e64b1d44..76683a1b92 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -719,6 +719,16 @@ class WindowsManifest(ViewerManifest): self.package_file = "copied_deps" def nsi_file_commands(self, install=True): + def INSTDIR(path): + # Note that '$INSTDIR' is purely textual here: we write + # exactly that into the .nsi file for NSIS to interpret. + # Pass the result through normpath() to handle the case in which + # path is the empty string. On Windows, that produces "$INSTDIR\". + # Unfortunately, if that's the last item on a line, NSIS takes + # that as line continuation and misinterprets the following line. + # Ensure we don't emit a trailing backslash. + return os.path.normpath(os.path.join('$INSTDIR', path)) + result = [] dest_files = [pair[1] for pair in self.file_list if pair[0] and os.path.isfile(pair[1])] # sort deepest hierarchy first @@ -727,7 +737,7 @@ class WindowsManifest(ViewerManifest): for pkg_file in dest_files: pkg_file = os.path.normpath(pkg_file) rel_file = self.relpath(pkg_file) - installed_dir = os.path.join('$INSTDIR', os.path.dirname(rel_file)) + installed_dir = INSTDIR(os.path.dirname(rel_file)) if install and installed_dir != out_path: out_path = installed_dir # emit SetOutPath every time it changes @@ -735,9 +745,7 @@ class WindowsManifest(ViewerManifest): if install: result.append('File ' + rel_file) else: - # Note that '$INSTDIR' is purely textual here: we write - # exactly that into the .nsi file for NSIS to interpret. - result.append('Delete ' + os.path.join('$INSTDIR', rel_file)) + result.append('Delete ' + INSTDIR(rel_file)) # at the end of a delete, just rmdir all the directories if not install: @@ -748,7 +756,7 @@ class WindowsManifest(ViewerManifest): for d in deleted_file_dirs)) # sort deepest hierarchy first for d in sorted(deleted_dirs, key=lambda f: (f.count(os.path.sep), f), reverse=True): - result.append('RMDir ' + os.path.join('$INSTDIR', os.path.normpath(d))) + result.append('RMDir ' + INSTDIR(d)) return '\n'.join(result) @@ -855,7 +863,7 @@ class WindowsManifest(ViewerManifest): print(f' {tempfile} '.center(72, '=')) with open(self.dst_path_of(tempfile)) as nsi: for line in nsi: - print(line) + print(line, end='') # already includes '\n' print(72 * '=') raise -- cgit v1.2.3 From ebe8e01ad13cf820fc89f2129928d2279e9d0f8a Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 13 Sep 2023 21:54:43 -0400 Subject: SL-19242: Capture the BugSplat @rpath as str, not bytes. --- indra/newview/viewer_manifest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 76683a1b92..0c54981af3 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -996,7 +996,8 @@ class DarwinManifest(ViewerManifest): # work, we need the build to noisily fail! oldpath = subprocess.check_output( ['objdump', '--macho', '--dylib-id', '--non-verbose', - os.path.join(relpkgdir, "BugsplatMac.framework", "BugsplatMac")] + os.path.join(relpkgdir, "BugsplatMac.framework", "BugsplatMac")], + text=True ).splitlines()[-1] # take the last line of output self.run_command( ['install_name_tool', '-change', oldpath, -- cgit v1.2.3 From e8dfbbaf880314359c0c2d18c944199e3f26db07 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 20 Sep 2023 11:34:56 -0400 Subject: SL-19242: Pass channel and imagename to sign-pkg-mac/action.yaml. The viewer_manifest.py logic to determine the name of the viewer installer .dmg is a little convoluted. Make it tell viewer-build-util/sign-pkg-mac that name, rather than passing it all the relevant inputs and composing it redundantly. sign-pkg-mac also wants the viewer channel to determine the application name. --- indra/newview/viewer_manifest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 0c54981af3..10f38fa7d8 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -1273,6 +1273,7 @@ class DarwinManifest(ViewerManifest): volname=CHANNEL_VENDOR_BASE+" Installer" # DO NOT CHANGE without understanding comment above imagename = self.installer_base_name() + self.set_github_output('imagename', imagename) sparsename = imagename + ".sparseimage" finalname = imagename + ".dmg" @@ -1292,7 +1293,7 @@ class DarwinManifest(ViewerManifest): try: devfile = re.search("/dev/disk([0-9]+)[^s]", hdi_output).group(0).strip() - volpath = re.search('HFS\s+(.+)', hdi_output).group(1).strip() + volpath = re.search(r'HFS\s+(.+)', hdi_output).group(1).strip() # Copy everything in to the mounted .dmg -- cgit v1.2.3 From ff71d3c742630751e3c7b3eeeeea334f714afd55 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 26 Sep 2023 10:22:40 -0400 Subject: SL-19242: Package Mac app image as tarball for artifact uploading. actions/upload-artifact doesn't preserve symlinks, which are important for our Mac viewer and its embedded frameworks. But tar does, so pack up the whole bundle as a tarball before posting as a GitHub artifact. --- indra/newview/viewer_manifest.py | 37 ++++++++++++++----------------------- 1 file changed, 14 insertions(+), 23 deletions(-) (limited to 'indra') diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 10f38fa7d8..b7dc705e95 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -39,6 +39,7 @@ import secrets import shutil import subprocess import sys +import tarfile import tempfile import time @@ -918,32 +919,22 @@ class DarwinManifest(ViewerManifest): RUNNER_TEMP = os.getenv('RUNNER_TEMP') # When running as a GitHub Action job, RUNNER_TEMP is the recommended # temp directory. If we're not running on GitHub, don't create this - # temp directory or this symlink: we don't clean them up, trusting + # temp directory or this tarball: we don't clean them up, trusting # that the runner is itself transient. On a dev machine, that would # result in temp-directory clutter. if RUNNER_TEMP: - # We want an artifact containing the "Second Life Mumble.app" - # directory, which in turn contains the whole app bundle. - # Unfortunately, the directory that contains the .app directory - # also contains other stuff, notably the xcarchive.zip, which is - # itself enormous. Create a temp directory containing only (a link - # to) our .app dir, and specify that as the directory to upload. - wrapdir = tempfile.mkdtemp(dir=RUNNER_TEMP) - applink = os.path.join(wrapdir, appname) - # This link will be used by a different job step, so link to an - # absolute path: we can't guarantee that the other step will have - # the same current directory. - # diagnostic output - parentdir = os.path.abspath(os.path.join(self.get_dst_prefix(), os.pardir)) - for dir in parentdir, os.path.join(parentdir, appname): - print(f'Contents of {dir}:') - for item in os.listdir(dir): - print(f' {item}') - # end diagnostic output - appreal = os.path.abspath(os.path.join(self.get_dst_prefix(), os.pardir, appname)) - print(f"Linking {applink} => {appreal}") - os.symlink(appreal, applink) - self.set_github_output_path('viewer_app', wrapdir) + # Per GitHub's actions/upload-artifact documentation + # https://github.com/actions/upload-artifact#maintaining-file-permissions-and-case-sensitive-files + # we must package the app bundle with tar before posting as an + # artifact. Posting individual files follows symlinks, which + # causes problems, especially with frameworks: a framework's top + # level must contain symlinks into its Versions/Current, which + # must itself be a symlink to some specific Versions subdir. + tarpath = os.path.join(RUNNER_TEMP, "viewer.tar.bz2") + print(f'Creating {tarpath} from {self.get_dst_prefix()}') + with tarfile.open(tarpath, mode="x:bz2") as tarball: + tarball.add(self.get_dst_prefix()) + self.set_github_output_path('viewer_app', tarpath) pkgdir = os.path.join(self.args['build'], os.pardir, 'packages') relpkgdir = os.path.join(pkgdir, "lib", "release") -- cgit v1.2.3 From 5f69db9d1907eac79ad7fa9e639733de42eec36f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 26 Sep 2023 11:47:18 -0400 Subject: SL-19242: Allow overwriting the Mac viewer's app bundle tarball. --- indra/newview/viewer_manifest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index b7dc705e95..899456dd09 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -932,7 +932,7 @@ class DarwinManifest(ViewerManifest): # must itself be a symlink to some specific Versions subdir. tarpath = os.path.join(RUNNER_TEMP, "viewer.tar.bz2") print(f'Creating {tarpath} from {self.get_dst_prefix()}') - with tarfile.open(tarpath, mode="x:bz2") as tarball: + with tarfile.open(tarpath, mode="w:bz2") as tarball: tarball.add(self.get_dst_prefix()) self.set_github_output_path('viewer_app', tarpath) -- cgit v1.2.3 From ae6027572816c0f67d68b8759a00cc8848e21fec Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 26 Sep 2023 13:58:19 -0400 Subject: SL-19242: Store Mac app bundle in tarball with top-level .app name. We were creating the tarball with the app bundle stored as the whole 'Users/someone/.../newview/Release/Second Life Mumble.app' path. Don't. --- indra/newview/viewer_manifest.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 899456dd09..5039f3db83 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -933,7 +933,10 @@ class DarwinManifest(ViewerManifest): tarpath = os.path.join(RUNNER_TEMP, "viewer.tar.bz2") print(f'Creating {tarpath} from {self.get_dst_prefix()}') with tarfile.open(tarpath, mode="w:bz2") as tarball: - tarball.add(self.get_dst_prefix()) + # store in the tarball as just 'Second Life Mumble.app' + # instead of 'Users/someone/.../newview/Release/Second...' + tarball.add(self.get_dst_prefix(), + arcname=os.path.basename(self.get_dst_prefix())) self.set_github_output_path('viewer_app', tarpath) pkgdir = os.path.join(self.args['build'], os.pardir, 'packages') -- 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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(-) (limited to 'indra') 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: - - - - - - - -