/indra/llcommon/lleventdispatcher.h
C++ Header | 541 lines | 258 code | 54 blank | 229 comment | 1 complexity | 672eb9173ffdbae8ca494c34c51241b7 MD5 | raw file
Possible License(s): LGPL-2.1
- /**
- * @file lleventdispatcher.h
- * @author Nat Goodspeed
- * @date 2009-06-18
- * @brief Central mechanism for dispatching events by string name. This is
- * useful when you have a single LLEventPump listener on which you can
- * request different operations, vs. instantiating a different
- * LLEventPump for each such operation.
- *
- * $LicenseInfo:firstyear=2009&license=viewerlgpl$
- * Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation;
- * version 2.1 of the License only.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * 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 <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;
- /**
- * Given an LLSD map, examine a string-valued key and call a corresponding
- * callable. This class is designed to be contained by an LLEventPump
- * listener class that will register some of its own methods, though any
- * callable can be used.
- */
- class LL_COMMON_API LLEventDispatcher
- {
- public:
- LLEventDispatcher(const std::string& desc, const std::string& key);
- 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;
- /**
- * 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,
- const std::string& desc,
- const Callable& callable,
- 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 (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,
- const std::string& desc,
- void (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 <class CLASS>
- void add(const std::string& name,
- const std::string& desc,
- void (CLASS::*method)(const LLSD&) const,
- const LLSD& required=LLSD())
- {
- addMethod<CLASS>(name, desc, method, 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);
- /// 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;
- /// 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;
- private:
- struct DispatchEntry
- {
- DispatchEntry(const std::string& desc);
- virtual ~DispatchEntry() {} // suppress MSVC warning, sigh
- std::string mDesc;
- virtual void call(const std::string& desc, const LLSD& event) const = 0;
- virtual LLSD addMetadata(LLSD) const = 0;
- };
- // 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,
- /// therefore our public interface doesn't expose DispatchMap iterators,
- /// or DispatchMap itself, or DispatchEntry. Instead we explicitly
- /// transform each DispatchMap item to NameDesc on dereferencing.
- typedef boost::transform_iterator<NameDesc(*)(const DispatchMap::value_type&), DispatchMap::const_iterator> const_iterator;
- const_iterator begin() const
- {
- return boost::make_transform_iterator(mDispatch.begin(), makeNameDesc);
- }
- const_iterator end() const
- {
- return boost::make_transform_iterator(mDispatch.end(), makeNameDesc);
- }
- //@}
- /// Get information about a specific Callable
- LLSD getMetadata(const std::string& name) const;
- /// Retrieve the LLSD key we use for one-arg <tt>operator()</tt> method
- std::string getDispatchKey() const { return mKey; }
- private:
- template <class CLASS, typename METHOD>
- void addMethod(const std::string& name, const std::string& desc,
- const METHOD& method, const LLSD& required)
- {
- CLASS* downcast = dynamic_cast<CLASS*>(this);
- if (! downcast)
- {
- addFail(name, typeid(CLASS).name());
- }
- else
- {
- add(name, desc, boost::bind(method, downcast, _1), required);
- }
- }
- void addFail(const std::string& name, const std::string& classname) const;
- std::string mDesc, mKey;
- DispatchMap mDispatch;
- static NameDesc makeNameDesc(const DispatchMap::value_type& item)
- {
- 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
- * LLEventPump name and dispatch key, and add() its methods. Incoming events
- * will automatically be dispatched.
- */
- class LL_COMMON_API LLDispatchListener: public LLEventDispatcher
- {
- public:
- LLDispatchListener(const std::string& pumpname, const std::string& key);
- std::string getPumpName() const { return mPump.getName(); }
- private:
- bool process(const LLSD& event);
- LLEventStream mPump;
- LLTempBoundListener mBoundListener;
- };
- #endif /* ! defined(LL_LLEVENTDISPATCHER_H) */