diff options
Diffstat (limited to 'indra/llcommon')
24 files changed, 2959 insertions, 579 deletions
| diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index ef4899978e..e02f69126e 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -16,7 +16,9 @@ include(Tracy)  set(llcommon_SOURCE_FILES +    apply.cpp      indra_constants.cpp +    lazyeventapi.cpp      llallocator.cpp      llallocator_heap_profile.cpp      llapp.cpp @@ -114,11 +116,15 @@ set(llcommon_SOURCE_FILES  set(llcommon_HEADER_FILES      CMakeLists.txt +    always_return.h +    apply.h      chrono.h      classic_callback.h      ctype_workaround.h      fix_macros.h +    function_types.h      indra_constants.h +    lazyeventapi.h      linden_common.h      llalignedarray.h      llallocator.h @@ -288,9 +294,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 0000000000..6b9f1fdeaf --- /dev/null +++ b/indra/llcommon/always_return.h @@ -0,0 +1,124 @@ +/** + * @file   always_return.h + * @author Nat Goodspeed + * @date   2023-01-20 + * @brief  Call specified callable with arbitrary arguments, but always return + *         specified type. + *  + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Copyright (c) 2023, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_ALWAYS_RETURN_H) +#define LL_ALWAYS_RETURN_H + +#include <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 0000000000..417e23d3b4 --- /dev/null +++ b/indra/llcommon/apply.cpp @@ -0,0 +1,29 @@ +/** + * @file   apply.cpp + * @author Nat Goodspeed + * @date   2022-12-21 + * @brief  Implementation for apply. + *  + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Copyright (c) 2022, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "apply.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "stringize.h" + +void LL::apply_validate_size(size_t size, size_t arity) +{ +    if (size != arity) +    { +        LLTHROW(apply_error(stringize("LL::apply(func(", arity, " args), " +                                      "std::vector(", size, " elements))"))); +    } +} diff --git a/indra/llcommon/apply.h b/indra/llcommon/apply.h index 7c58d63bc0..cf6161ed50 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 0000000000..3f42f6d640 --- /dev/null +++ b/indra/llcommon/function_types.h @@ -0,0 +1,49 @@ +/** + * @file   function_types.h + * @author Nat Goodspeed + * @date   2023-01-20 + * @brief  Extend boost::function_types to examine boost::function and + *         std::function + *  + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Copyright (c) 2023, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_FUNCTION_TYPES_H) +#define LL_FUNCTION_TYPES_H + +#include <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 0000000000..028af9f33f --- /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 0000000000..a815b119f0 --- /dev/null +++ b/indra/llcommon/lazyeventapi.h @@ -0,0 +1,204 @@ +/** + * @file   lazyeventapi.h + * @author Nat Goodspeed + * @date   2022-06-16 + * @brief  Declaring a static module-scope LazyEventAPI registers a specific + *         LLEventAPI for future on-demand instantiation. + *  + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Copyright (c) 2022, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LAZYEVENTAPI_H) +#define LL_LAZYEVENTAPI_H + +#include "apply.h" +#include "lleventapi.h" +#include "llinstancetracker.h" +#include <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. +            // We can't bind an unexpanded parameter pack into a lambda -- +            // shame really. Instead, capture it as a std::tuple and then, in +            // the lambda, use apply() to convert back to function args. +            mParams.init.connect_extended( +                [name, desc, rest = std::make_tuple(std::forward<ARGS>(rest)...)] +                (const boost::signals2::connection& conn, LLEventAPI* instance) +                { +                    // we only need this connection once +                    conn.disconnect(); +                    // Our add() method distinguishes name and desc because we +                    // capture them separately. But now, because apply() +                    // expects a tuple specifying ALL the arguments, expand to +                    // a tuple including add_trampoline() arguments: instance, +                    // name, desc, rest. +                    // apply() can't accept a template per se; it needs a +                    // particular specialization. +                    apply(&LazyEventAPIBase::add_trampoline<const std::string&, const std::string&,  ARGS...>, +                          std::tuple_cat(std::make_tuple(instance, name, desc), +                                         rest)); +                }); +        } + +        // 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/lleventapi.cpp b/indra/llcommon/lleventapi.cpp index ff5459c1eb..3d46ef1034 100644 --- a/indra/llcommon/lleventapi.cpp +++ b/indra/llcommon/lleventapi.cpp @@ -35,6 +35,7 @@  // external library headers  // other Linden headers  #include "llerror.h" +#include "lazyeventapi.h"  LLEventAPI::LLEventAPI(const std::string& name, const std::string& desc, const std::string& field):      lbase(name, field), @@ -43,6 +44,13 @@ LLEventAPI::LLEventAPI(const std::string& name, const std::string& desc, const s  {  } +LLEventAPI::LLEventAPI(const LL::LazyEventAPIParams& params): +    LLEventAPI(params.name, params.desc, params.field) +{ +    // call initialization functions with our brand-new instance pointer +    params.init(this); +} +  LLEventAPI::~LLEventAPI()  {  } diff --git a/indra/llcommon/lleventapi.h b/indra/llcommon/lleventapi.h index 5991fe8fd5..25f6becd8b 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. @@ -65,19 +70,6 @@ public:      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       * method. The reply will be sent if request.has(@a replyKey), default @@ -150,16 +142,20 @@ public:           * @endcode           */          LLSD& operator[](const LLSD::String& key) { return mResp[key]; } -		 -		 /** -		 * set the response to the given data -		 */ -		void setResponse(LLSD const & response){ mResp = response; } + +         /** +         * set the response to the given data +         */ +        void setResponse(LLSD const & response){ mResp = response; }          LLSD mResp, mReq;          LLSD::String mKey;      }; +protected: +    // constructor used only by subclasses registered by LazyEventAPI +    LLEventAPI(const LL::LazyEventAPIParams&); +  private:      std::string mDesc;  }; diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index cd0ab6bc29..99e2e74376 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -40,71 +40,13 @@  // 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> +    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 @@ private:      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,23 +337,44 @@ 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> +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::~LLEventDispatcher()  {  } +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)      {} @@ -414,22 +382,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; +            return 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);      }  }; @@ -439,17 +406,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() +            return callFail(err.what()); +        }      }  }; @@ -459,23 +436,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()) +                { +                    return callFail("LLEventDispatcher has no args key"); +                } +                if ((! event.has(argskey))) +                { +                    return callFail("missing required key ", std::quoted(argskey)); +                } +                args = event[argskey]; +            } +        } +        return ParamsDispatchEntry::call(desc, args, fromMap, argskey); +    } + +    LLSD 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);      }  }; @@ -485,11 +501,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 @@ -532,18 +548,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);      }  }; @@ -552,9 +577,9 @@ void LLEventDispatcher::addArrayParamsDispatchEntry(const std::string& name,                                                      const invoker_function& invoker,                                                      LLSD::Integer arity)  { -    mDispatch.insert( -        DispatchMap::value_type(name, DispatchMap::mapped_type( -                                    new ArrayParamsDispatchEntry(desc, invoker, arity)))); +    mDispatch.emplace( +        name, +        new ArrayParamsDispatchEntry(this, "", desc, invoker, arity));  }  void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name, @@ -563,25 +588,25 @@ void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name,                                                    const LLSD& params,                                                    const LLSD& defaults)  { -    mDispatch.insert( -        DispatchMap::value_type(name, DispatchMap::mapped_type( -                                    new MapParamsDispatchEntry(name, desc, invoker, params, defaults)))); +    // 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, DispatchMap::mapped_type( -                                    new 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 @@ -596,48 +621,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 +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> +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 @@ -647,26 +729,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 9e1244ef5b..939e3730e1 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();      /// @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 @@ public:      void add(const std::string& name,               const std::string& desc,               const Callable& callable, -             const LLSD& required=LLSD()); +             const LLSD& required=LLSD()) +    { +        addLLSD(name, desc, callable, required); +    } -    /** -     * The case of a free function (or static method) accepting(const LLSD&) -     * could also be intercepted by the arbitrary-args overload below. Ensure -     * that it's directed to the Callable overload above instead. -     */ +    template <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 @@ public:          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 @@ public:       * Register a free function with arbitrary parameters. (This also works       * for static class methods.)       * -     * @note This supports functions with up to about 6 parameters -- after -     * that you start getting dismaying compile errors in which -     * boost::fusion::joint_view is mentioned a surprising number of times. -     *       * When calling this name, pass an LLSD::Array. Each entry in turn will be       * converted to the corresponding parameter type using LLSDParam.       */ -    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 @@ public:       * 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 @@ public:       * boost::lambda::var(instance) or boost::lambda::constant(instance_ptr)       * produce suitable callables.       * +     * TODO: variant accepting a method of the containing class, no getter. +     *       * Pass an LLSD::Array of parameter names, and optionally another       * LLSD::Array of default parameter values, a la LLSDArgsMapper.       * @@ -240,42 +362,96 @@ public:       * 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,28 @@ public:  private:      struct DispatchEntry      { -        DispatchEntry(const std::string& desc); +        DispatchEntry(LLEventDispatcher* parent, const std::string& desc);          virtual ~DispatchEntry() {} // suppress MSVC warning, sigh +        // store a plain dumb back-pointer because the parent +        // LLEventDispatcher manages the lifespan of each DispatchEntry +        // subclass instance -- not the other way around +        LLEventDispatcher* mParent;          std::string mDesc; -        virtual 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> +        LLSD callFail(ARGS&&... args) const +        { +            mParent->callFail<LLEventDispatcher::DispatchError>(std::forward<ARGS>(args)...); +            // pacify the compiler +            return {}; +        }      }; -    // 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, boost::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,12 +505,58 @@ public:      /// 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 +564,85 @@ private:          }          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> +    void callFail(ARGS&&... args) const; +    template <typename EXCEPTION, typename... ARGS> +    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 +662,38 @@ private:  /*****************************************************************************  *   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 +702,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); +    // function_arity<member function> includes its implicit 'this' pointer +    constexpr auto arity = LL::function_arity< +        typename std::remove_reference<Method>::type>::value - 1; + +    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] +            () +            { +                // 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 +749,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 7613850fb2..1fb41e0297 100644 --- a/indra/llcommon/lleventfilter.h +++ b/indra/llcommon/lleventfilter.h @@ -435,16 +435,61 @@ public:          // generic type-appropriate store through mTarget, construct an          // LLSDParam<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 0a213bddef..1a305ec3dc 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 ae6e5aabc9..c1dbf4392f 100644 --- a/indra/llcommon/llevents.h +++ b/indra/llcommon/llevents.h @@ -268,6 +268,45 @@ public:      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. @@ -325,13 +364,13 @@ testable:      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 c87c0758fe..abbc4185c6 100644 --- a/indra/llcommon/llleap.cpp +++ b/indra/llcommon/llleap.cpp @@ -327,11 +327,28 @@ public:              }              else              { -                // The LLSD object we got from our stream contains the keys we -                // need. -                LLEventPumps::instance().obtain(data["pump"]).post(data["data"]); -                // Block calls to this method; resetting mBlocker unblocks calls -                // to the other method. +                try +                { +                    // The LLSD object we got from our stream contains the +                    // keys we need. +                    LLEventPumps::instance().obtain(data["pump"]).post(data["data"]); +                } +                catch (const std::exception& err) +                { +                    // No plugin should be allowed to crash the viewer by +                    // driving an exception -- intentionally or not. +                    LOG_UNHANDLED_EXCEPTION(stringize("handling request ", data)); +                    // Whether or not the plugin added a "reply" key to the +                    // request, send a reply. We happen to know who originated +                    // this request, and the reply LLEventPump of interest. +                    // Not our problem if the plugin ignores the reply event. +                    data["reply"] = mReplyPump.getName(); +                    sendReply(llsd::map("error", +                                        stringize(LLError::Log::classname(err), ": ", err.what())), +                              data); +                } +                // Block calls to this method; resetting mBlocker unblocks +                // calls to the other method.                  mBlocker.reset(new LLEventPump::Blocker(mStdoutDataConnection));                  // Go check for any more pending events in the buffer.                  if (childout.size()) diff --git a/indra/llcommon/llleaplistener.cpp b/indra/llcommon/llleaplistener.cpp index 11bfec1b31..471f52e91c 100644 --- a/indra/llcommon/llleaplistener.cpp +++ b/indra/llcommon/llleaplistener.cpp @@ -14,14 +14,16 @@  // associated header  #include "llleaplistener.h"  // STL headers -#include <map> +#include <algorithm>                // std::find_if  #include <functional> +#include <map> +#include <set>  // std headers  // external library headers -#include <boost/foreach.hpp>  // other Linden headers -#include "lluuid.h" +#include "lazyeventapi.h"  #include "llsdutil.h" +#include "lluuid.h"  #include "stringize.h"  /***************************************************************************** @@ -110,7 +112,7 @@ LLLeapListener::~LLLeapListener()      // value_type, and Bad Things would happen if you copied an      // LLTempBoundListener. (Destruction of the original would disconnect the      // listener, invalidating every stored connection.) -    BOOST_FOREACH(ListenersMap::value_type& pair, mListeners) +    for (ListenersMap::value_type& pair : mListeners)      {          pair.second.disconnect();      } @@ -208,31 +210,65 @@ void LLLeapListener::getAPIs(const LLSD& request) const  {      Response reply(LLSD(), request); +    // first, traverse existing LLEventAPI instances +    std::set<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/llptrto.h b/indra/llcommon/llptrto.h index 4082e30de6..9ef279fdbf 100644 --- a/indra/llcommon/llptrto.h +++ b/indra/llcommon/llptrto.h @@ -33,9 +33,12 @@  #include "llpointer.h"  #include "llrefcount.h"             // LLRefCount +#include <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 f70bee9903..e98fc0285a 100644 --- a/indra/llcommon/llsdutil.cpp +++ b/indra/llcommon/llsdutil.cpp @@ -1046,3 +1046,38 @@ LLSD llsd_shallow(LLSD value, LLSD filter)      return shallow;  } + +LLSD LL::apply_llsd_fix(size_t arity, const LLSD& args) +{ +    // LLSD supports a number of types, two of which are aggregates: Map and +    // Array. We don't try to support Map: supporting Map would seem to +    // promise that we could somehow match the string key to 'func's parameter +    // names. Uh sorry, maybe in some future version of C++ with reflection. +    if (args.isMap()) +    { +        LLTHROW(LL::apply_error("LL::apply(function, Map LLSD) unsupported")); +    } +    // We expect an LLSD array, but what the heck, treat isUndefined() as a +    // zero-length array for calling a nullary 'func'. +    if (args.isUndefined() || args.isArray()) +    { +        // this works because LLSD().size() == 0 +        if (args.size() != arity) +        { +            LLTHROW(LL::apply_error(stringize("LL::apply(function(", arity, " args), ", +                                              args.size(), "-entry LLSD array)"))); +        } +        return args; +    } + +    // args is one of the scalar types +    // scalar_LLSD.size() == 0, so don't test that here. +    // You can pass a scalar LLSD only to a unary 'func'. +    if (arity != 1) +    { +        LLTHROW(LL::apply_error(stringize("LL::apply(function(", arity, " args), " +                                          "LLSD ", LLSD::typeString(args.type()), ")"))); +    } +    // make an array of it +    return llsd::array(args); +} diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h index 372278c51a..a6fd2fdac2 100644 --- a/indra/llcommon/llsdutil.h +++ b/indra/llcommon/llsdutil.h @@ -29,8 +29,12 @@  #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 <type_traits>  // U32  LL_COMMON_API LLSD ll_sd_from_U32(const U32); @@ -333,6 +337,31 @@ private:  };  /** + * 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> +{ +private: +    LLSD value_; + +public: +    LLSDParam(const LLSD& value): value_(value) {} + +    /// if we're literally being asked for an LLSD parameter, avoid infinite +    /// recursion +    operator LLSD() const { return value_; } + +    /// otherwise, instantiate a more specific LLSDParam<T> to convert; that +    /// preserves the existing customization mechanism +    template <typename T> +    operator T() const { return LLSDParam<T>(value_); } +}; + +/**   * Turns out that several target types could accept an LLSD param using any of   * a few different conversions, e.g. LLUUID's constructor can accept LLUUID or   * std::string. Therefore, the compiler can't decide which LLSD conversion @@ -350,7 +379,7 @@ class LLSDParam<T>                              \  {                                               \  public:                                         \      LLSDParam(const LLSD& value):               \ -        _value((T)value.AS())                      \ +        _value((T)value.AS())                   \      {}                                          \                                                  \      operator T() const { return _value; }       \ @@ -555,4 +584,56 @@ struct hash<LLSD>      }  };  } + +namespace LL +{ + +/***************************************************************************** +*   apply(function, LLSD array) +*****************************************************************************/ +// validate incoming LLSD blob, and return an LLSD array suitable to pass to +// apply_impl() +LLSD apply_llsd_fix(size_t arity, const LLSD& args); + +// Derived from https://stackoverflow.com/a/20441189 +// and https://en.cppreference.com/w/cpp/utility/apply . +// We can't simply make a tuple from the LLSD array and then apply() that +// 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 0000000000..9a17afc18c --- /dev/null +++ b/indra/llcommon/tests/apply_test.cpp @@ -0,0 +1,237 @@ +/** + * @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" + +// 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 0000000000..31b2d6d17f --- /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 466f11f52a..0f27211d9e 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" @@ -32,8 +35,6 @@  #include <boost/bind.hpp>  #include <boost/function.hpp>  #include <boost/range.hpp> -#include <boost/foreach.hpp> -#define foreach BOOST_FOREACH  #include <boost/lambda/lambda.hpp> @@ -205,7 +206,7 @@ struct Vars      void methodnb(NPARAMSb)      {          std::ostringstream vbin; -        foreach(U8 byte, bin) +        for (U8 byte: bin)          {              vbin << std::hex << std::setfill('0') << std::setw(2) << unsigned(byte);          } @@ -315,6 +316,31 @@ void freenb(NPARAMSb)  *****************************************************************************/  namespace tut  { +    void ensure_has(const std::string& outer, const std::string& inner) +    { +        ensure(stringize("'", outer, "' does not contain '", inner, "'"), +               outer.find(inner) != std::string::npos); +    } + +    template <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"}; @@ -397,9 +423,9 @@ namespace tut              work.add(name, desc, &Dispatcher::cmethod1, required);              // Non-subclass method with/out required params              addf("method1", "method1", &v); -            work.add(name, desc, boost::bind(&Vars::method1, boost::ref(v), _1)); +            work.add(name, desc, [this](const LLSD& args){ return v.method1(args); });              addf("method1_req", "method1", &v); -            work.add(name, desc, boost::bind(&Vars::method1, boost::ref(v), _1), required); +            work.add(name, desc, [this](const LLSD& args){ return v.method1(args); }, required);              /*--------------- Arbitrary params, array style ----------------*/ @@ -461,7 +487,7 @@ namespace tut              debug("dft_array_full:\n",                    dft_array_full);              // Partial defaults arrays. -            foreach(LLSD::String a, ab) +            for (LLSD::String a: ab)              {                  LLSD::Integer partition(std::min(partial_offset, dft_array_full[a].size()));                  dft_array_partial[a] = @@ -471,7 +497,7 @@ namespace tut              debug("dft_array_partial:\n",                    dft_array_partial); -            foreach(LLSD::String a, ab) +            for(LLSD::String a: ab)              {                  // Generate full defaults maps by zipping (params, dft_array_full).                  dft_map_full[a] = zipmap(params[a], dft_array_full[a]); @@ -583,6 +609,7 @@ namespace tut          void addf(const std::string& n, const std::string& d, Vars* v)          { +            debug("addf('", n, "', '", d, "')");              // This method is to capture in our own DescMap the name and              // description of every registered function, for metadata query              // testing. @@ -598,19 +625,14 @@ namespace tut          {              // Copy descs to a temp map of same type.              DescMap forgotten(descs.begin(), descs.end()); -            // LLEventDispatcher intentionally provides only const_iterator: -            // since dereferencing that iterator generates values on the fly, -            // it's meaningless to have a modifiable iterator. But since our -            // 'work' object isn't const, by default BOOST_FOREACH() wants to -            // use non-const iterators. Persuade it to use the const_iterator. -            foreach(LLEventDispatcher::NameDesc nd, const_cast<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 @@ -621,41 +643,49 @@ namespace tut                  std::ostringstream out;                  out << "LLEventDispatcher failed to report";                  const char* delim = ": "; -                foreach(const DescMap::value_type& fme, forgotten) +                for (const DescMap::value_type& fme: forgotten)                  {                      out << delim << fme.first;                      delim = ", ";                  } -                ensure(out.str(), false); +                throw failure(out.str());              }          }          Vars* varsfor(const std::string& name)          {              VarsMap::const_iterator found = funcvars.find(name); -            ensure(STRINGIZE("No Vars* for " << name), found != funcvars.end()); -            ensure(STRINGIZE("NULL Vars* for " << name), found->second); +            ensure(stringize("No Vars* for ", name), found != funcvars.end()); +            ensure(stringize("NULL Vars* for ", name), found->second);              return found->second;          } -        void ensure_has(const std::string& outer, const std::string& inner) +        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;          } @@ -724,7 +754,7 @@ namespace tut          set_test_name("map-style registration with non-array params");          // Pass "param names" as scalar or as map          LLSD attempts(llsd::array(17, LLSDMap("pi", 3.14)("two", 2))); -        foreach(LLSD ae, inArray(attempts)) +        for (LLSD ae: inArray(attempts))          {              std::string threw = catch_what<std::exception>([this, &ae](){                      work.add("freena_err", "freena", freena, ae); @@ -799,7 +829,7 @@ namespace tut      {          set_test_name("query Callables with/out required params");          LLSD names(llsd::array("free1", "Dmethod1", "Dcmethod1", "method1")); -        foreach(LLSD nm, inArray(names)) +        for (LLSD nm: inArray(names))          {              LLSD metadata(getMetadata(nm));              ensure_equals("name mismatch", metadata["name"], nm); @@ -828,19 +858,19 @@ namespace tut                         (5, llsd::array("freena_array", "smethodna_array", "methodna_array")),                         llsd::array                         (5, llsd::array("freenb_array", "smethodnb_array", "methodnb_array")))); -        foreach(LLSD ae, inArray(expected)) +        for (LLSD ae: inArray(expected))          {              LLSD::Integer arity(ae[0].asInteger());              LLSD names(ae[1]);              LLSD req(LLSD::emptyArray());              if (arity)                  req[arity - 1] = LLSD(); -            foreach(LLSD nm, inArray(names)) +            for (LLSD nm: inArray(names))              {                  LLSD metadata(getMetadata(nm));                  ensure_equals("name mismatch", metadata["name"], nm);                  ensure_equals(metadata["desc"].asString(), descs[nm]); -                ensure_equals(STRINGIZE("mismatched required for " << nm.asString()), +                ensure_equals(stringize("mismatched required for ", nm.asString()),                                metadata["required"], req);                  ensure("should not have optional", metadata["optional"].isUndefined());              } @@ -854,7 +884,7 @@ namespace tut          // - (Free function | non-static method), map style, no params (ergo          //   no defaults)          LLSD names(llsd::array("free0_map", "smethod0_map", "method0_map")); -        foreach(LLSD nm, inArray(names)) +        for (LLSD nm: inArray(names))          {              LLSD metadata(getMetadata(nm));              ensure_equals("name mismatch", metadata["name"], nm); @@ -884,7 +914,7 @@ namespace tut                             llsd::array("smethodnb_map_adft", "smethodnb_map_mdft"),                             llsd::array("methodna_map_adft", "methodna_map_mdft"),                             llsd::array("methodnb_map_adft", "methodnb_map_mdft"))); -        foreach(LLSD eq, inArray(equivalences)) +        for (LLSD eq: inArray(equivalences))          {              LLSD adft(eq[0]);              LLSD mdft(eq[1]); @@ -898,8 +928,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);          }      } @@ -915,7 +945,7 @@ namespace tut          // params are required. Also maps containing left requirements for          // partial defaults arrays. Also defaults maps from defaults arrays.          LLSD allreq, leftreq, rightdft; -        foreach(LLSD::String a, ab) +        for (LLSD::String a: ab)          {              // The map in which all params are required uses params[a] as              // keys, with all isUndefined() as values. We can accomplish that @@ -943,9 +973,9 @@ namespace tut          // Generate maps containing parameter names not provided by the          // dft_map_partial maps.          LLSD skipreq(allreq); -        foreach(LLSD::String a, ab) +        for (LLSD::String a: ab)          { -            foreach(const MapEntry& me, inMap(dft_map_partial[a])) +            for (const MapEntry& me: inMap(dft_map_partial[a]))              {                  skipreq[a].erase(me.first);              } @@ -990,7 +1020,7 @@ namespace tut                       (llsd::array("freenb_map_mdft", "smethodnb_map_mdft", "methodnb_map_mdft"),                        llsd::array(LLSD::emptyMap(), dft_map_full["b"])))); // required, optional -        foreach(LLSD grp, inArray(groups)) +        for (LLSD grp: inArray(groups))          {              // Internal structure of each group in 'groups':              LLSD names(grp[0]); @@ -1003,14 +1033,14 @@ namespace tut                    optional);              // Loop through 'names' -            foreach(LLSD nm, inArray(names)) +            for (LLSD nm: inArray(names))              {                  LLSD metadata(getMetadata(nm));                  ensure_equals("name mismatch", metadata["name"], nm);                  ensure_equals(nm.asString(), metadata["desc"].asString(), descs[nm]); -                ensure_equals(STRINGIZE(nm << " required mismatch"), +                ensure_equals(stringize(nm, " required mismatch"),                                metadata["required"], required); -                ensure_equals(STRINGIZE(nm << " optional mismatch"), +                ensure_equals(stringize(nm, " optional mismatch"),                                metadata["optional"], optional);              }          } @@ -1031,13 +1061,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");      } @@ -1079,7 +1103,7 @@ namespace tut          // LLSD value matching 'required' according to llsd_matches() rules.          LLSD matching(LLSDMap("d", 3.14)("array", llsd::array("answer", true, answer)));          // Okay, walk through 'tests'. -        foreach(const CallablesTriple& tr, tests) +        for (const CallablesTriple& tr: tests)          {              // Should be able to pass 'answer' to Callables registered              // without 'required'. @@ -1087,7 +1111,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); @@ -1101,17 +1125,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); @@ -1125,7 +1152,7 @@ namespace tut                     ("free0_array", "free0_map",                      "smethod0_array", "smethod0_map",                      "method0_array", "method0_map")); -        foreach(LLSD name, inArray(names)) +        for (LLSD name: inArray(names))          {              // Look up the Vars instance for this function.              Vars* vars(varsfor(name)); @@ -1150,15 +1177,21 @@ namespace tut      template<> template<>      void object::test<19>()      { -        set_test_name("call array-style functions with too-short arrays"); -        // Could have two different too-short arrays, one for *na and one for -        // *nb, but since they both take 5 params... +        set_test_name("call array-style functions with wrong-length arrays"); +        // Could have different wrong-length arrays for *na and for *nb, but +        // since they both take 5 params...          LLSD tooshort(llsd::array("this", "array", "too", "short")); -        foreach(const LLSD& funcsab, inArray(array_funcs)) +        LLSD toolong (llsd::array("this", "array", "is",  "one", "too", "long")); +        LLSD badargs (llsd::array(tooshort, toolong)); +        for (const LLSD& toosomething: inArray(badargs))          { -            foreach(const llsd::MapEntry& e, inMap(funcsab)) +            for (const LLSD& funcsab: inArray(array_funcs))              { -                call_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"); +                }              }          }      } @@ -1166,7 +1199,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)          { @@ -1178,40 +1211,25 @@ namespace tut                                             LLDate("2011-02-03T15:07:00Z"),                                             LLURI("http://secondlife.com"),                                             binary))); -        LLSD argsplus(args); -        argsplus["a"].append("bogus"); -        argsplus["b"].append("bogus");          LLSD expect; -        foreach(LLSD::String a, ab) +        for (LLSD::String a: ab)          {              expect[a] = zipmap(params[a], args[a]);          }          // Adjust expect["a"]["cp"] for special Vars::cp treatment. -        expect["a"]["cp"] = std::string("'") + expect["a"]["cp"].asString() + "'"; +        expect["a"]["cp"] = stringize("'", expect["a"]["cp"].asString(), "'");          debug("expect: ", expect); -        // Use substantially the same logic for args and argsplus -        LLSD argsarrays(llsd::array(args, argsplus)); -        // So i==0 selects 'args', i==1 selects argsplus -        for (LLSD::Integer i(0), iend(argsarrays.size()); i < iend; ++i) +        for (const LLSD& funcsab: inArray(array_funcs))          { -            foreach(const LLSD& funcsab, inArray(array_funcs)) +            for (LLSD::String a: ab)              { -                foreach(LLSD::String a, ab) -                { -                    // Reset the Vars instance before each call -                    Vars* vars(varsfor(funcsab[a])); -                    *vars = Vars(); -                    work(funcsab[a], argsarrays[i][a]); -                    ensure_llsd(STRINGIZE(funcsab[a].asString() << -                                          ": expect[\"" << a << "\"] mismatch"), -                                vars->inspect(), expect[a], 7); // 7 bits ~= 2 decimal digits - -                    // TODO: in the i==1 or argsplus case, intercept LL_WARNS -                    // output? Even without that, using argsplus verifies that -                    // passing too many args isn't fatal; it works -- but -                    // would be nice to notice the warning too. -                } +                // Reset the Vars instance before each call +                Vars* vars(varsfor(funcsab[a])); +                *vars = Vars(); +                work(funcsab[a], args[a]); +                ensure_llsd(stringize(funcsab[a].asString(), ": expect[\"", a, "\"] mismatch"), +                            vars->inspect(), expect[a], 7); // 7 bits ~= 2 decimal digits              }          }      } @@ -1239,7 +1257,7 @@ namespace tut                          ("a", llsd::array(false, 255, 98.6, 1024.5, "pointer"))                          ("b", llsd::array("object", LLUUID::generateNewID(), LLDate::now(), LLURI("http://wiki.lindenlab.com/wiki"), LLSD::Binary(boost::begin(binary), boost::end(binary)))));          LLSD array_overfull(array_full); -        foreach(LLSD::String a, ab) +        for (LLSD::String a: ab)          {              array_overfull[a].append("bogus");          } @@ -1253,7 +1271,7 @@ namespace tut          ensure_not_equals("UUID collision",                            array_full["b"][1].asUUID(), dft_array_full["b"][1].asUUID());          LLSD map_full, map_overfull; -        foreach(LLSD::String a, ab) +        for (LLSD::String a: ab)          {              map_full[a] = zipmap(params[a], array_full[a]);              map_overfull[a] = map_full[a]; @@ -1294,21 +1312,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)); -        foreach(const LLSD& args, inArray(argssets)) +        for (const LLSD& args: inArray(argssets))          { -            foreach(LLSD::String a, ab) +            for (LLSD::String a: ab)              { -                foreach(LLSD::String name, inArray(names[a])) +                for (LLSD::String name: inArray(names[a]))                  {                      // Reset the Vars instance                      Vars* vars(varsfor(name));                      *vars = Vars();                      work(name, args[a]); -                    ensure_llsd(STRINGIZE(name << ": expect[\"" << a << "\"] mismatch"), +                    ensure_llsd(stringize(name, ": expect[\"", a, "\"] mismatch"),                                  vars->inspect(), expect[a], 7); // 7 bits, 2 decimal digits                      // intercept LL_WARNS for the two overfull cases?                  }              }          }      } + +    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/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index 7ee36a9ea6..168825a8cd 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -17,6 +17,7 @@  // std headers  #include <functional>  // external library headers +//#include <boost/algorithm/string/join.hpp>  #include <boost/assign/list_of.hpp>  #include <boost/phoenix/core/argument.hpp>  // other Linden headers diff --git a/indra/llcommon/tests/wrapllerrs.h b/indra/llcommon/tests/wrapllerrs.h index 3779fb41bc..d657b329bb 100644 --- a/indra/llcommon/tests/wrapllerrs.h +++ b/indra/llcommon/tests/wrapllerrs.h @@ -226,6 +226,11 @@ public:          return boost::dynamic_pointer_cast<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; | 
