diff options
Diffstat (limited to 'indra/llcommon/lleventdispatcher.h')
-rw-r--r-- | indra/llcommon/lleventdispatcher.h | 841 |
1 files changed, 592 insertions, 249 deletions
diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index 9e1244ef5b..a82bc7a69b 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,26 @@ public: private: struct DispatchEntry { - DispatchEntry(const std::string& desc); + DispatchEntry(LLEventDispatcher* parent, const std::string& desc); virtual ~DispatchEntry() {} // suppress MSVC warning, sigh + // store a plain dumb back-pointer because the parent + // LLEventDispatcher manages the lifespan of each DispatchEntry + // subclass instance -- not the other way around + LLEventDispatcher* mParent; std::string mDesc; - virtual void call(const std::string& desc, const LLSD& event) const = 0; - virtual LLSD addMetadata(LLSD) const = 0; + virtual LLSD call(const std::string& desc, const LLSD& event, + bool fromMap, const std::string& argskey) const = 0; + virtual LLSD getMetadata() const = 0; + + template <typename... ARGS> + [[noreturn]] void callFail(ARGS&&... args) const + { + mParent->callFail<LLEventDispatcher::DispatchError>(std::forward<ARGS>(args)...); + } }; - // Tried using boost::ptr_map<std::string, DispatchEntry>, but ptr_map<> - // wants its value type to be "clonable," even just to dereference an - // iterator. I don't want to clone entries -- if I have to copy an entry - // around, I want it to continue pointing to the same DispatchEntry - // subclass object. However, I definitely want DispatchMap to destroy - // DispatchEntry if no references are outstanding at the time an entry is - // removed. This looks like a job for boost::shared_ptr. - typedef std::map<std::string, 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 +503,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 +562,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> + [[noreturn]] void callFail(ARGS&&... args) const; + template <typename EXCEPTION, typename... ARGS> + [[noreturn]] static + void sCallFail(ARGS&&... args); + + // Manage transient state, e.g. which registered callable we're attempting + // to call, for error reporting + class SetState + { + public: + template <typename... ARGS> + SetState(const LLEventDispatcher* self, ARGS&&... args): + mSelf(self) + { + mSet = mSelf->setState(*this, stringize(std::forward<ARGS>(args)...)); + } + // RAII class: forbid both copy and move + SetState(const SetState&) = delete; + SetState(SetState&&) = delete; + SetState& operator=(const SetState&) = delete; + SetState& operator=(SetState&&) = delete; + virtual ~SetState() + { + // if we're the ones who succeeded in setting state, clear it + if (mSet) + { + mSelf->setState(*this, {}); + } + } + + private: + const LLEventDispatcher* mSelf; + bool mSet; + }; + +private: + std::string mDesc, mKey, mArgskey; DispatchMap mDispatch; + // transient state: must be fiber_specific since multiple threads and/or + // multiple fibers may be calling concurrently. Make it mutable so we can + // use SetState even within const methods. + mutable boost::fibers::fiber_specific_ptr<std::string> mState; + + std::string getState() const; + // setState() requires SetState& because only the SetState class should + // call it. Make it const so we can use SetState even within const methods. + bool setState(SetState&, const std::string& state) const; static NameDesc makeNameDesc(const DispatchMap::value_type& item) { return NameDesc(item.first, item.second->mDesc); } + class LLSDArgsMapper; struct LLSDDispatchEntry; struct ParamsDispatchEntry; struct ArrayParamsDispatchEntry; struct MapParamsDispatchEntry; - // Step 2 of parameter analysis. Instantiating invoker<some_function_type> - // implicitly sets its From and To parameters to the (compile time) begin - // and end iterators over that function's parameter types. - template< typename Function - , class From = typename boost::mpl::begin< boost::function_types::parameter_types<Function> >::type - , class To = typename boost::mpl::end< boost::function_types::parameter_types<Function> >::type - > - struct invoker; - - // deliver LLSD arguments one at a time - typedef boost::function<LLSD()> args_source; - // obtain args from an args_source to build param list and call target - // function - typedef boost::function<void(const args_source&)> invoker_function; + // call target function with args from LLSD array + typedef std::function<LLSD(const LLSD&)> invoker_function; template <typename Function> invoker_function make_invoker(Function f); @@ -387,101 +660,38 @@ 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 +700,45 @@ template <typename Function> LLEventDispatcher::invoker_function LLEventDispatcher::make_invoker(Function f) { - // Step 1 of parameter analysis, the top of the recursion. Passing a - // suitable f (see add()'s enable_if condition) to this method causes it - // to infer the function type; specifying that function type to invoker<> - // causes it to fill in the begin/end MPL iterators over the function's - // list of parameter types. - // While normally invoker::apply() could infer its template type from the - // boost::fusion::nil parameter value, here we must be explicit since - // we're boost::bind()ing it rather than calling it directly. - return boost::bind(&invoker<Function>::template apply<boost::fusion::nil>, - f, - _1, - boost::fusion::nil()); + // Return an invoker_function that accepts (const LLSD& args). + return [f](const LLSD& args) + { + // When called, call always_return<LLSD>, directing it to call + // f(expanded args). always_return<LLSD> guarantees we'll get an LLSD + // value back, even if it's undefined because 'f' doesn't return a + // type convertible to LLSD. + return LL::always_return<LLSD>( + [f, args] + () + { + return LL::apply(f, args); + }); + }; } template <typename Method, typename InstanceGetter> LLEventDispatcher::invoker_function LLEventDispatcher::make_invoker(Method f, const InstanceGetter& getter) { - // Use invoker::method_apply() to treat the instance (first) arg specially. - return boost::bind(&invoker<Method>::template method_apply<InstanceGetter>, - f, - _1, - getter); + return [f, getter](const LLSD& args) + { + // always_return<LLSD>() immediately calls the lambda we pass, and + // returns LLSD whether our passed lambda returns void or non-void. + return LL::always_return<LLSD>( + [f, getter, args] + () + { + // function_arity<member function> includes its implicit 'this' pointer + constexpr auto arity = LL::function_arity< + typename std::remove_reference<Method>::type>::value - 1; + + // Use bind_front() to bind the method to (a pointer to) the object + // returned by getter(). It's okay to capture and bind a pointer + // because this bind_front() object will last only as long as this + // lambda call. + return LL::apply_n<arity>(LL::bind_front(f, LL::get_ptr(getter())), args); + }); + }; } /***************************************************************************** @@ -521,21 +747,138 @@ LLEventDispatcher::make_invoker(Method f, const InstanceGetter& getter) /** * Bundle an LLEventPump and a listener with an LLEventDispatcher. A class * that contains (or derives from) LLDispatchListener need only specify the - * LLEventPump name and dispatch key, and add() its methods. Incoming events - * will automatically be dispatched. + * LLEventPump name and dispatch key, and add() its methods. Each incoming + * event ("request") will automatically be dispatched. + * + * If the request contains a "reply" key specifying the LLSD::String name of + * an LLEventPump to which to respond, LLDispatchListener will attempt to send + * a response to that LLEventPump. + * + * If some error occurs (e.g. nonexistent callable name, wrong params) and + * "reply" is present, LLDispatchListener will send a response map to the + * specified LLEventPump containing an "error" key whose value is the relevant + * error message. If "reply" is not present, the DispatchError exception will + * propagate. Since LLDispatchListener bundles an LLEventStream, which + * attempts the call immediately on receiving the post() call, there's a + * reasonable chance that the exception will highlight the post() call that + * triggered the error. + * + * If LLDispatchListener successfully calls the target callable, but no + * "reply" key is present, any value returned by that callable is discarded. + * If a "reply" key is present, but the target callable is void -- or it + * returns LLSD::isUndefined() -- no response is sent. If a void callable + * wants to send a response, it must do so explicitly. + * + * If the target callable returns a type convertible to LLSD (and, if it + * directly returns LLSD, the return value isDefined()), and if a "reply" key + * is present in the request, LLDispatchListener will post the returned value + * to the "reply" LLEventPump. If the returned value is an LLSD map, it will + * merge the echoed "reqid" key into the map and send that. Otherwise, it will + * send an LLSD map containing "reqid" and a "data" key whose value is the + * value returned by the target callable. + * + * (It is inadvisable for a target callable to return an LLSD map containing + * keys "data", "reqid" or "error", as that will confuse the invoker.) + * + * Normally the request will specify the value of the dispatch key as an + * LLSD::String naming the target callable. Alternatively, several such calls + * may be "batched" as described below. + * + * If the value of the dispatch key is itself an LLSD map (a "request map"), + * each map key must name a target callable, and the value of that key must + * contain the parameters to pass to that callable. If a "reply" key is + * present in the request, the response map will contain a key for each of the + * keys in the request map. The value of every such key is the value returned + * by the target callable. + * + * (Avoid naming any target callable in the LLDispatchListener "data", "reqid" + * or "error" to avoid confusion.) + * + * Since LLDispatchListener calls the target callables specified by a request + * map in arbitrary order, this form assumes that the batched operations are + * independent of each other. LLDispatchListener will attempt every call, even + * if some attempts produce errors. If any keys in the request map produce + * errors, LLDispatchListener builds a composite error message string + * collecting the relevant messages. The corresponding keys will be missing + * from the response map. As in the single-callable case, absent a "reply" key + * in the request, this error message will be thrown as a DispatchError. With + * a "reply" key, it will be returned as the value of the "error" key. This + * form can indicate partial success: some request keys might have + * return-value keys in the response, others might have message text in the + * "error" key. + * + * If a specific call sequence is required, the value of the dispatch key may + * instead be an LLSD array (a "request array"). Each entry in the request + * array ("request entry") names a target callable, to be called in + * array-index sequence. Arguments for that callable may be specified in + * either of two ways. + * + * The request entry may itself be a two-element array, whose [0] is an + * LLSD::String naming the target callable and whose [1] contains the + * arguments to pass to that callable. + * + * Alternatively, the request entry may be an LLSD::String naming the target + * callable, in which case the request must contain an arguments key (optional + * third constructor argument) whose value is an array matching the request + * array. The arguments for the request entry's target callable are found at + * the same index in the arguments key array. + * + * If a "reply" key is present in the request, the response map will contain a + * "data" key whose value is an array. Each entry in that response array will + * contain the result from the corresponding request entry. + * + * This form assumes that any of the batched operations might depend on the + * success of a previous operation in the same batch. The @emph first error + * encountered will terminate the sequence. The error message might either be + * thrown as DispatchError or, given a "reply" key, returned as the "error" + * key in the response map. This form can indicate partial success: the first + * few request entries might have return-value entries in the "data" response + * array, along with an "error" key whose value is the error message that + * stopped the sequence. */ -class LL_COMMON_API LLDispatchListener: public LLEventDispatcher +// Instead of containing an LLEventStream, LLDispatchListener derives from it. +// This allows an LLEventPumps::PumpFactory to return a pointer to an +// LLDispatchListener (subclass) instance, and still have ~LLEventPumps() +// properly clean it up. +class LL_COMMON_API LLDispatchListener: + public LLEventDispatcher, + public LLEventStream { public: - LLDispatchListener(const std::string& pumpname, const std::string& key); - - std::string getPumpName() const { return mPump.getName(); } + /// LLEventPump name, dispatch key [, arguments key (see LLEventDispatcher)] + template <typename... ARGS> + LLDispatchListener(const std::string& pumpname, const std::string& key, + ARGS&&... args); + virtual ~LLDispatchListener() {} private: - bool process(const LLSD& event); + bool process(const LLSD& event) const; + void call_one(const LLSD& name, const LLSD& event) const; + void call_map(const LLSD& reqmap, const LLSD& event) const; + void call_array(const LLSD& reqarray, const LLSD& event) const; + void reply(const LLSD& reply, const LLSD& request) const; - LLEventStream mPump; LLTempBoundListener mBoundListener; + static std::string mReplyKey; }; +template <typename... ARGS> +LLDispatchListener::LLDispatchListener(const std::string& pumpname, const std::string& key, + ARGS&&... args): + // pass through any additional arguments to LLEventDispatcher ctor + LLEventDispatcher(pumpname, key, std::forward<ARGS>(args)...), + // Do NOT tweak the passed pumpname. In practice, when someone + // instantiates a subclass of our LLEventAPI subclass, they intend to + // claim that LLEventPump name in the global LLEventPumps namespace. It + // would be mysterious and distressing if we allowed name tweaking, and + // someone else claimed pumpname first for a completely unrelated + // LLEventPump. Posted events would never reach our subclass listener + // because we would have silently changed its name; meanwhile listeners + // (if any) on that other LLEventPump would be confused by the events + // intended for our subclass. + LLEventStream(pumpname, false), + mBoundListener(listen("self", [this](const LLSD& event){ return process(event); })) +{ +} + #endif /* ! defined(LL_LLEVENTDISPATCHER_H) */ |