phoenix-firestorm/indra/llcommon/lleventdispatcher.h

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) */