diff options
author | Oz Linden <oz@lindenlab.com> | 2011-04-10 22:39:31 -0400 |
---|---|---|
committer | Oz Linden <oz@lindenlab.com> | 2011-04-10 22:39:31 -0400 |
commit | 60bceb759b592348b403689ffa8ed9c768126f8b (patch) | |
tree | 33b0ece80cc12f390683d89c30f2a706fa87bd50 /indra/llcommon/lleventdispatcher.h | |
parent | c9c192e2c476e73807d23cdda0814cb221a96b95 (diff) | |
parent | e2baffa940a5797f2055f13ceb9722de524b2ec3 (diff) |
merge changes for storm-954
Diffstat (limited to 'indra/llcommon/lleventdispatcher.h')
-rw-r--r-- | indra/llcommon/lleventdispatcher.h | 393 |
1 files changed, 362 insertions, 31 deletions
diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index dfffd59eb6..7acc61de4e 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -27,18 +27,56 @@ * * 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 int 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 int& nil(nil_); +#endif + #include <string> -#include <map> +#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/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 <typeinfo> #include "llevents.h" +#include "llsdutil.h" class LLSD; @@ -54,12 +92,18 @@ public: LLEventDispatcher(const std::string& desc, const std::string& key); virtual ~LLEventDispatcher(); - /// Accept any C++ callable, typically a boost::bind() expression + /// @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; /** - * Register a @a callable by @a name. The optional @a required parameter - * is used to validate the structure of each incoming event (see + * Register a @a callable by @a name. The passed @a callable accepts a + * single LLSD value and uses it in any way desired, e.g. extract + * parameters and call some other function. The optional @a required + * parameter is used to validate the structure of each incoming event (see * llsd_matches()). */ void add(const std::string& name, @@ -68,9 +112,23 @@ public: const LLSD& required=LLSD()); /** + * 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. + */ + void add(const std::string& name, + const std::string& desc, + void (*f)(const LLSD&), + const LLSD& required=LLSD()) + { + add(name, desc, Callable(f), required); + } + + /** * Special case: a subclass of this class can pass an unbound member - * function pointer without explicitly specifying the - * <tt>boost::bind()</tt> expression. + * function pointer (of an LLEventDispatcher subclass) without explicitly + * specifying the <tt>boost::bind()</tt> expression. The passed @a method + * accepts a single LLSD value, presumably containing other parameters. */ template <class CLASS> void add(const std::string& name, @@ -81,7 +139,8 @@ public: addMethod<CLASS>(name, desc, method, required); } - /// Overload for both const and non-const methods + /// Overload for both const and non-const methods. The passed @a method + /// accepts a single LLSD value, presumably containing other parameters. template <class CLASS> void add(const std::string& name, const std::string& desc, @@ -91,15 +150,106 @@ public: addMethod<CLASS>(name, desc, method, required); } - /// Convenience: for LLEventDispatcher, not every callable needs a - /// documentation string. - template <typename CALLABLE> - void add(const std::string& name, - CALLABLE callable, - const LLSD& required=LLSD()) - { - add(name, "", callable, required); - } + //@} + + /// @name Register functions with arbitrary param lists + //@{ + + /** + * 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); + + /** + * 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. + * + * 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); + + /** + * 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. + * + * When calling this name, pass an LLSD::Map. We will internally generate + * 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()); + + /** + * 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. + * + * Pass an LLSD::Array of parameter names, and optionally another + * LLSD::Array of default parameter values, a la LLSDArgsMapper. + * + * When calling this name, pass an LLSD::Map. We will internally generate + * 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()); + + //@} /// Unregister a callable bool remove(const std::string& name); @@ -109,12 +259,25 @@ public: /// the @a required prototype specified at add() time, die with LL_ERRS. void 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. + 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. + bool try_call(const LLSD& event) const; + /// @name Iterate over defined names //@{ typedef std::pair<std::string, std::string> NameDesc; @@ -122,16 +285,22 @@ public: private: struct DispatchEntry { - DispatchEntry(const Callable& func, const std::string& desc, const LLSD& required): - mFunc(func), - mDesc(desc), - mRequired(required) - {} - Callable mFunc; + DispatchEntry(const std::string& desc); + virtual ~DispatchEntry() {} // suppress MSVC warning, sigh + std::string mDesc; - LLSD mRequired; + + virtual void call(const std::string& desc, const LLSD& event) const = 0; + virtual LLSD addMetadata(LLSD) const = 0; }; - typedef std::map<std::string, DispatchEntry> DispatchMap; + // 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; public: /// We want the flexibility to redefine what data we store per name, @@ -149,10 +318,6 @@ public: } //@} - /// Fetch the Callable for the specified name. If no such name was - /// registered, return an empty() Callable. - Callable get(const std::string& name) const; - /// Get information about a specific Callable LLSD getMetadata(const std::string& name) const; @@ -175,18 +340,184 @@ private: } } void addFail(const std::string& name, const std::string& classname) const; - /// try to dispatch, return @c true if success - bool attemptCall(const std::string& name, const LLSD& event) const; std::string mDesc, mKey; DispatchMap mDispatch; static NameDesc makeNameDesc(const DispatchMap::value_type& item) { - return NameDesc(item.first, item.second.mDesc); + return NameDesc(item.first, item.second->mDesc); + } + + 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; + + template <typename Function> + invoker_function make_invoker(Function f); + template <typename Method, typename InstanceGetter> + invoker_function make_invoker(Method f, const InstanceGetter& getter); + void addArrayParamsDispatchEntry(const std::string& name, + const std::string& desc, + const invoker_function& invoker, + LLSD::Integer arity); + void addMapParamsDispatchEntry(const std::string& name, + const std::string& desc, + const invoker_function& invoker, + const LLSD& params, + const LLSD& defaults); +}; + +/***************************************************************************** +* 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) +{ + // Construct an invoker_function, a callable accepting const args_source&. + // 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); +} + +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) +{ + // 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); +} + +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) +{ + // 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) +{ + addMapParamsDispatchEntry(name, desc, make_invoker(f, getter), params, defaults); +} + +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()); +} + +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); +} + +/***************************************************************************** +* LLDispatchListener +*****************************************************************************/ /** * Bundle an LLEventPump and a listener with an LLEventDispatcher. A class * that contains (or derives from) LLDispatchListener need only specify the |