summaryrefslogtreecommitdiff
path: root/indra/llcommon/lleventdispatcher.h
diff options
context:
space:
mode:
authorNat Goodspeed <nat@lindenlab.com>2022-12-22 14:53:29 -0500
committerNat Goodspeed <nat@lindenlab.com>2023-07-13 12:47:45 -0400
commit07c5645f5f9130a7fc338df0bc2bb791d43bd702 (patch)
treeeb68ca7da17ab88e53c1f31be4126c3e47bebb6e /indra/llcommon/lleventdispatcher.h
parent45464ee2d2b83b750d45b860e6117a4b74242ead (diff)
DRTVWR-558: LLEventDispatcher uses LL::apply(), not boost::fusion.
While calling a C++ function with arguments taken from a runtime-variable data structure necessarily involves a bit of hocus-pocus, the best you can say for the boost::fusion based implementation is that it worked. Sadly, template recursion limited its applicability to a handful of function arguments. Now that we have LL::apply(), use that instead. This implementation is much more straightforward. In particular, the LLSDArgsSource class, whose job was to dole out elements of an LLSD array one at a time for the template recursion, goes away entirely. Make virtual LLEventDispatcher::DispatchEntry::call() return LLSD instead of void. All LLEventDispatcher target functions so far have been void; any function that wants to respond to its invoker must do so explicitly by calling sendReply() or constructing an LLEventAPI::Response instance. Supporting non- void functions permits LLEventDispatcher to respond implicitly with the returned value. Of course this requires a wrapper for void target functions that returns LLSD::isUndefined(). Break out LLEventDispatcher::reply() from callFail(), so we can reply with success as well as failure. Make LLEventDispatcher::try_call_log() prepend the actual leaf class name and description to any error returned by three-arg try_call(). That try_call() overload reported "LLEventDispatcher(desc): " for a couple specific errors, but no others. Hoist to try_call_log() to apply uniformly. Introduce new try_call_one() method to diagnose name-not-found errors and catch internal DispatchError and LL::apply_error exceptions. try_call_one() returns a std::pair, containing either an error message or an LLSD value. Make try_call_log() and three-arg try_call() accept LLSD 'name' instead of plain std::string, allowing for the possibility of an array or map. That lets us extend three-arg try_call() to break out new cases for the function selector LLSD: isUndefined(), isArray(), isMap() and (current case) scalar String. If try_call_one() reports an error, log it and try to send reply, as now. If it returns LLSD::isUndefined(), e.g. from a void target function wrapper, do nothing. But if it returns an LLSD map, try to send that back to the invoker. And if it returns an LLSD scalar or array, wrap it in a map with key "data" to respond to the invoker. Allowing a target function to return its result rather than explicitly sending it opens the possibility of batched requests (aggregate 'name') returning batched responses. Almost every place that constructs LLEventDispatcher's internal DispatchError exception called stringize() to format the what() string. Simplify calls by making DispatchError accept variadic arguments and forward to stringize(). Add LL::invoke() to apply.h. Like LL::apply(), this is a (limited) C++14 foreshadowing of std::invoke(), with preprocessor conditionals to switch to std::invoke() when that's available. Introduce LL::invoke() to handle a callable that's actually a pointer to method. Now our C++14 apply() implementation can accept pointer to method, using invoke() to generalize the actual function call. Also anticipate std::bind_front() with LL::bind_front(). For apply(func, std::array) and our extensions apply(func, std::vector) and apply(func, LLSD), we can't pass a pointer to method as the func unless the second argument happens to be an array or vector of pointers (or references) to instances of exactly the right class -- and of course LLSD can't store such at all. It's tempting to pass std::bind(std::mem_fn(ptr_to_method), instance), but that won't work: std::bind() requires a value or placeholder for each argument to pass to the bound function. The bind() expression above would only work for a nullary method. std::bind_front() would work, but that doesn't arrive until C++20. Again, once we get there we'll defer to the std:: implementation. Instead of the generic __cplusplus, check the appropriate feature-test macro for availability of each of std::invoke(), std::apply() and std::bind_front(). Change apply() error handling from assert() to new LL::apply_error exception. LLEventDispatcher must be able to intercept apply() errors. Move validation and synthesis of the relevant error message to new apply.cpp source file. Add to llptrto.h new LL::get_ref() and LL::get_ptr() template functions to unify the cases of a calling template accepting either a pointer or a reference. Wrapping the parameter in either get_ref() or get_ptr() allows dereferencing the parameter as desired. Move LL::apply(function, LLSD) argument validation/manipulation to a non- template function in llsdutil.cpp: no need to replicate that logic in the template for every CALLABLE specialization. The trouble with passing bind_front(std::mem_fn(ptr_to_method), instance) to apply() is that since bind_front() accepts and forwards variadic additional arguments, apply() can't infer the arity of the bound ptr_to_method. Address that by introducing apply_n<arity>(function, LLSD), permitting a caller to infer the arity of ptr_to_method and explicitly pass it to apply_n(). Polish up lleventdispatcher_test.cpp accordingly. Wrong LLSD type and wrong number of arguments now produce different (somewhat more informative) error messages. Moreover, passing too many entries in an LLSD array used to work: the extra arguments used to be ignored. Now we require that the size of the array match the arity of the target function. Change the too-many-arguments tests from success testing to error testing. Replace 'foreach' aka BOOST_FOREACH macro invocations with range 'for'. Replace STRINGIZE(item0 << item1 << ...) with stringize(item0, item1, ...). (cherry picked from commit 9c049563b5480bb7e8ed87d9313822595b479c3b)
Diffstat (limited to 'indra/llcommon/lleventdispatcher.h')
-rw-r--r--indra/llcommon/lleventdispatcher.h255
1 files changed, 87 insertions, 168 deletions
diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h
index 09b786b69e..cebce618df 100644
--- a/indra/llcommon/lleventdispatcher.h
+++ b/indra/llcommon/lleventdispatcher.h
@@ -27,54 +27,23 @@
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
- *
- * The invoker machinery that constructs a boost::fusion argument list for use
- * with boost::fusion::invoke() is derived from
- * http://www.boost.org/doc/libs/1_45_0/libs/function_types/example/interpreter.hpp
- * whose license information is copied below:
- *
- * "(C) Copyright Tobias Schwinger
- *
- * Use modification and distribution are subject to the boost Software License,
- * Version 1.0. (See http://www.boost.org/LICENSE_1_0.txt)."
*/
#if ! defined(LL_LLEVENTDISPATCHER_H)
#define LL_LLEVENTDISPATCHER_H
-// nil is too generic a term to be allowed to be a global macro. In
-// particular, boost::fusion defines a 'class nil' (properly encapsulated in a
-// namespace) that a global 'nil' macro breaks badly.
-#if defined(nil)
-// Capture the value of the macro 'nil', hoping int is an appropriate type.
-static const auto nil_(nil);
-// Now forget the macro.
-#undef nil
-// Finally, reintroduce 'nil' as a properly-scoped alias for the previously-
-// defined const 'nil_'. Make it static since otherwise it produces duplicate-
-// symbol link errors later.
-static const auto& nil(nil_);
-#endif
-
-#include <boost/bind.hpp>
#include <boost/iterator/transform_iterator.hpp>
+#include <boost/function_types/is_member_function_pointer.hpp>
#include <boost/function_types/is_nonmember_callable_builtin.hpp>
-#include <boost/function_types/parameter_types.hpp>
#include <boost/function_types/function_arity.hpp>
-#include <boost/type_traits/remove_cv.hpp>
-#include <boost/type_traits/remove_reference.hpp>
-#include <boost/fusion/include/push_back.hpp>
-#include <boost/fusion/include/cons.hpp>
-#include <boost/fusion/include/invoke.hpp>
-#include <boost/mpl/begin.hpp>
-#include <boost/mpl/end.hpp>
-#include <boost/mpl/next.hpp>
-#include <boost/mpl/deref.hpp>
+#include <boost/function_types/result_type.hpp>
#include <functional> // std::function
#include <memory> // std::unique_ptr
#include <string>
#include <typeinfo>
+#include <type_traits>
#include "llevents.h"
+#include "llptrto.h"
#include "llsdutil.h"
class LLSD;
@@ -94,8 +63,7 @@ public:
/// @name Register functions accepting(const LLSD&)
//@{
- /// Accept any C++ callable with the right signature, typically a
- /// boost::bind() expression
+ /// Accept any C++ callable with the right signature
typedef std::function<void(const LLSD&)> Callable;
/**
@@ -126,7 +94,7 @@ public:
/**
* Special case: a subclass of this class can pass an unbound member
* function pointer (of an LLEventDispatcher subclass) without explicitly
- * specifying the <tt>boost::bind()</tt> expression. The passed @a method
+ * specifying a <tt>std::bind()</tt> expression. The passed @a method
* accepts a single LLSD value, presumably containing other parameters.
*/
template <class CLASS>
@@ -158,10 +126,6 @@ public:
* Register a free function with arbitrary parameters. (This also works
* for static class methods.)
*
- * @note This supports functions with up to about 6 parameters -- after
- * that you start getting dismaying compile errors in which
- * boost::fusion::joint_view is mentioned a surprising number of times.
- *
* When calling this name, pass an LLSD::Array. Each entry in turn will be
* converted to the corresponding parameter type using LLSDParam.
*/
@@ -175,10 +139,6 @@ public:
/**
* 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
@@ -201,10 +161,6 @@ public:
* Register a free function with arbitrary parameters. (This also works
* for static class methods.)
*
- * @note This supports functions with up to about 6 parameters -- after
- * that you start getting dismaying compile errors in which
- * boost::fusion::joint_view is mentioned a surprising number of times.
- *
* Pass an LLSD::Array of parameter names, and optionally another
* LLSD::Array of default parameter values, a la LLSDArgsMapper.
*
@@ -222,10 +178,6 @@ public:
/**
* 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
@@ -290,7 +242,7 @@ private:
std::string mDesc;
- virtual void call(const std::string& desc, const LLSD& event) const = 0;
+ virtual LLSD call(const std::string& desc, const LLSD& event) const = 0;
virtual LLSD addMetadata(LLSD) const = 0;
};
typedef std::map<std::string, std::unique_ptr<DispatchEntry> > DispatchMap;
@@ -322,17 +274,28 @@ private:
void addMethod(const std::string& name, const std::string& desc,
const METHOD& method, const LLSD& required)
{
- CLASS* downcast = static_cast<CLASS*>(this);
- add(name, desc, boost::bind(method, downcast, _1), required);
+ CLASS* downcast = dynamic_cast<CLASS*>(this);
+ if (! downcast)
+ {
+ addFail(name, typeid(CLASS).name());
+ }
+ else
+ {
+ add(name, desc, std::bind(method, downcast, std::placeholders::_1), required);
+ }
}
void addFail(const std::string& name, const std::string& classname) const;
- std::string try_call_log(const std::string& key, const std::string& name,
+ std::string try_call_log(const std::string& key, const LLSD& name,
const LLSD& event) const;
- std::string try_call(const std::string& key, const std::string& name,
+ std::string try_call(const std::string& key, const LLSD& name,
const LLSD& event) const;
+ // returns either (empty string, LLSD) or (error message, isUndefined)
+ std::pair<std::string, LLSD>
+ try_call_one(const std::string& key, const std::string& name, const LLSD& event) const;
// Implement "it is an error" semantics for attempted call operations: if
// the incoming event includes a "reply" key, log and send an error reply.
void callFail(const LLSD& event, const std::string& msg) const;
+ void reply(const LLSD& response, const LLSD& event) const;
std::string mDesc, mKey;
DispatchMap mDispatch;
@@ -347,20 +310,8 @@ private:
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 std::function<LLSD()> args_source;
- // obtain args from an args_source to build param list and call target
- // function
- typedef std::function<void(const args_source&)> invoker_function;
+ // call target function with args from LLSD array
+ typedef std::function<LLSD(const LLSD&)> invoker_function;
template <typename Function>
invoker_function make_invoker(Function f);
@@ -375,92 +326,17 @@ private:
const invoker_function& invoker,
const LLSD& params,
const LLSD& defaults);
+ template <typename RETURNTYPE>
+ struct ReturnLLSD;
};
/*****************************************************************************
* 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(), bindable(getter())));
- }
-
- template <typename T>
- static inline
- auto bindable(T&& value,
- typename std::enable_if<std::is_pointer<T>::value, bool>::type=true)
- {
- // if passed a pointer, just return that pointer
- return std::forward<T>(value);
- }
-
- template <typename T>
- static inline
- auto bindable(T&& value,
- typename std::enable_if<! std::is_pointer<T>::value, bool>::type=true)
- {
- // if passed a reference, wrap it for binding
- return std::ref(std::forward<T>(value));
- }
-};
-
-// 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>
void LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f)
{
- // Construct an invoker_function, a callable accepting const args_source&.
+ // Construct an invoker_function, a callable accepting const LLSD&.
// Add to DispatchMap an ArrayParamsDispatchEntry that will handle the
// caller's LLSD::Array.
addArrayParamsDispatchEntry(name, desc, make_invoker(f),
@@ -493,33 +369,76 @@ void LLEventDispatcher::add(const std::string& name, const std::string& desc, Me
addMapParamsDispatchEntry(name, desc, make_invoker(f, getter), params, defaults);
}
+// general case, when f() has a non-void return type
+template <typename RETURNTYPE>
+struct LLEventDispatcher::ReturnLLSD
+{
+ template <typename Function>
+ LLSD operator()(Function f, const LLSD& args)
+ {
+ return { LL::apply(f, args) };
+ }
+
+ template <typename Method, typename InstanceGetter>
+ LLSD operator()(Method f, const InstanceGetter& getter, const LLSD& args)
+ {
+ constexpr auto arity = boost::function_types::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
+ // operator() call.
+ return { LL::apply_n<arity>(LL::bind_front(f, LL::get_ptr(getter())), args) };
+ }
+};
+
+// specialize for void return type
+template <>
+struct LLEventDispatcher::ReturnLLSD<void>
+{
+ template <typename Function>
+ LLSD operator()(Function f, const LLSD& args)
+ {
+ LL::apply(f, args);
+ return {};
+ }
+
+ template <typename Method, typename InstanceGetter>
+ LLSD operator()(Method f, const InstanceGetter& getter, const LLSD& args)
+ {
+ constexpr auto arity = boost::function_types::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
+ // operator() call.
+ LL::apply_n<arity>(LL::bind_front(f, LL::get_ptr(getter())), args);
+ return {};
+ }
+};
+
template <typename Function>
LLEventDispatcher::invoker_function
LLEventDispatcher::make_invoker(Function f)
{
- // Step 1 of parameter analysis, the top of the recursion. Passing a
- // suitable f (see add()'s enable_if condition) to this method causes it
- // to infer the function type; specifying that function type to invoker<>
- // causes it to fill in the begin/end MPL iterators over the function's
- // list of parameter types.
- // While normally invoker::apply() could infer its template type from the
- // boost::fusion::nil parameter value, here we must be explicit since
- // we're boost::bind()ing it rather than calling it directly.
- return boost::bind(&invoker<Function>::template apply<boost::fusion::nil>,
- f,
- _1,
- boost::fusion::nil());
+ return [f](const LLSD& args)
+ {
+ return ReturnLLSD<typename boost::function_types::result_type<Function>::type>()
+ (f, args);
+ };
}
template <typename Method, typename InstanceGetter>
LLEventDispatcher::invoker_function
LLEventDispatcher::make_invoker(Method f, const InstanceGetter& getter)
{
- // Use invoker::method_apply() to treat the instance (first) arg specially.
- return boost::bind(&invoker<Method>::template method_apply<InstanceGetter>,
- f,
- _1,
- getter);
+ return [f, getter](const LLSD& args)
+ {
+ return ReturnLLSD<typename boost::function_types::result_type<Method>::type>()
+ (f, getter, args);
+ };
}
/*****************************************************************************