summaryrefslogtreecommitdiff
path: root/indra/llcommon
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
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')
-rw-r--r--indra/llcommon/CMakeLists.txt1
-rw-r--r--indra/llcommon/apply.cpp29
-rw-r--r--indra/llcommon/apply.h100
-rw-r--r--indra/llcommon/lleventdispatcher.cpp224
-rw-r--r--indra/llcommon/lleventdispatcher.h255
-rw-r--r--indra/llcommon/llptrto.h88
-rw-r--r--indra/llcommon/llsdutil.cpp35
-rw-r--r--indra/llcommon/llsdutil.h49
-rw-r--r--indra/llcommon/tests/lleventdispatcher_test.cpp141
9 files changed, 543 insertions, 379 deletions
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index 941d2d7baf..33e8301e12 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -16,6 +16,7 @@ include(Tracy)
set(llcommon_SOURCE_FILES
+ apply.cpp
indra_constants.cpp
lazyeventapi.cpp
llallocator.cpp
diff --git a/indra/llcommon/apply.cpp b/indra/llcommon/apply.cpp
new file mode 100644
index 0000000000..417e23d3b4
--- /dev/null
+++ b/indra/llcommon/apply.cpp
@@ -0,0 +1,29 @@
+/**
+ * @file apply.cpp
+ * @author Nat Goodspeed
+ * @date 2022-12-21
+ * @brief Implementation for apply.
+ *
+ * $LicenseInfo:firstyear=2022&license=viewerlgpl$
+ * Copyright (c) 2022, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "apply.h"
+// STL headers
+// std headers
+// external library headers
+// other Linden headers
+#include "stringize.h"
+
+void LL::apply_validate_size(size_t size, size_t arity)
+{
+ if (size != arity)
+ {
+ LLTHROW(apply_error(stringize("LL::apply(func(", arity, " args), "
+ "std::vector(", size, " elements))")));
+ }
+}
diff --git a/indra/llcommon/apply.h b/indra/llcommon/apply.h
index 9f4c268895..357887dfd7 100644
--- a/indra/llcommon/apply.h
+++ b/indra/llcommon/apply.h
@@ -12,9 +12,11 @@
#if ! defined(LL_APPLY_H)
#define LL_APPLY_H
+#include "llexception.h"
#include <boost/type_traits/function_traits.hpp>
-#include <cassert>
+#include <functional> // std::mem_fn()
#include <tuple>
+#include <type_traits> // std::is_member_pointer
namespace LL
{
@@ -56,9 +58,39 @@ namespace LL
(ARGS))
/*****************************************************************************
-* apply(function, tuple)
+* invoke()
*****************************************************************************/
-#if __cplusplus >= 201703L
+#if __cpp_lib_invoke >= 201411L
+
+// C++17 implementation
+using std::invoke;
+
+#else // no std::invoke
+
+// Use invoke() to handle pointer-to-method:
+// derived from https://stackoverflow.com/a/38288251
+template<typename Fn, typename... Args,
+ typename std::enable_if<std::is_member_pointer<typename std::decay<Fn>::type>::value,
+ int>::type = 0 >
+auto invoke(Fn&& f, Args&&... args)
+{
+ return std::mem_fn(f)(std::forward<Args>(args)...);
+}
+
+template<typename Fn, typename... Args,
+ typename std::enable_if<!std::is_member_pointer<typename std::decay<Fn>::type>::value,
+ int>::type = 0 >
+auto invoke(Fn&& f, Args&&... args)
+{
+ return std::forward<Fn>(f)(std::forward<Args>(args)...);
+}
+
+#endif // no std::invoke
+
+/*****************************************************************************
+* apply(function, tuple); apply(function, array)
+*****************************************************************************/
+#if __cpp_lib_apply >= 201603L
// C++17 implementation
using std::apply;
@@ -71,7 +103,7 @@ template <typename CALLABLE, typename... ARGS, std::size_t... I>
auto apply_impl(CALLABLE&& func, const std::tuple<ARGS...>& args, std::index_sequence<I...>)
{
// call func(unpacked args)
- return std::forward<CALLABLE>(func)(std::move(std::get<I>(args))...);
+ return invoke(std::forward<CALLABLE>(func), std::get<I>(args)...);
}
template <typename CALLABLE, typename... ARGS>
@@ -85,11 +117,6 @@ auto apply(CALLABLE&& func, const std::tuple<ARGS...>& args)
std::index_sequence_for<ARGS...>{});
}
-#endif // C++14
-
-/*****************************************************************************
-* apply(function, std::array)
-*****************************************************************************/
// per https://stackoverflow.com/a/57510428/5533635
template <typename CALLABLE, typename T, size_t SIZE>
auto apply(CALLABLE&& func, const std::array<T, SIZE>& args)
@@ -97,6 +124,50 @@ auto apply(CALLABLE&& func, const std::array<T, SIZE>& args)
return apply(std::forward<CALLABLE>(func), std::tuple_cat(args));
}
+#endif // C++14
+
+/*****************************************************************************
+* bind_front()
+*****************************************************************************/
+// To invoke a non-static member function with a tuple, you need a callable
+// that binds your member function with an instance pointer or reference.
+// std::bind_front() is perfect: std::bind_front(&cls::method, instance).
+// Unfortunately bind_front() only enters the standard library in C++20.
+#if __cpp_lib_bind_front >= 201907L
+
+// C++20 implementation
+using std::bind_front;
+
+#else // no std::bind_front()
+
+template<typename Fn, typename... Args,
+ typename std::enable_if<!std::is_member_pointer<typename std::decay<Fn>::type>::value,
+ int>::type = 0 >
+auto bind_front(Fn&& f, Args&&... args)
+{
+ // Don't use perfect forwarding for f or args: we must bind them for later.
+ return [f, pfx_args=std::make_tuple(args...)]
+ (auto&&... sfx_args)
+ {
+ // Use perfect forwarding for sfx_args because we use them as soon as
+ // we receive them.
+ return apply(
+ f,
+ std::tuple_cat(pfx_args,
+ std::make_tuple(std::forward<decltype(sfx_args)>(sfx_args)...)));
+ };
+}
+
+template<typename Fn, typename... Args,
+ typename std::enable_if<std::is_member_pointer<typename std::decay<Fn>::type>::value,
+ int>::type = 0 >
+auto bind_front(Fn&& f, Args&&... args)
+{
+ return bind_front(std::mem_fn(std::forward<Fn>(f)), std::forward<Args>(args)...);
+}
+
+#endif // C++20 with std::bind_front()
+
/*****************************************************************************
* apply(function, std::vector)
*****************************************************************************/
@@ -108,6 +179,15 @@ auto apply_impl(CALLABLE&& func, const std::vector<T>& args, std::index_sequence
std::make_tuple(args[I]...));
}
+// produce suitable error if apply(func, vector) is the wrong size for func()
+void apply_validate_size(size_t size, size_t arity);
+
+/// possible exception from apply() validation
+struct apply_error: public LLException
+{
+ apply_error(const std::string& what): LLException(what) {}
+};
+
/**
* apply(function, std::vector) goes beyond C++17 std::apply(). For this case
* @a function @emph cannot be variadic: the compiler must know at compile
@@ -117,7 +197,7 @@ template <typename CALLABLE, typename T>
auto apply(CALLABLE&& func, const std::vector<T>& args)
{
constexpr auto arity = boost::function_traits<CALLABLE>::arity;
- assert(args.size() == arity);
+ apply_validate_size(args.size(), arity);
return apply_impl(std::forward<CALLABLE>(func),
args,
std::make_index_sequence<arity>());
diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp
index 3e45601429..e7e73125a7 100644
--- a/indra/llcommon/lleventdispatcher.cpp
+++ b/indra/llcommon/lleventdispatcher.cpp
@@ -50,69 +50,14 @@
*****************************************************************************/
struct DispatchError: public LLException
{
- DispatchError(const std::string& what): LLException(what) {}
+ // template constructor involving strings passes all arguments to
+ // stringize() to construct LLException's what() string
+ template <typename... ARGS>
+ DispatchError(const std::string& arg0, ARGS&&... args):
+ LLException(stringize(arg0, std::forward<ARGS>(args)...)) {}
};
/*****************************************************************************
-* LLSDArgsSource
-*****************************************************************************/
-/**
- * Store an LLSD array, producing its elements one at a time. It is an error
- * if the consumer requests more elements than the array contains.
- */
-class LL_COMMON_API LLSDArgsSource
-{
-public:
- LLSDArgsSource(const std::string function, const LLSD& args);
- ~LLSDArgsSource();
-
- LLSD next();
-
- void done() const;
-
-private:
- std::string _function;
- LLSD _args;
- LLSD::Integer _index;
-};
-
-LLSDArgsSource::LLSDArgsSource(const std::string function, const LLSD& args):
- _function(function),
- _args(args),
- _index(0)
-{
- if (! (_args.isUndefined() || _args.isArray()))
- {
- LLTHROW(DispatchError(stringize(_function, " needs an args array instead of ", _args)));
- }
-}
-
-LLSDArgsSource::~LLSDArgsSource()
-{
- done();
-}
-
-LLSD LLSDArgsSource::next()
-{
- if (_index >= _args.size())
- {
- LLTHROW(DispatchError(stringize(_function, " requires more arguments than the ",
- _args.size(), " provided: ", _args)));
- }
- return _args[_index++];
-}
-
-void LLSDArgsSource::done() const
-{
- if (_index < _args.size())
- {
- LL_WARNS("LLSDArgsSource") << _function << " only consumed " << _index
- << " of the " << _args.size() << " arguments provided: "
- << _args << LL_ENDL;
- }
-}
-
-/*****************************************************************************
* LLSDArgsMapper
*****************************************************************************/
/**
@@ -204,7 +149,7 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function,
{
if (! (_names.isUndefined() || _names.isArray()))
{
- LLTHROW(DispatchError(stringize(function, " names must be an array, not ", names)));
+ LLTHROW(DispatchError(function, " names must be an array, not ", names));
}
auto nparams(_names.size());
// From _names generate _indexes.
@@ -227,8 +172,8 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function,
// defaults is a (possibly empty) array. Right-align it with names.
if (ndefaults > nparams)
{
- LLTHROW(DispatchError(stringize(function, " names array ", names,
- " shorter than defaults array ", defaults)));
+ LLTHROW(DispatchError(function, " names array ", names,
+ " shorter than defaults array ", defaults));
}
// Offset by which we slide defaults array right to right-align with
@@ -265,14 +210,14 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function,
}
if (bogus.size())
{
- LLTHROW(DispatchError(stringize(function, " defaults specified for nonexistent params ",
- formatlist(bogus))));
+ LLTHROW(DispatchError(function, " defaults specified for nonexistent params ",
+ formatlist(bogus)));
}
}
else
{
- LLTHROW(DispatchError(stringize(function, " defaults must be a map or an array, not ",
- defaults)));
+ LLTHROW(DispatchError(function, " defaults must be a map or an array, not ",
+ defaults));
}
}
@@ -280,8 +225,8 @@ LLSD LLSDArgsMapper::map(const LLSD& argsmap) const
{
if (! (argsmap.isUndefined() || argsmap.isMap() || argsmap.isArray()))
{
- LLTHROW(DispatchError(stringize(_function, " map() needs a map or array, not ",
- argsmap)));
+ LLTHROW(DispatchError(_function, " map() needs a map or array, not ",
+ argsmap));
}
// Initialize the args array. Indexing a non-const LLSD array grows it
// to appropriate size, but we don't want to resize this one on each
@@ -378,8 +323,8 @@ LLSD LLSDArgsMapper::map(const LLSD& argsmap) const
// by argsmap, that's a problem.
if (unfilled.size())
{
- LLTHROW(DispatchError(stringize(_function, " missing required arguments ",
- formatlist(unfilled), " from ", argsmap)));
+ LLTHROW(DispatchError(_function, " missing required arguments ",
+ formatlist(unfilled), " from ", argsmap));
}
// done
@@ -399,6 +344,9 @@ std::string LLSDArgsMapper::formatlist(const LLSD& list)
return out.str();
}
+/*****************************************************************************
+* LLEventDispatcher
+*****************************************************************************/
LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key):
mDesc(desc),
mKey(key)
@@ -409,6 +357,10 @@ LLEventDispatcher::~LLEventDispatcher()
{
}
+LLEventDispatcher::DispatchEntry::DispatchEntry(const std::string& desc):
+ mDesc(desc)
+{}
+
/**
* DispatchEntry subclass used for callables accepting(const LLSD&)
*/
@@ -423,16 +375,17 @@ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchE
Callable mFunc;
LLSD mRequired;
- virtual void call(const std::string& desc, const LLSD& event) const
+ virtual LLSD call(const std::string& desc, const LLSD& event) const
{
// Validate the syntax of the event itself.
std::string mismatch(llsd_matches(mRequired, event));
if (! mismatch.empty())
{
- LLTHROW(DispatchError(stringize(desc, ": bad request: ", mismatch)));
+ LLTHROW(DispatchError(desc, ": bad request: ", mismatch));
}
// Event syntax looks good, go for it!
mFunc(event);
+ return {};
}
virtual LLSD addMetadata(LLSD meta) const
@@ -455,10 +408,9 @@ struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::Dispatc
invoker_function mInvoker;
- virtual void call(const std::string& desc, const LLSD& event) const
+ virtual LLSD call(const std::string&, const LLSD& event) const
{
- LLSDArgsSource src(desc, event);
- mInvoker(boost::bind(&LLSDArgsSource::next, boost::ref(src)));
+ return mInvoker(event);
}
};
@@ -541,11 +493,11 @@ struct LLEventDispatcher::MapParamsDispatchEntry: public LLEventDispatcher::Para
LLSD mRequired;
LLSD mOptional;
- virtual void call(const std::string& desc, const LLSD& event) const
+ virtual LLSD call(const std::string& desc, const LLSD& event) const
{
// Just convert from LLSD::Map to LLSD::Array using mMapper, then pass
// to base-class call() method.
- ParamsDispatchEntry::call(desc, mMapper.map(event));
+ return ParamsDispatchEntry::call(desc, mMapper.map(event));
}
virtual LLSD addMetadata(LLSD meta) const
@@ -617,12 +569,18 @@ void LLEventDispatcher::operator()(const LLSD& event) const
void LLEventDispatcher::callFail(const LLSD& event, const std::string& msg) const
{
+ // pass back a response that includes an "error" key with the message.
+ reply(llsd::map("error", msg), event);
+}
+
+void LLEventDispatcher::reply(const LLSD& response, const LLSD& event) const
+{
static LLSD::String key{ "reply" };
if (event.has(key))
{
- // Oh good, the incoming event specifies a reply pump -- pass back a
- // response that includes an "error" key with the message.
- sendReply(llsd::map("error", msg), event, key);
+ // Oh good, the incoming event specifies a reply pump -- pass back
+ // our response.
+ sendReply(response, event, key);
}
}
@@ -631,17 +589,30 @@ bool LLEventDispatcher::try_call(const LLSD& event) const
return try_call_log(mKey, event[mKey], event).empty();
}
+/*==========================================================================*|
+ TODO:
+
+* When mInvoker returns result.isDefined(), sendReply(llsd::map("data", result))
+* When try_call finds name.isArray(), construct response array from
+ dispatching each call, sendReply() as above
+* When try_call finds name.isMap(), construct response map from dispatching
+ each call, sendReply() as above -- note, caller can't care about order
+* Possible future transactional behavior: look up all names before calling any
+
+|*==========================================================================*/
bool LLEventDispatcher::try_call(const std::string& name, const LLSD& event) const
{
return try_call_log(std::string(), name, event).empty();
}
-std::string LLEventDispatcher::try_call_log(const std::string& key, const std::string& name,
+std::string LLEventDispatcher::try_call_log(const std::string& key, const LLSD& name,
const LLSD& event) const
{
std::string error{ try_call(key, name, event) };
if (! error.empty())
{
+ // If we're a subclass of LLEventDispatcher, e.g. LLEventAPI, report that.
+ error = stringize(LLError::Log::classname(this), "(", mDesc, "): ", error);
LL_WARNS("LLEventDispatcher") << error << LL_ENDL;
}
return error;
@@ -649,33 +620,100 @@ std::string LLEventDispatcher::try_call_log(const std::string& key, const std::s
// This internal method returns empty string if the call succeeded, else
// non-empty error message.
-std::string LLEventDispatcher::try_call(const std::string& key, const std::string& name,
+std::string LLEventDispatcher::try_call(const std::string& key, const LLSD& name,
const LLSD& event) const
{
+ if (name.isUndefined())
+ {
+ if (key.empty())
+ {
+ return "attempting to call with no name";
+ }
+ else
+ {
+ return stringize("no ", key);
+ }
+ }
+ else if (name.isArray())
+ {
+ return stringize(key, " array dispatch ", name, " not yet implemented");
+ }
+ else if (name.isMap())
+ {
+ return stringize(key, " map dispatch ", name, " not yet implemented");
+ }
+ else if (! name.isString())
+ {
+ return stringize(key, " bad type ", LLSD::typeString(name.type()), ' ', name,
+ " -- function names are String");
+ }
+ else // name is an LLSD::String
+ {
+ auto success{ try_call_one(key, name, event) };
+ // pretend to unpack
+ std::string& error{ success.first };
+ LLSD& result{ success.second };
+ // did try_call_one() report an error?
+ if (! error.empty())
+ {
+ // if we have a reply key, respond to invoker
+ reply(llsd::map("error", error), event);
+ // now tell caller
+ return error;
+ }
+ // try_call_one() succeeded in calling the target function --
+ // should we reply to invoker?
+ if (result.isUndefined())
+ {
+ // We would get result.isUndefined() if the target function has
+ // void return. In any case, even if the target function returns
+ // LLSD, isUndefined() means "don't bother sending response."
+ return {};
+ }
+ // result.isDefined(): the target function returned something.
+ // Respond to invoker if we have a "reply" key.
+ if (! result.isMap())
+ {
+ // wrap result in a map to play well with sendReply()
+ result = llsd::map("data", result);
+ }
+ reply(result, event);
+ return {};
+ }
+}
+
+std::pair<std::string, LLSD>
+LLEventDispatcher::try_call_one(const std::string& key, const std::string& name,
+ const LLSD& event) const
+{
DispatchMap::const_iterator found = mDispatch.find(name);
if (found == mDispatch.end())
{
if (key.empty())
{
- return stringize("LLEventDispatcher(", mDesc, "): '", name, "' not found");
+ return { stringize("'", name, "' not found"), {} };
}
else
{
- return stringize("LLEventDispatcher(", mDesc, "): bad ", key, " value '", name, "'");
+ return { stringize("bad ", key, " value '", name, "'"), {} };
}
}
try
{
// Found the name, so it's plausible to even attempt the call.
- found->second->call(stringize("LLEventDispatcher(", mDesc, ") calling '", name, "'"),
- event);
+ return { {}, found->second->call(stringize("calling '", name, "'"), event) };
}
catch (const DispatchError& err)
{
- return err.what();
+ // trouble preparing arguments
+ return { err.what(), {} };
+ }
+ catch (const LL::apply_error& err)
+ {
+ // could also hit runtime errors with LL::apply()
+ return { err.what(), {} };
}
- return {}; // tell caller we were able to call
}
LLSD LLEventDispatcher::getMetadata(const std::string& name) const
@@ -691,6 +729,9 @@ LLSD LLEventDispatcher::getMetadata(const std::string& name) const
return found->second->addMetadata(meta);
}
+/*****************************************************************************
+* LLDispatchListener
+*****************************************************************************/
LLDispatchListener::LLDispatchListener(const std::string& pumpname, const std::string& key):
LLEventDispatcher(pumpname, key),
// Do NOT tweak the passed pumpname. In practice, when someone
@@ -712,8 +753,3 @@ bool LLDispatchListener::process(const LLSD& event)
(*this)(event);
return false;
}
-
-LLEventDispatcher::DispatchEntry::DispatchEntry(const std::string& desc):
- mDesc(desc)
-{}
-
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);
+ };
}
/*****************************************************************************
diff --git a/indra/llcommon/llptrto.h b/indra/llcommon/llptrto.h
index 4082e30de6..9ef279fdbf 100644
--- a/indra/llcommon/llptrto.h
+++ b/indra/llcommon/llptrto.h
@@ -33,9 +33,12 @@
#include "llpointer.h"
#include "llrefcount.h" // LLRefCount
+#include <boost/intrusive_ptr.hpp>
+#include <boost/shared_ptr.hpp>
#include <boost/type_traits/is_base_of.hpp>
#include <boost/type_traits/remove_pointer.hpp>
-#include <boost/utility/enable_if.hpp>
+#include <memory> // std::shared_ptr, std::unique_ptr
+#include <type_traits>
/**
* LLPtrTo<TARGET>::type is either of two things:
@@ -55,14 +58,14 @@ struct LLPtrTo
/// specialize for subclasses of LLRefCount
template <class T>
-struct LLPtrTo<T, typename boost::enable_if< boost::is_base_of<LLRefCount, T> >::type>
+struct LLPtrTo<T, typename std::enable_if< boost::is_base_of<LLRefCount, T>::value >::type>
{
typedef LLPointer<T> type;
};
/// specialize for subclasses of LLThreadSafeRefCount
template <class T>
-struct LLPtrTo<T, typename boost::enable_if< boost::is_base_of<LLThreadSafeRefCount, T> >::type>
+struct LLPtrTo<T, typename std::enable_if< boost::is_base_of<LLThreadSafeRefCount, T>::value >::type>
{
typedef LLPointer<T> type;
};
@@ -83,4 +86,83 @@ struct LLRemovePointer< LLPointer<SOMECLASS> >
typedef SOMECLASS type;
};
+namespace LL
+{
+
+/*****************************************************************************
+* get_ref()
+*****************************************************************************/
+ template <typename T>
+ struct GetRef
+ {
+ // return const ref or non-const ref, depending on whether we can bind
+ // a non-const lvalue ref to the argument
+ const auto& operator()(const T& obj) const { return obj; }
+ auto& operator()(T& obj) const { return obj; }
+ };
+
+ template <typename T>
+ struct GetRef<const T*>
+ {
+ const auto& operator()(const T* ptr) const { return *ptr; }
+ };
+
+ template <typename T>
+ struct GetRef<T*>
+ {
+ auto& operator()(T* ptr) const { return *ptr; }
+ };
+
+ template <typename T>
+ struct GetRef< LLPointer<T> >
+ {
+ auto& operator()(LLPointer<T> ptr) const { return *ptr; }
+ };
+
+ /// whether we're passed a pointer or a reference, return a reference
+ template <typename T>
+ auto& get_ref(T& ptr_or_ref)
+ {
+ return GetRef<typename std::decay<T>::type>()(ptr_or_ref);
+ }
+
+ template <typename T>
+ const auto& get_ref(const T& ptr_or_ref)
+ {
+ return GetRef<typename std::decay<T>::type>()(ptr_or_ref);
+ }
+
+/*****************************************************************************
+* get_ptr()
+*****************************************************************************/
+ // if T is any pointer type we recognize, return it unchanged
+ template <typename T>
+ const T* get_ptr(const T* ptr) { return ptr; }
+
+ template <typename T>
+ T* get_ptr(T* ptr) { return ptr; }
+
+ template <typename T>
+ const std::shared_ptr<T>& get_ptr(const std::shared_ptr<T>& ptr) { return ptr; }
+
+ template <typename T>
+ const std::unique_ptr<T>& get_ptr(const std::unique_ptr<T>& ptr) { return ptr; }
+
+ template <typename T>
+ const boost::shared_ptr<T>& get_ptr(const boost::shared_ptr<T>& ptr) { return ptr; }
+
+ template <typename T>
+ const boost::intrusive_ptr<T>& get_ptr(const boost::intrusive_ptr<T>& ptr) { return ptr; }
+
+ template <typename T>
+ const LLPointer<T>& get_ptr(const LLPointer<T>& ptr) { return ptr; }
+
+ // T is not any pointer type we recognize, take a pointer to the parameter
+ template <typename T>
+ const T* get_ptr(const T& obj) { return &obj; }
+
+ template <typename T>
+ T* get_ptr(T& obj) { return &obj; }
+} // namespace LL
+
#endif /* ! defined(LL_LLPTRTO_H) */
diff --git a/indra/llcommon/llsdutil.cpp b/indra/llcommon/llsdutil.cpp
index f70bee9903..e98fc0285a 100644
--- a/indra/llcommon/llsdutil.cpp
+++ b/indra/llcommon/llsdutil.cpp
@@ -1046,3 +1046,38 @@ LLSD llsd_shallow(LLSD value, LLSD filter)
return shallow;
}
+
+LLSD LL::apply_llsd_fix(size_t arity, const LLSD& args)
+{
+ // LLSD supports a number of types, two of which are aggregates: Map and
+ // Array. We don't try to support Map: supporting Map would seem to
+ // promise that we could somehow match the string key to 'func's parameter
+ // names. Uh sorry, maybe in some future version of C++ with reflection.
+ if (args.isMap())
+ {
+ LLTHROW(LL::apply_error("LL::apply(function, Map LLSD) unsupported"));
+ }
+ // We expect an LLSD array, but what the heck, treat isUndefined() as a
+ // zero-length array for calling a nullary 'func'.
+ if (args.isUndefined() || args.isArray())
+ {
+ // this works because LLSD().size() == 0
+ if (args.size() != arity)
+ {
+ LLTHROW(LL::apply_error(stringize("LL::apply(function(", arity, " args), ",
+ args.size(), "-entry LLSD array)")));
+ }
+ return args;
+ }
+
+ // args is one of the scalar types
+ // scalar_LLSD.size() == 0, so don't test that here.
+ // You can pass a scalar LLSD only to a unary 'func'.
+ if (arity != 1)
+ {
+ LLTHROW(LL::apply_error(stringize("LL::apply(function(", arity, " args), "
+ "LLSD ", LLSD::typeString(args.type()), ")")));
+ }
+ // make an array of it
+ return llsd::array(args);
+}
diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h
index eddaa64bd2..546e27930d 100644
--- a/indra/llcommon/llsdutil.h
+++ b/indra/llcommon/llsdutil.h
@@ -29,10 +29,12 @@
#ifndef LL_LLSDUTIL_H
#define LL_LLSDUTIL_H
+#include "apply.h" // LL::invoke()
#include "llsd.h"
#include <boost/functional/hash.hpp>
-#include <boost/type_traits.hpp>
+#include <boost/function_types/function_arity.hpp>
#include <cassert>
+#include <type_traits>
// U32
LL_COMMON_API LLSD ll_sd_from_U32(const U32);
@@ -589,6 +591,10 @@ namespace LL
/*****************************************************************************
* apply(function, LLSD array)
*****************************************************************************/
+// validate incoming LLSD blob, and return an LLSD array suitable to pass to
+// apply_impl()
+LLSD apply_llsd_fix(size_t arity, const LLSD& args);
+
// Derived from https://stackoverflow.com/a/20441189
// and https://en.cppreference.com/w/cpp/utility/apply .
// We can't simply make a tuple from the LLSD array and then apply() that
@@ -602,6 +608,16 @@ auto apply_impl(CALLABLE&& func, const LLSD& array, std::index_sequence<I...>)
return std::forward<CALLABLE>(func)(LLSDParam<LLSD>(array[I])...);
}
+// use apply_n<ARITY>(function, LLSD) to call a specific arity of a variadic
+// function with (that many) items from the passed LLSD array
+template <size_t ARITY, typename CALLABLE>
+auto apply_n(CALLABLE&& func, const LLSD& args)
+{
+ return apply_impl(std::forward<CALLABLE>(func),
+ apply_llsd_fix(ARITY, args),
+ std::make_index_sequence<ARITY>());
+}
+
/**
* apply(function, LLSD) goes beyond C++17 std::apply(). For this case
* @a function @emph cannot be variadic: the compiler must know at compile
@@ -610,32 +626,11 @@ auto apply_impl(CALLABLE&& func, const LLSD& array, std::index_sequence<I...>)
template <typename CALLABLE>
auto apply(CALLABLE&& func, const LLSD& args)
{
- LLSD array;
- constexpr auto arity = boost::function_traits<CALLABLE>::arity;
- // LLSD supports a number of types, two of which are aggregates: Map and
- // Array. We don't try to support Map: supporting Map would seem to
- // promise that we could somehow match the string key to 'func's parameter
- // names. Uh sorry, maybe in some future version of C++ with reflection.
- assert(! args.isMap());
- // We expect an LLSD array, but what the heck, treat isUndefined() as a
- // zero-length array for calling a nullary 'func'.
- if (args.isUndefined() || args.isArray())
- {
- // this works because LLSD().size() == 0
- assert(args.size() == arity);
- array = args;
- }
- else // args is one of the scalar types
- {
- // scalar_LLSD.size() == 0, so don't test that here.
- // You can pass a scalar LLSD only to a unary 'func'.
- assert(arity == 1);
- // make an array of it
- array = llsd::array(args);
- }
- return apply_impl(std::forward<CALLABLE>(func),
- array,
- std::make_index_sequence<arity>());
+ // infer arity from the definition of func
+ constexpr auto arity = boost::function_types::function_arity<
+ typename std::remove_reference<CALLABLE>::type>::value;
+ // now that we have a compile-time arity, apply_n() works
+ return apply_n<arity>(std::forward<CALLABLE>(func), args);
}
} // namespace LL
diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp
index b38e47a773..f09dd63316 100644
--- a/indra/llcommon/tests/lleventdispatcher_test.cpp
+++ b/indra/llcommon/tests/lleventdispatcher_test.cpp
@@ -33,8 +33,6 @@
#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <boost/range.hpp>
-#include <boost/foreach.hpp>
-#define foreach BOOST_FOREACH
#include <boost/lambda/lambda.hpp>
@@ -206,7 +204,7 @@ struct Vars
void methodnb(NPARAMSb)
{
std::ostringstream vbin;
- foreach(U8 byte, bin)
+ for (U8 byte: bin)
{
vbin << std::hex << std::setfill('0') << std::setw(2) << unsigned(byte);
}
@@ -462,7 +460,7 @@ namespace tut
debug("dft_array_full:\n",
dft_array_full);
// Partial defaults arrays.
- foreach(LLSD::String a, ab)
+ for (LLSD::String a: ab)
{
LLSD::Integer partition(std::min(partial_offset, dft_array_full[a].size()));
dft_array_partial[a] =
@@ -472,7 +470,7 @@ namespace tut
debug("dft_array_partial:\n",
dft_array_partial);
- foreach(LLSD::String a, ab)
+ for(LLSD::String a: ab)
{
// Generate full defaults maps by zipping (params, dft_array_full).
dft_map_full[a] = zipmap(params[a], dft_array_full[a]);
@@ -599,19 +597,14 @@ namespace tut
{
// Copy descs to a temp map of same type.
DescMap forgotten(descs.begin(), descs.end());
- // LLEventDispatcher intentionally provides only const_iterator:
- // since dereferencing that iterator generates values on the fly,
- // it's meaningless to have a modifiable iterator. But since our
- // 'work' object isn't const, by default BOOST_FOREACH() wants to
- // use non-const iterators. Persuade it to use the const_iterator.
- foreach(LLEventDispatcher::NameDesc nd, const_cast<const Dispatcher&>(work))
+ for (LLEventDispatcher::NameDesc nd: work)
{
DescMap::iterator found = forgotten.find(nd.first);
- ensure(STRINGIZE("LLEventDispatcher records function '" << nd.first
- << "' we didn't enter"),
+ ensure(stringize("LLEventDispatcher records function '", nd.first,
+ "' we didn't enter"),
found != forgotten.end());
- ensure_equals(STRINGIZE("LLEventDispatcher desc '" << nd.second <<
- "' doesn't match what we entered: '" << found->second << "'"),
+ ensure_equals(stringize("LLEventDispatcher desc '", nd.second,
+ "' doesn't match what we entered: '", found->second, "'"),
nd.second, found->second);
// found in our map the name from LLEventDispatcher, good, erase
// our map entry
@@ -622,26 +615,26 @@ namespace tut
std::ostringstream out;
out << "LLEventDispatcher failed to report";
const char* delim = ": ";
- foreach(const DescMap::value_type& fme, forgotten)
+ for (const DescMap::value_type& fme: forgotten)
{
out << delim << fme.first;
delim = ", ";
}
- ensure(out.str(), false);
+ throw failure(out.str());
}
}
Vars* varsfor(const std::string& name)
{
VarsMap::const_iterator found = funcvars.find(name);
- ensure(STRINGIZE("No Vars* for " << name), found != funcvars.end());
- ensure(STRINGIZE("NULL Vars* for " << name), found->second);
+ ensure(stringize("No Vars* for ", name), found != funcvars.end());
+ ensure(stringize("NULL Vars* for ", name), found->second);
return found->second;
}
void ensure_has(const std::string& outer, const std::string& inner)
{
- ensure(STRINGIZE("'" << outer << "' does not contain '" << inner << "'").c_str(),
+ ensure(stringize("'", outer, "' does not contain '", inner, "'").c_str(),
outer.find(inner) != std::string::npos);
}
@@ -689,7 +682,7 @@ namespace tut
LLSD getMetadata(const std::string& name)
{
LLSD meta(work.getMetadata(name));
- ensure(STRINGIZE("No metadata for " << name), meta.isDefined());
+ ensure(stringize("No metadata for ", name), meta.isDefined());
return meta;
}
@@ -869,12 +862,12 @@ namespace tut
LLSD req(LLSD::emptyArray());
if (arity)
req[arity - 1] = LLSD();
- foreach(LLSD nm, inArray(names))
+ for (LLSD nm: inArray(names))
{
LLSD metadata(getMetadata(nm));
ensure_equals("name mismatch", metadata["name"], nm);
ensure_equals(metadata["desc"].asString(), descs[nm]);
- ensure_equals(STRINGIZE("mismatched required for " << nm.asString()),
+ ensure_equals(stringize("mismatched required for ", nm.asString()),
metadata["required"], req);
ensure("should not have optional", metadata["optional"].isUndefined());
}
@@ -932,8 +925,8 @@ namespace tut
ensure_equals("mdft name", mdft, mmeta["name"]);
ameta.erase("name");
mmeta.erase("name");
- ensure_equals(STRINGIZE("metadata for " << adft.asString()
- << " vs. " << mdft.asString()),
+ ensure_equals(stringize("metadata for ", adft.asString(),
+ " vs. ", mdft.asString()),
ameta, mmeta);
}
}
@@ -949,7 +942,7 @@ namespace tut
// params are required. Also maps containing left requirements for
// partial defaults arrays. Also defaults maps from defaults arrays.
LLSD allreq, leftreq, rightdft;
- foreach(LLSD::String a, ab)
+ for (LLSD::String a: ab)
{
// The map in which all params are required uses params[a] as
// keys, with all isUndefined() as values. We can accomplish that
@@ -977,9 +970,9 @@ namespace tut
// Generate maps containing parameter names not provided by the
// dft_map_partial maps.
LLSD skipreq(allreq);
- foreach(LLSD::String a, ab)
+ for (LLSD::String a: ab)
{
- foreach(const MapEntry& me, inMap(dft_map_partial[a]))
+ for (const MapEntry& me: inMap(dft_map_partial[a]))
{
skipreq[a].erase(me.first);
}
@@ -1024,7 +1017,7 @@ namespace tut
(llsd::array("freenb_map_mdft", "smethodnb_map_mdft", "methodnb_map_mdft"),
llsd::array(LLSD::emptyMap(), dft_map_full["b"])))); // required, optional
- foreach(LLSD grp, inArray(groups))
+ for (LLSD grp: inArray(groups))
{
// Internal structure of each group in 'groups':
LLSD names(grp[0]);
@@ -1037,14 +1030,14 @@ namespace tut
optional);
// Loop through 'names'
- foreach(LLSD nm, inArray(names))
+ for (LLSD nm: inArray(names))
{
LLSD metadata(getMetadata(nm));
ensure_equals("name mismatch", metadata["name"], nm);
ensure_equals(nm.asString(), metadata["desc"].asString(), descs[nm]);
- ensure_equals(STRINGIZE(nm << " required mismatch"),
+ ensure_equals(stringize(nm, " required mismatch"),
metadata["required"], required);
- ensure_equals(STRINGIZE(nm << " optional mismatch"),
+ ensure_equals(stringize(nm, " optional mismatch"),
metadata["optional"], optional);
}
}
@@ -1107,7 +1100,7 @@ namespace tut
// LLSD value matching 'required' according to llsd_matches() rules.
LLSD matching(LLSDMap("d", 3.14)("array", llsd::array("answer", true, answer)));
// Okay, walk through 'tests'.
- foreach(const CallablesTriple& tr, tests)
+ for (const CallablesTriple& tr: tests)
{
// Should be able to pass 'answer' to Callables registered
// without 'required'.
@@ -1129,14 +1122,17 @@ namespace tut
set_test_name("passing wrong args to (map | array)-style registrations");
// Pass scalar/map to array-style functions, scalar/array to map-style
- // functions. As that validation happens well before we engage the
- // argument magic, it seems pointless to repeat this with every
- // variation: (free function | non-static method), (no | arbitrary)
- // args. We should only need to engage it for one map-style
- // registration and one array-style registration.
- std::string array_exc("needs an args array");
- call_logerr("free0_array", 17, array_exc);
- call_logerr("free0_array", LLSDMap("pi", 3.14), array_exc);
+ // functions. It seems pointless to repeat this with every variation:
+ // (free function | non-static method), (no | arbitrary) args. We
+ // should only need to engage it for one map-style registration and
+ // one array-style registration.
+ // Now that LLEventDispatcher has been extended to treat an LLSD
+ // scalar as a single-entry array, the error we expect in this case is
+ // that apply() is trying to pass that non-empty array to a nullary
+ // function.
+ call_logerr("free0_array", 17, "LL::apply");
+ // similarly, apply() doesn't accept an LLSD Map
+ call_logerr("free0_array", LLSDMap("pi", 3.14), "unsupported");
std::string map_exc("needs a map");
call_logerr("free0_map", 17, map_exc);
@@ -1178,15 +1174,21 @@ namespace tut
template<> template<>
void object::test<19>()
{
- set_test_name("call array-style functions with too-short arrays");
- // Could have two different too-short arrays, one for *na and one for
- // *nb, but since they both take 5 params...
+ set_test_name("call array-style functions with wrong-length arrays");
+ // Could have different wrong-length arrays for *na and for *nb, but
+ // since they both take 5 params...
LLSD tooshort(llsd::array("this", "array", "too", "short"));
- foreach(const LLSD& funcsab, inArray(array_funcs))
+ LLSD toolong (llsd::array("this", "array", "is", "one", "too", "long"));
+ LLSD badargs (llsd::array(tooshort, toolong));
+ for (const LLSD& toosomething: inArray(badargs))
{
- foreach(const llsd::MapEntry& e, inMap(funcsab))
+ for (const LLSD& funcsab: inArray(array_funcs))
{
- call_logerr(e.second, tooshort, "requires more arguments");
+ for (const llsd::MapEntry& e: inMap(funcsab))
+ {
+ // apply() complains about wrong number of array entries
+ call_logerr(e.second, toosomething, "LL::apply");
+ }
}
}
}
@@ -1206,40 +1208,25 @@ namespace tut
LLDate("2011-02-03T15:07:00Z"),
LLURI("http://secondlife.com"),
binary)));
- LLSD argsplus(args);
- argsplus["a"].append("bogus");
- argsplus["b"].append("bogus");
LLSD expect;
- foreach(LLSD::String a, ab)
+ for (LLSD::String a: ab)
{
expect[a] = zipmap(params[a], args[a]);
}
// Adjust expect["a"]["cp"] for special Vars::cp treatment.
- expect["a"]["cp"] = std::string("'") + expect["a"]["cp"].asString() + "'";
+ expect["a"]["cp"] = stringize("'", expect["a"]["cp"].asString(), "'");
debug("expect: ", expect);
- // Use substantially the same logic for args and argsplus
- LLSD argsarrays(llsd::array(args, argsplus));
- // So i==0 selects 'args', i==1 selects argsplus
- for (LLSD::Integer i(0), iend(argsarrays.size()); i < iend; ++i)
+ for (const LLSD& funcsab: inArray(array_funcs))
{
- foreach(const LLSD& funcsab, inArray(array_funcs))
+ for (LLSD::String a: ab)
{
- foreach(LLSD::String a, ab)
- {
- // Reset the Vars instance before each call
- Vars* vars(varsfor(funcsab[a]));
- *vars = Vars();
- work(funcsab[a], argsarrays[i][a]);
- ensure_llsd(STRINGIZE(funcsab[a].asString() <<
- ": expect[\"" << a << "\"] mismatch"),
- vars->inspect(), expect[a], 7); // 7 bits ~= 2 decimal digits
-
- // TODO: in the i==1 or argsplus case, intercept LL_WARNS
- // output? Even without that, using argsplus verifies that
- // passing too many args isn't fatal; it works -- but
- // would be nice to notice the warning too.
- }
+ // Reset the Vars instance before each call
+ Vars* vars(varsfor(funcsab[a]));
+ *vars = Vars();
+ work(funcsab[a], args[a]);
+ ensure_llsd(stringize(funcsab[a].asString(), ": expect[\"", a, "\"] mismatch"),
+ vars->inspect(), expect[a], 7); // 7 bits ~= 2 decimal digits
}
}
}
@@ -1267,7 +1254,7 @@ namespace tut
("a", llsd::array(false, 255, 98.6, 1024.5, "pointer"))
("b", llsd::array("object", LLUUID::generateNewID(), LLDate::now(), LLURI("http://wiki.lindenlab.com/wiki"), LLSD::Binary(boost::begin(binary), boost::end(binary)))));
LLSD array_overfull(array_full);
- foreach(LLSD::String a, ab)
+ for (LLSD::String a: ab)
{
array_overfull[a].append("bogus");
}
@@ -1281,7 +1268,7 @@ namespace tut
ensure_not_equals("UUID collision",
array_full["b"][1].asUUID(), dft_array_full["b"][1].asUUID());
LLSD map_full, map_overfull;
- foreach(LLSD::String a, ab)
+ for (LLSD::String a: ab)
{
map_full[a] = zipmap(params[a], array_full[a]);
map_overfull[a] = map_full[a];
@@ -1324,15 +1311,15 @@ namespace tut
LLSD argssets(llsd::array(array_full, array_overfull, map_full, map_overfull));
foreach(const LLSD& args, inArray(argssets))
{
- foreach(LLSD::String a, ab)
+ for (LLSD::String a: ab)
{
- foreach(LLSD::String name, inArray(names[a]))
+ for (LLSD::String name: inArray(names[a]))
{
// Reset the Vars instance
Vars* vars(varsfor(name));
*vars = Vars();
work(name, args[a]);
- ensure_llsd(STRINGIZE(name << ": expect[\"" << a << "\"] mismatch"),
+ ensure_llsd(stringize(name, ": expect[\"", a, "\"] mismatch"),
vars->inspect(), expect[a], 7); // 7 bits, 2 decimal digits
// intercept LL_WARNS for the two overfull cases?
}