885 lines
36 KiB
C++
885 lines
36 KiB
C++
/**
|
|
* @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$
|
|
*/
|
|
|
|
#if ! defined(LL_LLEVENTDISPATCHER_H)
|
|
#define LL_LLEVENTDISPATCHER_H
|
|
|
|
#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/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;
|
|
|
|
/**
|
|
* 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:
|
|
/**
|
|
* 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
|
|
typedef std::function<LLSD(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())
|
|
{
|
|
addLLSD(name, desc, callable, required);
|
|
}
|
|
|
|
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,
|
|
CALLABLE&& callable,
|
|
const LLSD& required=LLSD())
|
|
{
|
|
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 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,
|
|
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);
|
|
}
|
|
|
|
// 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
|
|
//@{
|
|
|
|
/**
|
|
* Register a free function with arbitrary parameters. (This also works
|
|
* for static class methods.)
|
|
*
|
|
* When calling this name, pass an LLSD::Array. Each entry in turn will be
|
|
* converted to the corresponding parameter type using LLSDParam.
|
|
*/
|
|
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.
|
|
*
|
|
* 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.
|
|
*
|
|
* 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 = 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.)
|
|
*
|
|
* 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 = 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.
|
|
*
|
|
* 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.
|
|
*
|
|
* 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.
|
|
*
|
|
* 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 = 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);
|
|
|
|
/// 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>. 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 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
|
|
//@{
|
|
typedef std::pair<std::string, std::string> NameDesc;
|
|
|
|
private:
|
|
struct DispatchEntry
|
|
{
|
|
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 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)...);
|
|
}
|
|
};
|
|
typedef std::map<std::string, std::unique_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; }
|
|
/// 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:
|
|
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)
|
|
{
|
|
addFail(name, typeid(CLASS).name());
|
|
}
|
|
else
|
|
{
|
|
// 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; });
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
// 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);
|
|
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
|
|
*****************************************************************************/
|
|
template <typename Function>
|
|
void LLEventDispatcher::addV(const std::string& name, const std::string& desc, Function f)
|
|
{
|
|
// 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),
|
|
LL::function_arity<Function>::value);
|
|
}
|
|
|
|
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),
|
|
LL::function_arity<Method>::value - 1);
|
|
}
|
|
|
|
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>
|
|
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);
|
|
}
|
|
|
|
template <typename Function>
|
|
LLEventDispatcher::invoker_function
|
|
LLEventDispatcher::make_invoker(Function f)
|
|
{
|
|
// 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)
|
|
{
|
|
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);
|
|
});
|
|
};
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* 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. 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.
|
|
*/
|
|
// 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:
|
|
/// 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) 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;
|
|
|
|
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) */
|