diff --git a/.gitignore b/.gitignore index 4c910ec89690225dbb8d54806f6547e9e805fc90..9a265372d7702acbb17a2f9f32769e5b7a4885c0 100755 --- a/.gitignore +++ b/.gitignore @@ -83,3 +83,4 @@ web/locale.* web/secondlife.com.* /Pipfile.lock .env +.vscode diff --git a/doc/contributions.txt b/doc/contributions.txt index 2fbe8ab7cc49d1cd2451219f9c3375c292c0d8b1..a097aad7f63da59771d358f7da0365671ed02dc1 100755 --- a/doc/contributions.txt +++ b/doc/contributions.txt @@ -240,6 +240,7 @@ Ansariel Hiller SL-18432 SL-19140 SL-4126 + SL-20224 Aralara Rajal Arare Chantilly CHUIBUG-191 @@ -929,6 +930,8 @@ LSL Scientist Lamorna Proctor Lares Carter Larry Pixel +Lars Næsbye Christensen + SL-20054 Laurent Bechir Leal Choche Lenae Munz diff --git a/indra/CMakeLists.txt b/indra/CMakeLists.txt index 48cfb6e5917efd6fb188fb83d98822a979b3ba1e..f8099ca3e0e0cc9a5420202a58d4d4d540143155 100644 --- a/indra/CMakeLists.txt +++ b/indra/CMakeLists.txt @@ -60,7 +60,6 @@ else() endif() set_property(GLOBAL PROPERTY USE_FOLDERS ON) - if (NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Build type. One of: Debug Release RelWithDebInfo" FORCE) diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index c7e34b83f05e805c460d0d4c7f8aa8611729af88..9490a342e0d4ab76c46624f51c87d17887c50d0d 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -19,8 +19,10 @@ include(XXHash) set(llcommon_SOURCE_FILES + apply.cpp commoncontrol.cpp indra_constants.cpp + lazyeventapi.cpp llapp.cpp llapr.cpp llassettype.cpp @@ -117,12 +119,16 @@ set(llcommon_SOURCE_FILES set(llcommon_HEADER_FILES CMakeLists.txt + always_return.h + apply.h chrono.h classic_callback.h commoncontrol.h ctype_workaround.h fix_macros.h + function_types.h indra_constants.h + lazyeventapi.h linden_common.h llalignedarray.h llapp.h @@ -329,9 +335,11 @@ 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}") 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/always_return.h b/indra/llcommon/always_return.h new file mode 100644 index 0000000000000000000000000000000000000000..6b9f1fdeafabcaf7119b4d461134a701aa6c2de4 --- /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 <type_traits> // std::enable_if, std::is_convertible + +namespace LL +{ + +#if __cpp_lib_is_invocable >= 201703L // C++17 + template <typename CALLABLE, typename... ARGS> + using invoke_result = std::invoke_result<CALLABLE, ARGS...>; +#else // C++14 + template <typename CALLABLE, typename... ARGS> + using invoke_result = std::result_of<CALLABLE(ARGS...)>; +#endif // C++14 + + /** + * AlwaysReturn<T>()(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 <typename DESIRED> + 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 <typename CALLABLE, typename... ARGS, + typename std::enable_if< + ! std::is_convertible< + typename invoke_result<CALLABLE, ARGS...>::type, + DESIRED + >::value, + bool + >::type=true> + DESIRED operator()(CALLABLE&& callable, ARGS&&... args) + { + // discard whatever callable(args) returns + std::forward<CALLABLE>(callable)(std::forward<ARGS>(args)...); + return mDefault; + } + + // callable returns a type convertible to DESIRED + template <typename CALLABLE, typename... ARGS, + typename std::enable_if< + std::is_convertible< + typename invoke_result<CALLABLE, ARGS...>::type, + DESIRED + >::value, + bool + >::type=true> + DESIRED operator()(CALLABLE&& callable, ARGS&&... args) + { + return { std::forward<CALLABLE>(callable)(std::forward<ARGS>(args)...) }; + } + + private: + DESIRED mDefault; + }; + + /** + * always_return<T>(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 <typename DESIRED, typename CALLABLE, typename... ARGS> + DESIRED always_return(CALLABLE&& callable, ARGS&&... args) + { + return AlwaysReturn<DESIRED>()(std::forward<CALLABLE>(callable), + std::forward<ARGS>(args)...); + } + + /** + * make_always_return<T>(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 <typename DESIRED, typename CALLABLE> + auto make_always_return(CALLABLE&& callable, const DESIRED& dft=DESIRED()) + { + return + [dft, callable = std::forward<CALLABLE>(callable)] + (auto&&... args) + { + return AlwaysReturn<DESIRED>(dft)(callable, + std::forward<decltype(args)>(args)...); + }; + } + +} // namespace LL + +#endif /* ! defined(LL_ALWAYS_RETURN_H) */ diff --git a/indra/llcommon/apply.cpp b/indra/llcommon/apply.cpp new file mode 100644 index 0000000000000000000000000000000000000000..417e23d3b480442eb64e1650cbf59f7a7bdb665c --- /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 7c58d63bc03f4a45aac4db3fe1b20177ade5b652..cf6161ed5012b8001bf07821711dfac02f6b7cf6 100644 --- a/indra/llcommon/apply.h +++ b/indra/llcommon/apply.h @@ -12,8 +12,11 @@ #if ! defined(LL_APPLY_H) #define LL_APPLY_H +#include "llexception.h" #include <boost/type_traits/function_traits.hpp> +#include <functional> // std::mem_fn() #include <tuple> +#include <type_traits> // std::is_member_pointer namespace LL { @@ -54,20 +57,67 @@ namespace LL }, \ (ARGS)) -#if __cplusplus >= 201703L +/***************************************************************************** +* invoke() +*****************************************************************************/ +#if __cpp_lib_invoke >= 201411L // C++17 implementation -using std::apply; +using std::invoke; + +#else // no std::invoke + +// Use invoke() to handle pointer-to-method: +// derived from https://stackoverflow.com/a/38288251 +template<typename Fn, typename... Args, + typename std::enable_if<std::is_member_pointer<typename std::decay<Fn>::type>::value, + int>::type = 0 > +auto invoke(Fn&& f, Args&&... args) +{ + return std::mem_fn(std::forward<Fn>(f))(std::forward<Args>(args)...); +} + +template<typename Fn, typename... Args, + typename std::enable_if<!std::is_member_pointer<typename std::decay<Fn>::type>::value, + int>::type = 0 > +auto invoke(Fn&& f, Args&&... args) +{ + return std::forward<Fn>(f)(std::forward<Args>(args)...); +} + +#endif // no std::invoke + +/***************************************************************************** +* apply(function, tuple); apply(function, array) +*****************************************************************************/ +#if __cpp_lib_apply >= 201603L + +// C++17 implementation +// 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 <typename CALLABLE, typename... ARGS> +auto apply(CALLABLE&& func, const std::tuple<ARGS...>& args) +{ + return std::apply(std::forward<CALLABLE>(func), args); +} #else // C++14 // Derived from https://stackoverflow.com/a/20441189 // and https://en.cppreference.com/w/cpp/utility/apply -template <typename CALLABLE, typename TUPLE, std::size_t... I> -auto apply_impl(CALLABLE&& func, TUPLE&& args, std::index_sequence<I...>) +template <typename CALLABLE, typename... ARGS, std::size_t... I> +auto apply_impl(CALLABLE&& func, const std::tuple<ARGS...>& args, std::index_sequence<I...>) { + // We accept const std::tuple& so a caller can construct an tuple on the + // fly. But std::get<I>(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<std::tuple<ARGS...>&>(args) }; + // call func(unpacked args) - return std::forward<CALLABLE>(func)(std::move(std::get<I>(args))...); + return invoke(std::forward<CALLABLE>(func), + std::forward<ARGS>(std::get<I>(non_const_args))...); } template <typename CALLABLE, typename... ARGS> @@ -81,6 +131,8 @@ auto apply(CALLABLE&& func, const std::tuple<ARGS...>& args) std::index_sequence_for<ARGS...>{}); } +#endif // C++14 + // per https://stackoverflow.com/a/57510428/5533635 template <typename CALLABLE, typename T, size_t SIZE> auto apply(CALLABLE&& func, const std::array<T, SIZE>& args) @@ -88,28 +140,92 @@ auto apply(CALLABLE&& func, const std::array<T, SIZE>& args) return apply(std::forward<CALLABLE>(func), std::tuple_cat(args)); } +/***************************************************************************** +* 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<typename Fn, typename... Args, + typename std::enable_if<!std::is_member_pointer<typename std::decay<Fn>::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<decltype(sfx_args)>(sfx_args)...))); + }; +} + +template<typename Fn, typename... Args, + typename std::enable_if<std::is_member_pointer<typename std::decay<Fn>::type>::value, + int>::type = 0 > +auto bind_front(Fn&& f, Args&&... args) +{ + return bind_front(std::mem_fn(std::forward<Fn>(f)), std::forward<Args>(args)...); +} + +#endif // C++20 with std::bind_front() + +/***************************************************************************** +* apply(function, std::vector) +*****************************************************************************/ // per https://stackoverflow.com/a/28411055/5533635 template <typename CALLABLE, typename T, std::size_t... I> auto apply_impl(CALLABLE&& func, const std::vector<T>& args, std::index_sequence<I...>) { + return apply(std::forward<CALLABLE>(func), + 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) {} +}; + +template <size_t ARITY, typename CALLABLE, typename T> +auto apply_n(CALLABLE&& func, const std::vector<T>& args) +{ + apply_validate_size(args.size(), ARITY); return apply_impl(std::forward<CALLABLE>(func), - std::make_tuple(std::forward<T>(args[I])...), - I...); + args, + std::make_index_sequence<ARITY>()); } -// 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. (But see apply_n() to + * pass a specific number of args to a variadic function.) + */ template <typename CALLABLE, typename T> auto apply(CALLABLE&& func, const std::vector<T>& args) { + // infer arity from the definition of func constexpr auto arity = boost::function_traits<CALLABLE>::arity; - assert(args.size() == arity); - return apply_impl(std::forward<CALLABLE>(func), - args, - std::make_index_sequence<arity>()); + // now that we have a compile-time arity, apply_n() works + return apply_n<arity>(std::forward<CALLABLE>(func), args); } -#endif // C++14 - } // namespace LL #endif /* ! defined(LL_APPLY_H) */ diff --git a/indra/llcommon/function_types.h b/indra/llcommon/function_types.h new file mode 100644 index 0000000000000000000000000000000000000000..3f42f6d64046e218599802172314d3a7426445a3 --- /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 <boost/function.hpp> +#include <boost/function_types/function_arity.hpp> +#include <functional> + +namespace LL +{ + + template <typename F> + struct function_arity_impl + { + static constexpr auto value = boost::function_types::function_arity<F>::value; + }; + + template <typename F> + struct function_arity_impl<std::function<F>> + { + static constexpr auto value = function_arity_impl<F>::value; + }; + + template <typename F> + struct function_arity_impl<boost::function<F>> + { + static constexpr auto value = function_arity_impl<F>::value; + }; + + template <typename F> + struct function_arity + { + static constexpr auto value = function_arity_impl<typename std::decay<F>::type>::value; + }; + +} // namespace LL + +#endif /* ! defined(LL_FUNCTION_TYPES_H) */ diff --git a/indra/llcommon/lazyeventapi.cpp b/indra/llcommon/lazyeventapi.cpp new file mode 100644 index 0000000000000000000000000000000000000000..028af9f33f3a9f9dc3619af6bcb667e594e5eb7a --- /dev/null +++ b/indra/llcommon/lazyeventapi.cpp @@ -0,0 +1,72 @@ +/** + * @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 +#include <algorithm> // 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) +{ + // 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); + } +} + +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 new file mode 100644 index 0000000000000000000000000000000000000000..e36831270b0071a55bcc696a5b4944cedac1c4f6 --- /dev/null +++ b/indra/llcommon/lazyeventapi.h @@ -0,0 +1,205 @@ +/** + * @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 <boost/signals2/signal.hpp> +#include <string> +#include <tuple> +#include <utility> // std::pair +#include <vector> + +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<void(LLEventAPI*)> init; + }; + + /** + * 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<LazyEventAPIBase> + { + 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; + + // capture add() calls we want to play back on LLEventAPI construction + template <typename... ARGS> + 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. + + // 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 + <const std::string&, const std::string&, ARGS...> }; + // 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<ARGS>(rest)...) }; + + mParams.init.connect_extended( + [func, args] + (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(func, std::tuple_cat(std::make_tuple(instance), args)); + }); + } + + // 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<std::string, std::string>; + + private: + // metadata that might be queried by LLLeapListener + std::vector<NameDesc> 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 + // 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 <typename... ARGS> + static + void add_trampoline(LLEventAPI* instance, ARGS&&... args) + { + instance->add(std::forward<ARGS>(args)...); + } + }; + + /** + * 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 + * <tt>(const LL::LazyEventAPIParams& params)</tt> + * and forward that reference to (the protected) + * <tt>LLEventAPI(const LL::LazyEventAPIParams&)</tt> constructor. + * + * Then derive your listener registrar from + * <tt>LazyEventAPI<your LLEventAPI subclass></tt>. 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 EVENTAPI> + 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) + {} + + private: + 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/llapr.cpp b/indra/llcommon/llapr.cpp index db4d8d5df381e8005b1476c5c1355e03ad73d9d0..383b36a6841cb1186ea82faa94f886049ab3e5f1 100644 --- a/indra/llcommon/llapr.cpp +++ b/indra/llcommon/llapr.cpp @@ -38,6 +38,12 @@ const S32 FULL_VOLATILE_APR_POOL = 1024 ; //number of references to LLVolatileAP bool gAPRInitialized = false; +int abortfunc(int retcode) +{ + LL_WARNS("APR") << "Allocation failure in apr pool with code " << (S32)retcode << LL_ENDL; + return 0; +} + void ll_init_apr() { // Initialize APR and create the global pool @@ -45,7 +51,7 @@ void ll_init_apr() if (!gAPRPoolp) { - apr_pool_create(&gAPRPoolp, NULL); + apr_pool_create_ex(&gAPRPoolp, NULL, abortfunc, NULL); } if(!LLAPRFile::sAPRFilePoolp) diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp index e936646a956446654ebe7376a3d252c2a42bb396..0b651ae7e5d304d668a8e05ae00e4346876c5440 100644 --- a/indra/llcommon/llcoros.cpp +++ b/indra/llcommon/llcoros.cpp @@ -274,6 +274,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/llcommon/lleventapi.cpp b/indra/llcommon/lleventapi.cpp index eeaf9977c17a96282c95f7f8539b11bf50520110..0da73bdd5da6e69e4dd3c03dfdefb0b73daca67e 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::Response::Response(const LLSD& seed, const LLSD& request, const LLSD::String& replyKey): mResp(seed), mReq(request), diff --git a/indra/llcommon/lleventapi.h b/indra/llcommon/lleventapi.h index 08e3626091fa490c17d26455acca947c6ea07858..7019824546c26925f487c1935cbb1185737bb229 100644 --- a/indra/llcommon/lleventapi.h +++ b/indra/llcommon/lleventapi.h @@ -35,6 +35,11 @@ #include "llinstancetracker.h" #include <string> +namespace LL +{ + class LazyEventAPIParams; +} + /** * LLEventAPI not only provides operation dispatch functionality, inherited * from LLDispatchListener -- it also gives us event API introspection. @@ -64,19 +69,6 @@ class LL_COMMON_API LLEventAPI: public LLDispatchListener, /// 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 <typename CALLABLE> - 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 @@ -150,16 +142,20 @@ class LL_COMMON_API LLEventAPI: public LLDispatchListener, * @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 66989fae5fe0b7987d1855af48ca58ecc405e20c..40c344b504a7907de20655a8c09c58a70ad02146 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -40,70 +40,12 @@ // other Linden headers #include "llevents.h" #include "llerror.h" +#include "llexception.h" #include "llsdutil.h" #include "stringize.h" +#include <iomanip> // std::quoted() #include <memory> // std::auto_ptr -/***************************************************************************** -* LLSDArgsSource -*****************************************************************************/ -/** - * Store an LLSD array, producing its elements one at a time. Die with LL_ERRS - * 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())) - { - LL_ERRS("LLSDArgsSource") << _function << " needs an args array instead of " - << _args << LL_ENDL; - } -} - -LLSDArgsSource::~LLSDArgsSource() -{ - done(); -} - -LLSD LLSDArgsSource::next() -{ - if (_index >= _args.size()) - { - LL_ERRS("LLSDArgsSource") << _function << " requires more arguments than the " - << _args.size() << " provided: " << _args << LL_ENDL; - } - 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 *****************************************************************************/ @@ -156,19 +98,26 @@ void LLSDArgsSource::done() const * - 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 /// 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 LL_ERRS. + /// Given arguments map, return LLSD::Array of parameter values, or + /// trigger error. LLSD map(const LLSD& argsmap) const; private: static std::string formatlist(const LLSD&); + template <typename... ARGS> + [[noreturn]] 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; @@ -187,15 +136,18 @@ class LL_COMMON_API LLSDArgsMapper FilledVector _has_dft; }; -LLSDArgsMapper::LLSDArgsMapper(const std::string& function, - const LLSD& names, const LLSD& defaults): +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()) { if (! (_names.isUndefined() || _names.isArray())) { - LL_ERRS("LLSDArgsMapper") << function << " names must be an array, not " << names << LL_ENDL; + callFail(" names must be an array, not ", names); } auto nparams(_names.size()); // From _names generate _indexes. @@ -218,8 +170,7 @@ 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; + callFail(" names array ", names, " shorter than defaults array ", defaults); } // Offset by which we slide defaults array right to right-align with @@ -256,23 +207,20 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function, } if (bogus.size()) { - LL_ERRS("LLSDArgsMapper") << function << " defaults specified for nonexistent params " - << formatlist(bogus) << LL_ENDL; + callFail(" defaults specified for nonexistent params ", formatlist(bogus)); } } else { - LL_ERRS("LLSDArgsMapper") << function << " defaults must be a map or an array, not " - << defaults << LL_ENDL; + 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())) { - LL_ERRS("LLSDArgsMapper") << _function << " map() needs a map or array, not " - << argsmap << LL_ENDL; + 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 @@ -369,15 +317,14 @@ 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; + 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 = ""; @@ -390,19 +337,40 @@ std::string LLSDArgsMapper::formatlist(const LLSD& list) return out.str(); } -LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key): - mDesc(desc), - mKey(key) +template <typename... ARGS> +[[noreturn]] void LLEventDispatcher::LLSDArgsMapper::callFail(ARGS&&... args) const { + _parent->callFail<LLEventDispatcher::DispatchError> + (_function, std::forward<ARGS>(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), + mArgskey(argskey) +{} + +LLEventDispatcher::DispatchEntry::DispatchEntry(LLEventDispatcher* parent, const std::string& desc): + mParent(parent), + mDesc(desc) +{} + /** * DispatchEntry subclass used for callables accepting(const LLSD&) */ 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) {} @@ -410,22 +378,21 @@ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchE Callable mFunc; LLSD mRequired; - virtual void 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()) { - LL_ERRS("LLEventDispatcher") << desc << ": bad request: " << mismatch << LL_ENDL; + callFail(desc, ": bad request: ", mismatch); } // Event syntax looks good, go for it! - mFunc(event); + return mFunc(event); } - virtual LLSD addMetadata(LLSD meta) const + LLSD getMetadata() const override { - meta["required"] = mRequired; - return meta; + return llsd::map("required", mRequired); } }; @@ -435,17 +402,27 @@ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchE */ struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::DispatchEntry { - ParamsDispatchEntry(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) {} + std::string mName; invoker_function mInvoker; - virtual void call(const std::string& desc, const LLSD& event) const + LLSD call(const std::string&, const LLSD& event, bool, const std::string&) const override { - LLSDArgsSource src(desc, event); - mInvoker(boost::bind(&LLSDArgsSource::next, boost::ref(src))); + try + { + return mInvoker(event); + } + catch (const LL::apply_error& err) + { + // could hit runtime errors with LL::apply() + callFail(err.what()); + } } }; @@ -455,23 +432,62 @@ struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::Dispatc */ struct LLEventDispatcher::ArrayParamsDispatchEntry: public LLEventDispatcher::ParamsDispatchEntry { - ArrayParamsDispatchEntry(const std::string& desc, const invoker_function& func, + ArrayParamsDispatchEntry(LLEventDispatcher* parent, const std::string& name, + const std::string& desc, const invoker_function& func, LLSD::Integer arity): - ParamsDispatchEntry(desc, func), + ParamsDispatchEntry(parent, 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) + { + // 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 + // array, we must have an argskey. + if (argskey.empty()) + { + callFail("LLEventDispatcher has no args key"); + } + if ((! event.has(argskey))) + { + callFail("missing required key ", std::quoted(argskey)); + } + args = event[argskey]; + } + } + return ParamsDispatchEntry::call(desc, args, fromMap, argskey); + } + + 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); } }; @@ -481,11 +497,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(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 @@ -528,18 +544,27 @@ struct LLEventDispatcher::MapParamsDispatchEntry: public LLEventDispatcher::Para LLSD mRequired; LLSD mOptional; - virtual void 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. - 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 getMetadata() const override { - meta["required"] = mRequired; - meta["optional"] = mOptional; - return meta; + return llsd::map("required", mRequired, "optional", mOptional); } }; @@ -548,9 +573,9 @@ void LLEventDispatcher::addArrayParamsDispatchEntry(const std::string& name, const invoker_function& invoker, LLSD::Integer arity) { - mDispatch.insert( - DispatchMap::value_type(name, std::static_pointer_cast<DispatchEntry>( - std::make_shared<ArrayParamsDispatchEntry>(desc, invoker, arity)))); + mDispatch.emplace( + name, + new ArrayParamsDispatchEntry(this, "", desc, invoker, arity)); } void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name, @@ -559,25 +584,25 @@ void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name, const LLSD& params, const LLSD& defaults) { - mDispatch.insert( - DispatchMap::value_type(name, std::static_pointer_cast<DispatchEntry>( - std::make_shared<MapParamsDispatchEntry>(name, desc, invoker, params, defaults)))); + // Pass instance info as well as this entry name for error messages. + mDispatch.emplace( + name, + new MapParamsDispatchEntry(this, "", 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) +void LLEventDispatcher::addLLSD(const std::string& name, const std::string& desc, + const Callable& callable, const LLSD& required) { - mDispatch.insert( - DispatchMap::value_type(name, std::static_pointer_cast< - DispatchEntry>(std::make_shared<LLSDDispatchEntry>(desc, callable, required)))); + mDispatch.emplace(name, new LLSDDispatchEntry(this, desc, callable, required)); } -void LLEventDispatcher::addFail(const std::string& name, const std::string& classname) const +void LLEventDispatcher::addFail(const std::string& name, const char* classname) const { LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << ")::add(" << name - << "): " << classname << " is not a subclass " - << "of LLEventDispatcher" << LL_ENDL; + << "): " << LLError::Log::demangle(classname) + << " is not a subclass of LLEventDispatcher" + << LL_ENDL; } /// Unregister a callable @@ -592,48 +617,105 @@ 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. -void LLEventDispatcher::operator()(const std::string& name, const LLSD& event) const +/// Call a registered callable with an explicitly-specified name. It is an +/// error if no such callable exists. +LLSD LLEventDispatcher::operator()(const std::string& name, const LLSD& event) const { - if (! try_call(name, event)) - { - LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): '" << name - << "' not found" << LL_ENDL; - } + 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. If no such -/// callable exists, die with LL_ERRS. -void LLEventDispatcher::operator()(const LLSD& event) const +bool LLEventDispatcher::try_call(const std::string& name, 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)) + try { - LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): bad " << mKey - << " value '" << name << "'" << LL_ENDL; + try_call(std::string(), name, event); + return true; + } + // 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&) + { + return false; } } +/// 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(mKey, event[mKey], event); +} + bool LLEventDispatcher::try_call(const LLSD& event) const { - return try_call(event[mKey], event); + try + { + try_call(mKey, event[mKey], event); + return true; + } + catch (const DispatchMissing&) + { + return false; + } } -bool LLEventDispatcher::try_call(const std::string& name, const LLSD& event) const +LLSD LLEventDispatcher::try_call(const std::string& key, const std::string& name, + const LLSD& event) const { + if (name.empty()) + { + if (key.empty()) + { + callFail<DispatchError>("attempting to call with no name"); + } + else + { + callFail<DispatchError>("no ", key); + } + } + DispatchMap::const_iterator found = mDispatch.find(name); if (found == mDispatch.end()) { - return false; + // 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: + // 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()) + { + callFail<DispatchMissing>(std::quoted(name), " not found"); + } + else + { + callFail<DispatchMissing>("bad ", key, " value ", std::quoted(name)); + } } + // 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 + 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 <typename EXCEPTION, typename... ARGS> +//static +[[noreturn]] void LLEventDispatcher::sCallFail(ARGS&&... args) +{ + auto error = stringize(std::forward<ARGS>(args)...); + LL_WARNS("LLEventDispatcher") << error << LL_ENDL; + LLTHROW(EXCEPTION(error)); +} + +template <typename EXCEPTION, typename... ARGS> +[[noreturn]] void LLEventDispatcher::callFail(ARGS&&... args) const +{ + // Describe this instance in addition to the error itself. + sCallFail<EXCEPTION>(*this, ": ", std::forward<ARGS>(args)...); } LLSD LLEventDispatcher::getMetadata(const std::string& name) const @@ -643,26 +725,243 @@ 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) +{ + // If we're a subclass of LLEventDispatcher, e.g. LLEventAPI, report that. + // Also report whatever transient state is active. + return out << LLError::Log::classname(self) << '(' << self.mDesc << ')' + << self.getState(); } -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))) +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 LLDispatchListener::process(const LLSD& event) +bool LLEventDispatcher::setState(SetState&, const std::string& state) const { - (*this)(event); + // 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; +} + +/***************************************************************************** +* LLDispatchListener +*****************************************************************************/ +std::string LLDispatchListener::mReplyKey{ "reply" }; + +bool LLDispatchListener::process(const LLSD& event) const +{ + // 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; } -LLEventDispatcher::DispatchEntry::DispatchEntry(const std::string& desc): - mDesc(desc) -{} +void LLDispatchListener::call_one(const LLSD& name, const LLSD& event) const +{ + LLSD result; + try + { + 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 + // registered callable is void or non-void. If it's void, + // LLEventDispatcher returned 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); + } +} + +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 = ""; + + for (const auto& pair : llsd::inMap(reqmap)) + { + const LLSD::String& name{ pair.first }; + 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. + result[name] = (*this)(name, args); + } + catch (const std::exception& err) + { + // 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"; + } + } + + // so, were there any errors? + std::string error = errors.str(); + if (! error.empty()) + { + if (! event.has(mReplyKey)) + { + // can't send reply, throw + sCallFail<DispatchError>(error); + } + else + { + // reply key present + result["error"] = error; + } + } + + reply(result, event); +} + +void LLDispatchListener::call_array(const LLSD& reqarray, const LLSD& event) const +{ + // 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()] }; + // 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 + { + // 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 std::exception& err) + { + // 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; + } + } + + LLSD result; + // was there an error? + if (! error.empty()) + { + if (! event.has(mReplyKey)) + { + // can't send reply, throw + sCallFail<DispatchError>(error); + } + else + { + // reply key present + result["error"] = error; + } + } + + // 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 +{ + // 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 b8d21245d7c2d0f28809699b7367df4587724182..041c12f981ec28e90dabeb60622cc0ee718cf937 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -27,55 +27,26 @@ * * 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 <string> -#include <boost/shared_ptr.hpp> -#include <boost/function.hpp> -#include <boost/bind.hpp> -#include <boost/iterator/transform_iterator.hpp> -#include <boost/utility/enable_if.hpp> +#include <boost/fiber/fss.hpp> +#include <boost/function_types/is_member_function_pointer.hpp> #include <boost/function_types/is_nonmember_callable_builtin.hpp> -#include <boost/function_types/parameter_types.hpp> -#include <boost/function_types/function_arity.hpp> -#include <boost/type_traits/remove_cv.hpp> -#include <boost/type_traits/remove_reference.hpp> -#include <boost/fusion/include/push_back.hpp> -#include <boost/fusion/include/cons.hpp> -#include <boost/fusion/include/invoke.hpp> -#include <boost/mpl/begin.hpp> -#include <boost/mpl/end.hpp> -#include <boost/mpl/next.hpp> -#include <boost/mpl/deref.hpp> +#include <boost/hof/is_invocable.hpp> // until C++17, when we get std::is_invocable +#include <boost/iterator/transform_iterator.hpp> +#include <functional> // std::function +#include <memory> // std::unique_ptr +#include <string> #include <typeinfo> +#include <type_traits> +#include <utility> // std::pair +#include "always_return.h" +#include "function_types.h" // LL::function_arity #include "llevents.h" +#include "llptrto.h" #include "llsdutil.h" class LLSD; @@ -89,15 +60,27 @@ 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() = default; /// @name Register functions accepting(const LLSD&) //@{ - /// Accept any C++ callable with the right signature, typically a - /// boost::bind() expression - typedef boost::function<void(const LLSD&)> Callable; + /// Accept any C++ callable with the right signature + typedef std::function<LLSD(const LLSD&)> Callable; /** * Register a @a callable by @a name. The passed @a callable accepts a @@ -109,27 +92,54 @@ class LL_COMMON_API LLEventDispatcher 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 <typename CALLABLE, + typename=typename std::enable_if< + boost::hof::is_invocable<CALLABLE, LLSD>::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<LLSD>(std::forward<CALLABLE>(callable))), + required); } /** * Special case: a subclass of this class can pass an unbound member * function pointer (of an LLEventDispatcher subclass) without explicitly - * specifying the <tt>boost::bind()</tt> expression. The passed @a method + * specifying a <tt>std::bind()</tt> expression. The passed @a method * accepts a single LLSD value, presumably containing other parameters. */ + template <typename R, class CLASS> + void add(const std::string& name, + const std::string& desc, + R (CLASS::*method)(const LLSD&), + const LLSD& required=LLSD()) + { + addMethod<CLASS>(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 <typename R, class CLASS> + void add(const std::string& name, + const std::string& desc, + R (CLASS::*method)(const LLSD&) const, + const LLSD& required=LLSD()) + { + addMethod<CLASS>(name, desc, method, required); + } + + // because the compiler can't match a method returning void to the above template <class CLASS> void add(const std::string& name, const std::string& desc, @@ -150,6 +160,128 @@ class LL_COMMON_API LLEventDispatcher addMethod<CLASS>(name, desc, method, required); } + // non-const nullary method + template <typename R, class CLASS> + void add(const std::string& name, + const std::string& desc, + R (CLASS::*method)()) + { + addVMethod<CLASS>(name, desc, method); + } + + // const nullary method + template <typename R, class CLASS> + void add(const std::string& name, + const std::string& desc, + R (CLASS::*method)() const) + { + addVMethod<CLASS>(name, desc, method); + } + + // non-const nullary method returning void + template <class CLASS> + void add(const std::string& name, + const std::string& desc, + void (CLASS::*method)()) + { + addVMethod<CLASS>(name, desc, method); + } + + // const nullary method returning void + template <class CLASS> + void add(const std::string& name, + const std::string& desc, + void (CLASS::*method)() const) + { + addVMethod<CLASS>(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 <typename R, class CLASS, typename ARG, + typename = typename std::enable_if< + ! std::is_same<typename std::decay<ARG>::type, LLSD>::value + >::type> + void add(const std::string& name, + const std::string& desc, + R (CLASS::*method)(ARG)) + { + addVMethod<CLASS>(name, desc, method); + } + + // const unary method (but method accepting LLSD should use the other add()) + template <typename R, class CLASS, typename ARG, + typename = typename std::enable_if< + ! std::is_same<typename std::decay<ARG>::type, LLSD>::value + >::type> + void add(const std::string& name, + const std::string& desc, + R (CLASS::*method)(ARG) const) + { + addVMethod<CLASS>(name, desc, method); + } + + // non-const unary method returning void + // enable_if usage per https://stackoverflow.com/a/39913395/5533635 + template <class CLASS, typename ARG, + typename = typename std::enable_if< + ! std::is_same<typename std::decay<ARG>::type, LLSD>::value + >::type> + void add(const std::string& name, + const std::string& desc, + void (CLASS::*method)(ARG)) + { + addVMethod<CLASS>(name, desc, method); + } + + // const unary method returning void + template <class CLASS, typename ARG, + typename = typename std::enable_if< + ! std::is_same<typename std::decay<ARG>::type, LLSD>::value + >::type> + void add(const std::string& name, + const std::string& desc, + void (CLASS::*method)(ARG) const) + { + addVMethod<CLASS>(name, desc, method); + } + + // non-const binary (or more) method + template <typename R, class CLASS, typename ARG0, typename ARG1, typename... ARGS> + void add(const std::string& name, + const std::string& desc, + R (CLASS::*method)(ARG0, ARG1, ARGS...)) + { + addVMethod<CLASS>(name, desc, method); + } + + // const binary (or more) method + template <typename R, class CLASS, typename ARG0, typename ARG1, typename... ARGS> + void add(const std::string& name, + const std::string& desc, + R (CLASS::*method)(ARG0, ARG1, ARGS...) const) + { + addVMethod<CLASS>(name, desc, method); + } + + // non-const binary (or more) method returning void + template <class CLASS, typename ARG0, typename ARG1, typename... ARGS> + void add(const std::string& name, + const std::string& desc, + void (CLASS::*method)(ARG0, ARG1, ARGS...)) + { + addVMethod<CLASS>(name, desc, method); + } + + // const binary (or more) method returning void + template <class CLASS, typename ARG0, typename ARG1, typename... ARGS> + void add(const std::string& name, + const std::string& desc, + void (CLASS::*method)(ARG0, ARG1, ARGS...) const) + { + addVMethod<CLASS>(name, desc, method); + } + //@} /// @name Register functions with arbitrary param lists @@ -159,51 +291,43 @@ class LL_COMMON_API LLEventDispatcher * 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. */ - template<typename Function> - typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin<Function> - >::type add(const std::string& name, - const std::string& desc, - Function f); + template <typename CALLABLE, + typename=typename std::enable_if< + ! boost::hof::is_invocable<CALLABLE, LLSD>() + >::type> + void add(const std::string& name, + const std::string& desc, + CALLABLE&& f) + { + addV(name, desc, f); + } /** * 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 - * instance. If you already have an instance in hand, - * boost::lambda::var(instance) or boost::lambda::constant(instance_ptr) - * produce suitable callables. + * instance. * * When calling this name, pass an LLSD::Array. Each entry in turn will be * converted to the corresponding parameter type using LLSDParam. */ - template<typename Method, typename InstanceGetter> - typename boost::enable_if< boost::function_types::is_member_function_pointer<Method> - >::type add(const std::string& name, - const std::string& desc, - Method f, - const InstanceGetter& getter); + template<typename Method, typename InstanceGetter, + typename = typename std::enable_if< + boost::function_types::is_member_function_pointer<Method>::value && + ! std::is_convertible<InstanceGetter, LLSD>::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 * 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. * @@ -211,21 +335,17 @@ class LL_COMMON_API LLEventDispatcher * an LLSD::Array using LLSDArgsMapper and then convert each entry in turn * to the corresponding parameter type using LLSDParam. */ - template<typename Function> - typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin<Function> - >::type add(const std::string& name, - const std::string& desc, - Function f, - const LLSD& params, - const LLSD& defaults=LLSD()); + template<typename Function, + typename = typename std::enable_if< + boost::function_types::is_nonmember_callable_builtin<Function>::value && + ! boost::hof::is_invocable<Function, LLSD>::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. * - * @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 @@ -233,6 +353,8 @@ class LL_COMMON_API LLEventDispatcher * 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. * @@ -240,42 +362,96 @@ class LL_COMMON_API LLEventDispatcher * an LLSD::Array using LLSDArgsMapper and then convert each entry in turn * to the corresponding parameter type using LLSDParam. */ - template<typename Method, typename InstanceGetter> - typename boost::enable_if< boost::function_types::is_member_function_pointer<Method> - >::type add(const std::string& name, - const std::string& desc, - Method f, - const InstanceGetter& getter, - const LLSD& params, - const LLSD& defaults=LLSD()); + template<typename Method, typename InstanceGetter, + typename = typename std::enable_if< + boost::function_types::is_member_function_pointer<Method>::value && + ! std::is_convertible<InstanceGetter, LLSD>::value + >::type> + void add(const std::string& name, const std::string& desc, Method f, + const InstanceGetter& getter, const LLSD& params, + const LLSD& defaults=LLSD()); //@} /// 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. - 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 <tt>true</tt>. If no such callable exists, return - /// <tt>false</tt>. 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 and + * return <tt>true</tt>. If no such callable exists, return + * <tt>false</tt>. 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. 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. - 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 <tt>true</tt>. - /// If no such callable exists, return <tt>false</tt>. If the @a event - /// fails to match the @a required prototype specified at add() time, die - /// with LL_ERRS. + /** + * 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 <tt>true</tt>. If no such callable exists, + * return <tt>false</tt>. 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 @@ -285,22 +461,26 @@ class LL_COMMON_API LLEventDispatcher private: struct DispatchEntry { - DispatchEntry(const std::string& desc); + DispatchEntry(LLEventDispatcher* parent, const std::string& desc); virtual ~DispatchEntry() = default; // 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 void call(const std::string& desc, const LLSD& event) const = 0; - virtual LLSD addMetadata(LLSD) const = 0; + virtual LLSD call(const std::string& desc, const LLSD& event, + bool fromMap, const std::string& argskey) const = 0; + virtual LLSD getMetadata() const = 0; + + template <typename... ARGS> + [[noreturn]] void callFail(ARGS&&... args) const + { + mParent->callFail<LLEventDispatcher::DispatchError>(std::forward<ARGS>(args)...); + } }; - // Tried using boost::ptr_map<std::string, DispatchEntry>, 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<std::string, std::shared_ptr<DispatchEntry> > DispatchMap; + typedef std::map<std::string, std::unique_ptr<DispatchEntry> > DispatchMap; public: /// We want the flexibility to redefine what data we store per name, @@ -323,11 +503,57 @@ class LL_COMMON_API LLEventDispatcher /// Retrieve the LLSD key we use for one-arg <tt>operator()</tt> 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&); private: - template <class CLASS, typename METHOD> + void addLLSD(const std::string& name, + const std::string& desc, + const Callable& callable, + const LLSD& required); + + template <class CLASS, typename METHOD, + typename std::enable_if< + std::is_base_of<LLEventDispatcher, CLASS>::value, + bool + >::type=true> void addMethod(const std::string& name, const std::string& desc, const METHOD& method, const LLSD& 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<CLASS*>(this); + add(name, + desc, + Callable(LL::make_always_return<LLSD>( + [downcast, method] + (const LLSD& args) + { + return (downcast->*method)(args); + })), + required); + } + + template <class CLASS, typename METHOD, + typename std::enable_if< + ! std::is_base_of<LLEventDispatcher, CLASS>::value, + bool + >::type=true> + void addMethod(const std::string& name, const std::string& desc, + const METHOD&, const LLSD&) + { + addFail(name, typeid(CLASS).name()); + } + + template <class CLASS, typename METHOD> + void addVMethod(const std::string& name, const std::string& desc, + const METHOD& method) { CLASS* downcast = dynamic_cast<CLASS*>(this); if (! downcast) @@ -336,38 +562,85 @@ class LL_COMMON_API LLEventDispatcher } else { - add(name, desc, boost::bind(method, downcast, _1), required); + // 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; }); } } - void addFail(const std::string& name, const std::string& classname) const; - std::string mDesc, mKey; + template <typename Function> + void addV(const std::string& name, const std::string& desc, Function f); + + 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; + +protected: + // raise specified EXCEPTION with specified stringize(ARGS) + template <typename EXCEPTION, typename... ARGS> + [[noreturn]] void callFail(ARGS&&... args) const; + template <typename EXCEPTION, typename... ARGS> + [[noreturn]] 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 <typename... ARGS> + SetState(const LLEventDispatcher* self, ARGS&&... args): + mSelf(self) + { + mSet = mSelf->setState(*this, stringize(std::forward<ARGS>(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<std::string> 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) { return NameDesc(item.first, item.second->mDesc); } + class LLSDArgsMapper; struct LLSDDispatchEntry; struct ParamsDispatchEntry; struct ArrayParamsDispatchEntry; struct MapParamsDispatchEntry; - // Step 2 of parameter analysis. Instantiating invoker<some_function_type> - // 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<Function> >::type - , class To = typename boost::mpl::end< boost::function_types::parameter_types<Function> >::type - > - struct invoker; - - // deliver LLSD arguments one at a time - typedef boost::function<LLSD()> args_source; - // obtain args from an args_source to build param list and call target - // function - typedef boost::function<void(const args_source&)> invoker_function; + // call target function with args from LLSD array + typedef std::function<LLSD(const LLSD&)> invoker_function; template <typename Function> invoker_function make_invoker(Function f); @@ -387,101 +660,38 @@ class LL_COMMON_API LLEventDispatcher /***************************************************************************** * LLEventDispatcher template implementation details *****************************************************************************/ -// Step 3 of parameter analysis, the recursive case. -template<typename Function, class From, class To> -struct LLEventDispatcher::invoker -{ - template<typename T> - struct remove_cv_ref - : boost::remove_cv< typename boost::remove_reference<T>::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<typename Args> - static inline - void apply(Function func, const args_source& argsrc, Args const & args) - { - typedef typename boost::mpl::deref<From>::type arg_type; - typedef typename boost::mpl::next<From>::type next_iter_type; - typedef typename remove_cv_ref<arg_type>::type plain_arg_type; - - invoker<Function, next_iter_type, To>::apply - ( func, argsrc, boost::fusion::push_back(args, LLSDParam<plain_arg_type>(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 <typename InstanceGetter> - static inline - void method_apply(Function func, const args_source& argsrc, const InstanceGetter& getter) - { - typedef typename boost::mpl::next<From>::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<Function, next_iter_type, To>::apply - ( func, argsrc, boost::fusion::push_back(boost::fusion::nil(), boost::ref(getter()))); - } -}; - -// Step 4 of parameter analysis, the leaf case. When the general -// invoker<Function, From, To> logic has advanced From until it matches To, -// the compiler will pick this template specialization. -template<typename Function, class To> -struct LLEventDispatcher::invoker<Function,To,To> -{ - // the argument list is complete, now call the function - template<typename Args> - static inline - void apply(Function func, const args_source&, Args const & args) - { - boost::fusion::invoke(func, args); - } -}; - -template<typename Function> -typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin<Function> >::type -LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f) +template <typename Function> +void LLEventDispatcher::addV(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), - boost::function_types::function_arity<Function>::value); + LL::function_arity<Function>::value); } -template<typename Method, typename InstanceGetter> -typename boost::enable_if< boost::function_types::is_member_function_pointer<Method> >::type -LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, - const InstanceGetter& getter) +template<typename Method, typename InstanceGetter, typename> +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. addArrayParamsDispatchEntry(name, desc, make_invoker(f, getter), - boost::function_types::function_arity<Method>::value - 1); + LL::function_arity<Method>::value - 1); } -template<typename Function> -typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin<Function> >::type -LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f, - const LLSD& params, const LLSD& defaults) +template<typename Function, typename> +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 Method, typename InstanceGetter> -typename boost::enable_if< boost::function_types::is_member_function_pointer<Method> >::type -LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, - const InstanceGetter& getter, - const LLSD& params, const LLSD& defaults) +template<typename Method, typename InstanceGetter, typename> +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); } @@ -490,29 +700,45 @@ template <typename Function> 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<Function>::template apply<boost::fusion::nil>, - f, - _1, - boost::fusion::nil()); + // Return an invoker_function that accepts (const LLSD& args). + return [f](const LLSD& args) + { + // When called, call always_return<LLSD>, directing it to call + // f(expanded args). always_return<LLSD> 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<LLSD>( + [f, args] + () + { + return LL::apply(f, args); + }); + }; } template <typename Method, typename InstanceGetter> 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<Method>::template method_apply<InstanceGetter>, - f, - _1, - getter); + return [f, getter](const LLSD& args) + { + // always_return<LLSD>() immediately calls the lambda we pass, and + // returns LLSD whether our passed lambda returns void or non-void. + return LL::always_return<LLSD>( + [f, getter, args] + () + { + // function_arity<member function> includes its implicit 'this' pointer + constexpr auto arity = LL::function_arity< + typename std::remove_reference<Method>::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 + // lambda call. + return LL::apply_n<arity>(LL::bind_front(f, LL::get_ptr(getter())), args); + }); + }; } /***************************************************************************** @@ -521,21 +747,138 @@ 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 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 + * specified LLEventPump containing an "error" key whose value is the relevant + * error message. If "reply" is not present, the DispatchError exception will + * 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. + * 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 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 "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. */ -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(); } + /// LLEventPump name, dispatch key [, arguments key (see LLEventDispatcher)] + template <typename... ARGS> + 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 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; - LLEventStream mPump; LLTempBoundListener mBoundListener; + static std::string mReplyKey; }; +template <typename... ARGS> +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>(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/lleventfilter.h b/indra/llcommon/lleventfilter.h index 9dd2cbb684ed151d378dbd3de9016f062b6318de..db4dbeda560aca6a95144ac0a0dabbbfbc274610 100644 --- a/indra/llcommon/lleventfilter.h +++ b/indra/llcommon/lleventfilter.h @@ -435,16 +435,61 @@ class LLStoreListener: public LLEventFilter // generic type-appropriate store through mTarget, construct an // LLSDParam<T> and store that, thus engaging LLSDParam's custom // conversions. - mTarget = LLSDParam<T>(llsd::drill(event, mPath)); + storeTarget(LLSDParam<T>(llsd::drill(event, mPath))); return mConsume; } private: + // This method disambiguates LLStoreListener<LLSD>. Directly assigning + // some_LLSD_var = LLSDParam<LLSD>(some_LLSD_value); + // is problematic because the compiler has too many choices: LLSD has + // multiple assignment operator overloads, and LLSDParam<LLSD> has a + // templated conversion operator. But LLSDParam<LLSD> 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 <typename T> +struct LLVarHolder +{ + T mVar; +}; + +/** + * LLCaptureListener isa LLStoreListener that bundles the target variable of + * interest. + */ +template <typename T> +class LLCaptureListener: public LLVarHolder<T>, + public LLStoreListener<T> +{ +private: + using holder = LLVarHolder<T>; + using super = LLStoreListener<T>; + +public: + LLCaptureListener(const LLSD& path=LLSD(), bool consume=false): + super(*this, holder::mVar, path, consume) + {} + + void set(T&& newval=T()) { holder::mVar = std::forward<T>(newval); } + + const T& get() const { return holder::mVar; } + operator const T&() { return holder::mVar; } +}; + /***************************************************************************** * LLEventLogProxy *****************************************************************************/ diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp index 0a213bddef3da7f7e56554bc2419284db70f2355..1a305ec3dc453de445f2d00dbf9ac9c1eb5635a9 100644 --- a/indra/llcommon/llevents.cpp +++ b/indra/llcommon/llevents.cpp @@ -68,19 +68,78 @@ 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; +} + +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? + 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; + // 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); @@ -114,7 +173,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 6968ef52ce35f984827d8dfdbdf6803f72c1b0e0..6a1e6d158b9b091cfeeac4860478226122180f8b 100644 --- a/indra/llcommon/llevents.h +++ b/indra/llcommon/llevents.h @@ -260,6 +260,45 @@ class LL_COMMON_API LLEventPumps final : public LLSingleton<LLEventPumps>, LLEventPump& make(const std::string& name, bool tweak=false, const std::string& type=std::string()); + /// function passed to registerTypeFactory() + typedef std::function<LLEventPump*(const std::string& name, bool tweak, const std::string& type)> 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); + void unregisterTypeFactory(const std::string& type); + + /// function passed to registerPumpFactory() + typedef std::function<LLEventPump*(const std::string&)> 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 + * <i>that particular</i> @a name is passed to obtain(). Bear in mind that + * <tt>obtain(name)</tt> 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 <tt>registerPumpFactory(name)</tt> call (returns + * true) and a call to <tt>obtain(name)</tt>, someone explicitly + * 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. * If the pump does not exist, do nothing. @@ -317,13 +356,13 @@ class LL_COMMON_API LLEventPumps final : public LLSingleton<LLEventPumps>, typedef std::set<LLEventPump*> PumpSet; PumpSet mOurPumps; // for make(), map string type name to LLEventPump subclass factory function - typedef std::map<std::string, std::function<LLEventPump*(const std::string&, bool)>> PumpFactories; + typedef std::map<std::string, TypeFactory> 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/llleap.cpp b/indra/llcommon/llleap.cpp index 051384f342919801d3f9910b9cf63cc0e3f1c0f2..fdb25fbe759e264e64201051fcf10f654feef253 100644 --- a/indra/llcommon/llleap.cpp +++ b/indra/llcommon/llleap.cpp @@ -332,11 +332,28 @@ class LLLeapImpl: public LLLeap } 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()) diff --git a/indra/llcommon/llleaplistener.cpp b/indra/llcommon/llleaplistener.cpp index 5a9bf75b0a3b1d0dc2ea43029352c25a772b3684..471f52e91c841a18a32b0bb65ea59c1c136ec3c1 100644 --- a/indra/llcommon/llleaplistener.cpp +++ b/indra/llcommon/llleaplistener.cpp @@ -14,13 +14,16 @@ // associated header #include "llleaplistener.h" // STL headers -#include <map> +#include <algorithm> // std::find_if #include <functional> +#include <map> +#include <set> // std headers // external library headers // other Linden headers -#include "lluuid.h" +#include "lazyeventapi.h" #include "llsdutil.h" +#include "lluuid.h" #include "stringize.h" /***************************************************************************** @@ -109,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.) - for(ListenersMap::value_type& pair : mListeners) + for (ListenersMap::value_type& pair : mListeners) { pair.second.disconnect(); } @@ -207,31 +210,65 @@ void LLLeapListener::getAPIs(const LLSD& request) const { Response reply(LLSD(), request); + // first, traverse existing LLEventAPI instances + std::set<std::string> 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 <typename API> +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/llprocess.cpp b/indra/llcommon/llprocess.cpp index c14d7bca21def7dd0f0cd51d1999b7ea04acb3a8..eb70da1be600b283233c02179039f69d19f99287 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -535,6 +535,7 @@ LLProcess::LLProcess(const LLSDOrParams& params): // preserve existing semantics, we promise that mAttached defaults to the // same setting as mAutokill. mAttached(params.attached.isProvided()? params.attached : params.autokill), + mPool(NULL), mPipes(NSLOTS) { // Hmm, when you construct a ptr_vector with a size, it merely reserves @@ -555,8 +556,14 @@ LLProcess::LLProcess(const LLSDOrParams& params): mPostend = params.postend; + apr_pool_create(&mPool, gAPRPoolp); + if (!mPool) + { + LLTHROW(LLProcessError(STRINGIZE("failed to create apr pool"))); + } + apr_procattr_t *procattr = NULL; - chkapr(apr_procattr_create(&procattr, gAPRPoolp)); + chkapr(apr_procattr_create(&procattr, mPool)); // IQA-490, CHOP-900: On Windows, ask APR to jump through hoops to // constrain the set of handles passed to the child process. Before we @@ -695,14 +702,14 @@ LLProcess::LLProcess(const LLSDOrParams& params): // one. Hand-expand chkapr() macro so we can fill in the actual command // string instead of the variable names. if (ll_apr_warn_status(apr_proc_create(&mProcess, argv[0], &argv[0], NULL, procattr, - gAPRPoolp))) + mPool))) { LLTHROW(LLProcessError(STRINGIZE(params << " failed"))); } // arrange to call status_callback() apr_proc_other_child_register(&mProcess, &LLProcess::status_callback, this, mProcess.in, - gAPRPoolp); + mPool); // and make sure we poll it once per "mainloop" tick sProcessListener.addPoll(*this); mStatus.mState = RUNNING; @@ -821,6 +828,12 @@ LLProcess::~LLProcess() { kill("destructor"); } + + if (mPool) + { + apr_pool_destroy(mPool); + mPool = NULL; + } } bool LLProcess::kill(const std::string& who) diff --git a/indra/llcommon/llprocess.h b/indra/llcommon/llprocess.h index 98021450e1c7736e68fd6ad38b0ef3b4481a9fe6..8e23cc945c7120170f335956db0dea0efb7ceabf 100644 --- a/indra/llcommon/llprocess.h +++ b/indra/llcommon/llprocess.h @@ -566,6 +566,7 @@ class LL_COMMON_API LLProcess: public boost::noncopyable // explicitly want this ptr_vector to be able to store NULLs typedef boost::ptr_vector< boost::nullable<BasePipe> > PipeVector; PipeVector mPipes; + apr_pool_t* mPool; }; /// for logging diff --git a/indra/llcommon/llptrto.h b/indra/llcommon/llptrto.h index 4082e30de670bebdb422074b5b6f45f72847c7c8..9ef279fdbfd973f93b778b29dfe31df91734b163 100644 --- a/indra/llcommon/llptrto.h +++ b/indra/llcommon/llptrto.h @@ -33,9 +33,12 @@ #include "llpointer.h" #include "llrefcount.h" // LLRefCount +#include <boost/intrusive_ptr.hpp> +#include <boost/shared_ptr.hpp> #include <boost/type_traits/is_base_of.hpp> #include <boost/type_traits/remove_pointer.hpp> -#include <boost/utility/enable_if.hpp> +#include <memory> // std::shared_ptr, std::unique_ptr +#include <type_traits> /** * LLPtrTo<TARGET>::type is either of two things: @@ -55,14 +58,14 @@ struct LLPtrTo /// specialize for subclasses of LLRefCount template <class T> -struct LLPtrTo<T, typename boost::enable_if< boost::is_base_of<LLRefCount, T> >::type> +struct LLPtrTo<T, typename std::enable_if< boost::is_base_of<LLRefCount, T>::value >::type> { typedef LLPointer<T> type; }; /// specialize for subclasses of LLThreadSafeRefCount template <class T> -struct LLPtrTo<T, typename boost::enable_if< boost::is_base_of<LLThreadSafeRefCount, T> >::type> +struct LLPtrTo<T, typename std::enable_if< boost::is_base_of<LLThreadSafeRefCount, T>::value >::type> { typedef LLPointer<T> type; }; @@ -83,4 +86,83 @@ struct LLRemovePointer< LLPointer<SOMECLASS> > typedef SOMECLASS type; }; +namespace LL +{ + +/***************************************************************************** +* get_ref() +*****************************************************************************/ + template <typename T> + 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 <typename T> + struct GetRef<const T*> + { + const auto& operator()(const T* ptr) const { return *ptr; } + }; + + template <typename T> + struct GetRef<T*> + { + auto& operator()(T* ptr) const { return *ptr; } + }; + + template <typename T> + struct GetRef< LLPointer<T> > + { + auto& operator()(LLPointer<T> ptr) const { return *ptr; } + }; + + /// whether we're passed a pointer or a reference, return a reference + template <typename T> + auto& get_ref(T& ptr_or_ref) + { + return GetRef<typename std::decay<T>::type>()(ptr_or_ref); + } + + template <typename T> + const auto& get_ref(const T& ptr_or_ref) + { + return GetRef<typename std::decay<T>::type>()(ptr_or_ref); + } + +/***************************************************************************** +* get_ptr() +*****************************************************************************/ + // if T is any pointer type we recognize, return it unchanged + template <typename T> + const T* get_ptr(const T* ptr) { return ptr; } + + template <typename T> + T* get_ptr(T* ptr) { return ptr; } + + template <typename T> + const std::shared_ptr<T>& get_ptr(const std::shared_ptr<T>& ptr) { return ptr; } + + template <typename T> + const std::unique_ptr<T>& get_ptr(const std::unique_ptr<T>& ptr) { return ptr; } + + template <typename T> + const boost::shared_ptr<T>& get_ptr(const boost::shared_ptr<T>& ptr) { return ptr; } + + template <typename T> + const boost::intrusive_ptr<T>& get_ptr(const boost::intrusive_ptr<T>& ptr) { return ptr; } + + template <typename T> + const LLPointer<T>& get_ptr(const LLPointer<T>& ptr) { return ptr; } + + // T is not any pointer type we recognize, take a pointer to the parameter + template <typename T> + const T* get_ptr(const T& obj) { return &obj; } + + template <typename T> + 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 dd2ecda802bda1f032c0d1796f700b7a6bb8563d..a8c3f2dea2e8dd7ab24d989ff1ff192c994b553c 100644 --- a/indra/llcommon/llsdutil.cpp +++ b/indra/llcommon/llsdutil.cpp @@ -1045,3 +1045,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 c730393906df1d57709cb66ed30967217bb3a685..e6f404d59f009b1b35cb53f905fc9db33954a707 100644 --- a/indra/llcommon/llsdutil.h +++ b/indra/llcommon/llsdutil.h @@ -29,8 +29,14 @@ #ifndef LL_LLSDUTIL_H #define LL_LLSDUTIL_H +#include "apply.h" // LL::invoke() +#include "function_types.h" // LL::function_arity #include "llsd.h" #include <boost/functional/hash.hpp> +#include <cassert> +#include <memory> // std::shared_ptr +#include <type_traits> +#include <vector> // U32 LL_COMMON_API LLSD ll_sd_from_U32(const U32); @@ -298,6 +304,11 @@ LLSD map(Ts&&... vs) /***************************************************************************** * LLSDParam *****************************************************************************/ +struct LLSDParamBase +{ + virtual ~LLSDParamBase() {} +}; + /** * LLSDParam is a customization point for passing LLSD values to function * parameters of more or less arbitrary type. LLSD provides a small set of @@ -315,7 +326,7 @@ LLSD map(Ts&&... vs) * @endcode */ template <typename T> -class LLSDParam +class LLSDParam: public LLSDParamBase { public: /** @@ -323,13 +334,66 @@ class LLSDParam * value for later retrieval */ LLSDParam(const LLSD& value): - _value(value) + value_(value) {} - operator T() const { return _value; } + operator T() const { return value_; } private: - T _value; + T value_; +}; + +/** + * LLSDParam<LLSD> is for when you don't already have the target parameter + * type in hand. Instantiate LLSDParam<LLSD>(your LLSD object), and the + * templated conversion operator will try to select a more specific LLSDParam + * specialization. + */ +template <> +class LLSDParam<LLSD>: public LLSDParamBase +{ +private: + LLSD value_; + // LLSDParam<LLSD>::operator T() works by instantiating an LLSDParam<T> on + // demand. Returning that engages LLSDParam<T>::operator T(), producing + // the desired result. But LLSDParam<const char*> owns a std::string whose + // c_str() is returned by its operator const char*(). If we return a temp + // LLSDParam<const char*>, the compiler can destroy it right away, as soon + // as we've called operator const char*(). That's a problem! That + // invalidates the const char* we've just passed to the subject function. + // This LLSDParam<LLSD> is presumably guaranteed to survive until the + // subject function has returned, so we must ensure that any constructed + // LLSDParam<T> lives just as long as this LLSDParam<LLSD> does. Putting + // each LLSDParam<T> on the heap and capturing a smart pointer in a vector + // works. We would have liked to use std::unique_ptr, but vector entries + // must be copyable. + // (Alternatively we could assume that every instance of LLSDParam<LLSD> + // will be asked for at most ONE conversion. We could store a scalar + // std::unique_ptr and, when constructing an new LLSDParam<T>, assert that + // the unique_ptr is empty. But some future change in usage patterns, and + // consequent failure of that assertion, would be very mysterious. Instead + // of explaining how to fix it, just fix it now.) + mutable std::vector<std::shared_ptr<LLSDParamBase>> converters_; + +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<T> to convert; that + /// preserves the existing customization mechanism + template <typename T> + operator T() const + { + // capture 'ptr' with the specific subclass type because converters_ + // only stores LLSDParamBase pointers + auto ptr{ std::make_shared<LLSDParam<std::decay_t<T>>>(value_) }; + // keep the new converter alive until we ourselves are destroyed + converters_.push_back(ptr); + return *ptr; + } }; /** @@ -346,17 +410,17 @@ class LLSDParam */ #define LLSDParam_for(T, AS) \ template <> \ -class LLSDParam<T> \ +class LLSDParam<T>: public LLSDParamBase \ { \ public: \ LLSDParam(const LLSD& value): \ - _value((T)value.AS()) \ + value_((T)value.AS()) \ {} \ \ - operator T() const { return _value; } \ + operator T() const { return value_; } \ \ private: \ - T _value; \ + T value_; \ } LLSDParam_for(float, asReal); @@ -372,31 +436,31 @@ LLSDParam_for(LLSD::Binary, asBinary); * safely pass an LLSDParam<const char*>(yourLLSD). */ template <> -class LLSDParam<const char*> +class LLSDParam<const char*>: public LLSDParamBase { private: // The difference here is that we store a std::string rather than a const // char*. It's important that the LLSDParam object own the std::string. - std::string _value; + std::string value_; // We don't bother storing the incoming LLSD object, but we do have to - // distinguish whether _value is an empty string because the LLSD object + // distinguish whether value_ is an empty string because the LLSD object // contains an empty string or because it's isUndefined(). - bool _undefined; + bool undefined_; public: LLSDParam(const LLSD& value): - _value(value), - _undefined(value.isUndefined()) + value_(value), + undefined_(value.isUndefined()) {} - // The const char* we retrieve is for storage owned by our _value member. + // The const char* we retrieve is for storage owned by our value_ member. // That's how we guarantee that the const char* is valid for the lifetime // of this LLSDParam object. Constructing your LLSDParam in the argument // list should ensure that the LLSDParam object will persist for the // duration of the function call. operator const char*() const { - if (_undefined) + if (undefined_) { // By default, an isUndefined() LLSD object's asString() method // will produce an empty string. But for a function accepting @@ -406,7 +470,7 @@ class LLSDParam<const char*> // case, though, no LLSD value could pass NULL. return NULL; } - return _value.c_str(); + return value_.c_str(); } }; @@ -555,4 +619,56 @@ struct hash<LLSD> } }; } + +namespace LL +{ + +/***************************************************************************** +* apply(function, LLSD array) +*****************************************************************************/ +// validate incoming LLSD blob, and return an LLSD array suitable to pass to +// the function of interest +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 +// 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 <typename CALLABLE, std::size_t... I> +auto apply_impl(CALLABLE&& func, const LLSD& array, std::index_sequence<I...>) +{ + // call func(unpacked args), using generic LLSDParam<LLSD> to convert each + // entry in 'array' to the target parameter type + return std::forward<CALLABLE>(func)(LLSDParam<LLSD>(array[I])...); +} + +// use apply_n<ARITY>(function, LLSD) to call a specific arity of a variadic +// function with (that many) items from the passed LLSD array +template <size_t ARITY, typename CALLABLE> +auto apply_n(CALLABLE&& func, const LLSD& args) +{ + return apply_impl(std::forward<CALLABLE>(func), + apply_llsd_fix(ARITY, args), + std::make_index_sequence<ARITY>()); +} + +/** + * 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. (But see apply_n() to + * pass a specific number of args to a variadic function.) + */ +template <typename CALLABLE> +auto apply(CALLABLE&& func, const LLSD& args) +{ + // infer arity from the definition of func + constexpr auto arity = function_arity< + typename std::remove_reference<CALLABLE>::type>::value; + // now that we have a compile-time arity, apply_n() works + return apply_n<arity>(std::forward<CALLABLE>(func), args); +} + +} // 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 0000000000000000000000000000000000000000..56b497e0c84951e3ac77219aba8965c72b133486 --- /dev/null +++ b/indra/llcommon/tests/apply_test.cpp @@ -0,0 +1,240 @@ +/** + * @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 +#include <iomanip> +// external library headers +// other Linden headers +#include "llsd.h" +#include "llsdutil.h" +#include <array> +#include <string> +#include <vector> + +// for ensure_equals +std::ostream& operator<<(std::ostream& out, const std::vector<std::string>& 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<std::string>) +#include "../test/lltut.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<LLSD::String> quick + { + "The", "quick", "brown", "fox", "etc." + }; + + static std::array<int, 5> 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 }; + // capture calls from collect() + std::vector<std::string> collected; + + /*------------------------- 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 sdfunc(const LLSD& sd) + { + called = true; + ensure_equals("sd mismatch", sd.asInteger(), statics::i); + } + + void intfunc(int i) + { + called = true; + ensure_equals("i mismatch", i, statics::i); + } + + void voidfunc() + { + called = true; + } + + // recursion tail + void collect() + { + called = true; + } + + // collect(arbitrary) + template <typename... ARGS> + void collect(const std::string& first, ARGS&&... rest) + { + statics::collected.push_back(first); + collect(std::forward<ARGS>(rest)...); + } + } // namespace statics + + struct apply_data + { + apply_data() + { + // reset called before each test + statics::called = false; + statics::collected.clear(); + } + }; + typedef test_group<apply_data> 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(fn(int), LLSD scalar)"); + LL::apply(statics::intfunc, LLSD(statics::i)); + 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<LLSD> 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, + 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); + } + + template<> template<> + void object::test<8>() + { + set_test_name("VAPPLY()"); + // Make a std::array<std::string> from statics::quick. We can't call a + // variadic function with a data structure of dynamic length. + std::array<std::string, 5> 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 diff --git a/indra/llcommon/tests/lazyeventapi_test.cpp b/indra/llcommon/tests/lazyeventapi_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..31b2d6d17fdd8fb1bb8ab936f2b6c979fe642cf6 --- /dev/null +++ b/indra/llcommon/tests/lazyeventapi_test.cpp @@ -0,0 +1,136 @@ +/** + * @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" +#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) + {} + + // example operation, registered by LazyEventAPI subclass below + void set_data(const LLSD& event) + { + data = event["data"]; + } +}; + +// LazyEventAPI registrar subclass +class MyRegistrar: public LL::LazyEventAPI<MyListener> +{ + using super = LL::LazyEventAPI<MyListener>; + 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("set", "This is a set operation", &listener::set_data); + } +}; +// Normally we'd declare a static instance of MyRegistrar -- but because we +// want to test both with and without, defer declaration to individual test +// methods. + +/***************************************************************************** +* TUT +*****************************************************************************/ +namespace tut +{ + struct lazyeventapi_data + { + lazyeventapi_data() + { + // before every test, reset 'data' + data.clear(); + } + ~lazyeventapi_data() + { + // after every test, reset LLEventPumps + LLEventPumps::deleteSingleton(); + } + }; + typedef test_group<lazyeventapi_data> 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(llsd::map("op", "set", "data", "hey")); + ensure_equals("failed to set data", data.asString(), "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(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; + // 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<const MyRegistrar*>(®istrar))) + break; + ensure("Failed to find MyRegistrar via LLInstanceTracker", found); + + 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<LL::LazyEventAPIBase::NameDesc> 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 diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index 4f4195f2d5a0d51f3dcd06a42302c6dba2c5a71e..4f3be2600724de626a0bfd7a49aab0c1e365d0b6 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -18,9 +18,12 @@ // external library headers // other Linden headers #include "../test/lltut.h" +#include "lleventfilter.h" #include "llsd.h" #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" @@ -175,6 +178,7 @@ struct Vars /*-------- Arbitrary-params (non-const, const, static) methods ---------*/ void methodna(NPARAMSa) { + DEBUG; // Because our const char* param cp might be NULL, and because we // intend to capture the value in a std::string, have to distinguish // between the NULL value and any non-NULL value. Use a convention @@ -186,7 +190,7 @@ struct Vars else vcp = std::string("'") + cp + "'"; - debug()("methodna(", b, + this->debug()("methodna(", b, ", ", i, ", ", f, ", ", d, @@ -203,7 +207,7 @@ struct Vars void methodnb(NPARAMSb) { std::ostringstream vbin; - for (U8 byte : bin) + for (U8 byte: bin) { vbin << std::hex << std::setfill('0') << std::setw(2) << unsigned(byte); } @@ -224,7 +228,8 @@ struct Vars void cmethodna(NPARAMSa) const { - debug()('c', NONL); + DEBUG; + this->debug()('c', NONL); const_cast<Vars*>(this)->methodna(NARGSa); } @@ -313,6 +318,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 <typename CALLABLE> + std::string call_exc(CALLABLE&& func, const std::string& exc_frag) + { + std::string what = + catch_what<LLEventDispatcher::DispatchError>(std::forward<CALLABLE>(func)); + ensure_has(what, exc_frag); + return what; + } + + template <typename CALLABLE> + 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<LLEventDispatcher::DispatchError>(std::forward<CALLABLE>(func)); + capture.messageWith(frag); + } + struct lleventdispatcher_data { Debug debug{"test"}; @@ -395,9 +425,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 ----------------*/ @@ -459,7 +489,7 @@ namespace tut debug("dft_array_full:\n", dft_array_full); // Partial defaults arrays. - for (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] = @@ -469,7 +499,7 @@ namespace tut debug("dft_array_partial:\n", dft_array_partial); - for (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]); @@ -581,6 +611,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. @@ -596,19 +627,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. - for (LLEventDispatcher::NameDesc nd : const_cast<const Dispatcher&>(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 @@ -619,41 +645,49 @@ namespace tut std::ostringstream out; out << "LLEventDispatcher failed to report"; const char* delim = ": "; - for (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) + std::string call_exc(const std::string& func, const LLSD& args, const std::string& exc_frag) { - ensure(STRINGIZE("'" << outer << "' does not contain '" << inner << "'").c_str(), - outer.find(inner) != std::string::npos); + return tut::call_exc( + [this, func, args]() + { + if (func.empty()) + { + work(args); + } + else + { + work(func, args); + } + }, + exc_frag); } - void call_exc(const std::string& func, const LLSD& args, const std::string& exc_frag) + void call_logerr(const std::string& func, const LLSD& args, const std::string& frag) { - std::string threw = catch_what<std::runtime_error>([this, &func, &args](){ - work(func, args); - }); - ensure_has(threw, exc_frag); + tut::call_logerr([this, func, args](){ work(func, args); }, frag); } 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; } @@ -722,7 +756,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))); - for (LLSD ae : inArray(attempts)) + for (LLSD ae: inArray(attempts)) { std::string threw = catch_what<std::exception>([this, &ae](){ work.add("freena_err", "freena", freena, ae); @@ -797,7 +831,7 @@ namespace tut { set_test_name("query Callables with/out required params"); LLSD names(llsd::array("free1", "Dmethod1", "Dcmethod1", "method1")); - for (LLSD nm : inArray(names)) + for (LLSD nm: inArray(names)) { LLSD metadata(getMetadata(nm)); ensure_equals("name mismatch", metadata["name"], nm); @@ -826,19 +860,19 @@ namespace tut (5, llsd::array("freena_array", "smethodna_array", "methodna_array")), llsd::array (5, llsd::array("freenb_array", "smethodnb_array", "methodnb_array")))); - for (LLSD ae : inArray(expected)) + for (LLSD ae: inArray(expected)) { LLSD::Integer arity(ae[0].asInteger()); LLSD names(ae[1]); LLSD req(LLSD::emptyArray()); if (arity) req[arity - 1] = LLSD(); - for (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()); } @@ -852,7 +886,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")); - for (LLSD nm : inArray(names)) + for (LLSD nm: inArray(names)) { LLSD metadata(getMetadata(nm)); ensure_equals("name mismatch", metadata["name"], nm); @@ -882,7 +916,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"))); - for (LLSD eq : inArray(equivalences)) + for (LLSD eq: inArray(equivalences)) { LLSD adft(eq[0]); LLSD mdft(eq[1]); @@ -896,8 +930,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); } } @@ -913,7 +947,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; - for (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 @@ -941,7 +975,7 @@ namespace tut // Generate maps containing parameter names not provided by the // dft_map_partial maps. LLSD skipreq(allreq); - for (LLSD::String a : ab) + for (LLSD::String a: ab) { for (const MapEntry& me : inMap(dft_map_partial[a])) { @@ -988,7 +1022,7 @@ namespace tut (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)) + for (LLSD grp: inArray(groups)) { // Internal structure of each group in 'groups': LLSD names(grp[0]); @@ -1001,14 +1035,14 @@ namespace tut optional); // Loop through 'names' - for (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); } } @@ -1029,13 +1063,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<std::runtime_error>([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"); } @@ -1077,7 +1105,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'. - for (const CallablesTriple& tr : tests) + for (const CallablesTriple& tr: tests) { // Should be able to pass 'answer' to Callables registered // without 'required'. @@ -1085,7 +1113,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); @@ -1099,17 +1127,20 @@ 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_exc("free0_array", 17, array_exc); - call_exc("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_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", llsd::array("a", "b"), map_exc); @@ -1123,7 +1154,7 @@ namespace tut ("free0_array", "free0_map", "smethod0_array", "smethod0_map", "method0_array", "method0_map")); - for (LLSD name : inArray(names)) + for (LLSD name: inArray(names)) { // Look up the Vars instance for this function. Vars* vars(varsfor(name)); @@ -1148,15 +1179,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")); - for (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)) { - for (const llsd::MapEntry& e : inMap(funcsab)) + for (const LLSD& funcsab: inArray(array_funcs)) { - call_exc(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"); + } } } } @@ -1164,7 +1201,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<U8> binary; for (size_t h(0x01), i(0); i < 5; h+= 0x22, ++i) { @@ -1176,40 +1213,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; - for (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)) { - for (const LLSD& funcsab : inArray(array_funcs)) + for (LLSD::String a: ab) { - for (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 } } } @@ -1237,7 +1259,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); - for (LLSD::String a : ab) + for (LLSD::String a: ab) { array_overfull[a].append("bogus"); } @@ -1251,7 +1273,7 @@ namespace tut ensure_not_equals("UUID collision", array_full["b"][1].asUUID(), dft_array_full["b"][1].asUUID()); LLSD map_full, map_overfull; - for (LLSD::String a : ab) + for (LLSD::String a: ab) { map_full[a] = zipmap(params[a], array_full[a]); map_overfull[a] = map_full[a]; @@ -1292,21 +1314,360 @@ 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)); - for (const LLSD& args : inArray(argssets)) + for (const LLSD& args: inArray(argssets)) { - for (LLSD::String a : ab) + for (LLSD::String a: ab) { - for (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? } } } } + + struct DispatchResult: public LLDispatchListener + { + using DR = DispatchResult; + + DispatchResult(): LLDispatchListener("results", "op") + { + 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)); + } + LLSD arrayfunc(int i, const std::string& str) const + { + return llsd::array(intfunc(i), strfunc(str)); + } + }; + + template<> template<> + void object::test<23>() + { + set_test_name("string result"); + DispatchResult service; + LLSD result{ service("strfunc", "a string") }; + ensure_equals("strfunc() mismatch", result.asString(), "got 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", -17) }; + ensure_equals("intfunc() mismatch", result.asInteger(), 17); + } + + 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; + LLSD result{ service("mapfunc", llsd::array(-12, "value")) }; + ensure_equals("mapfunc() mismatch", result, llsd::map("i", 12, "str", "got value")); + } + + template<> template<> + void object::test<28>() + { + set_test_name("array LLSD result"); + DispatchResult service; + LLSD result{ service("arrayfunc", llsd::array(-8, "word")) }; + ensure_equals("arrayfunc() mismatch", result, llsd::array(8, "got word")); + } + + template<> template<> + void object::test<29>() + { + 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<30>() + { + set_test_name("listener error with reply"); + DispatchResult service; + LLCaptureListener<LLSD> 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<31>() + { + set_test_name("listener call to void function"); + DispatchResult service; + LLCaptureListener<LLSD> 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<32>() + { + set_test_name("listener call to string function"); + DispatchResult service; + LLCaptureListener<LLSD> result; + service.post(llsd::map( + "op", "strfunc", + "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(), "got a string"); + } + + template<> template<> + void object::test<33>() + { + set_test_name("listener call to map function"); + DispatchResult service; + LLCaptureListener<LLSD> result; + service.post(llsd::map( + "op", "mapfunc", + "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 i from mapfunc", reply["i"].asInteger(), 7); + ensure_equals("bad str from mapfunc", reply["str"], "got value"); + } + + template<> template<> + void object::test<34>() + { + set_test_name("batched map success"); + DispatchResult service; + LLCaptureListener<LLSD> 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<35>() + { + set_test_name("batched map error"); + DispatchResult service; + LLCaptureListener<LLSD> 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<36>() + { + 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<37>() + { + set_test_name("batched array success"); + DispatchResult service; + LLCaptureListener<LLSD> 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<38>() + { + set_test_name("batched array error"); + DispatchResult service; + LLCaptureListener<LLSD> 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<39>() + { + 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 diff --git a/indra/llcommon/tests/wrapllerrs.h b/indra/llcommon/tests/wrapllerrs.h index ebf9f5253a106c8513239ca62aa963a7d26310dd..6978c296b39a3a1077fda95c6b9a62dbfd366bf6 100644 --- a/indra/llcommon/tests/wrapllerrs.h +++ b/indra/llcommon/tests/wrapllerrs.h @@ -226,6 +226,11 @@ class CaptureLog : public boost::noncopyable return std::dynamic_pointer_cast<CaptureLogRecorder>(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; diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp index 224f45fa174be6ec1e690eeb1dbbc3f276d0c820..f5d16479fc26061720524e3798c589a7a3f562d0 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 975ce73898eada0fb08af8214be384a335e9f8b1..28e19ffd1b2c15f1e21ccf4212dee3c89bd2dbdd 100644 --- a/indra/llui/lllineeditor.h +++ b/indra/llui/lllineeditor.h @@ -286,7 +286,10 @@ class LLLineEditor void setBgImage(LLPointer<LLUIImage> image) { mBgImage = image; } void setBgImageFocused(LLPointer<LLUIImage> image) { mBgImageFocused = image; } -private: + void setShowContextMenu(bool show) { mShowContextMenu = show; } + bool getShowContextMenu() const { return mShowContextMenu; } + + private: // private helper methods void pasteHelper(bool is_primary); @@ -406,6 +409,8 @@ class LLLineEditor LLHandle<LLContextMenu> mContextMenuHandle; + bool mShowContextMenu; + private: // Instances that by default point to the statics but can be overidden in XML. LLPointer<LLUIImage> mBgImage; diff --git a/indra/llui/lltabcontainer.cpp b/indra/llui/lltabcontainer.cpp index 44ae56597f8dfb534f8218f1c6954cc740b74184..397f6108baa2208139437d22b81fc2bbf739e3c6 100644 --- a/indra/llui/lltabcontainer.cpp +++ b/indra/llui/lltabcontainer.cpp @@ -2152,14 +2152,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->mButton->getVisible() && !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; + } } } } diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index cdf9ccef8ef491c05f1444396a348f5f9007f021..e8cf5ad7c452c0cbfd6fc7dc150f8f4aa2a8e9c1 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -1613,7 +1613,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/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index 9f6adcff1b3f2f6b6cfbf2e177d2e9908eff07b9..589471a5618e6f6462f2a14f82d10536e0f50525 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" @@ -1605,3 +1607,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<std::string, LLLocalizationData>::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<std::string, LLLocalizationData>::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<LLScrollListItem::Params>::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 f07c8fc05c29ba0a356786b2454f05f5ed6ae7c8..731ac25e51479896248b93263bc93412dbd47668 100644 --- a/indra/llui/llurlentry.h +++ b/indra/llui/llurlentry.h @@ -563,4 +563,37 @@ class LLUrlEntryIPv6 : public LLUrlEntryBase 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<std::string, LLLocalizationData> mLocalizations; + LLKeyBindingToStringHandler* pHandler; +}; + #endif diff --git a/indra/llui/llurlregistry.cpp b/indra/llui/llurlregistry.cpp index c91fc168b2addcc0e274f39785ab47fc930b60c3..e8e932c7d438b9122c1eee37cbeb281dc06f1df1 100644 --- a/indra/llui/llurlregistry.cpp +++ b/indra/llui/llurlregistry.cpp @@ -79,6 +79,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()); @@ -313,3 +315,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 d52f44bec04addb26dc3dfd56e5e114455b210d0..a696cef14f3b0babd87e26fa2338a54dd4812336 100644 --- a/indra/llui/llurlregistry.h +++ b/indra/llui/llurlregistry.h @@ -36,6 +36,8 @@ #include <string> #include <vector> +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 @@ class LLUrlRegistry final : public LLSingleton<LLUrlRegistry> 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<LLUrlEntryBase *> mUrlEntry; LLUrlEntryBase* mUrlEntryTrusted; @@ -96,6 +101,7 @@ class LLUrlRegistry final : public LLSingleton<LLUrlRegistry> LLUrlEntryBase* mUrlEntryHTTPLabel; LLUrlEntryBase* mUrlEntrySLLabel; LLUrlEntryBase* mUrlEntryNoLink; + LLUrlEntryBase* mUrlEntryKeybinding; }; #endif diff --git a/indra/llwindow/llkeyboard.cpp b/indra/llwindow/llkeyboard.cpp index 6cf58277ba595fcd51c54aa6816f09496d3941a8..86d12571b3f72874e139898e8ed26b64dbc94158 100644 --- a/indra/llwindow/llkeyboard.cpp +++ b/indra/llwindow/llkeyboard.cpp @@ -365,6 +365,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) { @@ -432,6 +471,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(std::string_view instring, MASK* mask) diff --git a/indra/llwindow/llkeyboard.h b/indra/llwindow/llkeyboard.h index 3455d74314e2ce4a57eb1b45558abefd0ff82940..a776a9044be3fac2c764b1c719e6d5d52db54d4f 100644 --- a/indra/llwindow/llkeyboard.h +++ b/indra/llwindow/llkeyboard.h @@ -97,8 +97,10 @@ class LLKeyboard static BOOL maskFromString(std::string_view 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. @@ -131,6 +133,13 @@ class LLKeyboard static std::map<std::string,KEY> 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/VIEWER_VERSION.txt b/indra/newview/VIEWER_VERSION.txt index 21c8c7b46b8993bd95ff730695cad0d26e4239f3..0e7b60da8a3ba25d7f31fc658b67a85d7420bf92 100644 --- a/indra/newview/VIEWER_VERSION.txt +++ b/indra/newview/VIEWER_VERSION.txt @@ -1 +1 @@ -7.1.1 +7.1.2 \ No newline at end of file diff --git a/indra/newview/app_settings/shaders/class1/deferred/emissiveF.glsl b/indra/newview/app_settings/shaders/class1/deferred/emissiveF.glsl index 9e61b6b894da3cf43f6ed52cadd203388a941cf9..c95f791dbf395a039839ebd1ba285fafedd48caa 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/emissiveF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/emissiveF.glsl @@ -34,6 +34,6 @@ void main() { // NOTE: when this shader is used, only alpha is being written to float a = diffuseLookup(vary_texcoord0.xy).a*vertex_color.a; - frag_color = vec4(0, 0, 0, a); + frag_color = max(vec4(0, 0, 0, a), vec4(0)); } diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index e0e5bc5d071fba13beef034bc47d00f3000dde57..1233458b84acf0452036574c05989b5af46efbea 100644 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -511,7 +511,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"); @@ -2741,30 +2745,31 @@ void LLAgent::setStartPosition( U32 location_id ) body["HomeLocation"] = homeLocation; - if (!requestPostCapability("HomeLocation", body, - boost::bind(&LLAgent::setStartPositionSuccess, this, _1))) + if (!requestPostCapability("HomeLocation", body, + boost::bind(&LLAgent::setStartPositionSuccess, this, _1))) { - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_SetStartLocationRequest); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgentID); - msg->addUUIDFast(_PREHASH_SessionID, gAgentSessionID); - msg->nextBlockFast(_PREHASH_StartLocationData); - // corrected by sim - msg->addStringFast(_PREHASH_SimName, ""); - msg->addU32Fast(_PREHASH_LocationID, homeLocation["LocationId"].asInteger()); - msg->addVector3Fast(_PREHASH_LocationPos, - ll_vector3_from_sdmap(homeLocation["LocationPos"])); - msg->addVector3Fast(_PREHASH_LocationLookAt, - ll_vector3_from_sdmap(homeLocation["LocationLookAt"])); - gAgent.sendReliableMessage(); - } - - const U32 HOME_INDEX = 1; - if( HOME_INDEX == location_id ) - { - setHomePosRegion( mRegionp->getHandle(), getPositionAgent() ); - } + if(LLGridManager::instance().isInSecondlife()) + { + LL_WARNS() << "Unable to post to HomeLocation capability." << LL_ENDL; + } + else + { + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_SetStartLocationRequest); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgentID); + msg->addUUIDFast(_PREHASH_SessionID, gAgentSessionID); + msg->nextBlockFast(_PREHASH_StartLocationData); + // corrected by sim + msg->addStringFast(_PREHASH_SimName, ""); + msg->addU32Fast(_PREHASH_LocationID, homeLocation["LocationId"].asInteger()); + msg->addVector3Fast(_PREHASH_LocationPos, + ll_vector3_from_sdmap(homeLocation["LocationPos"])); + msg->addVector3Fast(_PREHASH_LocationLookAt, + ll_vector3_from_sdmap(homeLocation["LocationLookAt"])); + gAgent.sendReliableMessage(); + } + } } void LLAgent::setStartPositionSuccess(const LLSD &result) diff --git a/indra/newview/llagentcamera.cpp b/indra/newview/llagentcamera.cpp index 3e454724689730f6fae63788b9650d61f9108fe6..031b02570618b09a40fd3fe891b33a5fb4001391 100644 --- a/indra/newview/llagentcamera.cpp +++ b/indra/newview/llagentcamera.cpp @@ -106,6 +106,12 @@ const F32 GROUND_TO_AIR_CAMERA_TRANSITION_START_TIME = 0.5f; const F32 OBJECT_EXTENTS_PADDING = 0.5f; +static bool isDisableCameraConstraints() +{ + static LLCachedControl<bool> sDisableCameraConstraints(gSavedSettings, "DisableCameraConstraints", false); + return sDisableCameraConstraints; +} + // The agent instance. LLAgentCamera gAgentCamera; @@ -591,9 +597,10 @@ LLVector3 LLAgentCamera::calcFocusOffset(LLViewerObject *object, LLVector3 origi 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() || - ALControlCache::DisableCameraConstraints) + isDisableCameraConstraints()) { obj_min_distance = 0.f; return TRUE; @@ -763,39 +770,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) @@ -808,6 +820,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); @@ -845,6 +861,7 @@ void LLAgentCamera::setCameraZoomFraction(F32 fraction) // [/RLVa:KB] // mCameraFocusOffsetTarget = camera_offset_dir * rescale(fraction, 0.f, 1.f, max_zoom, min_zoom); } + startCameraAnimation(); } @@ -949,15 +966,15 @@ 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; - static LLCachedControl<bool> disable_camera_limits(gSavedSettings, "AlchemyCameraNoZoomLimit", false); - if (!disable_camera_limits) + // Unless camera is unlocked + if (!isDisableCameraConstraints()) { + F32 min_zoom = LAND_MIN_ZOOM; + // Don't move through focus point if (mFocusObject) { @@ -972,30 +989,17 @@ void LLAgentCamera::cameraZoomIn(const F32 fraction) min_zoom = OBJECT_MIN_ZOOM; } } - - new_distance = llmax(new_distance, min_zoom); - } - F32 max_distance = getCameraMaxZoomDistance(true); + new_distance = llmax(new_distance, min_zoom); - max_distance = llmin(max_distance, current_distance * 4.f); //Scaled max relative to current distance. MAINT-3154 + 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); - if (new_distance > max_distance) - { - 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 ); } // [RLVa:KB] - Checked: 2.0.0 @@ -1026,19 +1030,22 @@ 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; - - static LLCachedControl<bool> disable_camera_limits(gSavedSettings, "AlchemyCameraNoZoomLimit", false); - if (!disable_camera_limits) + + // Unless camera is unlocked + if (!isDisableCameraConstraints()) { F32 min_zoom = LAND_MIN_ZOOM; - + // Don't move through focus point if (mFocusObject.notNull()) { @@ -1053,24 +1060,16 @@ void LLAgentCamera::cameraOrbitIn(const F32 meters) } new_distance = llmax(new_distance, min_zoom); - } - F32 max_distance = getCameraMaxZoomDistance(true); + F32 max_distance = getCameraMaxZoomDistance(); + new_distance = llmin(new_distance, max_distance); - if (new_distance > max_distance) - { - // Unless camera is unlocked - if (!ALControlCache::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 ); - } - // [RLVa:KB] - Checked: 2.0.0 if ( (RlvActions::isRlvEnabled()) && (!allowFocusOffsetChange(new_distance * camera_offset_unit)) ) return; @@ -1082,7 +1081,6 @@ void LLAgentCamera::cameraOrbitIn(const F32 meters) } } - //----------------------------------------------------------------------------- // cameraPanIn() //----------------------------------------------------------------------------- @@ -1928,7 +1926,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); @@ -2050,7 +2049,7 @@ LLVector3d LLAgentCamera::calcCameraPositionTargetGlobal(BOOL *hit_limit) auto& worldInst = LLWorld::instance(); - if (!ALControlCache::DisableCameraConstraints && !gAgent.isGodlike()) + if (!isDisableCameraConstraints() && !gAgent.isGodlike()) { LLViewerRegion* regionp = worldInst.getRegionFromPosGlobal(camera_position_global); bool constrain = true; @@ -2126,16 +2125,14 @@ LLVector3d LLAgentCamera::calcCameraPositionTargetGlobal(BOOL *hit_limit) // Don't let camera go underground F32 camera_min_off_ground = getCameraMinOffGround(); - camera_land_height = worldInst.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; } - if (hit_limit) { *hit_limit = isConstrained; @@ -2336,7 +2333,13 @@ F32 LLAgentCamera::getCameraMinOffGround() { return 0.f; } - return ALControlCache::DisableCameraConstraints ? -1000.f : 0.5f; + + if (isDisableCameraConstraints()) + { + return -1000.f; + } + + return 0.5f; } diff --git a/indra/newview/llagenthandler.cpp b/indra/newview/llagenthandler.cpp index f2b46e0e906e272e099e7e93ed6f4967860014c0..d98f943bcaa2b40feb2b6c2c365ec44add495ae6 100644 --- a/indra/newview/llagenthandler.cpp +++ b/indra/newview/llagenthandler.cpp @@ -52,7 +52,8 @@ class LLAgentHandler : public LLCommandHandler 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/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp index 65a486de82b5ad309bb0424b0ac1c37cc5204960..ecdb0c91a6a110da152a0d06a7b09e8bcbaa6a7d 100644 --- a/indra/newview/llappearancemgr.cpp +++ b/indra/newview/llappearancemgr.cpp @@ -3074,9 +3074,23 @@ 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 + LLUUID cat_id = category->getUUID(); + LLInventoryModelBackgroundFetch::getInstance()->fetchFolderAndLinks( + cat_id, + [cat_id, copy, append] + { + LLAppearanceMgr::instance().wearCategoryFinal(cat_id, copy, append); + }); + } + else + { + callAfterCategoryFetch(category->getUUID(), boost::bind(&LLAppearanceMgr::wearCategoryFinal, + &LLAppearanceMgr::instance(), + category->getUUID(), copy, append)); + } } } @@ -3085,7 +3099,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; @@ -5017,30 +5031,20 @@ class CallAfterCategoryFetchStage1: public LLInventoryFetchDescendentsObserver void callAfterCOFFetch(nullary_func_t cb) { - LLUUID cat_id = LLAppearanceMgr::instance().getCOF(); - LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id); - if (AISAPI::isAvailable()) { - // Mark cof (update timer) so that background fetch won't request it - cat->setFetching(LLViewerInventoryCategory::FETCH_RECURSIVE); // For reliability assume that we have no relevant cache, so // fetch cof along with items cof's links point to. - AISAPI::FetchCOF([cb](const LLUUID& id) - { - cb(); - LLUUID cat_id = LLAppearanceMgr::instance().getCOF(); - LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id); - if (cat) - { - cat->setFetching(LLViewerInventoryCategory::FETCH_NONE); - } - }); + LLInventoryModelBackgroundFetch::getInstance()->fetchCOF(cb); } else { LL_INFOS() << "AIS API v3 not available, using callAfterCategoryFetch" << LL_ENDL; - // startup should have marked folder as fetching, remove that + LLUUID cat_id = LLAppearanceMgr::instance().getCOF(); + LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id); + + // Special case, startup should have marked cof as FETCH_RECURSIVE + // to prevent dupplicate request, remove that cat->setFetching(LLViewerInventoryCategory::FETCH_NONE); callAfterCategoryFetch(cat_id, cb); } @@ -5062,30 +5066,16 @@ void callAfterCategoryFetch(const LLUUID& cat_id, nullary_func_t cb) void callAfterCategoryLinksFetch(const LLUUID &cat_id, nullary_func_t cb) { - LLViewerInventoryCategory *cat = gInventory.getCategory(cat_id); if (AISAPI::isAvailable()) { - // Mark folder (update timer) so that background fetch won't request it - cat->setFetching(LLViewerInventoryCategory::FETCH_RECURSIVE); // Assume that we have no relevant cache. Fetch folder, and items folder's links point to. - AISAPI::FetchCategoryLinks(cat_id, - [cb, cat_id](const LLUUID &id) - { - cb(); - LLViewerInventoryCategory *cat = gInventory.getCategory(cat_id); - if (cat) - { - cat->setFetching(LLViewerInventoryCategory::FETCH_NONE); - } - }); - } - else - { - LL_WARNS() << "AIS API v3 not available, can't use AISAPI::FetchCOF" << LL_ENDL; - // startup should have marked folder as fetching, remove that - cat->setFetching(LLViewerInventoryCategory::FETCH_NONE); - callAfterCategoryFetch(cat_id, cb); - } + LLInventoryModelBackgroundFetch::getInstance()->fetchFolderAndLinks(cat_id, cb); + } + else + { + LL_WARNS() << "AIS API v3 not available, can't use AISAPI::FetchCOF" << LL_ENDL; + callAfterCategoryFetch(cat_id, cb); + } } diff --git a/indra/newview/llappearancemgr.h b/indra/newview/llappearancemgr.h index d7bb1c1dbe6ac1fedf830078c6dbbc7f547a0e24..d88c1d797e2d23dff077240cc5a80a8f2eca796d 100644 --- a/indra/newview/llappearancemgr.h +++ b/indra/newview/llappearancemgr.h @@ -63,7 +63,7 @@ class LLAppearanceMgr final : public LLSingleton<LLAppearanceMgr> // [/RLVa:KB] 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/llappviewer.cpp b/indra/newview/llappviewer.cpp index cb88d73862aabf1b96645779697c3569dab8b5e0..1a5a40728bbfa70273cc73319eefb675a017f75e 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -208,6 +208,7 @@ #include "llhudeffecttrail.h" #include "llvectorperfoptions.h" #include "llslurl.h" +#include "llurlregistry.h" #include "llwatchdog.h" // Included so that constants/settings might be initialized @@ -4186,6 +4187,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/llavatariconctrl.cpp b/indra/newview/llavatariconctrl.cpp index 3374a1bc31f89e3a3e3847eb392ede7393fc8cf9..00924b3dea1b8f87a1c3d68eb898dda5548f2d10 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/llavatarrenderinfoaccountant.cpp b/indra/newview/llavatarrenderinfoaccountant.cpp index 4f16a3063aec81c0195a14fc9c78febadec1241b..6f189c1b9dacaa523b4d8d9e1fb777e33b1d6868 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(std::make_shared<LLCoreHttpUtil::HttpCoroutineAdapter>("AvatarRenderInfoAccountant", httpPolicy)); LLCore::HttpRequest::ptr_t httpRequest(std::make_shared<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(std::make_shared<LLCoreHttpUtil::HttpCoroutineAdapter>("AvatarRenderInfoAccountant", httpPolicy)); LLCore::HttpRequest::ptr_t httpRequest(std::make_shared<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) diff --git a/indra/newview/llconversationmodel.h b/indra/newview/llconversationmodel.h index 72572ec3bce6a861088c83d893d6a1ee96df3fdd..7efe03da21c5eab77ef349a66750fe020ac2bdfc 100644 --- a/indra/newview/llconversationmodel.h +++ b/indra/newview/llconversationmodel.h @@ -40,7 +40,7 @@ class LLConversationItem; class LLConversationItemSession; class LLConversationItemParticipant; -typedef std::map<LLUUID, LLConversationItem*> conversations_items_map; +typedef std::map<LLUUID, LLPointer<LLConversationItem> > conversations_items_map; typedef std::map<LLUUID, LLFolderViewItem*> conversations_widgets_map; typedef std::vector<std::string> menuentry_vec_t; diff --git a/indra/newview/lldrawable.cpp b/indra/newview/lldrawable.cpp index f2ebbae0d48d908a3aa3c7c753ca02fecc7fd0ba..257a21ef5e9bff4258b03c4c50bdf58d69808aa4 100644 --- a/indra/newview/lldrawable.cpp +++ b/indra/newview/lldrawable.cpp @@ -758,19 +758,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<LLControlAvatar*>(getVObj().get()); - if (controlAvatar && controlAvatar->mControlAVBridge) - { - ((LLSpatialGroup*)controlAvatar->mControlAVBridge->mOctree->getListener(0))->setState(LLViewerOctreeGroup::DIRTY); - } - } } } diff --git a/indra/newview/llfloaterimcontainer.cpp b/indra/newview/llfloaterimcontainer.cpp index 6d41754195d7e5c3b933224b70ebb40151fa0c27..a9f7308e32cd90402476b61c54f1672649659565 100644 --- a/indra/newview/llfloaterimcontainer.cpp +++ b/indra/newview/llfloaterimcontainer.cpp @@ -158,6 +158,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); @@ -614,7 +628,8 @@ void LLFloaterIMContainer::handleConversationModelEvent(const LLSD& event) } else if (type == "add_participant") { - LLConversationItemSession* session_model = dynamic_cast<LLConversationItemSession*>(mConversationsItems[session_id]); + LLConversationItem* item = getSessionModel(session_id); + LLConversationItemSession* session_model = dynamic_cast<LLConversationItemSession*>(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) @@ -1810,10 +1825,9 @@ BOOL LLFloaterIMContainer::selectConversationPair(const LLUUID& session_id, bool void LLFloaterIMContainer::setTimeNow(const LLUUID& session_id, const LLUUID& participant_id) { - LLConversationItemSession* item = dynamic_cast<LLConversationItemSession*>(get_ptr_in_map(mConversationsItems,session_id)); + LLConversationItemSession* item = dynamic_cast<LLConversationItemSession*>(getSessionModel(session_id)); if (item) { - item->setTimeNow(participant_id); mConversationViewModel.requestSortAll(); mConversationsRoot->arrangeAll(); } @@ -1822,7 +1836,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<LLConversationItemSession*>(get_ptr_in_map(mConversationsItems,LLUUID())); + LLConversationItemSession* item = dynamic_cast<LLConversationItemSession*>(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 d7af1ede37bbef242a5e60f0a09ffff0223a03cb..af0e5bdb2e24c2e0d4f15d9f4c04c07f2f2c8722 100644 --- a/indra/newview/llfloaterimcontainer.h +++ b/indra/newview/llfloaterimcontainer.h @@ -106,7 +106,7 @@ class LLFloaterIMContainer final 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 diff --git a/indra/newview/llfloaterpreference.cpp b/indra/newview/llfloaterpreference.cpp index 3745c6d4aff1bc94ae92e408f9a39272c6156ba6..e6f50bd706d4a6065f79e0a28ae4a7637ddcd7af 100644 --- a/indra/newview/llfloaterpreference.cpp +++ b/indra/newview/llfloaterpreference.cpp @@ -282,11 +282,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, + const std::string& grid, LLMediaCtrl* web) + { + if (params.size() < 1) return false; + + LLFloaterPreference* prefsfloater = dynamic_cast<LLFloaterPreference*> + (LLFloaterReg::showInstance("preferences")); + + if (prefsfloater) + { + // find 'controls' panel and bring it the front + LLTabContainer* tabcontainer = prefsfloater->getChild<LLTabContainer>("pref core"); + LLPanel* panel = prefsfloater->getChild<LLPanel>("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/llfloaterurlentry.cpp b/indra/newview/llfloaterurlentry.cpp index b012c740e367140a7361168025ee84acf9148f31..bea28d24c09f8c6c25dee8fbbea5c95de90c383b 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<LLFloater> pa LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(std::make_shared<LLCoreHttpUtil::HttpCoroutineAdapter>("getMediaTypeCoro", httpPolicy)); LLCore::HttpRequest::ptr_t httpRequest(std::make_shared<LLCore::HttpRequest>()); + LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); LLCore::HttpOptions::ptr_t httpOpts(std::make_shared<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<LLFloater> 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)) diff --git a/indra/newview/llfloaterworldmap.cpp b/indra/newview/llfloaterworldmap.cpp index 1808584208013f6e6987d52a65cd1d0ec6be5641..b16f0f54866d5de256eeec77d2d028c21a7da7ac 100644 --- a/indra/newview/llfloaterworldmap.cpp +++ b/indra/newview/llfloaterworldmap.cpp @@ -128,10 +128,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) @@ -167,12 +184,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/llfriendcard.cpp b/indra/newview/llfriendcard.cpp index 43837e448f6f64ec52369e7002bf7d247e93f893..f634bb59da14bbe58f5c66132cc0f38f30997c96 100644 --- a/indra/newview/llfriendcard.cpp +++ b/indra/newview/llfriendcard.cpp @@ -549,20 +549,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/llgroupactions.cpp b/indra/newview/llgroupactions.cpp index 1d032a711bf90fe3c14e8abec200a495e1396b33..2d0aa2880d5fec9bd05f986a945c892b71e3a712 100644 --- a/indra/newview/llgroupactions.cpp +++ b/indra/newview/llgroupactions.cpp @@ -72,7 +72,8 @@ class LLGroupCommandHandler : public LLCommandHandler 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/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp index c0a5bf0a55820bf898d62f50881902874aa97e6d..1495a7a1d14cc51951a978bb4c96ff4d5ac23607 100644 --- a/indra/newview/llinventorybridge.cpp +++ b/indra/newview/llinventorybridge.cpp @@ -4558,7 +4558,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")); } @@ -6828,6 +6829,7 @@ void LLGestureBridge::buildContextMenu(LLMenuGL& menu, U32 flags) { items.push_back(std::string("Activate")); } + items.push_back(std::string("PlayGesture")); } addLinkReplaceMenuOption(items, disabled_items); hide_context_entries(menu, items, disabled_items); diff --git a/indra/newview/llinventorymodelbackgroundfetch.cpp b/indra/newview/llinventorymodelbackgroundfetch.cpp index 706c768250419b7b51be334858bd7b4b98f2d24e..e1bfe50076af1b3f792f367938f1e63adff9be2f 100644 --- a/indra/newview/llinventorymodelbackgroundfetch.cpp +++ b/indra/newview/llinventorymodelbackgroundfetch.cpp @@ -357,6 +357,7 @@ void LLInventoryModelBackgroundFetch::scheduleFolderFetch(const LLUUID& cat_id, if (mFetchFolderQueue.empty() || mFetchFolderQueue.front().mUUID != cat_id) { mBackgroundFetchActive = true; + mFolderFetchActive = true; // Specific folder requests go to front of queue. mFetchFolderQueue.push_front(FetchQueueInfo(cat_id, forced ? FT_FORCED : FT_DEFAULT)); @@ -375,6 +376,61 @@ void LLInventoryModelBackgroundFetch::scheduleItemFetch(const LLUUID& item_id, b } } +void LLInventoryModelBackgroundFetch::fetchFolderAndLinks(const LLUUID& cat_id, nullary_func_t callback) +{ + LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id); + if (cat) + { + // Mark folder (update timer) so that background fetch won't request it + cat->setFetching(LLViewerInventoryCategory::FETCH_RECURSIVE); + } + incrFetchFolderCount(1); + mExpectedFolderIds.push_back(cat_id); + + // Assume that we have no relevant cache. Fetch folder, and items folder's links point to. + AISAPI::FetchCategoryLinks(cat_id, + [callback, cat_id](const LLUUID& id) + { + callback(); + if (id.isNull()) + { + LL_WARNS() << "Failed to fetch category links " << cat_id << LL_ENDL; + } + LLInventoryModelBackgroundFetch::getInstance()->onAISFolderCalback(cat_id, id, FT_DEFAULT); + }); + + // start idle loop to track completion + mBackgroundFetchActive = true; + mFolderFetchActive = true; + gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); +} + +void LLInventoryModelBackgroundFetch::fetchCOF(nullary_func_t callback) +{ + LLUUID cat_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); + LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id); + if (cat) + { + // Mark cof (update timer) so that background fetch won't request it + cat->setFetching(LLViewerInventoryCategory::FETCH_RECURSIVE); + } + incrFetchFolderCount(1); + mExpectedFolderIds.push_back(cat_id); + // For reliability assume that we have no relevant cache, so + // fetch cof along with items cof's links point to. + AISAPI::FetchCOF([callback](const LLUUID& id) + { + callback(); + LLUUID cat_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); + LLInventoryModelBackgroundFetch::getInstance()->onAISFolderCalback(cat_id, id, FT_DEFAULT); + }); + + // start idle loop to track completion + mBackgroundFetchActive = true; + mFolderFetchActive = true; + gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); +} + void LLInventoryModelBackgroundFetch::findLostItems() { mBackgroundFetchActive = true; diff --git a/indra/newview/llinventorymodelbackgroundfetch.h b/indra/newview/llinventorymodelbackgroundfetch.h index 54bbafc0a2116273f050d0af78a97a9521858934..60d5d150b5662b48f306bf37ff089d6d3456b891 100644 --- a/indra/newview/llinventorymodelbackgroundfetch.h +++ b/indra/newview/llinventorymodelbackgroundfetch.h @@ -53,6 +53,13 @@ class LLInventoryModelBackgroundFetch final : public LLSingleton<LLInventoryMode void scheduleFolderFetch(const LLUUID& cat_id, bool forced = false); void scheduleItemFetch(const LLUUID& item_id, bool forced = false); + typedef boost::function<void()> nullary_func_t; + // AIS3 only, Fetches folder and everithing links inside the folder point to + // Intended for outfits + void fetchFolderAndLinks(const LLUUID& cat_id, nullary_func_t callback); + // AIS3 only + void fetchCOF(nullary_func_t callback); + BOOL folderFetchActive() const; bool isEverythingFetched() const; // completing the fetch once per session should be sufficient diff --git a/indra/newview/llinventoryobserver.cpp b/indra/newview/llinventoryobserver.cpp index 35a9afac282a1c97bf15419eb6e5542c5575e5a7..5078807e835774c68f56a768619146769ec5cf8a 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 @@ -421,10 +418,22 @@ void LLInventoryFetchDescendentsObserver::changed(U32 mask) } ++it; } - if (mIncomplete.empty()) - { - done(); - } + + if (mIncomplete.empty()) + { + done(); + } + else + { + LLInventoryModelBackgroundFetch* fetcher = LLInventoryModelBackgroundFetch::getInstance(); + if (fetcher->isEverythingFetched() + && !fetcher->folderFetchActive()) + { + // If fetcher is done with folders yet we are waiting, fetch either + // failed or version is somehow stuck at -1 + done(); + } + } } void LLInventoryFetchDescendentsObserver::startFetch() @@ -435,12 +444,8 @@ void LLInventoryFetchDescendentsObserver::startFetch() if (!cat) continue; if (!isCategoryComplete(cat)) { - // CHECK IT: isCategoryComplete() checks both version and descendant count but - // fetch() only works for Unknown version and doesn't care about descentants, - // as result fetch won't start and folder will potentially get stuck as - // incomplete in observer. - // Likely either both should use only version or both should check descendants. - cat->fetch(); //blindly fetch it without seeing if anything else is fetching it. + //blindly fetch it without seeing if anything else is fetching it. + LLInventoryModelBackgroundFetch::getInstance()->scheduleFolderFetch(*it, true); mIncomplete.push_back(*it); //Add to list of things being downloaded for this observer. } else diff --git a/indra/newview/llkeyconflict.cpp b/indra/newview/llkeyconflict.cpp index e1aac23aa7c7c0dcbae50b623c50ccfb0c4871d4..0d04f8b7d2b9a7df67aa0e2fc39159e0bef8ff1c 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/lllocationinputctrl.cpp b/indra/newview/lllocationinputctrl.cpp index b432cfaaf23230acf938cf255283bbb8516b4096..435250e92bbc97c33a97e62747142ad9d483cf8a 100644 --- a/indra/newview/lllocationinputctrl.cpp +++ b/indra/newview/lllocationinputctrl.cpp @@ -398,7 +398,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. diff --git a/indra/newview/llpanelface.cpp b/indra/newview/llpanelface.cpp index 3579883c9ee246d13c274a305ebb95f19e57202e..577b7e91200f40910a2da3fc0d21e582d1c4f5b4 100644 --- a/indra/newview/llpanelface.cpp +++ b/indra/newview/llpanelface.cpp @@ -4666,8 +4666,6 @@ void LLPanelFace::onPasteTexture(LLViewerObject* objectp, S32 te) { LLUUID object_id = objectp->getID(); - LLSelectedTEMaterial::setAlphaMaskCutoff(this, (U8)te_data["material"]["SpecRot"].asInteger(), te, object_id); - // Normal // Replace placeholders with target's if (te_data["material"].has("NormMapNoCopy")) @@ -4714,6 +4712,7 @@ void LLPanelFace::onPasteTexture(LLViewerObject* objectp, S32 te) LLSelectedTEMaterial::setSpecularLightExponent(this, (U8)te_data["material"]["SpecExp"].asInteger(), te, object_id); LLSelectedTEMaterial::setEnvironmentIntensity(this, (U8)te_data["material"]["EnvIntensity"].asInteger(), te, object_id); LLSelectedTEMaterial::setDiffuseAlphaMode(this, (U8)te_data["material"]["DiffuseAlphaMode"].asInteger(), te, object_id); + LLSelectedTEMaterial::setAlphaMaskCutoff(this, (U8)te_data["material"]["AlphaMaskCutoff"].asInteger(), te, object_id); if (te_data.has("te") && te_data["te"].has("shiny")) { objectp->setTEShiny(te, (U8)te_data["te"]["shiny"].asInteger()); diff --git a/indra/newview/llpanelgrouplandmoney.cpp b/indra/newview/llpanelgrouplandmoney.cpp index 5aa7447ff11d6c54c61a3afd1b74f538c6f7643e..3fac5f46f6a8bfd1aa5925e8a7d2a6e380d6fac3 100644 --- a/indra/newview/llpanelgrouplandmoney.cpp +++ b/indra/newview/llpanelgrouplandmoney.cpp @@ -1078,7 +1078,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 @@ -1233,7 +1233,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/llpanelprofile.cpp b/indra/newview/llpanelprofile.cpp index e59258b629fc9eb3dda6cb6bd1eadfa4fa2239b6..0a5b8142ca4466f08f4cdac9f7a99ea3d53c39e4 100644 --- a/indra/newview/llpanelprofile.cpp +++ b/indra/newview/llpanelprofile.cpp @@ -279,9 +279,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<void(bool)> callback) { LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t @@ -302,10 +302,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); + } } ////////////////////////////////////////////////////////////////////////// @@ -332,6 +338,166 @@ class LLWebProfileHandler : public LLCommandHandler } }; LLWebProfileHandler gWebProfileHandler; +#if 0 // ALCHMERGE +LLProfileHandler gProfileHandler; + + +////////////////////////////////////////////////////////////////////////// +// LLAgentHandler + +class LLAgentHandler : public LLCommandHandler +{ +public: + // requires trusted browser to trigger + LLAgentHandler() : LLCommandHandler("agent", UNTRUSTED_THROTTLE) { } + + virtual bool canHandleUntrusted( + const LLSD& params, + const LLSD& query_map, + LLMediaCtrl* web, + const std::string& nav_type) + { + if (params.size() < 2) + { + return true; // don't block, will fail later + } + + if (nav_type == NAV_TYPE_CLICKED + || nav_type == NAV_TYPE_EXTERNAL) + { + return true; + } + + const std::string verb = params[1].asString(); + if (verb == "about" || verb == "inspect" || verb == "reportAbuse") + { + return true; + } + return false; + } + + bool handle(const LLSD& params, + const LLSD& query_map, + const std::string& grid, + LLMediaCtrl* web) + { + if (params.size() < 2) return false; + LLUUID avatar_id; + if (!avatar_id.set(params[0], FALSE)) + { + return false; + } + + const std::string verb = params[1].asString(); + if (verb == "about") + { + LLAvatarActions::showProfile(avatar_id); + return true; + } + + if (verb == "inspect") + { + LLFloaterReg::showInstance("inspect_avatar", LLSD().with("avatar_id", avatar_id)); + return true; + } + + if (verb == "im") + { + LLAvatarActions::startIM(avatar_id); + return true; + } + + if (verb == "pay") + { + if (!LLUI::getInstance()->mSettingGroups["config"]->getBOOL("EnableAvatarPay")) + { + LLNotificationsUtil::add("NoAvatarPay", LLSD(), LLSD(), std::string("SwitchToStandardSkinAndQuit")); + return true; + } + + LLAvatarActions::pay(avatar_id); + return true; + } + + if (verb == "offerteleport") + { + LLAvatarActions::offerTeleport(avatar_id); + return true; + } + + if (verb == "requestfriend") + { + LLAvatarActions::requestFriendshipDialog(avatar_id); + return true; + } + + if (verb == "removefriend") + { + LLAvatarActions::removeFriendDialog(avatar_id); + return true; + } + + if (verb == "mute") + { + if (! LLAvatarActions::isBlocked(avatar_id)) + { + LLAvatarActions::toggleBlock(avatar_id); + } + return true; + } + + if (verb == "unmute") + { + if (LLAvatarActions::isBlocked(avatar_id)) + { + LLAvatarActions::toggleBlock(avatar_id); + } + return true; + } + + if (verb == "block") + { + if (params.size() > 2) + { + const std::string object_name = LLURI::unescape(params[2].asString()); + LLMute mute(avatar_id, object_name, LLMute::OBJECT); + LLMuteList::getInstance()->add(mute); + LLPanelBlockedList::showPanelAndSelect(mute.mID); + } + return true; + } + + if (verb == "unblock") + { + if (params.size() > 2) + { + const std::string object_name = params[2].asString(); + LLMute mute(avatar_id, object_name, LLMute::OBJECT); + LLMuteList::getInstance()->remove(mute); + } + return true; + } + + // reportAbuse is here due to convoluted avatar handling + // in LLScrollListCtrl and LLTextBase + if (verb == "reportAbuse" && web == NULL) + { + LLAvatarName av_name; + if (LLAvatarNameCache::get(avatar_id, &av_name)) + { + LLFloaterReporter::showFromAvatar(avatar_id, av_name.getCompleteName()); + } + else + { + LLFloaterReporter::showFromAvatar(avatar_id, "not avaliable"); + } + return true; + } + return false; + } +}; +LLAgentHandler gAgentHandler; +#endif ///---------------------------------------------------------------------------- /// LLFloaterProfilePermissions @@ -1466,7 +1632,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 { @@ -1481,7 +1647,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 { @@ -1628,10 +1794,19 @@ void LLPanelProfileSecondLife::onCommitProfileImage(const LLUUID& id) std::string cap_url = gAgent.getRegionCapability(PROFILE_PROPERTIES_CAP); if (!cap_url.empty()) { + std::function<void(bool)> 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) @@ -1968,7 +2143,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()) @@ -2012,7 +2187,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 { @@ -2200,7 +2375,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 { diff --git a/indra/newview/llpanelprofileclassifieds.cpp b/indra/newview/llpanelprofileclassifieds.cpp index 6f45f85b95c0aee07f9c78fb860798efecd870e1..72303a2da21fe197b1bb06d13f79e542d3f41d84 100644 --- a/indra/newview/llpanelprofileclassifieds.cpp +++ b/indra/newview/llpanelprofileclassifieds.cpp @@ -92,7 +92,8 @@ class LLClassifiedHandler : public LLCommandHandler, public LLAvatarPropertiesOb 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 3ad0fe95b4a0da36ce84ae15210556633e1a3c81..7bf98100deef7f4dadab8d7a572b44b332e54058 100644 --- a/indra/newview/llpanelprofilepicks.cpp +++ b/indra/newview/llpanelprofilepicks.cpp @@ -77,7 +77,8 @@ class LLPickHandler : public LLCommandHandler 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/llprofileimagepicker.cpp b/indra/newview/llprofileimagepicker.cpp index eb40cdcc105fa6c59264acf01ec7cb12e4fce190..6dfc0e1662c705d2cb0ab7fd85e56001f0a5eec2 100644 --- a/indra/newview/llprofileimagepicker.cpp +++ b/indra/newview/llprofileimagepicker.cpp @@ -27,6 +27,7 @@ #include "llviewerprecompiledheaders.h" #include "llprofileimagepicker.h" +#include "llavatariconctrl.h" #include "llagent.h" #include "llloadingindicator.h" #include "llnotificationsutil.h" @@ -203,6 +204,13 @@ void post_profile_image_coro(std::string cap_url, EProfileImageType type, std::s cb(result); + 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); } diff --git a/indra/newview/llspatialpartition.cpp b/indra/newview/llspatialpartition.cpp index 3da37e3f23c3a1e99db607dfb04e5f1477431439..2215976ac30bda3e8f51e5b9463785e2289b04e6 100644 --- a/indra/newview/llspatialpartition.cpp +++ b/indra/newview/llspatialpartition.cpp @@ -835,8 +835,44 @@ 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::destroyGLState(bool keep_occlusion) +void LLSpatialGroup::destroyGLState(bool keep_occlusion) { setState(LLSpatialGroup::GEOM_DIRTY | LLSpatialGroup::IMAGE_DIRTY); diff --git a/indra/newview/llspatialpartition.h b/indra/newview/llspatialpartition.h index 314a521d01e5e3fec6e029e538b357213daa1082..275c106be1c40e3d0ceddb4294a93f80dd7ba48b 100644 --- a/indra/newview/llspatialpartition.h +++ b/indra/newview/llspatialpartition.h @@ -193,6 +193,7 @@ class LLDrawInfo final : public LLRefCount LL_ALIGN_PREFIX(16) class LLSpatialGroup final : public LLOcclusionCullingGroup { + using super = LLOcclusionCullingGroup; friend class LLSpatialPartition; friend class LLOctreeStateCheck; public: @@ -317,6 +318,9 @@ class LLSpatialGroup final : public LLOcclusionCullingGroup 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); diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index 412cae0d15e07595f40ec34031ea767990932cbe..5fc9cfc477ac1a15260f48ec74c28f80645ec64e 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -3008,8 +3008,7 @@ void LLStartUp::loadInitialOutfit( const std::string& outfit_folder_name, // Need to fetch cof contents before we can wear. if (do_copy) { - callAfterCategoryFetch(LLAppearanceMgr::instance().getCOF(), - boost::bind(&LLAppearanceMgr::wearInventoryCategory, LLAppearanceMgr::getInstance(), cat, do_copy, do_append)); + callAfterCOFFetch(boost::bind(&LLAppearanceMgr::wearInventoryCategory, LLAppearanceMgr::getInstance(), cat, do_copy, do_append)); } else { diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index 2883b1a2e94c89eeed090a701471650ec5ad2f39..8ac8656448d20a04c2edb8fe6dbc7d72b4dc8ba1 100644 --- a/indra/newview/llviewerfloaterreg.cpp +++ b/indra/newview/llviewerfloaterreg.cpp @@ -225,7 +225,11 @@ class LLFloaterOpenHandler : public LLCommandHandler 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<std::string> blacklist_clicked = { "camera_presets", diff --git a/indra/newview/llviewerinput.cpp b/indra/newview/llviewerinput.cpp index bdc8613e5521417c38ca161516185370146eb534..e7e0d52bfd35bb653976ecb024fcd6bf4358df26 100644 --- a/indra/newview/llviewerinput.cpp +++ b/indra/newview/llviewerinput.cpp @@ -1067,34 +1067,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 @@ -1298,6 +1314,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) { @@ -1379,6 +1396,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) { @@ -1877,3 +1895,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<LLKeyboardBinding>::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<LLMouseBinding>::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 e2383b3b1cf4b3537d11e1635fe1f497bc76cd7d..db3d93156b1759da8e8367889317a4e4697e37a5 100644 --- a/indra/newview/llviewerinput.h +++ b/indra/newview/llviewerinput.h @@ -28,7 +28,6 @@ #define LL_LLVIEWERINPUT_H #include "llkeyboard.h" // For EKeystate -#include "llinitparam.h" #include <boost/unordered/unordered_flat_map.hpp> #include <boost/unordered/unordered_flat_set.hpp> @@ -37,6 +36,8 @@ 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: @@ -54,6 +55,7 @@ class LLKeyboardBinding MASK mMask; LLKeyFunc mFunction; + std::string mFunctionName; }; class LLMouseBinding @@ -63,6 +65,7 @@ class LLMouseBinding MASK mMask; LLKeyFunc mFunction; + std::string mFunctionName; }; @@ -75,11 +78,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<KeyBinding> @@ -110,6 +109,7 @@ class LLViewerInput }; LLViewerInput(); + virtual ~LLViewerInput(); BOOL handleKey(KEY key, MASK mask, BOOL repeated); BOOL handleKeyUp(KEY key, MASK mask); @@ -124,7 +124,7 @@ class LLViewerInput 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, @@ -139,6 +139,9 @@ class LLViewerInput 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<LLKeyboardBinding> &binding, S32 binding_count, diff --git a/indra/newview/llviewerinventory.cpp b/indra/newview/llviewerinventory.cpp index 1f40c5f27d9388c7afb3c1e81dc33cae5ebc4f00..45526c2581f89032a66c7bdd463429a588627845 100644 --- a/indra/newview/llviewerinventory.cpp +++ b/indra/newview/llviewerinventory.cpp @@ -263,9 +263,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, @@ -1195,14 +1215,29 @@ void create_inventory_item( gAgent.sendReliableMessage(); } +void create_inventory_callingcard_callback(LLPointer<LLInventoryCallback> 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.isNull() ? gInventory.findCategoryUUIDForType(LLFolderType::FT_CALLINGCARD) : 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<LLInventoryCallback> 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.isNull() ? gInventory.findCategoryUUIDForType(LLFolderType::FT_CALLINGCARD) : 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, diff --git a/indra/newview/llviewerobject.cpp b/indra/newview/llviewerobject.cpp index 161927b16ab8ec6bfa6f8e34a8dbcae32454c5f8..9614b5747f4e897f439ba10d2fdba911039f5e24 100644 --- a/indra/newview/llviewerobject.cpp +++ b/indra/newview/llviewerobject.cpp @@ -3545,6 +3545,12 @@ void LLViewerObject::doInventoryCallback() void LLViewerObject::removeInventory(const LLUUID& item_id) { + // close associated floater properties + LLSD params; + params["id"] = item_id; + params["object"] = mID; + LLFloaterReg::hideInstance("item_properties", params); + LLMessageSystem* msg = gMessageSystem; msg->newMessageFast(_PREHASH_RemoveTaskInventory); msg->nextBlockFast(_PREHASH_AgentData); diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index a6d75e9956dafab4c1dd37becc76e7ad15eeaf4d..7544e8b4192a0d18e30f9c8518c6d24b624594fb 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -5391,6 +5391,9 @@ U32 LLVOAvatar::renderRigid() return 0; } + bool should_alpha_mask = shouldAlphaMask(); + LLGLState test(GL_ALPHA_TEST, should_alpha_mask); + if (isTextureVisible(TEX_EYES_BAKED) || (getOverallAppearance() == AOA_JELLYDOLL && !isControlAvatar()) || isUIAvatar()) { LLViewerJoint* eyeball_left = getViewerJoint(MESH_ID_EYEBALL_LEFT); diff --git a/indra/newview/llvoavatarself.cpp b/indra/newview/llvoavatarself.cpp index f5af599db82e694b82f1496b92b3239724c8ddb1..481457d816f5c1eb12c79fb13f55b5a3c3718415 100644 --- a/indra/newview/llvoavatarself.cpp +++ b/indra/newview/llvoavatarself.cpp @@ -1005,7 +1005,7 @@ void LLVOAvatarSelf::updateRegion(LLViewerRegion *regionp) } //-------------------------------------------------------------------- -// draw tractor beam when editing objects +// draw tractor (selection) beam when editing objects //-------------------------------------------------------------------- //virtual void LLVOAvatarSelf::idleUpdateTractorBeam() @@ -1365,6 +1365,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) { @@ -3161,12 +3182,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()); } diff --git a/indra/newview/llvoavatarself.h b/indra/newview/llvoavatarself.h index c90f304f901b8557cdf0c3bfdf9c3615e50768fa..c99f450fd506402baa6174813b7c66666e486ca6 100644 --- a/indra/newview/llvoavatarself.h +++ b/indra/newview/llvoavatarself.h @@ -302,6 +302,8 @@ class LLVOAvatarSelf final : /*virtual*/ BOOL detachObject(LLViewerObject *viewer_object) override; static BOOL detachAttachmentIntoInventory(const LLUUID& item_id); + bool hasAttachmentsInTrash(); + // [RLVa:KB] - Checked: 2012-07-28 (RLVa-1.4.7) enum EAttachAction { ACTION_ATTACH, ACTION_DETACH }; typedef boost::signals2::signal<void (LLViewerObject*, const LLViewerJointAttachment*, EAttachAction)> attachment_signal_t; 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 d6406939c19dfd8a70f97545f042c2add1a8f3ca..37a17f4bd666dbafbc4096f83d109a8956ddf5be 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 @@ <text name="ItemcountText"> Genstande: </text> - <filter_editor label="Filter" name="inventory search editor"/> - <tab_container name="inventory filter tabs"> - <inventory_panel label="Alle ting" name="All Items"/> - <recent_inventory_panel label="Nye ting" name="Recent Items"/> - </tab_container> - <layout_stack name="bottom_panel"> - <layout_panel name="options_gear_btn_panel"> - <button name="options_gear_btn" tool_tip="Vis yderligere valg"/> - </layout_panel> - <layout_panel name="add_btn_panel"> - <button name="add_btn" tool_tip="Tilføj ny genstand"/> - </layout_panel> - <layout_panel name="trash_btn_panel"> - <dnd_button name="trash_btn" tool_tip="Fjern valgte genstand"/> - </layout_panel> - </layout_stack> + <layout_stack name="top_stack"> + <layout_panel name="filter_layout_panel"> + <filter_editor label="Filter" name="inventory search editor"/> + <button name="options_gear_btn" tool_tip="Vis yderligere valg"/> + <button name="add_btn" tool_tip="Tilføj ny genstand"/> + </layout_panel> + </layout_stack> + <panel name="default_inventory_panel"> + <tab_container name="inventory filter tabs"> + <inventory_panel label="Alle ting" name="All Items"/> + <recent_inventory_panel label="Nye ting" name="Recent Items"/> + </tab_container> + </panel> </panel> diff --git a/indra/newview/skins/default/xui/de/panel_main_inventory.xml b/indra/newview/skins/default/xui/de/panel_main_inventory.xml index 175f6e1003f281de1f60837dc2a5857d436447c5..072bd993937b203ab381622a842d63602e316588 100644 --- a/indra/newview/skins/default/xui/de/panel_main_inventory.xml +++ b/indra/newview/skins/default/xui/de/panel_main_inventory.xml @@ -13,27 +13,24 @@ <text name="ItemcountText"> Objekte: </text> - <filter_editor label="Suchtext eingeben" name="inventory search editor"/> - <combo_box name="search_type"> - <item label="Name" name="Name" value="search_by_name"/> - <item label="Ersteller" name="Creator" value="search_by_creator"/> - <item label="Beschreibung" name="Description" value="search_by_description"/> - <item label="UUID" name="UUID" value="search_by_UUID"/> - </combo_box> - <tab_container name="inventory filter tabs"> - <inventory_panel label="MEIN INVENTAR" name="All Items"/> - <recent_inventory_panel label="AKTUELL" name="Recent Items"/> - <inventory_panel label="GETRAGEN" name="Worn Items"/> - </tab_container> - <layout_stack name="bottom_panel"> - <layout_panel name="options_gear_btn_panel"> - <menu_button name="options_gear_btn" tool_tip="Zusätzliche Optionen anzeigen"/> - </layout_panel> - <layout_panel name="add_btn_panel"> - <button name="add_btn" tool_tip="Neues Objekt hinzufügen"/> - </layout_panel> - <layout_panel name="trash_btn_panel"> - <dnd_button name="trash_btn" tool_tip="Auswahl löschen"/> - </layout_panel> - </layout_stack> + <layout_stack name="top_stack"> + <layout_panel name="filter_layout_panel"> + <combo_box name="search_type"> + <item label="Name" name="Name" value="search_by_name"/> + <item label="Ersteller" name="Creator" value="search_by_creator"/> + <item label="Beschreibung" name="Description" value="search_by_description"/> + <item label="UUID" name="UUID" value="search_by_UUID"/> + </combo_box> + <filter_editor label="Suchtext eingeben" name="inventory search editor"/> + <menu_button name="options_gear_btn" tool_tip="Zusätzliche Optionen anzeigen"/> + <button name="add_btn" tool_tip="Neues Objekt hinzufügen"/> + </layout_panel> + </layout_stack> + <panel name="default_inventory_panel"> + <tab_container name="inventory filter tabs"> + <inventory_panel label="MEIN INVENTAR" name="All Items"/> + <recent_inventory_panel label="AKTUELL" name="Recent Items"/> + <inventory_panel label="GETRAGEN" name="Worn Items"/> + </tab_container> + </panel> </panel> diff --git a/indra/newview/skins/default/xui/en/menu_inventory.xml b/indra/newview/skins/default/xui/en/menu_inventory.xml index 6692086b844d21ff450f3d941cddf0291030348a..d2887ab84ece5432f758967ca7e475e8f0b72380 100644 --- a/indra/newview/skins/default/xui/en/menu_inventory.xml +++ b/indra/newview/skins/default/xui/en/menu_inventory.xml @@ -575,6 +575,14 @@ <menu_item_separator layout="topleft" name="Gesture Separator" /> + <menu_item_call + label="Play" + layout="topleft" + name="PlayGesture"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="play" /> + </menu_item_call> <menu_item_call label="Activate" layout="topleft" diff --git a/indra/newview/skins/default/xui/en/panel_main_inventory.xml b/indra/newview/skins/default/xui/en/panel_main_inventory.xml index 9ba9a597ff0b8621fd950ec34e968de549e9bf04..0f555f07f52c787bae076d5953fdf847ad229b8e 100644 --- a/indra/newview/skins/default/xui/en/panel_main_inventory.xml +++ b/indra/newview/skins/default/xui/en/panel_main_inventory.xml @@ -47,7 +47,8 @@ top_pad="10" left="2" right="-4" - orientation="horizontal"> + orientation="horizontal" + name="top_stack"> <layout_panel border="false" bevel_style="in" @@ -101,7 +102,8 @@ user_resize="false" height="25" width="381" - visible="true"> + visible="true" + name="filter_layout_panel"> <combo_box height="23" layout="topleft" 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 edf1e1efd47eec5c32c69e99c257939a3a8c9cd4..0b3639f7792cf4d0b355d3f947b48684b80e3210 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"/> <line_editor follows="left|top" enabled="false" @@ -516,7 +516,7 @@ follows="left|top" layout="topleft" height="12" - width="52" + width="53" left_delta="2" top_pad="2" halign="right" @@ -533,7 +533,7 @@ mouse_opaque="false" visible="true" top_delta="-3" - left_pad="2"/> + left_pad="1"/> <line_editor follows="left|top" enabled="false" diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index fd01bd0a955baab5929f9d4573411e25d5b910e5..e621f0dcda69adddd67b2b774802746554bfe7e7 100644 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -2907,7 +2907,7 @@ If you continue to receive this message, please contact Second Life support for <string name="GroupMoneyBalance">Balance</string> <string name="GroupMoneyCredits">Credits</string> <string name="GroupMoneyDebits">Debits</string> - <string name="GroupMoneyDate">[weekday,datetime,utc] [mth,datetime,utc] [day,datetime,utc], [year,datetime,utc]</string> + <string name="GroupMoneyStartDate">Transactions since [weekday,datetime,utc] [mth,datetime,utc] [day,datetime,utc], [year,datetime,utc]</string> <!-- Viewer menu --> <string name="AcquiredItems">Acquired Items</string> diff --git a/indra/newview/skins/default/xui/es/panel_main_inventory.xml b/indra/newview/skins/default/xui/es/panel_main_inventory.xml index bf1205046bf54bf05c40372bfa75af4b3bc86dab..a1a88b095e82e90d057902d32532183b7bfc2459 100644 --- a/indra/newview/skins/default/xui/es/panel_main_inventory.xml +++ b/indra/newview/skins/default/xui/es/panel_main_inventory.xml @@ -13,27 +13,24 @@ <text name="ItemcountText"> Ãtems: </text> - <filter_editor label="Ingresar texto de búsqueda" name="inventory search editor"/> - <combo_box name="search_type"> - <item label="Nombre" name="Name" value="search_by_name"/> - <item label="Creador" name="Creator" value="search_by_creator"/> - <item label="Descripción" name="Description" value="search_by_description"/> - <item label="UUID" name="UUID" value="search_by_UUID"/> - </combo_box> - <tab_container name="inventory filter tabs"> - <inventory_panel label="Todos los Ãtems" name="All Items"/> - <recent_inventory_panel label="Ãtems recientes" name="Recent Items"/> - <inventory_panel label="(VESTIMENTA)" name="Worn Items"/> - </tab_container> - <layout_stack name="bottom_panel"> - <layout_panel name="options_gear_btn_panel"> - <menu_button name="options_gear_btn" tool_tip="Ver más opciones"/> - </layout_panel> - <layout_panel name="add_btn_panel"> - <button name="add_btn" tool_tip="Añadir un Ãtem nuevo"/> - </layout_panel> - <layout_panel name="trash_btn_panel"> - <dnd_button name="trash_btn" tool_tip="Quitar el Ãtem seleccionado"/> - </layout_panel> - </layout_stack> + <layout_stack name="top_stack"> + <layout_panel name="filter_layout_panel"> + <combo_box name="search_type"> + <item label="Nombre" name="Name" value="search_by_name"/> + <item label="Creador" name="Creator" value="search_by_creator"/> + <item label="Descripción" name="Description" value="search_by_description"/> + <item label="UUID" name="UUID" value="search_by_UUID"/> + </combo_box> + <filter_editor label="Ingresar texto de búsqueda" name="inventory search editor"/> + <menu_button name="options_gear_btn" tool_tip="Ver más opciones"/> + <button name="add_btn" tool_tip="Añadir un Ãtem nuevo"/> + </layout_panel> + </layout_stack> + <panel name="default_inventory_panel"> + <tab_container name="inventory filter tabs"> + <inventory_panel label="Todos los Ãtems" name="All Items"/> + <recent_inventory_panel label="Ãtems recientes" name="Recent Items"/> + <inventory_panel label="(VESTIMENTA)" name="Worn Items"/> + </tab_container> + </panel> </panel> diff --git a/indra/newview/skins/default/xui/fr/panel_main_inventory.xml b/indra/newview/skins/default/xui/fr/panel_main_inventory.xml index 5bf4d6c15d0bfe77a4e8e4adf0923036d67672ae..aebb042cfe9eaefa45991dd39ef12a0e47b3dd95 100644 --- a/indra/newview/skins/default/xui/fr/panel_main_inventory.xml +++ b/indra/newview/skins/default/xui/fr/panel_main_inventory.xml @@ -13,27 +13,24 @@ <text name="ItemcountText"> Articles : </text> - <filter_editor label="Saisir ici le texte de la recherche" name="inventory search editor"/> - <combo_box name="search_type"> - <item label="Nom" name="Name" value="search_by_name"/> - <item label="Créateur" name="Creator" value="search_by_creator"/> - <item label="Description" name="Description" value="search_by_description"/> - <item label="UUID" name="UUID" value="search_by_UUID"/> - </combo_box> - <tab_container name="inventory filter tabs"> - <inventory_panel label="MON INVENTAIRE" name="All Items"/> - <recent_inventory_panel label="RÉCENT" name="Recent Items"/> - <inventory_panel label="PORTÉ" name="Worn Items"/> - </tab_container> - <layout_stack name="bottom_panel"> - <layout_panel name="options_gear_btn_panel"> - <menu_button name="options_gear_btn" tool_tip="Afficher d'autres options"/> - </layout_panel> - <layout_panel name="add_btn_panel"> - <button name="add_btn" tool_tip="Ajouter un nouvel article"/> - </layout_panel> - <layout_panel name="trash_btn_panel"> - <dnd_button name="trash_btn" tool_tip="Supprimer l'article sélectionné"/> - </layout_panel> - </layout_stack> + <layout_stack name="top_stack"> + <layout_panel name="filter_layout_panel"> + <combo_box name="search_type"> + <item label="Nom" name="Name" value="search_by_name"/> + <item label="Créateur" name="Creator" value="search_by_creator"/> + <item label="Description" name="Description" value="search_by_description"/> + <item label="UUID" name="UUID" value="search_by_UUID"/> + </combo_box> + <filter_editor label="Saisir ici le texte de la recherche" name="inventory search editor"/> + <menu_button name="options_gear_btn" tool_tip="Afficher d'autres options"/> + <button name="add_btn" tool_tip="Ajouter un nouvel article"/> + </layout_panel> + </layout_stack> + <panel name="default_inventory_panel"> + <tab_container name="inventory filter tabs"> + <inventory_panel label="MON INVENTAIRE" name="All Items"/> + <recent_inventory_panel label="RÉCENT" name="Recent Items"/> + <inventory_panel label="PORTÉ" name="Worn Items"/> + </tab_container> + </panel> </panel> diff --git a/indra/newview/skins/default/xui/it/panel_main_inventory.xml b/indra/newview/skins/default/xui/it/panel_main_inventory.xml index d6890229e7ef667838b99497b9144f3ca072f75a..da8592e5d7e8dc1de072e3d7e574f06d6a94b028 100644 --- a/indra/newview/skins/default/xui/it/panel_main_inventory.xml +++ b/indra/newview/skins/default/xui/it/panel_main_inventory.xml @@ -13,27 +13,24 @@ <text name="ItemcountText"> Oggetti: </text> - <filter_editor label="Inserisci ricerca" name="inventory search editor"/> - <combo_box name="search_type"> - <item label="Nome" name="Name" value="search_by_name"/> - <item label="Creatore" name="Creator" value="search_by_creator"/> - <item label="Descrizione" name="Description" value="search_by_description"/> - <item label="UUID" name="UUID" value="search_by_UUID"/> - </combo_box> - <tab_container name="inventory filter tabs"> - <inventory_panel label="Tutti gli elementi" name="All Items"/> - <recent_inventory_panel label="Elementi recenti" name="Recent Items"/> - <inventory_panel label="INDOSSATI" name="Worn Items"/> - </tab_container> - <layout_stack name="bottom_panel"> - <layout_panel name="options_gear_btn_panel"> - <menu_button name="options_gear_btn" tool_tip="Mostra opzioni addizionali"/> - </layout_panel> - <layout_panel name="add_btn_panel"> - <button name="add_btn" tool_tip="Aggiungi nuovo elemento"/> - </layout_panel> - <layout_panel name="trash_btn_panel"> - <dnd_button name="trash_btn" tool_tip="Rimuovi l'articolo selezionato"/> - </layout_panel> - </layout_stack> + <layout_stack name="top_stack"> + <layout_panel name="filter_layout_panel"> + <combo_box name="search_type"> + <item label="Nome" name="Name" value="search_by_name"/> + <item label="Creatore" name="Creator" value="search_by_creator"/> + <item label="Descrizione" name="Description" value="search_by_description"/> + <item label="UUID" name="UUID" value="search_by_UUID"/> + </combo_box> + <filter_editor label="Inserisci ricerca" name="inventory search editor"/> + <menu_button name="options_gear_btn" tool_tip="Mostra opzioni addizionali"/> + <button name="add_btn" tool_tip="Aggiungi nuovo elemento"/> + </layout_panel> + </layout_stack> + <panel name="default_inventory_panel"> + <tab_container name="inventory filter tabs"> + <inventory_panel label="Tutti gli elementi" name="All Items"/> + <recent_inventory_panel label="Elementi recenti" name="Recent Items"/> + <inventory_panel label="INDOSSATI" name="Worn Items"/> + </tab_container> + </panel> </panel> diff --git a/indra/newview/skins/default/xui/pl/panel_main_inventory.xml b/indra/newview/skins/default/xui/pl/panel_main_inventory.xml index 1011c38378fedc193b3453c9a14b948d105383ff..34d9d995bc093080e7bb1f023c92223b88651dcd 100644 --- a/indra/newview/skins/default/xui/pl/panel_main_inventory.xml +++ b/indra/newview/skins/default/xui/pl/panel_main_inventory.xml @@ -10,20 +10,17 @@ <text name="ItemcountText"> Obiekty: </text> - <filter_editor label="Filtruj SzafÄ™" name="inventory search editor" /> - <tab_container name="inventory filter tabs"> - <inventory_panel label="MOJA SZAFA" name="All Items" /> - <recent_inventory_panel label="OSTATNIE" name="Recent Items" /> - </tab_container> - <layout_stack name="bottom_panel"> - <layout_panel name="options_gear_btn_panel"> - <menu_button tool_tip="Pokaż dodatkowe opcje" name="options_gear_btn" /> - </layout_panel> - <layout_panel name="add_btn_panel"> - <button name="add_btn" tool_tip="Dodaj nowy obiekt" /> - </layout_panel> - <layout_panel name="trash_btn_panel"> - <dnd_button name="trash_btn" tool_tip="UsuÅ„ zaznaczony obiekt" /> - </layout_panel> - </layout_stack> + <layout_stack name="top_stack"> + <layout_panel name="filter_layout_panel"> + <filter_editor label="Filtruj SzafÄ™" name="inventory search editor" /> + <menu_button tool_tip="Pokaż dodatkowe opcje" name="options_gear_btn" /> + <button name="add_btn" tool_tip="Dodaj nowy obiekt" /> + </layout_panel> + </layout_stack> + <panel name="default_inventory_panel"> + <tab_container name="inventory filter tabs"> + <inventory_panel label="MOJA SZAFA" name="All Items" /> + <recent_inventory_panel label="OSTATNIE" name="Recent Items" /> + </tab_container> + </panel> </panel> diff --git a/indra/newview/skins/default/xui/pt/panel_main_inventory.xml b/indra/newview/skins/default/xui/pt/panel_main_inventory.xml index e0cf528468ee7492b4c7debbb60a1cd4c099b3eb..debd601966de83938cfaeb1b6f5b196028e38dc4 100644 --- a/indra/newview/skins/default/xui/pt/panel_main_inventory.xml +++ b/indra/newview/skins/default/xui/pt/panel_main_inventory.xml @@ -13,27 +13,24 @@ <text name="ItemcountText"> Itens: </text> - <filter_editor label="Digite o texto de pesquisa" name="inventory search editor"/> - <combo_box name="search_type"> - <item label="Nome" name="Name" value="search_by_name"/> - <item label="Criador" name="Creator" value="search_by_creator"/> - <item label="Descrição" name="Description" value="search_by_description"/> - <item label="UUID" name="UUID" value="search_by_UUID"/> - </combo_box> - <tab_container name="inventory filter tabs"> - <inventory_panel label="Todos os itens" name="All Items"/> - <recent_inventory_panel label="Itens recentes" name="Recent Items"/> - <inventory_panel label="USADO" name="Worn Items"/> - </tab_container> - <layout_stack name="bottom_panel"> - <layout_panel name="options_gear_btn_panel"> - <menu_button name="options_gear_btn" tool_tip="Mostrar opções adicionais"/> - </layout_panel> - <layout_panel name="add_btn_panel"> - <button name="add_btn" tool_tip="Adicionar novo item"/> - </layout_panel> - <layout_panel name="trash_btn_panel"> - <dnd_button name="trash_btn" tool_tip="Remover item selecionado"/> - </layout_panel> - </layout_stack> + <layout_stack name="top_stack"> + <layout_panel name="filter_layout_panel"> + <combo_box name="search_type"> + <item label="Nome" name="Name" value="search_by_name"/> + <item label="Criador" name="Creator" value="search_by_creator"/> + <item label="Descrição" name="Description" value="search_by_description"/> + <item label="UUID" name="UUID" value="search_by_UUID"/> + </combo_box> + <filter_editor label="Digite o texto de pesquisa" name="inventory search editor"/> + <menu_button name="options_gear_btn" tool_tip="Mostrar opções adicionais"/> + <button name="add_btn" tool_tip="Adicionar novo item"/> + </layout_panel> + </layout_stack> + <panel name="default_inventory_panel"> + <tab_container name="inventory filter tabs"> + <inventory_panel label="Todos os itens" name="All Items"/> + <recent_inventory_panel label="Itens recentes" name="Recent Items"/> + <inventory_panel label="USADO" name="Worn Items"/> + </tab_container> + </panel> </panel> diff --git a/indra/newview/skins/default/xui/ru/panel_main_inventory.xml b/indra/newview/skins/default/xui/ru/panel_main_inventory.xml index b473fb8f9826c0c900077ab6cb0d11ceb0dacffe..a0ff3a9f67f63458c622671e64d4bcb875f73a15 100644 --- a/indra/newview/skins/default/xui/ru/panel_main_inventory.xml +++ b/indra/newview/skins/default/xui/ru/panel_main_inventory.xml @@ -13,27 +13,24 @@ <text name="ItemcountText"> Вещи: </text> - <filter_editor label="Введите текÑÑ‚ поиÑка" name="inventory search editor"/> - <combo_box name="search_type"> - <item label="Ðазвание" name="Name" value="search_by_name"/> - <item label="Создатель" name="Creator" value="search_by_creator"/> - <item label="ОпиÑание" name="Description" value="search_by_description"/> - <item label="УУИд" name="UUID" value="search_by_UUID"/> - </combo_box> - <tab_container name="inventory filter tabs"> - <inventory_panel label="МОЙ ИÐВЕÐТÐРЬ" name="All Items"/> - <recent_inventory_panel label="ÐЕДÐÐ’ÐИЕ" name="Recent Items"/> - <inventory_panel label="ÐОСИМОЕ" name="Worn Items"/> - </tab_container> - <layout_stack name="bottom_panel"> - <layout_panel name="options_gear_btn_panel"> - <menu_button name="options_gear_btn" tool_tip="Показать дополнительные параметры"/> - </layout_panel> - <layout_panel name="add_btn_panel"> - <button name="add_btn" tool_tip="Добавить новую вещь"/> - </layout_panel> - <layout_panel name="trash_btn_panel"> - <dnd_button name="trash_btn" tool_tip="Удалить выбранную вещь"/> - </layout_panel> - </layout_stack> + <layout_stack name="top_stack"> + <layout_panel name="filter_layout_panel"> + <combo_box name="search_type"> + <item label="Ðазвание" name="Name" value="search_by_name"/> + <item label="Создатель" name="Creator" value="search_by_creator"/> + <item label="ОпиÑание" name="Description" value="search_by_description"/> + <item label="УУИд" name="UUID" value="search_by_UUID"/> + </combo_box> + <filter_editor label="Введите текÑÑ‚ поиÑка" name="inventory search editor"/> + <menu_button name="options_gear_btn" tool_tip="Показать дополнительные параметры"/> + <button name="add_btn" tool_tip="Добавить новую вещь"/> + </layout_panel> + </layout_stack> + <panel name="default_inventory_panel"> + <tab_container name="inventory filter tabs"> + <inventory_panel label="МОЙ ИÐВЕÐТÐРЬ" name="All Items"/> + <recent_inventory_panel label="ÐЕДÐÐ’ÐИЕ" name="Recent Items"/> + <inventory_panel label="ÐОСИМОЕ" name="Worn Items"/> + </tab_container> + </panel> </panel> diff --git a/indra/newview/skins/default/xui/tr/panel_main_inventory.xml b/indra/newview/skins/default/xui/tr/panel_main_inventory.xml index 7e980786354df4f16d73c54000704aa193606497..af55d4cafc88fed12a511540658c8517e0076d45 100644 --- a/indra/newview/skins/default/xui/tr/panel_main_inventory.xml +++ b/indra/newview/skins/default/xui/tr/panel_main_inventory.xml @@ -13,27 +13,24 @@ <text name="ItemcountText"> Ögeler: </text> - <filter_editor label="Arama metnini gir" name="inventory search editor"/> - <combo_box name="search_type"> - <item label="Ad" name="Name" value="search_by_name"/> - <item label="OluÅŸturan" name="Creator" value="search_by_creator"/> - <item label="Açıklama" name="Description" value="search_by_description"/> - <item label="UUID" name="UUID" value="search_by_UUID"/> - </combo_box> - <tab_container name="inventory filter tabs"> - <inventory_panel label="ENVANTERÄ°M" name="All Items"/> - <recent_inventory_panel label="SON" name="Recent Items"/> - <inventory_panel label="GÄ°YÄ°LEN" name="Worn Items"/> - </tab_container> - <layout_stack name="bottom_panel"> - <layout_panel name="options_gear_btn_panel"> - <menu_button name="options_gear_btn" tool_tip="Ä°lave seçenekleri göster"/> - </layout_panel> - <layout_panel name="add_btn_panel"> - <button name="add_btn" tool_tip="Yeni öge ekle"/> - </layout_panel> - <layout_panel name="trash_btn_panel"> - <dnd_button name="trash_btn" tool_tip="Seçilen öğeyi sil"/> - </layout_panel> - </layout_stack> + <layout_stack name="top_stack"> + <layout_panel name="filter_layout_panel"> + <combo_box name="search_type"> + <item label="Ad" name="Name" value="search_by_name"/> + <item label="OluÅŸturan" name="Creator" value="search_by_creator"/> + <item label="Açıklama" name="Description" value="search_by_description"/> + <item label="UUID" name="UUID" value="search_by_UUID"/> + </combo_box> + <filter_editor label="Arama metnini gir" name="inventory search editor"/> + <menu_button name="options_gear_btn" tool_tip="Ä°lave seçenekleri göster"/> + <button name="add_btn" tool_tip="Yeni öge ekle"/> + </layout_panel> + </layout_stack> + <panel name="default_inventory_panel"> + <tab_container name="inventory filter tabs"> + <inventory_panel label="ENVANTERÄ°M" name="All Items"/> + <recent_inventory_panel label="SON" name="Recent Items"/> + <inventory_panel label="GÄ°YÄ°LEN" name="Worn Items"/> + </tab_container> + </panel> </panel> diff --git a/indra/newview/skins/default/xui/zh/panel_main_inventory.xml b/indra/newview/skins/default/xui/zh/panel_main_inventory.xml index 9ffa9323cc55c80592a7df5305e84c39e479464b..6a8ead28b41deb07f9adaa219368a73ef11e4b9a 100644 --- a/indra/newview/skins/default/xui/zh/panel_main_inventory.xml +++ b/indra/newview/skins/default/xui/zh/panel_main_inventory.xml @@ -13,27 +13,24 @@ <text name="ItemcountText"> 物å“: </text> - <filter_editor label="輸入æœå°‹æ–‡å—" name="inventory search editor"/> - <combo_box name="search_type"> - <item label="å稱" name="Name" value="search_by_name"/> - <item label="å‰µé€ è€…" name="Creator" value="search_by_creator"/> - <item label="æè¿°" name="Description" value="search_by_description"/> - <item label="UUID" name="UUID" value="search_by_UUID"/> - </combo_box> - <tab_container name="inventory filter tabs"> - <inventory_panel label="我的收ç´å€" name="All Items"/> - <recent_inventory_panel label="最近" name="Recent Items"/> - <inventory_panel label="已穿戴" name="Worn Items"/> - </tab_container> - <layout_stack name="bottom_panel"> - <layout_panel name="options_gear_btn_panel"> - <menu_button name="options_gear_btn" tool_tip="顯示é¡å¤–é¸é …"/> - </layout_panel> - <layout_panel name="add_btn_panel"> - <button name="add_btn" tool_tip="æ·»åŠ æ–°ç‰©å“"/> - </layout_panel> - <layout_panel name="trash_btn_panel"> - <dnd_button name="trash_btn" tool_tip="移除所é¸æ“‡çš„物å“"/> - </layout_panel> - </layout_stack> + <layout_stack name="top_stack"> + <layout_panel name="filter_layout_panel"> + <combo_box name="search_type"> + <item label="å稱" name="Name" value="search_by_name"/> + <item label="å‰µé€ è€…" name="Creator" value="search_by_creator"/> + <item label="æè¿°" name="Description" value="search_by_description"/> + <item label="UUID" name="UUID" value="search_by_UUID"/> + </combo_box> + <filter_editor label="輸入æœå°‹æ–‡å—" name="inventory search editor"/> + <menu_button name="options_gear_btn" tool_tip="顯示é¡å¤–é¸é …"/> + <button name="add_btn" tool_tip="æ·»åŠ æ–°ç‰©å“"/> + </layout_panel> + </layout_stack> + <panel name="default_inventory_panel"> + <tab_container name="inventory filter tabs"> + <inventory_panel label="我的收ç´å€" name="All Items"/> + <recent_inventory_panel label="最近" name="Recent Items"/> + <inventory_panel label="已穿戴" name="Worn Items"/> + </tab_container> + </panel> </panel> diff --git a/indra/test/CMakeLists.txt b/indra/test/CMakeLists.txt index b4d57b26099d48a679288af1002831a6fd6b5830..97f24f2e9caf7d07f75265e1486732a3c5c2a815 100644 --- a/indra/test/CMakeLists.txt +++ b/indra/test/CMakeLists.txt @@ -71,6 +71,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 "$<TARGET_FILE_DIR:lltest>/Resources", + # but the CMake $<TARGET_FILE_DIR> 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 $<TARGET_FILE:lltest>) diff --git a/indra/test/test.cpp b/indra/test/test.cpp index 06ae583d76dfa98c16f58089a0a6e5ebc4051e90..dd1ac369eb693f7ddd61f4f52846a952b0e3e31c 100644 --- a/indra/test/test.cpp +++ b/indra/test/test.cpp @@ -252,7 +252,7 @@ class LLTestCallback : public chained_callback 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;