summaryrefslogtreecommitdiff
path: root/indra/llcommon
diff options
context:
space:
mode:
authorBrad Linden <brad@lindenlab.com>2023-12-14 12:07:45 -0800
committerBrad Linden <brad@lindenlab.com>2023-12-14 12:07:45 -0800
commit32de0cb67d824498d0fc9d489ce19731ebfddfdf (patch)
tree9fd52f420543a5c4dc45215d7ed49faf94f386fe /indra/llcommon
parent7bfe590a3669a005d4269cdcf378891080264dce (diff)
parent4c791e098d3abafa406e0e269038c40aae67f66c (diff)
Merge branch 'DRTVWR-596' into DRTVWR-601
Diffstat (limited to 'indra/llcommon')
-rw-r--r--indra/llcommon/CMakeLists.txt8
-rw-r--r--indra/llcommon/always_return.h124
-rw-r--r--indra/llcommon/apply.cpp29
-rw-r--r--indra/llcommon/apply.h144
-rw-r--r--indra/llcommon/function_types.h49
-rw-r--r--indra/llcommon/lazyeventapi.cpp72
-rw-r--r--indra/llcommon/lazyeventapi.h205
-rw-r--r--indra/llcommon/llapr.cpp8
-rw-r--r--indra/llcommon/llcoros.cpp1
-rw-r--r--indra/llcommon/lleventapi.cpp8
-rw-r--r--indra/llcommon/lleventapi.h32
-rw-r--r--indra/llcommon/lleventdispatcher.cpp619
-rw-r--r--indra/llcommon/lleventdispatcher.h841
-rw-r--r--indra/llcommon/lleventfilter.h47
-rw-r--r--indra/llcommon/llevents.cpp71
-rw-r--r--indra/llcommon/llevents.h43
-rw-r--r--indra/llcommon/llleap.cpp27
-rw-r--r--indra/llcommon/llleaplistener.cpp70
-rw-r--r--indra/llcommon/llprocess.cpp19
-rw-r--r--indra/llcommon/llprocess.h1
-rw-r--r--indra/llcommon/llptrto.h88
-rw-r--r--indra/llcommon/llsdutil.cpp35
-rw-r--r--indra/llcommon/llsdutil.h150
-rw-r--r--indra/llcommon/tests/apply_test.cpp240
-rw-r--r--indra/llcommon/tests/lazyeventapi_test.cpp136
-rw-r--r--indra/llcommon/tests/lleventdispatcher_test.cpp569
-rw-r--r--indra/llcommon/tests/wrapllerrs.h5
27 files changed, 3040 insertions, 601 deletions
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index fa9c6eaa79..5f4ed2fffa 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -16,8 +16,10 @@ include(Tracy)
set(llcommon_SOURCE_FILES
+ apply.cpp
commoncontrol.cpp
indra_constants.cpp
+ lazyeventapi.cpp
llallocator.cpp
llallocator_heap_profile.cpp
llapp.cpp
@@ -115,12 +117,16 @@ set(llcommon_SOURCE_FILES
set(llcommon_HEADER_FILES
CMakeLists.txt
+ always_return.h
+ apply.h
chrono.h
classic_callback.h
commoncontrol.h
ctype_workaround.h
fix_macros.h
+ function_types.h
indra_constants.h
+ lazyeventapi.h
linden_common.h
llalignedarray.h
llallocator.h
@@ -292,9 +298,11 @@ if (LL_TESTS)
#set(TEST_DEBUG on)
set(test_libs llcommon)
+ LL_ADD_INTEGRATION_TEST(apply "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(bitpack "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(classic_callback "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(commonmisc "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(lazyeventapi "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llbase64 "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llcond "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lldate "" "${test_libs}")
diff --git a/indra/llcommon/always_return.h b/indra/llcommon/always_return.h
new file mode 100644
index 0000000000..6b9f1fdeaf
--- /dev/null
+++ b/indra/llcommon/always_return.h
@@ -0,0 +1,124 @@
+/**
+ * @file always_return.h
+ * @author Nat Goodspeed
+ * @date 2023-01-20
+ * @brief Call specified callable with arbitrary arguments, but always return
+ * specified type.
+ *
+ * $LicenseInfo:firstyear=2023&license=viewerlgpl$
+ * Copyright (c) 2023, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_ALWAYS_RETURN_H)
+#define LL_ALWAYS_RETURN_H
+
+#include <type_traits> // std::enable_if, std::is_convertible
+
+namespace LL
+{
+
+#if __cpp_lib_is_invocable >= 201703L // C++17
+ template <typename CALLABLE, typename... ARGS>
+ using invoke_result = std::invoke_result<CALLABLE, ARGS...>;
+#else // C++14
+ template <typename CALLABLE, typename... ARGS>
+ using invoke_result = std::result_of<CALLABLE(ARGS...)>;
+#endif // C++14
+
+ /**
+ * AlwaysReturn<T>()(some_function, some_args...) calls
+ * some_function(some_args...). It is guaranteed to return a value of type
+ * T, regardless of the return type of some_function(). If some_function()
+ * returns a type convertible to T, it will convert and return that value.
+ * Otherwise (notably if some_function() is void), AlwaysReturn returns
+ * T().
+ *
+ * When some_function() returns a type not convertible to T, if
+ * you want AlwaysReturn to return some T value other than
+ * default-constructed T(), pass that value to AlwaysReturn's constructor.
+ */
+ template <typename DESIRED>
+ class AlwaysReturn
+ {
+ public:
+ /// pass explicit default value if other than default-constructed type
+ AlwaysReturn(const DESIRED& dft=DESIRED()): mDefault(dft) {}
+
+ // callable returns a type not convertible to DESIRED, return default
+ template <typename CALLABLE, typename... ARGS,
+ typename std::enable_if<
+ ! std::is_convertible<
+ typename invoke_result<CALLABLE, ARGS...>::type,
+ DESIRED
+ >::value,
+ bool
+ >::type=true>
+ DESIRED operator()(CALLABLE&& callable, ARGS&&... args)
+ {
+ // discard whatever callable(args) returns
+ std::forward<CALLABLE>(callable)(std::forward<ARGS>(args)...);
+ return mDefault;
+ }
+
+ // callable returns a type convertible to DESIRED
+ template <typename CALLABLE, typename... ARGS,
+ typename std::enable_if<
+ std::is_convertible<
+ typename invoke_result<CALLABLE, ARGS...>::type,
+ DESIRED
+ >::value,
+ bool
+ >::type=true>
+ DESIRED operator()(CALLABLE&& callable, ARGS&&... args)
+ {
+ return { std::forward<CALLABLE>(callable)(std::forward<ARGS>(args)...) };
+ }
+
+ private:
+ DESIRED mDefault;
+ };
+
+ /**
+ * always_return<T>(some_function, some_args...) calls
+ * some_function(some_args...). It is guaranteed to return a value of type
+ * T, regardless of the return type of some_function(). If some_function()
+ * returns a type convertible to T, it will convert and return that value.
+ * Otherwise (notably if some_function() is void), always_return() returns
+ * T().
+ */
+ template <typename DESIRED, typename CALLABLE, typename... ARGS>
+ DESIRED always_return(CALLABLE&& callable, ARGS&&... args)
+ {
+ return AlwaysReturn<DESIRED>()(std::forward<CALLABLE>(callable),
+ std::forward<ARGS>(args)...);
+ }
+
+ /**
+ * make_always_return<T>(some_function) returns a callable which, when
+ * called with appropriate some_function() arguments, always returns a
+ * value of type T, regardless of the return type of some_function(). If
+ * some_function() returns a type convertible to T, the returned callable
+ * will convert and return that value. Otherwise (notably if
+ * some_function() is void), the returned callable returns T().
+ *
+ * When some_function() returns a type not convertible to T, if
+ * you want the returned callable to return some T value other than
+ * default-constructed T(), pass that value to make_always_return() as its
+ * optional second argument.
+ */
+ template <typename DESIRED, typename CALLABLE>
+ auto make_always_return(CALLABLE&& callable, const DESIRED& dft=DESIRED())
+ {
+ return
+ [dft, callable = std::forward<CALLABLE>(callable)]
+ (auto&&... args)
+ {
+ return AlwaysReturn<DESIRED>(dft)(callable,
+ std::forward<decltype(args)>(args)...);
+ };
+ }
+
+} // namespace LL
+
+#endif /* ! defined(LL_ALWAYS_RETURN_H) */
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 7c58d63bc0..cf6161ed50 100644
--- a/indra/llcommon/apply.h
+++ b/indra/llcommon/apply.h
@@ -12,8 +12,11 @@
#if ! defined(LL_APPLY_H)
#define LL_APPLY_H
+#include "llexception.h"
#include <boost/type_traits/function_traits.hpp>
+#include <functional> // std::mem_fn()
#include <tuple>
+#include <type_traits> // std::is_member_pointer
namespace LL
{
@@ -54,20 +57,67 @@ namespace LL
}, \
(ARGS))
-#if __cplusplus >= 201703L
+/*****************************************************************************
+* invoke()
+*****************************************************************************/
+#if __cpp_lib_invoke >= 201411L
// C++17 implementation
-using std::apply;
+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(std::forward<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
+// We don't just say 'using std::apply;' because that template is too general:
+// it also picks up the apply(function, vector) case, which we want to handle
+// below.
+template <typename CALLABLE, typename... ARGS>
+auto apply(CALLABLE&& func, const std::tuple<ARGS...>& args)
+{
+ return std::apply(std::forward<CALLABLE>(func), args);
+}
#else // C++14
// Derived from https://stackoverflow.com/a/20441189
// and https://en.cppreference.com/w/cpp/utility/apply
-template <typename CALLABLE, typename TUPLE, std::size_t... I>
-auto apply_impl(CALLABLE&& func, TUPLE&& args, std::index_sequence<I...>)
+template <typename CALLABLE, typename... ARGS, std::size_t... I>
+auto apply_impl(CALLABLE&& func, const std::tuple<ARGS...>& args, std::index_sequence<I...>)
{
+ // We accept const std::tuple& so a caller can construct an tuple on the
+ // fly. But std::get<I>(const tuple) adds a const qualifier to everything
+ // it extracts. Get a non-const ref to this tuple so we can extract
+ // without the extraneous const.
+ auto& non_const_args{ const_cast<std::tuple<ARGS...>&>(args) };
+
// call func(unpacked args)
- return std::forward<CALLABLE>(func)(std::move(std::get<I>(args))...);
+ return invoke(std::forward<CALLABLE>(func),
+ std::forward<ARGS>(std::get<I>(non_const_args))...);
}
template <typename CALLABLE, typename... ARGS>
@@ -81,6 +131,8 @@ auto apply(CALLABLE&& func, const std::tuple<ARGS...>& args)
std::index_sequence_for<ARGS...>{});
}
+#endif // C++14
+
// 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)
@@ -88,28 +140,92 @@ auto apply(CALLABLE&& func, const std::array<T, SIZE>& args)
return apply(std::forward<CALLABLE>(func), std::tuple_cat(args));
}
+/*****************************************************************************
+* 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)
+*****************************************************************************/
// per https://stackoverflow.com/a/28411055/5533635
template <typename CALLABLE, typename T, std::size_t... I>
auto apply_impl(CALLABLE&& func, const std::vector<T>& args, std::index_sequence<I...>)
{
+ return apply(std::forward<CALLABLE>(func),
+ 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) {}
+};
+
+template <size_t ARITY, typename CALLABLE, typename T>
+auto apply_n(CALLABLE&& func, const std::vector<T>& args)
+{
+ apply_validate_size(args.size(), ARITY);
return apply_impl(std::forward<CALLABLE>(func),
- std::make_tuple(std::forward<T>(args[I])...),
- I...);
+ args,
+ std::make_index_sequence<ARITY>());
}
-// this goes beyond C++17 std::apply()
+/**
+ * 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
+ * time how many arguments to pass. This isn't Python. (But see apply_n() to
+ * pass a specific number of args to a variadic function.)
+ */
template <typename CALLABLE, typename T>
auto apply(CALLABLE&& func, const std::vector<T>& args)
{
+ // infer arity from the definition of func
constexpr auto arity = boost::function_traits<CALLABLE>::arity;
- assert(args.size() == arity);
- return apply_impl(std::forward<CALLABLE>(func),
- args,
- std::make_index_sequence<arity>());
+ // now that we have a compile-time arity, apply_n() works
+ return apply_n<arity>(std::forward<CALLABLE>(func), args);
}
-#endif // C++14
-
} // namespace LL
#endif /* ! defined(LL_APPLY_H) */
diff --git a/indra/llcommon/function_types.h b/indra/llcommon/function_types.h
new file mode 100644
index 0000000000..3f42f6d640
--- /dev/null
+++ b/indra/llcommon/function_types.h
@@ -0,0 +1,49 @@
+/**
+ * @file function_types.h
+ * @author Nat Goodspeed
+ * @date 2023-01-20
+ * @brief Extend boost::function_types to examine boost::function and
+ * std::function
+ *
+ * $LicenseInfo:firstyear=2023&license=viewerlgpl$
+ * Copyright (c) 2023, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_FUNCTION_TYPES_H)
+#define LL_FUNCTION_TYPES_H
+
+#include <boost/function.hpp>
+#include <boost/function_types/function_arity.hpp>
+#include <functional>
+
+namespace LL
+{
+
+ template <typename F>
+ struct function_arity_impl
+ {
+ static constexpr auto value = boost::function_types::function_arity<F>::value;
+ };
+
+ template <typename F>
+ struct function_arity_impl<std::function<F>>
+ {
+ static constexpr auto value = function_arity_impl<F>::value;
+ };
+
+ template <typename F>
+ struct function_arity_impl<boost::function<F>>
+ {
+ static constexpr auto value = function_arity_impl<F>::value;
+ };
+
+ template <typename F>
+ struct function_arity
+ {
+ static constexpr auto value = function_arity_impl<typename std::decay<F>::type>::value;
+ };
+
+} // namespace LL
+
+#endif /* ! defined(LL_FUNCTION_TYPES_H) */
diff --git a/indra/llcommon/lazyeventapi.cpp b/indra/llcommon/lazyeventapi.cpp
new file mode 100644
index 0000000000..028af9f33f
--- /dev/null
+++ b/indra/llcommon/lazyeventapi.cpp
@@ -0,0 +1,72 @@
+/**
+ * @file lazyeventapi.cpp
+ * @author Nat Goodspeed
+ * @date 2022-06-17
+ * @brief Implementation for lazyeventapi.
+ *
+ * $LicenseInfo:firstyear=2022&license=viewerlgpl$
+ * Copyright (c) 2022, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "lazyeventapi.h"
+// STL headers
+// std headers
+#include <algorithm> // std::find_if
+// external library headers
+// other Linden headers
+#include "llevents.h"
+#include "llsdutil.h"
+
+LL::LazyEventAPIBase::LazyEventAPIBase(
+ const std::string& name, const std::string& desc, const std::string& field)
+{
+ // populate embedded LazyEventAPIParams instance
+ mParams.name = name;
+ mParams.desc = desc;
+ mParams.field = field;
+ // mParams.init and mOperations are populated by subsequent add() calls.
+
+ // Our raison d'etre: register as an LLEventPumps::PumpFactory
+ // so obtain() will notice any request for this name and call us.
+ // Of course, our subclass constructor must finish running (making add()
+ // calls) before mParams will be fully populated, but we expect that to
+ // happen well before the first LLEventPumps::obtain(name) call.
+ mRegistered = LLEventPumps::instance().registerPumpFactory(
+ name,
+ [this](const std::string& name){ return construct(name); });
+}
+
+LL::LazyEventAPIBase::~LazyEventAPIBase()
+{
+ // If our constructor's registerPumpFactory() call was unsuccessful, that
+ // probably means somebody else claimed the name first. If that's the
+ // case, do NOT unregister their name out from under them!
+ // If this is a static instance being destroyed at process shutdown,
+ // LLEventPumps will probably have been cleaned up already.
+ if (mRegistered && ! LLEventPumps::wasDeleted())
+ {
+ // unregister the callback to this doomed instance
+ LLEventPumps::instance().unregisterPumpFactory(mParams.name);
+ }
+}
+
+LLSD LL::LazyEventAPIBase::getMetadata(const std::string& name) const
+{
+ // Since mOperations is a vector rather than a map, just search.
+ auto found = std::find_if(mOperations.begin(), mOperations.end(),
+ [&name](const auto& namedesc)
+ { return (namedesc.first == name); });
+ if (found == mOperations.end())
+ return {};
+
+ // LLEventDispatcher() supplements the returned metadata in different
+ // ways, depending on metadata provided to the specific add() method.
+ // Don't try to emulate all that. At some point we might consider more
+ // closely unifying LLEventDispatcher machinery with LazyEventAPI, but for
+ // now this will have to do.
+ return llsd::map("name", found->first, "desc", found->second);
+}
diff --git a/indra/llcommon/lazyeventapi.h b/indra/llcommon/lazyeventapi.h
new file mode 100644
index 0000000000..e36831270b
--- /dev/null
+++ b/indra/llcommon/lazyeventapi.h
@@ -0,0 +1,205 @@
+/**
+ * @file lazyeventapi.h
+ * @author Nat Goodspeed
+ * @date 2022-06-16
+ * @brief Declaring a static module-scope LazyEventAPI registers a specific
+ * LLEventAPI for future on-demand instantiation.
+ *
+ * $LicenseInfo:firstyear=2022&license=viewerlgpl$
+ * Copyright (c) 2022, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_LAZYEVENTAPI_H)
+#define LL_LAZYEVENTAPI_H
+
+#include "apply.h"
+#include "lleventapi.h"
+#include "llinstancetracker.h"
+#include <boost/signals2/signal.hpp>
+#include <string>
+#include <tuple>
+#include <utility> // std::pair
+#include <vector>
+
+namespace LL
+{
+ /**
+ * Bundle params we want to pass to LLEventAPI's protected constructor. We
+ * package them this way so a subclass constructor can simply forward an
+ * opaque reference to the LLEventAPI constructor.
+ */
+ // This is a class instead of a plain struct mostly so when we forward-
+ // declare it we don't have to remember the distinction.
+ class LazyEventAPIParams
+ {
+ public:
+ // package the parameters used by the normal LLEventAPI constructor
+ std::string name, desc, field;
+ // bundle LLEventAPI::add() calls collected by LazyEventAPI::add(), so
+ // the special LLEventAPI constructor we engage can "play back" those
+ // add() calls
+ boost::signals2::signal<void(LLEventAPI*)> init;
+ };
+
+ /**
+ * LazyEventAPIBase implements most of the functionality of LazyEventAPI
+ * (q.v.), but we need the LazyEventAPI template subclass so we can accept
+ * the specific LLEventAPI subclass type.
+ */
+ // No LLInstanceTracker key: we don't need to find a specific instance,
+ // LLLeapListener just needs to be able to enumerate all instances.
+ class LazyEventAPIBase: public LLInstanceTracker<LazyEventAPIBase>
+ {
+ public:
+ LazyEventAPIBase(const std::string& name, const std::string& desc,
+ const std::string& field);
+ virtual ~LazyEventAPIBase();
+
+ // Do not copy or move: once constructed, LazyEventAPIBase must stay
+ // put: we bind its instance pointer into a callback.
+ LazyEventAPIBase(const LazyEventAPIBase&) = delete;
+ LazyEventAPIBase(LazyEventAPIBase&&) = delete;
+ LazyEventAPIBase& operator=(const LazyEventAPIBase&) = delete;
+ LazyEventAPIBase& operator=(LazyEventAPIBase&&) = delete;
+
+ // capture add() calls we want to play back on LLEventAPI construction
+ template <typename... ARGS>
+ void add(const std::string& name, const std::string& desc, ARGS&&... rest)
+ {
+ // capture the metadata separately
+ mOperations.push_back(std::make_pair(name, desc));
+ // Use connect_extended() so the lambda is passed its own
+ // connection.
+
+ // apply() can't accept a template per se; it needs a particular
+ // specialization. Specialize out here to work around a clang bug:
+ // https://github.com/llvm/llvm-project/issues/41999
+ auto func{ &LazyEventAPIBase::add_trampoline
+ <const std::string&, const std::string&, ARGS...> };
+ // We can't bind an unexpanded parameter pack into a lambda --
+ // shame really. Instead, capture all our args as a std::tuple and
+ // then, in the lambda, use apply() to pass to add_trampoline().
+ auto args{ std::make_tuple(name, desc, std::forward<ARGS>(rest)...) };
+
+ mParams.init.connect_extended(
+ [func, args]
+ (const boost::signals2::connection& conn, LLEventAPI* instance)
+ {
+ // we only need this connection once
+ conn.disconnect();
+ // apply() expects a tuple specifying ALL the arguments,
+ // so prepend instance.
+ apply(func, std::tuple_cat(std::make_tuple(instance), args));
+ });
+ }
+
+ // The following queries mimic the LLEventAPI / LLEventDispatcher
+ // query API.
+
+ // Get the string name of the subject LLEventAPI
+ std::string getName() const { return mParams.name; }
+ // Get the documentation string
+ std::string getDesc() const { return mParams.desc; }
+ // Retrieve the LLSD key we use for dispatching
+ std::string getDispatchKey() const { return mParams.field; }
+
+ // operations
+ using NameDesc = std::pair<std::string, std::string>;
+
+ private:
+ // metadata that might be queried by LLLeapListener
+ std::vector<NameDesc> mOperations;
+
+ public:
+ using const_iterator = decltype(mOperations)::const_iterator;
+ const_iterator begin() const { return mOperations.begin(); }
+ const_iterator end() const { return mOperations.end(); }
+ LLSD getMetadata(const std::string& name) const;
+
+ protected:
+ // Params with which to instantiate the companion LLEventAPI subclass
+ LazyEventAPIParams mParams;
+
+ private:
+ // true if we successfully registered our LLEventAPI on construction
+ bool mRegistered;
+
+ // actually instantiate the companion LLEventAPI subclass
+ virtual LLEventPump* construct(const std::string& name) = 0;
+
+ // Passing an overloaded function to any function that accepts an
+ // arbitrary callable is a PITB because you have to specify the
+ // correct overload. What we want is for the compiler to select the
+ // correct overload, based on the carefully-wrought enable_ifs in
+ // LLEventDispatcher. This (one and only) add_trampoline() method
+ // exists solely to pass to LL::apply(). Once add_trampoline() is
+ // called with the expanded arguments, we hope the compiler will Do
+ // The Right Thing in selecting the correct LLEventAPI::add()
+ // overload.
+ template <typename... ARGS>
+ static
+ void add_trampoline(LLEventAPI* instance, ARGS&&... args)
+ {
+ instance->add(std::forward<ARGS>(args)...);
+ }
+ };
+
+ /**
+ * LazyEventAPI provides a way to register a particular LLEventAPI to be
+ * instantiated on demand, that is, when its name is passed to
+ * LLEventPumps::obtain().
+ *
+ * Derive your listener from LLEventAPI as usual, with its various
+ * operation methods, but code your constructor to accept
+ * <tt>(const LL::LazyEventAPIParams& params)</tt>
+ * and forward that reference to (the protected)
+ * <tt>LLEventAPI(const LL::LazyEventAPIParams&)</tt> constructor.
+ *
+ * Then derive your listener registrar from
+ * <tt>LazyEventAPI<your LLEventAPI subclass></tt>. The constructor should
+ * look very like a traditional LLEventAPI constructor:
+ *
+ * * pass (name, desc [, field]) to LazyEventAPI's constructor
+ * * in the body, make a series of add() calls referencing your LLEventAPI
+ * subclass methods.
+ *
+ * You may use any LLEventAPI::add() methods, that is, any
+ * LLEventDispatcher::add() methods. But the target methods you pass to
+ * add() must belong to your LLEventAPI subclass, not the LazyEventAPI
+ * subclass.
+ *
+ * Declare a static instance of your LazyEventAPI listener registrar
+ * class. When it's constructed at static initialization time, it will
+ * register your LLEventAPI subclass with LLEventPumps. It will also
+ * collect metadata for the LLEventAPI and its operations to provide to
+ * LLLeapListener's introspection queries.
+ *
+ * When someone later calls LLEventPumps::obtain() to post an event to
+ * your LLEventAPI subclass, obtain() will instantiate it using
+ * LazyEventAPI's name, desc, field and add() calls.
+ */
+ template <class EVENTAPI>
+ class LazyEventAPI: public LazyEventAPIBase
+ {
+ public:
+ // for subclass constructor to reference handler methods
+ using listener = EVENTAPI;
+
+ LazyEventAPI(const std::string& name, const std::string& desc,
+ const std::string& field="op"):
+ // Forward ctor params to LazyEventAPIBase
+ LazyEventAPIBase(name, desc, field)
+ {}
+
+ private:
+ LLEventPump* construct(const std::string& /*name*/) override
+ {
+ // base class has carefully assembled LazyEventAPIParams embedded
+ // in this instance, just pass to LLEventAPI subclass constructor
+ return new EVENTAPI(mParams);
+ }
+ };
+} // namespace LL
+
+#endif /* ! defined(LL_LAZYEVENTAPI_H) */
diff --git a/indra/llcommon/llapr.cpp b/indra/llcommon/llapr.cpp
index e49f72722b..575c524219 100644
--- a/indra/llcommon/llapr.cpp
+++ b/indra/llcommon/llapr.cpp
@@ -38,6 +38,12 @@ const S32 FULL_VOLATILE_APR_POOL = 1024 ; //number of references to LLVolatileAP
bool gAPRInitialized = false;
+int abortfunc(int retcode)
+{
+ LL_WARNS("APR") << "Allocation failure in apr pool with code " << (S32)retcode << LL_ENDL;
+ return 0;
+}
+
void ll_init_apr()
{
// Initialize APR and create the global pool
@@ -45,7 +51,7 @@ void ll_init_apr()
if (!gAPRPoolp)
{
- apr_pool_create(&gAPRPoolp, NULL);
+ apr_pool_create_ex(&gAPRPoolp, NULL, abortfunc, NULL);
}
if(!LLAPRFile::sAPRFilePoolp)
diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp
index cfaf3415e7..3ab97b557f 100644
--- a/indra/llcommon/llcoros.cpp
+++ b/indra/llcommon/llcoros.cpp
@@ -278,6 +278,7 @@ std::string LLCoros::launch(const std::string& prefix, const callable_t& callabl
catch (std::bad_alloc&)
{
// Out of memory on stack allocation?
+ printActiveCoroutines();
LL_ERRS("LLCoros") << "Bad memory allocation in LLCoros::launch(" << prefix << ")!" << LL_ENDL;
}
diff --git a/indra/llcommon/lleventapi.cpp b/indra/llcommon/lleventapi.cpp
index ff5459c1eb..3d46ef1034 100644
--- a/indra/llcommon/lleventapi.cpp
+++ b/indra/llcommon/lleventapi.cpp
@@ -35,6 +35,7 @@
// external library headers
// other Linden headers
#include "llerror.h"
+#include "lazyeventapi.h"
LLEventAPI::LLEventAPI(const std::string& name, const std::string& desc, const std::string& field):
lbase(name, field),
@@ -43,6 +44,13 @@ LLEventAPI::LLEventAPI(const std::string& name, const std::string& desc, const s
{
}
+LLEventAPI::LLEventAPI(const LL::LazyEventAPIParams& params):
+ LLEventAPI(params.name, params.desc, params.field)
+{
+ // call initialization functions with our brand-new instance pointer
+ params.init(this);
+}
+
LLEventAPI::~LLEventAPI()
{
}
diff --git a/indra/llcommon/lleventapi.h b/indra/llcommon/lleventapi.h
index 5991fe8fd5..25f6becd8b 100644
--- a/indra/llcommon/lleventapi.h
+++ b/indra/llcommon/lleventapi.h
@@ -35,6 +35,11 @@
#include "llinstancetracker.h"
#include <string>
+namespace LL
+{
+ class LazyEventAPIParams;
+}
+
/**
* LLEventAPI not only provides operation dispatch functionality, inherited
* from LLDispatchListener -- it also gives us event API introspection.
@@ -65,19 +70,6 @@ public:
std::string getDesc() const { return mDesc; }
/**
- * Publish only selected add() methods from LLEventDispatcher.
- * Every LLEventAPI add() @em must have a description string.
- */
- template <typename CALLABLE>
- void add(const std::string& name,
- const std::string& desc,
- CALLABLE callable,
- const LLSD& required=LLSD())
- {
- LLEventDispatcher::add(name, desc, callable, required);
- }
-
- /**
* Instantiate a Response object in any LLEventAPI subclass method that
* wants to guarantee a reply (if requested) will be sent on exit from the
* method. The reply will be sent if request.has(@a replyKey), default
@@ -150,16 +142,20 @@ public:
* @endcode
*/
LLSD& operator[](const LLSD::String& key) { return mResp[key]; }
-
- /**
- * set the response to the given data
- */
- void setResponse(LLSD const & response){ mResp = response; }
+
+ /**
+ * set the response to the given data
+ */
+ void setResponse(LLSD const & response){ mResp = response; }
LLSD mResp, mReq;
LLSD::String mKey;
};
+protected:
+ // constructor used only by subclasses registered by LazyEventAPI
+ LLEventAPI(const LL::LazyEventAPIParams&);
+
private:
std::string mDesc;
};
diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp
index cd0ab6bc29..da96de18f7 100644
--- a/indra/llcommon/lleventdispatcher.cpp
+++ b/indra/llcommon/lleventdispatcher.cpp
@@ -40,71 +40,13 @@
// other Linden headers
#include "llevents.h"
#include "llerror.h"
+#include "llexception.h"
#include "llsdutil.h"
#include "stringize.h"
+#include <iomanip> // std::quoted()
#include <memory> // std::auto_ptr
/*****************************************************************************
-* LLSDArgsSource
-*****************************************************************************/
-/**
- * Store an LLSD array, producing its elements one at a time. Die with LL_ERRS
- * 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()))
- {
- LL_ERRS("LLSDArgsSource") << _function << " needs an args array instead of "
- << _args << LL_ENDL;
- }
-}
-
-LLSDArgsSource::~LLSDArgsSource()
-{
- done();
-}
-
-LLSD LLSDArgsSource::next()
-{
- if (_index >= _args.size())
- {
- LL_ERRS("LLSDArgsSource") << _function << " requires more arguments than the "
- << _args.size() << " provided: " << _args << LL_ENDL;
- }
- 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
*****************************************************************************/
/**
@@ -156,19 +98,26 @@ void LLSDArgsSource::done() const
* - Holes are filled with the default values.
* - Any remaining holes constitute an error.
*/
-class LL_COMMON_API LLSDArgsMapper
+class LL_COMMON_API LLEventDispatcher::LLSDArgsMapper
{
public:
/// Accept description of function: function name, param names, param
/// default values
- LLSDArgsMapper(const std::string& function, const LLSD& names, const LLSD& defaults);
+ LLSDArgsMapper(LLEventDispatcher* parent, const std::string& function,
+ const LLSD& names, const LLSD& defaults);
- /// Given arguments map, return LLSD::Array of parameter values, or LL_ERRS.
+ /// Given arguments map, return LLSD::Array of parameter values, or
+ /// trigger error.
LLSD map(const LLSD& argsmap) const;
private:
static std::string formatlist(const LLSD&);
+ template <typename... ARGS>
+ [[noreturn]] void callFail(ARGS&&... args) const;
+ // store a plain dumb back-pointer because we don't have to manage the
+ // parent LLEventDispatcher's lifespan
+ LLEventDispatcher* _parent;
// The function-name string is purely descriptive. We want error messages
// to be able to indicate which function's LLSDArgsMapper has the problem.
std::string _function;
@@ -187,15 +136,18 @@ private:
FilledVector _has_dft;
};
-LLSDArgsMapper::LLSDArgsMapper(const std::string& function,
- const LLSD& names, const LLSD& defaults):
+LLEventDispatcher::LLSDArgsMapper::LLSDArgsMapper(LLEventDispatcher* parent,
+ const std::string& function,
+ const LLSD& names,
+ const LLSD& defaults):
+ _parent(parent),
_function(function),
_names(names),
_has_dft(names.size())
{
if (! (_names.isUndefined() || _names.isArray()))
{
- LL_ERRS("LLSDArgsMapper") << function << " names must be an array, not " << names << LL_ENDL;
+ callFail(" names must be an array, not ", names);
}
auto nparams(_names.size());
// From _names generate _indexes.
@@ -218,8 +170,7 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function,
// defaults is a (possibly empty) array. Right-align it with names.
if (ndefaults > nparams)
{
- LL_ERRS("LLSDArgsMapper") << function << " names array " << names
- << " shorter than defaults array " << defaults << LL_ENDL;
+ callFail(" names array ", names, " shorter than defaults array ", defaults);
}
// Offset by which we slide defaults array right to right-align with
@@ -256,23 +207,20 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function,
}
if (bogus.size())
{
- LL_ERRS("LLSDArgsMapper") << function << " defaults specified for nonexistent params "
- << formatlist(bogus) << LL_ENDL;
+ callFail(" defaults specified for nonexistent params ", formatlist(bogus));
}
}
else
{
- LL_ERRS("LLSDArgsMapper") << function << " defaults must be a map or an array, not "
- << defaults << LL_ENDL;
+ callFail(" defaults must be a map or an array, not ", defaults);
}
}
-LLSD LLSDArgsMapper::map(const LLSD& argsmap) const
+LLSD LLEventDispatcher::LLSDArgsMapper::map(const LLSD& argsmap) const
{
if (! (argsmap.isUndefined() || argsmap.isMap() || argsmap.isArray()))
{
- LL_ERRS("LLSDArgsMapper") << _function << " map() needs a map or array, not "
- << argsmap << LL_ENDL;
+ callFail(" 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
@@ -369,15 +317,14 @@ LLSD LLSDArgsMapper::map(const LLSD& argsmap) const
// by argsmap, that's a problem.
if (unfilled.size())
{
- LL_ERRS("LLSDArgsMapper") << _function << " missing required arguments "
- << formatlist(unfilled) << " from " << argsmap << LL_ENDL;
+ callFail(" missing required arguments ", formatlist(unfilled), " from ", argsmap);
}
// done
return args;
}
-std::string LLSDArgsMapper::formatlist(const LLSD& list)
+std::string LLEventDispatcher::LLSDArgsMapper::formatlist(const LLSD& list)
{
std::ostringstream out;
const char* delim = "";
@@ -390,23 +337,44 @@ std::string LLSDArgsMapper::formatlist(const LLSD& list)
return out.str();
}
-LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key):
- mDesc(desc),
- mKey(key)
+template <typename... ARGS>
+[[noreturn]] void LLEventDispatcher::LLSDArgsMapper::callFail(ARGS&&... args) const
{
+ _parent->callFail<LLEventDispatcher::DispatchError>
+ (_function, std::forward<ARGS>(args)...);
}
+/*****************************************************************************
+* LLEventDispatcher
+*****************************************************************************/
+LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key):
+ LLEventDispatcher(desc, key, "args")
+{}
+
+LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key,
+ const std::string& argskey):
+ mDesc(desc),
+ mKey(key),
+ mArgskey(argskey)
+{}
+
LLEventDispatcher::~LLEventDispatcher()
{
}
+LLEventDispatcher::DispatchEntry::DispatchEntry(LLEventDispatcher* parent, const std::string& desc):
+ mParent(parent),
+ mDesc(desc)
+{}
+
/**
* DispatchEntry subclass used for callables accepting(const LLSD&)
*/
struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchEntry
{
- LLSDDispatchEntry(const std::string& desc, const Callable& func, const LLSD& required):
- DispatchEntry(desc),
+ LLSDDispatchEntry(LLEventDispatcher* parent, const std::string& desc,
+ const Callable& func, const LLSD& required):
+ DispatchEntry(parent, desc),
mFunc(func),
mRequired(required)
{}
@@ -414,22 +382,21 @@ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchE
Callable mFunc;
LLSD mRequired;
- virtual void call(const std::string& desc, const LLSD& event) const
+ LLSD call(const std::string& desc, const LLSD& event, bool, const std::string&) const override
{
// Validate the syntax of the event itself.
std::string mismatch(llsd_matches(mRequired, event));
if (! mismatch.empty())
{
- LL_ERRS("LLEventDispatcher") << desc << ": bad request: " << mismatch << LL_ENDL;
+ callFail(desc, ": bad request: ", mismatch);
}
// Event syntax looks good, go for it!
- mFunc(event);
+ return mFunc(event);
}
- virtual LLSD addMetadata(LLSD meta) const
+ LLSD getMetadata() const override
{
- meta["required"] = mRequired;
- return meta;
+ return llsd::map("required", mRequired);
}
};
@@ -439,17 +406,27 @@ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchE
*/
struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::DispatchEntry
{
- ParamsDispatchEntry(const std::string& desc, const invoker_function& func):
- DispatchEntry(desc),
+ ParamsDispatchEntry(LLEventDispatcher* parent, const std::string& name,
+ const std::string& desc, const invoker_function& func):
+ DispatchEntry(parent, desc),
+ mName(name),
mInvoker(func)
{}
+ std::string mName;
invoker_function mInvoker;
- virtual void call(const std::string& desc, const LLSD& event) const
+ LLSD call(const std::string&, const LLSD& event, bool, const std::string&) const override
{
- LLSDArgsSource src(desc, event);
- mInvoker(boost::bind(&LLSDArgsSource::next, boost::ref(src)));
+ try
+ {
+ return mInvoker(event);
+ }
+ catch (const LL::apply_error& err)
+ {
+ // could hit runtime errors with LL::apply()
+ callFail(err.what());
+ }
}
};
@@ -459,23 +436,62 @@ struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::Dispatc
*/
struct LLEventDispatcher::ArrayParamsDispatchEntry: public LLEventDispatcher::ParamsDispatchEntry
{
- ArrayParamsDispatchEntry(const std::string& desc, const invoker_function& func,
+ ArrayParamsDispatchEntry(LLEventDispatcher* parent, const std::string& name,
+ const std::string& desc, const invoker_function& func,
LLSD::Integer arity):
- ParamsDispatchEntry(desc, func),
+ ParamsDispatchEntry(parent, name, desc, func),
mArity(arity)
{}
LLSD::Integer mArity;
- virtual LLSD addMetadata(LLSD meta) const
+ LLSD call(const std::string& desc, const LLSD& event, bool fromMap, const std::string& argskey) const override
+ {
+// std::string context { stringize(desc, "(", event, ") with argskey ", std::quoted(argskey), ": ") };
+ // Whether we try to extract arguments from 'event' depends on whether
+ // the LLEventDispatcher consumer called one of the (name, event)
+ // methods (! fromMap) or one of the (event) methods (fromMap). If we
+ // were called with (name, event), the passed event must itself be
+ // suitable to pass to the registered callable, no args extraction
+ // required or even attempted. Only if called with plain (event) do we
+ // consider extracting args from that event. Initially assume 'event'
+ // itself contains the arguments.
+ LLSD args{ event };
+ if (fromMap)
+ {
+ if (! mArity)
+ {
+ // When the target function is nullary, and we're called from
+ // an (event) method, just ignore the rest of the map entries.
+ args.clear();
+ }
+ else
+ {
+ // We only require/retrieve argskey if the target function
+ // isn't nullary. For all others, since we require an LLSD
+ // array, we must have an argskey.
+ if (argskey.empty())
+ {
+ callFail("LLEventDispatcher has no args key");
+ }
+ if ((! event.has(argskey)))
+ {
+ callFail("missing required key ", std::quoted(argskey));
+ }
+ args = event[argskey];
+ }
+ }
+ return ParamsDispatchEntry::call(desc, args, fromMap, argskey);
+ }
+
+ LLSD getMetadata() const override
{
LLSD array(LLSD::emptyArray());
// Resize to number of arguments required
if (mArity)
array[mArity - 1] = LLSD();
llassert_always(array.size() == mArity);
- meta["required"] = array;
- return meta;
+ return llsd::map("required", array);
}
};
@@ -485,11 +501,11 @@ struct LLEventDispatcher::ArrayParamsDispatchEntry: public LLEventDispatcher::Pa
*/
struct LLEventDispatcher::MapParamsDispatchEntry: public LLEventDispatcher::ParamsDispatchEntry
{
- MapParamsDispatchEntry(const std::string& name, const std::string& desc,
- const invoker_function& func,
+ MapParamsDispatchEntry(LLEventDispatcher* parent, const std::string& name,
+ const std::string& desc, const invoker_function& func,
const LLSD& params, const LLSD& defaults):
- ParamsDispatchEntry(desc, func),
- mMapper(name, params, defaults),
+ ParamsDispatchEntry(parent, name, desc, func),
+ mMapper(parent, name, params, defaults),
mRequired(LLSD::emptyMap())
{
// Build the set of all param keys, then delete the ones that are
@@ -532,18 +548,27 @@ struct LLEventDispatcher::MapParamsDispatchEntry: public LLEventDispatcher::Para
LLSD mRequired;
LLSD mOptional;
- virtual void call(const std::string& desc, const LLSD& event) const
+ LLSD call(const std::string& desc, const LLSD& event, bool fromMap, const std::string& argskey) const override
{
- // Just convert from LLSD::Map to LLSD::Array using mMapper, then pass
- // to base-class call() method.
- ParamsDispatchEntry::call(desc, mMapper.map(event));
+ // by default, pass the whole event as the arguments map
+ LLSD args{ event };
+ // Were we called by one of the (event) methods (instead of the (name,
+ // event) methods), do we have an argskey, and does the incoming event
+ // have that key?
+ if (fromMap && (! argskey.empty()) && event.has(argskey))
+ {
+ // if so, extract the value of argskey from the incoming event,
+ // and use that as the arguments map
+ args = event[argskey];
+ }
+ // Now convert args from LLSD map to LLSD array using mMapper, then
+ // pass to base-class call() method.
+ return ParamsDispatchEntry::call(desc, mMapper.map(args), fromMap, argskey);
}
- virtual LLSD addMetadata(LLSD meta) const
+ LLSD getMetadata() const override
{
- meta["required"] = mRequired;
- meta["optional"] = mOptional;
- return meta;
+ return llsd::map("required", mRequired, "optional", mOptional);
}
};
@@ -552,9 +577,9 @@ void LLEventDispatcher::addArrayParamsDispatchEntry(const std::string& name,
const invoker_function& invoker,
LLSD::Integer arity)
{
- mDispatch.insert(
- DispatchMap::value_type(name, DispatchMap::mapped_type(
- new ArrayParamsDispatchEntry(desc, invoker, arity))));
+ mDispatch.emplace(
+ name,
+ new ArrayParamsDispatchEntry(this, "", desc, invoker, arity));
}
void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name,
@@ -563,25 +588,25 @@ void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name,
const LLSD& params,
const LLSD& defaults)
{
- mDispatch.insert(
- DispatchMap::value_type(name, DispatchMap::mapped_type(
- new MapParamsDispatchEntry(name, desc, invoker, params, defaults))));
+ // Pass instance info as well as this entry name for error messages.
+ mDispatch.emplace(
+ name,
+ new MapParamsDispatchEntry(this, "", desc, invoker, params, defaults));
}
/// Register a callable by name
-void LLEventDispatcher::add(const std::string& name, const std::string& desc,
- const Callable& callable, const LLSD& required)
+void LLEventDispatcher::addLLSD(const std::string& name, const std::string& desc,
+ const Callable& callable, const LLSD& required)
{
- mDispatch.insert(
- DispatchMap::value_type(name, DispatchMap::mapped_type(
- new LLSDDispatchEntry(desc, callable, required))));
+ mDispatch.emplace(name, new LLSDDispatchEntry(this, desc, callable, required));
}
-void LLEventDispatcher::addFail(const std::string& name, const std::string& classname) const
+void LLEventDispatcher::addFail(const std::string& name, const char* classname) const
{
LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << ")::add(" << name
- << "): " << classname << " is not a subclass "
- << "of LLEventDispatcher" << LL_ENDL;
+ << "): " << LLError::Log::demangle(classname)
+ << " is not a subclass of LLEventDispatcher"
+ << LL_ENDL;
}
/// Unregister a callable
@@ -596,48 +621,105 @@ bool LLEventDispatcher::remove(const std::string& name)
return true;
}
-/// Call a registered callable with an explicitly-specified name. If no
-/// such callable exists, die with LL_ERRS.
-void LLEventDispatcher::operator()(const std::string& name, const LLSD& event) const
+/// Call a registered callable with an explicitly-specified name. It is an
+/// error if no such callable exists.
+LLSD LLEventDispatcher::operator()(const std::string& name, const LLSD& event) const
{
- if (! try_call(name, event))
- {
- LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): '" << name
- << "' not found" << LL_ENDL;
- }
+ return try_call(std::string(), name, event);
}
-/// Extract the @a key value from the incoming @a event, and call the
-/// callable whose name is specified by that map @a key. If no such
-/// callable exists, die with LL_ERRS.
-void LLEventDispatcher::operator()(const LLSD& event) const
+bool LLEventDispatcher::try_call(const std::string& name, const LLSD& event) const
{
- // This could/should be implemented in terms of the two-arg overload.
- // However -- we can produce a more informative error message.
- std::string name(event[mKey]);
- if (! try_call(name, event))
+ try
{
- LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): bad " << mKey
- << " value '" << name << "'" << LL_ENDL;
+ try_call(std::string(), name, event);
+ return true;
+ }
+ // Note that we don't catch the generic DispatchError, only the specific
+ // DispatchMissing. try_call() only promises to return false if the
+ // specified callable name isn't found -- not for general errors.
+ catch (const DispatchMissing&)
+ {
+ return false;
}
}
+/// Extract the @a key value from the incoming @a event, and call the callable
+/// whose name is specified by that map @a key. It is an error if no such
+/// callable exists.
+LLSD LLEventDispatcher::operator()(const LLSD& event) const
+{
+ return try_call(mKey, event[mKey], event);
+}
+
bool LLEventDispatcher::try_call(const LLSD& event) const
{
- return try_call(event[mKey], event);
+ try
+ {
+ try_call(mKey, event[mKey], event);
+ return true;
+ }
+ catch (const DispatchMissing&)
+ {
+ return false;
+ }
}
-bool LLEventDispatcher::try_call(const std::string& name, const LLSD& event) const
+LLSD LLEventDispatcher::try_call(const std::string& key, const std::string& name,
+ const LLSD& event) const
{
+ if (name.empty())
+ {
+ if (key.empty())
+ {
+ callFail<DispatchError>("attempting to call with no name");
+ }
+ else
+ {
+ callFail<DispatchError>("no ", key);
+ }
+ }
+
DispatchMap::const_iterator found = mDispatch.find(name);
if (found == mDispatch.end())
{
- return false;
+ // Here we were passed a non-empty name, but there's no registered
+ // callable with that name. This is the one case in which we throw
+ // DispatchMissing instead of the generic DispatchError.
+ // Distinguish the public method by which our caller reached here:
+ // key.empty() means the name was passed explicitly, non-empty means
+ // we extracted the name from the incoming event using that key.
+ if (key.empty())
+ {
+ callFail<DispatchMissing>(std::quoted(name), " not found");
+ }
+ else
+ {
+ callFail<DispatchMissing>("bad ", key, " value ", std::quoted(name));
+ }
}
+
// Found the name, so it's plausible to even attempt the call.
- found->second->call(STRINGIZE("LLEventDispatcher(" << mDesc << ") calling '" << name << "'"),
- event);
- return true; // tell caller we were able to call
+ const char* delim = (key.empty()? "" : "=");
+ // append either "[key=name]" or just "[name]"
+ SetState transient(this, '[', key, delim, name, ']');
+ return found->second->call("", event, (! key.empty()), mArgskey);
+}
+
+template <typename EXCEPTION, typename... ARGS>
+//static
+[[noreturn]] void LLEventDispatcher::sCallFail(ARGS&&... args)
+{
+ auto error = stringize(std::forward<ARGS>(args)...);
+ LL_WARNS("LLEventDispatcher") << error << LL_ENDL;
+ LLTHROW(EXCEPTION(error));
+}
+
+template <typename EXCEPTION, typename... ARGS>
+[[noreturn]] void LLEventDispatcher::callFail(ARGS&&... args) const
+{
+ // Describe this instance in addition to the error itself.
+ sCallFail<EXCEPTION>(*this, ": ", std::forward<ARGS>(args)...);
}
LLSD LLEventDispatcher::getMetadata(const std::string& name) const
@@ -647,26 +729,243 @@ LLSD LLEventDispatcher::getMetadata(const std::string& name) const
{
return LLSD();
}
- LLSD meta;
+ LLSD meta{ found->second->getMetadata() };
meta["name"] = name;
meta["desc"] = found->second->mDesc;
- return found->second->addMetadata(meta);
+ return meta;
+}
+
+std::ostream& operator<<(std::ostream& out, const LLEventDispatcher& self)
+{
+ // If we're a subclass of LLEventDispatcher, e.g. LLEventAPI, report that.
+ // Also report whatever transient state is active.
+ return out << LLError::Log::classname(self) << '(' << self.mDesc << ')'
+ << self.getState();
}
-LLDispatchListener::LLDispatchListener(const std::string& pumpname, const std::string& key):
- LLEventDispatcher(pumpname, key),
- mPump(pumpname, true), // allow tweaking for uniqueness
- mBoundListener(mPump.listen("self", boost::bind(&LLDispatchListener::process, this, _1)))
+std::string LLEventDispatcher::getState() const
{
+ // default value of fiber_specific_ptr is nullptr, and ~SetState() reverts
+ // to that; infer empty string
+ if (! mState.get())
+ return {};
+ else
+ return *mState;
}
-bool LLDispatchListener::process(const LLSD& event)
+bool LLEventDispatcher::setState(SetState&, const std::string& state) const
{
- (*this)(event);
+ // If SetState is instantiated at multiple levels of function call, ignore
+ // the lower-level call because the outer call presumably provides more
+ // context.
+ if (mState.get())
+ return false;
+
+ // Pass us empty string (a la ~SetState()) to reset to nullptr, else take
+ // a heap copy of the passed state string so we can delete it on
+ // subsequent reset().
+ mState.reset(state.empty()? nullptr : new std::string(state));
+ return true;
+}
+
+/*****************************************************************************
+* LLDispatchListener
+*****************************************************************************/
+std::string LLDispatchListener::mReplyKey{ "reply" };
+
+bool LLDispatchListener::process(const LLSD& event) const
+{
+ // Decide what to do based on the incoming value of the specified dispatch
+ // key.
+ LLSD name{ event[getDispatchKey()] };
+ if (name.isMap())
+ {
+ call_map(name, event);
+ }
+ else if (name.isArray())
+ {
+ call_array(name, event);
+ }
+ else
+ {
+ call_one(name, event);
+ }
return false;
}
-LLEventDispatcher::DispatchEntry::DispatchEntry(const std::string& desc):
- mDesc(desc)
-{}
+void LLDispatchListener::call_one(const LLSD& name, const LLSD& event) const
+{
+ LLSD result;
+ try
+ {
+ result = (*this)(event);
+ }
+ catch (const DispatchError& err)
+ {
+ if (! event.has(mReplyKey))
+ {
+ // Without a reply key, let the exception propagate.
+ throw;
+ }
+
+ // Here there was an error and the incoming event has mReplyKey. Reply
+ // with a map containing an "error" key explaining the problem.
+ return reply(llsd::map("error", err.what()), event);
+ }
+ // We seem to have gotten a valid result. But we don't know whether the
+ // registered callable is void or non-void. If it's void,
+ // LLEventDispatcher returned isUndefined(). Otherwise, try to send it
+ // back to our invoker.
+ if (result.isDefined())
+ {
+ if (! result.isMap())
+ {
+ // wrap the result in a map as the "data" key
+ result = llsd::map("data", result);
+ }
+ reply(result, event);
+ }
+}
+
+void LLDispatchListener::call_map(const LLSD& reqmap, const LLSD& event) const
+{
+ // LLSD map containing returned values
+ LLSD result;
+ // cache dispatch key
+ std::string key{ getDispatchKey() };
+ // collect any error messages here
+ std::ostringstream errors;
+ const char* delim = "";
+
+ for (const auto& pair : llsd::inMap(reqmap))
+ {
+ const LLSD::String& name{ pair.first };
+ const LLSD& args{ pair.second };
+ try
+ {
+ // in case of errors, tell user the dispatch key, the fact that
+ // we're processing a request map and the current key in that map
+ SetState(this, '[', key, '[', name, "]]");
+ // With this form, capture return value even if undefined:
+ // presence of the key in the response map can be used to detect
+ // which request keys succeeded.
+ result[name] = (*this)(name, args);
+ }
+ catch (const std::exception& err)
+ {
+ // Catch not only DispatchError, but any C++ exception thrown by
+ // the target callable. Collect exception name and message in
+ // 'errors'.
+ errors << delim << LLError::Log::classname(err) << ": " << err.what();
+ delim = "\n";
+ }
+ }
+
+ // so, were there any errors?
+ std::string error = errors.str();
+ if (! error.empty())
+ {
+ if (! event.has(mReplyKey))
+ {
+ // can't send reply, throw
+ sCallFail<DispatchError>(error);
+ }
+ else
+ {
+ // reply key present
+ result["error"] = error;
+ }
+ }
+
+ reply(result, event);
+}
+
+void LLDispatchListener::call_array(const LLSD& reqarray, const LLSD& event) const
+{
+ // LLSD array containing returned values
+ LLSD results;
+ // cache the dispatch key
+ std::string key{ getDispatchKey() };
+ // arguments array, if present -- const because, if it's shorter than
+ // reqarray, we don't want to grow it
+ const LLSD argsarray{ event[getArgsKey()] };
+ // error message, if any
+ std::string error;
+
+ // classic index loop because we need the index
+ for (size_t i = 0, size = reqarray.size(); i < size; ++i)
+ {
+ const auto& reqentry{ reqarray[i] };
+ std::string name;
+ LLSD args;
+ if (reqentry.isString())
+ {
+ name = reqentry.asString();
+ args = argsarray[i];
+ }
+ else if (reqentry.isArray() && reqentry.size() == 2 && reqentry[0].isString())
+ {
+ name = reqentry[0].asString();
+ args = reqentry[1];
+ }
+ else
+ {
+ // reqentry isn't in either of the documented forms
+ error = stringize(*this, ": ", getDispatchKey(), '[', i, "] ",
+ reqentry, " unsupported");
+ break;
+ }
+
+ // reqentry is one of the valid forms, got name and args
+ try
+ {
+ // in case of errors, tell user the dispatch key, the fact that
+ // we're processing a request array, the current entry in that
+ // array and the corresponding callable name
+ SetState(this, '[', key, '[', i, "]=", name, ']');
+ // With this form, capture return value even if undefined
+ results.append((*this)(name, args));
+ }
+ catch (const std::exception& err)
+ {
+ // Catch not only DispatchError, but any C++ exception thrown by
+ // the target callable. Report the exception class as well as the
+ // error string.
+ error = stringize(LLError::Log::classname(err), ": ", err.what());
+ break;
+ }
+ }
+
+ LLSD result;
+ // was there an error?
+ if (! error.empty())
+ {
+ if (! event.has(mReplyKey))
+ {
+ // can't send reply, throw
+ sCallFail<DispatchError>(error);
+ }
+ else
+ {
+ // reply key present
+ result["error"] = error;
+ }
+ }
+
+ // wrap the results array as response map "data" key, as promised
+ if (results.isDefined())
+ {
+ result["data"] = results;
+ }
+
+ reply(result, event);
+}
+
+void LLDispatchListener::reply(const LLSD& reply, const LLSD& request) const
+{
+ // Call sendReply() unconditionally: sendReply() itself tests whether the
+ // specified reply key is present in the incoming request, and does
+ // nothing if there's no such key.
+ sendReply(reply, request, mReplyKey);
+}
diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h
index 9e1244ef5b..a82bc7a69b 100644
--- a/indra/llcommon/lleventdispatcher.h
+++ b/indra/llcommon/lleventdispatcher.h
@@ -27,55 +27,26 @@
*
* 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 <string>
-#include <boost/shared_ptr.hpp>
-#include <boost/function.hpp>
-#include <boost/bind.hpp>
-#include <boost/iterator/transform_iterator.hpp>
-#include <boost/utility/enable_if.hpp>
+#include <boost/fiber/fss.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/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;
@@ -89,15 +60,27 @@ class LLSD;
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, typically a
- /// boost::bind() expression
- typedef boost::function<void(const LLSD&)> Callable;
+ /// 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
@@ -109,27 +92,54 @@ public:
void add(const std::string& name,
const std::string& desc,
const Callable& callable,
- const LLSD& required=LLSD());
+ const LLSD& required=LLSD())
+ {
+ addLLSD(name, desc, callable, required);
+ }
- /**
- * The case of a free function (or static method) accepting(const LLSD&)
- * could also be intercepted by the arbitrary-args overload below. Ensure
- * that it's directed to the Callable overload above instead.
- */
+ 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,
- void (*f)(const LLSD&),
+ CALLABLE&& callable,
const LLSD& required=LLSD())
{
- add(name, desc, Callable(f), required);
+ 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 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 <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,
@@ -150,6 +160,128 @@ public:
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
@@ -159,51 +291,43 @@ 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.
*/
- template<typename Function>
- typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin<Function>
- >::type add(const std::string& name,
- const std::string& desc,
- Function f);
+ 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.
*
- * @note This supports functions with up to about 6 parameters -- after
- * that you start getting dismaying compile errors in which
- * boost::fusion::joint_view is mentioned a surprising number of times.
- *
* To cover cases such as a method on an LLSingleton we don't yet want to
* instantiate, instead of directly storing an instance pointer, accept a
* nullary callable returning a pointer/reference to the desired class
- * instance. If you already have an instance in hand,
- * boost::lambda::var(instance) or boost::lambda::constant(instance_ptr)
- * produce suitable callables.
+ * 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 boost::enable_if< boost::function_types::is_member_function_pointer<Method>
- >::type add(const std::string& name,
- const std::string& desc,
- Method f,
- const InstanceGetter& getter);
+ 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.)
*
- * @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.
*
@@ -211,21 +335,17 @@ public:
* an LLSD::Array using LLSDArgsMapper and then convert each entry in turn
* to the corresponding parameter type using LLSDParam.
*/
- template<typename Function>
- typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin<Function>
- >::type add(const std::string& name,
- const std::string& desc,
- Function f,
- const LLSD& params,
- const LLSD& defaults=LLSD());
+ 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.
*
- * @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
@@ -233,6 +353,8 @@ public:
* 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.
*
@@ -240,42 +362,96 @@ public:
* an LLSD::Array using LLSDArgsMapper and then convert each entry in turn
* to the corresponding parameter type using LLSDParam.
*/
- template<typename Method, typename InstanceGetter>
- typename boost::enable_if< boost::function_types::is_member_function_pointer<Method>
- >::type add(const std::string& name,
- const std::string& desc,
- Method f,
- const InstanceGetter& getter,
- const LLSD& params,
- const LLSD& defaults=LLSD());
+ 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);
- /// Call a registered callable with an explicitly-specified name. If no
- /// such callable exists, die with LL_ERRS. If the @a event fails to match
- /// the @a required prototype specified at add() time, die with LL_ERRS.
- void operator()(const std::string& name, const LLSD& event) const;
+ /// 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>. If the @a event fails to match the @a required
- /// prototype specified at add() time, die with LL_ERRS.
+ /**
+ * 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 value from the incoming @a event, and call the
- /// callable whose name is specified by that map @a key. If no such
- /// callable exists, die with LL_ERRS. If the @a event fails to match the
- /// @a required prototype specified at add() time, die with LL_ERRS.
- void operator()(const LLSD& event) const;
-
- /// Extract the @a key value from the incoming @a event, call the callable
- /// whose name is specified by that map @a key and return <tt>true</tt>.
- /// If no such callable exists, return <tt>false</tt>. If the @a event
- /// fails to match the @a required prototype specified at add() time, die
- /// with LL_ERRS.
+ /**
+ * 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
@@ -285,22 +461,26 @@ public:
private:
struct DispatchEntry
{
- DispatchEntry(const std::string& desc);
+ 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 void call(const std::string& desc, const LLSD& event) const = 0;
- virtual LLSD addMetadata(LLSD) const = 0;
+ 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)...);
+ }
};
- // Tried using boost::ptr_map<std::string, DispatchEntry>, but ptr_map<>
- // wants its value type to be "clonable," even just to dereference an
- // iterator. I don't want to clone entries -- if I have to copy an entry
- // around, I want it to continue pointing to the same DispatchEntry
- // subclass object. However, I definitely want DispatchMap to destroy
- // DispatchEntry if no references are outstanding at the time an entry is
- // removed. This looks like a job for boost::shared_ptr.
- typedef std::map<std::string, boost::shared_ptr<DispatchEntry> > DispatchMap;
+ typedef std::map<std::string, std::unique_ptr<DispatchEntry> > DispatchMap;
public:
/// We want the flexibility to redefine what data we store per name,
@@ -323,12 +503,58 @@ public:
/// 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:
- template <class CLASS, typename METHOD>
+ 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)
{
@@ -336,38 +562,85 @@ private:
}
else
{
- add(name, desc, boost::bind(method, downcast, _1), required);
+ // 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; });
}
}
- void addFail(const std::string& name, const std::string& classname) const;
- std::string mDesc, mKey;
+ 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;
- // Step 2 of parameter analysis. Instantiating invoker<some_function_type>
- // implicitly sets its From and To parameters to the (compile time) begin
- // and end iterators over that function's parameter types.
- template< typename Function
- , class From = typename boost::mpl::begin< boost::function_types::parameter_types<Function> >::type
- , class To = typename boost::mpl::end< boost::function_types::parameter_types<Function> >::type
- >
- struct invoker;
-
- // deliver LLSD arguments one at a time
- typedef boost::function<LLSD()> args_source;
- // obtain args from an args_source to build param list and call target
- // function
- typedef boost::function<void(const args_source&)> invoker_function;
+ // 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);
@@ -387,101 +660,38 @@ private:
/*****************************************************************************
* LLEventDispatcher template implementation details
*****************************************************************************/
-// Step 3 of parameter analysis, the recursive case.
-template<typename Function, class From, class To>
-struct LLEventDispatcher::invoker
-{
- template<typename T>
- struct remove_cv_ref
- : boost::remove_cv< typename boost::remove_reference<T>::type >
- { };
-
- // apply() accepts an arbitrary boost::fusion sequence as args. It
- // examines the next parameter type in the parameter-types sequence
- // bounded by From and To, obtains the next LLSD object from the passed
- // args_source and constructs an LLSDParam of appropriate type to try
- // to convert the value. It then recurs with the next parameter-types
- // iterator, passing the args sequence thus far.
- template<typename Args>
- static inline
- void apply(Function func, const args_source& argsrc, Args const & args)
- {
- typedef typename boost::mpl::deref<From>::type arg_type;
- typedef typename boost::mpl::next<From>::type next_iter_type;
- typedef typename remove_cv_ref<arg_type>::type plain_arg_type;
-
- invoker<Function, next_iter_type, To>::apply
- ( func, argsrc, boost::fusion::push_back(args, LLSDParam<plain_arg_type>(argsrc())));
- }
-
- // Special treatment for instance (first) parameter of a non-static member
- // function. Accept the instance-getter callable, calling that to produce
- // the first args value. Since we know we're at the top of the recursion
- // chain, we need not also require a partial args sequence from our caller.
- template <typename InstanceGetter>
- static inline
- void method_apply(Function func, const args_source& argsrc, const InstanceGetter& getter)
- {
- typedef typename boost::mpl::next<From>::type next_iter_type;
-
- // Instead of grabbing the first item from argsrc and making an
- // LLSDParam of it, call getter() and pass that as the instance param.
- invoker<Function, next_iter_type, To>::apply
- ( func, argsrc, boost::fusion::push_back(boost::fusion::nil(), boost::ref(getter())));
- }
-};
-
-// Step 4 of parameter analysis, the leaf case. When the general
-// invoker<Function, From, To> logic has advanced From until it matches To,
-// the compiler will pick this template specialization.
-template<typename Function, class To>
-struct LLEventDispatcher::invoker<Function,To,To>
-{
- // the argument list is complete, now call the function
- template<typename Args>
- static inline
- void apply(Function func, const args_source&, Args const & args)
- {
- boost::fusion::invoke(func, args);
- }
-};
-
-template<typename Function>
-typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin<Function> >::type
-LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f)
+template <typename Function>
+void LLEventDispatcher::addV(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),
- boost::function_types::function_arity<Function>::value);
+ LL::function_arity<Function>::value);
}
-template<typename Method, typename InstanceGetter>
-typename boost::enable_if< boost::function_types::is_member_function_pointer<Method> >::type
-LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f,
- const InstanceGetter& getter)
+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),
- boost::function_types::function_arity<Method>::value - 1);
+ LL::function_arity<Method>::value - 1);
}
-template<typename Function>
-typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin<Function> >::type
-LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f,
- const LLSD& params, const LLSD& defaults)
+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 boost::enable_if< boost::function_types::is_member_function_pointer<Method> >::type
-LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f,
- const InstanceGetter& getter,
- const LLSD& params, const LLSD& defaults)
+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);
}
@@ -490,29 +700,45 @@ 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 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)
{
- // 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)
+ {
+ // 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);
+ });
+ };
}
/*****************************************************************************
@@ -521,21 +747,138 @@ LLEventDispatcher::make_invoker(Method f, const InstanceGetter& getter)
/**
* Bundle an LLEventPump and a listener with an LLEventDispatcher. A class
* that contains (or derives from) LLDispatchListener need only specify the
- * LLEventPump name and dispatch key, and add() its methods. Incoming events
- * will automatically be dispatched.
+ * 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.
*/
-class LL_COMMON_API LLDispatchListener: public LLEventDispatcher
+// 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:
- LLDispatchListener(const std::string& pumpname, const std::string& key);
-
- std::string getPumpName() const { return mPump.getName(); }
+ /// 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);
+ 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;
- LLEventStream mPump;
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) */
diff --git a/indra/llcommon/lleventfilter.h b/indra/llcommon/lleventfilter.h
index 7613850fb2..1fb41e0297 100644
--- a/indra/llcommon/lleventfilter.h
+++ b/indra/llcommon/lleventfilter.h
@@ -435,16 +435,61 @@ public:
// generic type-appropriate store through mTarget, construct an
// LLSDParam<T> and store that, thus engaging LLSDParam's custom
// conversions.
- mTarget = LLSDParam<T>(llsd::drill(event, mPath));
+ storeTarget(LLSDParam<T>(llsd::drill(event, mPath)));
return mConsume;
}
private:
+ // This method disambiguates LLStoreListener<LLSD>. Directly assigning
+ // some_LLSD_var = LLSDParam<LLSD>(some_LLSD_value);
+ // is problematic because the compiler has too many choices: LLSD has
+ // multiple assignment operator overloads, and LLSDParam<LLSD> has a
+ // templated conversion operator. But LLSDParam<LLSD> can convert to a
+ // (const LLSD&) parameter, and LLSD::operator=(const LLSD&) works.
+ void storeTarget(const T& value)
+ {
+ mTarget = value;
+ }
+
T& mTarget;
const LLSD mPath;
const bool mConsume;
};
+/**
+ * LLVarHolder bundles a target variable of the specified type. We use it as a
+ * base class so the target variable will be fully constructed by the time a
+ * subclass constructor tries to pass a reference to some other base class.
+ */
+template <typename T>
+struct LLVarHolder
+{
+ T mVar;
+};
+
+/**
+ * LLCaptureListener isa LLStoreListener that bundles the target variable of
+ * interest.
+ */
+template <typename T>
+class LLCaptureListener: public LLVarHolder<T>,
+ public LLStoreListener<T>
+{
+private:
+ using holder = LLVarHolder<T>;
+ using super = LLStoreListener<T>;
+
+public:
+ LLCaptureListener(const LLSD& path=LLSD(), bool consume=false):
+ super(*this, holder::mVar, path, consume)
+ {}
+
+ void set(T&& newval=T()) { holder::mVar = std::forward<T>(newval); }
+
+ const T& get() const { return holder::mVar; }
+ operator const T&() { return holder::mVar; }
+};
+
/*****************************************************************************
* LLEventLogProxy
*****************************************************************************/
diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp
index 0a213bddef..1a305ec3dc 100644
--- a/indra/llcommon/llevents.cpp
+++ b/indra/llcommon/llevents.cpp
@@ -68,19 +68,78 @@
LLEventPumps::LLEventPumps():
mFactories
{
- { "LLEventStream", [](const std::string& name, bool tweak)
+ { "LLEventStream", [](const std::string& name, bool tweak, const std::string& /*type*/)
{ return new LLEventStream(name, tweak); } },
- { "LLEventMailDrop", [](const std::string& name, bool tweak)
+ { "LLEventMailDrop", [](const std::string& name, bool tweak, const std::string& /*type*/)
{ return new LLEventMailDrop(name, tweak); } }
},
mTypes
{
- // LLEventStream is the default for obtain(), so even if somebody DOES
- // call obtain("placeholder"), this sample entry won't break anything.
- { "placeholder", "LLEventStream" }
+// { "placeholder", "LLEventStream" }
}
{}
+bool LLEventPumps::registerTypeFactory(const std::string& type, const TypeFactory& factory)
+{
+ auto found = mFactories.find(type);
+ // can't re-register a TypeFactory for a type name that's already registered
+ if (found != mFactories.end())
+ return false;
+ // doesn't already exist, go ahead and register
+ mFactories[type] = factory;
+ return true;
+}
+
+void LLEventPumps::unregisterTypeFactory(const std::string& type)
+{
+ auto found = mFactories.find(type);
+ if (found != mFactories.end())
+ mFactories.erase(found);
+}
+
+bool LLEventPumps::registerPumpFactory(const std::string& name, const PumpFactory& factory)
+{
+ // Do we already have a pump by this name?
+ if (mPumpMap.find(name) != mPumpMap.end())
+ return false;
+ // Do we already have an override for this pump name?
+ if (mTypes.find(name) != mTypes.end())
+ return false;
+ // Leverage the two-level lookup implemented by mTypes (pump name -> type
+ // name) and mFactories (type name -> factory). We could instead create a
+ // whole separate (pump name -> factory) map, and look in both; or we
+ // could change mTypes to (pump name -> factory) and, for typical type-
+ // based lookups, use a "factory" that looks up the real factory in
+ // mFactories. But this works, and we don't expect many calls to make() -
+ // either explicit or implicit via obtain().
+ // Create a bogus type name extremely unlikely to collide with an actual type.
+ static std::string nul(1, '\0');
+ std::string type_name{ nul + name };
+ mTypes[name] = type_name;
+ // TypeFactory is called with (name, tweak, type), whereas PumpFactory
+ // accepts only name. We could adapt with std::bind(), but this lambda
+ // does the trick.
+ mFactories[type_name] =
+ [factory]
+ (const std::string& name, bool /*tweak*/, const std::string& /*type*/)
+ { return factory(name); };
+ return true;
+}
+
+void LLEventPumps::unregisterPumpFactory(const std::string& name)
+{
+ auto tfound = mTypes.find(name);
+ if (tfound != mTypes.end())
+ {
+ auto ffound = mFactories.find(tfound->second);
+ if (ffound != mFactories.end())
+ {
+ mFactories.erase(ffound);
+ }
+ mTypes.erase(tfound);
+ }
+}
+
LLEventPump& LLEventPumps::obtain(const std::string& name)
{
PumpMap::iterator found = mPumpMap.find(name);
@@ -114,7 +173,7 @@ LLEventPump& LLEventPumps::make(const std::string& name, bool tweak,
// Passing an unrecognized type name is a no-no
LLTHROW(BadType(type));
}
- auto newInstance = (found->second)(name, tweak);
+ auto newInstance = (found->second)(name, tweak, type);
// LLEventPump's constructor implicitly registers each new instance in
// mPumpMap. But remember that we instantiated it (in mOurPumps) so we'll
// delete it later.
diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h
index ae6e5aabc9..c1dbf4392f 100644
--- a/indra/llcommon/llevents.h
+++ b/indra/llcommon/llevents.h
@@ -268,6 +268,45 @@ public:
LLEventPump& make(const std::string& name, bool tweak=false,
const std::string& type=std::string());
+ /// function passed to registerTypeFactory()
+ typedef std::function<LLEventPump*(const std::string& name, bool tweak, const std::string& type)> TypeFactory;
+
+ /**
+ * Register a TypeFactory for use with make(). When make() is called with
+ * the specified @a type string, call @a factory(name, tweak, type) to
+ * instantiate it.
+ *
+ * Returns true if successfully registered, false if there already exists
+ * a TypeFactory for the specified @a type name.
+ */
+ bool registerTypeFactory(const std::string& type, const TypeFactory& factory);
+ void unregisterTypeFactory(const std::string& type);
+
+ /// function passed to registerPumpFactory()
+ typedef std::function<LLEventPump*(const std::string&)> PumpFactory;
+
+ /**
+ * Register a PumpFactory for use with obtain(). When obtain() is called
+ * with the specified @a name string, if an LLEventPump with the specified
+ * @a name doesn't already exist, call @a factory(name) to instantiate it.
+ *
+ * Returns true if successfully registered, false if there already exists
+ * a factory override for the specified @a name.
+ *
+ * PumpFactory does not support @a tweak because it's only called when
+ * <i>that particular</i> @a name is passed to obtain(). Bear in mind that
+ * <tt>obtain(name)</tt> might still bypass the caller's PumpFactory for a
+ * couple different reasons:
+ *
+ * * registerPumpFactory() returns false because there's already a factory
+ * override for the specified @name
+ * * between a successful <tt>registerPumpFactory(name)</tt> call (returns
+ * true) and a call to <tt>obtain(name)</tt>, someone explicitly
+ * instantiated an LLEventPump(name), so obtain(name) returned that.
+ */
+ bool registerPumpFactory(const std::string& name, const PumpFactory& factory);
+ void unregisterPumpFactory(const std::string& name);
+
/**
* Find the named LLEventPump instance. If it exists post the message to it.
* If the pump does not exist, do nothing.
@@ -325,13 +364,13 @@ testable:
typedef std::set<LLEventPump*> PumpSet;
PumpSet mOurPumps;
// for make(), map string type name to LLEventPump subclass factory function
- typedef std::map<std::string, std::function<LLEventPump*(const std::string&, bool)>> PumpFactories;
+ typedef std::map<std::string, TypeFactory> TypeFactories;
// Data used by make().
// One might think mFactories and mTypes could reasonably be static. So
// they could -- if not for the fact that make() or obtain() might be
// called before this module's static variables have been initialized.
// This is why we use singletons in the first place.
- PumpFactories mFactories;
+ TypeFactories mFactories;
// for obtain(), map desired string instance name to string type when
// obtain() must create the instance
diff --git a/indra/llcommon/llleap.cpp b/indra/llcommon/llleap.cpp
index 5b5bf97cef..8f88e728ce 100644
--- a/indra/llcommon/llleap.cpp
+++ b/indra/llcommon/llleap.cpp
@@ -340,11 +340,28 @@ public:
}
else
{
- // The LLSD object we got from our stream contains the keys we
- // need.
- LLEventPumps::instance().obtain(data["pump"]).post(data["data"]);
- // Block calls to this method; resetting mBlocker unblocks calls
- // to the other method.
+ try
+ {
+ // The LLSD object we got from our stream contains the
+ // keys we need.
+ LLEventPumps::instance().obtain(data["pump"]).post(data["data"]);
+ }
+ catch (const std::exception& err)
+ {
+ // No plugin should be allowed to crash the viewer by
+ // driving an exception -- intentionally or not.
+ LOG_UNHANDLED_EXCEPTION(stringize("handling request ", data));
+ // Whether or not the plugin added a "reply" key to the
+ // request, send a reply. We happen to know who originated
+ // this request, and the reply LLEventPump of interest.
+ // Not our problem if the plugin ignores the reply event.
+ data["reply"] = mReplyPump.getName();
+ sendReply(llsd::map("error",
+ stringize(LLError::Log::classname(err), ": ", err.what())),
+ data);
+ }
+ // Block calls to this method; resetting mBlocker unblocks
+ // calls to the other method.
mBlocker.reset(new LLEventPump::Blocker(mStdoutDataConnection));
// Go check for any more pending events in the buffer.
if (childout.size())
diff --git a/indra/llcommon/llleaplistener.cpp b/indra/llcommon/llleaplistener.cpp
index 11bfec1b31..471f52e91c 100644
--- a/indra/llcommon/llleaplistener.cpp
+++ b/indra/llcommon/llleaplistener.cpp
@@ -14,14 +14,16 @@
// associated header
#include "llleaplistener.h"
// STL headers
-#include <map>
+#include <algorithm> // std::find_if
#include <functional>
+#include <map>
+#include <set>
// std headers
// external library headers
-#include <boost/foreach.hpp>
// other Linden headers
-#include "lluuid.h"
+#include "lazyeventapi.h"
#include "llsdutil.h"
+#include "lluuid.h"
#include "stringize.h"
/*****************************************************************************
@@ -110,7 +112,7 @@ LLLeapListener::~LLLeapListener()
// value_type, and Bad Things would happen if you copied an
// LLTempBoundListener. (Destruction of the original would disconnect the
// listener, invalidating every stored connection.)
- BOOST_FOREACH(ListenersMap::value_type& pair, mListeners)
+ for (ListenersMap::value_type& pair : mListeners)
{
pair.second.disconnect();
}
@@ -208,31 +210,65 @@ void LLLeapListener::getAPIs(const LLSD& request) const
{
Response reply(LLSD(), request);
+ // first, traverse existing LLEventAPI instances
+ std::set<std::string> instances;
for (auto& ea : LLEventAPI::instance_snapshot())
{
- LLSD info;
- info["desc"] = ea.getDesc();
- reply[ea.getName()] = info;
+ // remember which APIs are actually instantiated
+ instances.insert(ea.getName());
+ reply[ea.getName()] = llsd::map("desc", ea.getDesc());
+ }
+ // supplement that with *potential* instances: that is, instances of
+ // LazyEventAPI that can each instantiate an LLEventAPI on demand
+ for (const auto& lea : LL::LazyEventAPIBase::instance_snapshot())
+ {
+ // skip any LazyEventAPI that's already instantiated its LLEventAPI
+ if (instances.find(lea.getName()) == instances.end())
+ {
+ reply[lea.getName()] = llsd::map("desc", lea.getDesc());
+ }
}
}
+// Because LazyEventAPI deliberately mimics LLEventAPI's query API, this
+// function can be passed either -- even though they're unrelated types.
+template <typename API>
+void reportAPI(LLEventAPI::Response& reply, const API& api)
+{
+ reply["name"] = api.getName();
+ reply["desc"] = api.getDesc();
+ reply["key"] = api.getDispatchKey();
+ LLSD ops;
+ for (const auto& namedesc : api)
+ {
+ ops.append(api.getMetadata(namedesc.first));
+ }
+ reply["ops"] = ops;
+}
+
void LLLeapListener::getAPI(const LLSD& request) const
{
Response reply(LLSD(), request);
- auto found = LLEventAPI::getInstance(request["api"]);
- if (found)
+ // check first among existing LLEventAPI instances
+ auto foundea = LLEventAPI::getInstance(request["api"]);
+ if (foundea)
+ {
+ reportAPI(reply, *foundea);
+ }
+ else
{
- reply["name"] = found->getName();
- reply["desc"] = found->getDesc();
- reply["key"] = found->getDispatchKey();
- LLSD ops;
- for (LLEventAPI::const_iterator oi(found->begin()), oend(found->end());
- oi != oend; ++oi)
+ // Here the requested LLEventAPI doesn't yet exist, but do we have a
+ // registered LazyEventAPI for it?
+ LL::LazyEventAPIBase::instance_snapshot snap;
+ auto foundlea = std::find_if(snap.begin(), snap.end(),
+ [api = request["api"].asString()]
+ (const auto& lea)
+ { return (lea.getName() == api); });
+ if (foundlea != snap.end())
{
- ops.append(found->getMetadata(oi->first));
+ reportAPI(reply, *foundlea);
}
- reply["ops"] = ops;
}
}
diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp
index 97a38ea992..0d65762284 100644
--- a/indra/llcommon/llprocess.cpp
+++ b/indra/llcommon/llprocess.cpp
@@ -529,6 +529,7 @@ LLProcess::LLProcess(const LLSDOrParams& params):
// preserve existing semantics, we promise that mAttached defaults to the
// same setting as mAutokill.
mAttached(params.attached.isProvided()? params.attached : params.autokill),
+ mPool(NULL),
mPipes(NSLOTS)
{
// Hmm, when you construct a ptr_vector with a size, it merely reserves
@@ -549,8 +550,14 @@ LLProcess::LLProcess(const LLSDOrParams& params):
mPostend = params.postend;
+ apr_pool_create(&mPool, gAPRPoolp);
+ if (!mPool)
+ {
+ LLTHROW(LLProcessError(STRINGIZE("failed to create apr pool")));
+ }
+
apr_procattr_t *procattr = NULL;
- chkapr(apr_procattr_create(&procattr, gAPRPoolp));
+ chkapr(apr_procattr_create(&procattr, mPool));
// IQA-490, CHOP-900: On Windows, ask APR to jump through hoops to
// constrain the set of handles passed to the child process. Before we
@@ -689,14 +696,14 @@ LLProcess::LLProcess(const LLSDOrParams& params):
// one. Hand-expand chkapr() macro so we can fill in the actual command
// string instead of the variable names.
if (ll_apr_warn_status(apr_proc_create(&mProcess, argv[0], &argv[0], NULL, procattr,
- gAPRPoolp)))
+ mPool)))
{
LLTHROW(LLProcessError(STRINGIZE(params << " failed")));
}
// arrange to call status_callback()
apr_proc_other_child_register(&mProcess, &LLProcess::status_callback, this, mProcess.in,
- gAPRPoolp);
+ mPool);
// and make sure we poll it once per "mainloop" tick
sProcessListener.addPoll(*this);
mStatus.mState = RUNNING;
@@ -815,6 +822,12 @@ LLProcess::~LLProcess()
{
kill("destructor");
}
+
+ if (mPool)
+ {
+ apr_pool_destroy(mPool);
+ mPool = NULL;
+ }
}
bool LLProcess::kill(const std::string& who)
diff --git a/indra/llcommon/llprocess.h b/indra/llcommon/llprocess.h
index e3386ad88e..0842f2eb07 100644
--- a/indra/llcommon/llprocess.h
+++ b/indra/llcommon/llprocess.h
@@ -568,6 +568,7 @@ private:
// explicitly want this ptr_vector to be able to store NULLs
typedef boost::ptr_vector< boost::nullable<BasePipe> > PipeVector;
PipeVector mPipes;
+ apr_pool_t* mPool;
};
/// for logging
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 372278c51a..ad54d1b0be 100644
--- a/indra/llcommon/llsdutil.h
+++ b/indra/llcommon/llsdutil.h
@@ -29,8 +29,14 @@
#ifndef LL_LLSDUTIL_H
#define LL_LLSDUTIL_H
+#include "apply.h" // LL::invoke()
+#include "function_types.h" // LL::function_arity
#include "llsd.h"
#include <boost/functional/hash.hpp>
+#include <cassert>
+#include <memory> // std::shared_ptr
+#include <type_traits>
+#include <vector>
// U32
LL_COMMON_API LLSD ll_sd_from_U32(const U32);
@@ -298,6 +304,11 @@ LLSD map(Ts&&... vs)
/*****************************************************************************
* LLSDParam
*****************************************************************************/
+struct LLSDParamBase
+{
+ virtual ~LLSDParamBase() {}
+};
+
/**
* LLSDParam is a customization point for passing LLSD values to function
* parameters of more or less arbitrary type. LLSD provides a small set of
@@ -315,7 +326,7 @@ LLSD map(Ts&&... vs)
* @endcode
*/
template <typename T>
-class LLSDParam
+class LLSDParam: public LLSDParamBase
{
public:
/**
@@ -323,13 +334,66 @@ public:
* value for later retrieval
*/
LLSDParam(const LLSD& value):
- _value(value)
+ value_(value)
{}
- operator T() const { return _value; }
+ operator T() const { return value_; }
private:
- T _value;
+ T value_;
+};
+
+/**
+ * LLSDParam<LLSD> is for when you don't already have the target parameter
+ * type in hand. Instantiate LLSDParam<LLSD>(your LLSD object), and the
+ * templated conversion operator will try to select a more specific LLSDParam
+ * specialization.
+ */
+template <>
+class LLSDParam<LLSD>: public LLSDParamBase
+{
+private:
+ LLSD value_;
+ // LLSDParam<LLSD>::operator T() works by instantiating an LLSDParam<T> on
+ // demand. Returning that engages LLSDParam<T>::operator T(), producing
+ // the desired result. But LLSDParam<const char*> owns a std::string whose
+ // c_str() is returned by its operator const char*(). If we return a temp
+ // LLSDParam<const char*>, the compiler can destroy it right away, as soon
+ // as we've called operator const char*(). That's a problem! That
+ // invalidates the const char* we've just passed to the subject function.
+ // This LLSDParam<LLSD> is presumably guaranteed to survive until the
+ // subject function has returned, so we must ensure that any constructed
+ // LLSDParam<T> lives just as long as this LLSDParam<LLSD> does. Putting
+ // each LLSDParam<T> on the heap and capturing a smart pointer in a vector
+ // works. We would have liked to use std::unique_ptr, but vector entries
+ // must be copyable.
+ // (Alternatively we could assume that every instance of LLSDParam<LLSD>
+ // will be asked for at most ONE conversion. We could store a scalar
+ // std::unique_ptr and, when constructing an new LLSDParam<T>, assert that
+ // the unique_ptr is empty. But some future change in usage patterns, and
+ // consequent failure of that assertion, would be very mysterious. Instead
+ // of explaining how to fix it, just fix it now.)
+ mutable std::vector<std::shared_ptr<LLSDParamBase>> converters_;
+
+public:
+ LLSDParam(const LLSD& value): value_(value) {}
+
+ /// if we're literally being asked for an LLSD parameter, avoid infinite
+ /// recursion
+ operator LLSD() const { return value_; }
+
+ /// otherwise, instantiate a more specific LLSDParam<T> to convert; that
+ /// preserves the existing customization mechanism
+ template <typename T>
+ operator T() const
+ {
+ // capture 'ptr' with the specific subclass type because converters_
+ // only stores LLSDParamBase pointers
+ auto ptr{ std::make_shared<LLSDParam<std::decay_t<T>>>(value_) };
+ // keep the new converter alive until we ourselves are destroyed
+ converters_.push_back(ptr);
+ return *ptr;
+ }
};
/**
@@ -346,17 +410,17 @@ private:
*/
#define LLSDParam_for(T, AS) \
template <> \
-class LLSDParam<T> \
+class LLSDParam<T>: public LLSDParamBase \
{ \
public: \
LLSDParam(const LLSD& value): \
- _value((T)value.AS()) \
+ value_((T)value.AS()) \
{} \
\
- operator T() const { return _value; } \
+ operator T() const { return value_; } \
\
private: \
- T _value; \
+ T value_; \
}
LLSDParam_for(float, asReal);
@@ -372,31 +436,31 @@ LLSDParam_for(LLSD::Binary, asBinary);
* safely pass an LLSDParam<const char*>(yourLLSD).
*/
template <>
-class LLSDParam<const char*>
+class LLSDParam<const char*>: public LLSDParamBase
{
private:
// The difference here is that we store a std::string rather than a const
// char*. It's important that the LLSDParam object own the std::string.
- std::string _value;
+ std::string value_;
// We don't bother storing the incoming LLSD object, but we do have to
- // distinguish whether _value is an empty string because the LLSD object
+ // distinguish whether value_ is an empty string because the LLSD object
// contains an empty string or because it's isUndefined().
- bool _undefined;
+ bool undefined_;
public:
LLSDParam(const LLSD& value):
- _value(value),
- _undefined(value.isUndefined())
+ value_(value),
+ undefined_(value.isUndefined())
{}
- // The const char* we retrieve is for storage owned by our _value member.
+ // The const char* we retrieve is for storage owned by our value_ member.
// That's how we guarantee that the const char* is valid for the lifetime
// of this LLSDParam object. Constructing your LLSDParam in the argument
// list should ensure that the LLSDParam object will persist for the
// duration of the function call.
operator const char*() const
{
- if (_undefined)
+ if (undefined_)
{
// By default, an isUndefined() LLSD object's asString() method
// will produce an empty string. But for a function accepting
@@ -406,7 +470,7 @@ public:
// case, though, no LLSD value could pass NULL.
return NULL;
}
- return _value.c_str();
+ return value_.c_str();
}
};
@@ -555,4 +619,56 @@ struct hash<LLSD>
}
};
}
+
+namespace LL
+{
+
+/*****************************************************************************
+* apply(function, LLSD array)
+*****************************************************************************/
+// validate incoming LLSD blob, and return an LLSD array suitable to pass to
+// the function of interest
+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
+// tuple to the function -- how would make_tuple() deduce the correct
+// parameter type for each entry? We must go directly to the target function.
+template <typename CALLABLE, std::size_t... I>
+auto apply_impl(CALLABLE&& func, const LLSD& array, std::index_sequence<I...>)
+{
+ // call func(unpacked args), using generic LLSDParam<LLSD> to convert each
+ // entry in 'array' to the target parameter type
+ 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
+ * time how many arguments to pass. This isn't Python. (But see apply_n() to
+ * pass a specific number of args to a variadic function.)
+ */
+template <typename CALLABLE>
+auto apply(CALLABLE&& func, const LLSD& args)
+{
+ // infer arity from the definition of func
+ constexpr auto arity = 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
+
#endif // LL_LLSDUTIL_H
diff --git a/indra/llcommon/tests/apply_test.cpp b/indra/llcommon/tests/apply_test.cpp
new file mode 100644
index 0000000000..56b497e0c8
--- /dev/null
+++ b/indra/llcommon/tests/apply_test.cpp
@@ -0,0 +1,240 @@
+/**
+ * @file apply_test.cpp
+ * @author Nat Goodspeed
+ * @date 2022-12-19
+ * @brief Test 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
+#include <iomanip>
+// external library headers
+// other Linden headers
+#include "llsd.h"
+#include "llsdutil.h"
+#include <array>
+#include <string>
+#include <vector>
+
+// for ensure_equals
+std::ostream& operator<<(std::ostream& out, const std::vector<std::string>& stringvec)
+{
+ const char* delim = "[";
+ for (const auto& str : stringvec)
+ {
+ out << delim << std::quoted(str);
+ delim = ", ";
+ }
+ return out << ']';
+}
+
+// the above must be declared BEFORE ensure_equals(std::vector<std::string>)
+#include "../test/lltut.h"
+
+/*****************************************************************************
+* TUT
+*****************************************************************************/
+namespace tut
+{
+ namespace statics
+ {
+ /*------------------------------ data ------------------------------*/
+ // Although we're using types from the LLSD namespace, we're not
+ // constructing LLSD values, but rather instances of the C++ types
+ // supported by LLSD.
+ static LLSD::Boolean b{true};
+ static LLSD::Integer i{17};
+ static LLSD::Real f{3.14};
+ static LLSD::String s{ "hello" };
+ static LLSD::UUID uu{ "baadf00d-dead-beef-baad-feedb0ef" };
+ static LLSD::Date dt{ "2022-12-19" };
+ static LLSD::URI uri{ "http://secondlife.com" };
+ static LLSD::Binary bin{ 0x01, 0x02, 0x03, 0x04, 0x05 };
+
+ static std::vector<LLSD::String> quick
+ {
+ "The", "quick", "brown", "fox", "etc."
+ };
+
+ static std::array<int, 5> fibs
+ {
+ 0, 1, 1, 2, 3
+ };
+
+ // ensure that apply() actually reaches the target method --
+ // lack of ensure_equals() failure could be due to no-op apply()
+ bool called{ false };
+ // capture calls from collect()
+ std::vector<std::string> collected;
+
+ /*------------------------- test functions -------------------------*/
+ void various(LLSD::Boolean b, LLSD::Integer i, LLSD::Real f, const LLSD::String& s,
+ const LLSD::UUID& uu, const LLSD::Date& dt,
+ const LLSD::URI& uri, const LLSD::Binary& bin)
+ {
+ called = true;
+ ensure_equals( "b mismatch", b, statics::b);
+ ensure_equals( "i mismatch", i, statics::i);
+ ensure_equals( "f mismatch", f, statics::f);
+ ensure_equals( "s mismatch", s, statics::s);
+ ensure_equals( "uu mismatch", uu, statics::uu);
+ ensure_equals( "dt mismatch", dt, statics::dt);
+ ensure_equals("uri mismatch", uri, statics::uri);
+ ensure_equals("bin mismatch", bin, statics::bin);
+ }
+
+ void strings(std::string s0, std::string s1, std::string s2, std::string s3, std::string s4)
+ {
+ called = true;
+ ensure_equals("s0 mismatch", s0, statics::quick[0]);
+ ensure_equals("s1 mismatch", s1, statics::quick[1]);
+ ensure_equals("s2 mismatch", s2, statics::quick[2]);
+ ensure_equals("s3 mismatch", s3, statics::quick[3]);
+ ensure_equals("s4 mismatch", s4, statics::quick[4]);
+ }
+
+ void ints(int i0, int i1, int i2, int i3, int i4)
+ {
+ called = true;
+ ensure_equals("i0 mismatch", i0, statics::fibs[0]);
+ ensure_equals("i1 mismatch", i1, statics::fibs[1]);
+ ensure_equals("i2 mismatch", i2, statics::fibs[2]);
+ ensure_equals("i3 mismatch", i3, statics::fibs[3]);
+ ensure_equals("i4 mismatch", i4, statics::fibs[4]);
+ }
+
+ void sdfunc(const LLSD& sd)
+ {
+ called = true;
+ ensure_equals("sd mismatch", sd.asInteger(), statics::i);
+ }
+
+ void intfunc(int i)
+ {
+ called = true;
+ ensure_equals("i mismatch", i, statics::i);
+ }
+
+ void voidfunc()
+ {
+ called = true;
+ }
+
+ // recursion tail
+ void collect()
+ {
+ called = true;
+ }
+
+ // collect(arbitrary)
+ template <typename... ARGS>
+ void collect(const std::string& first, ARGS&&... rest)
+ {
+ statics::collected.push_back(first);
+ collect(std::forward<ARGS>(rest)...);
+ }
+ } // namespace statics
+
+ struct apply_data
+ {
+ apply_data()
+ {
+ // reset called before each test
+ statics::called = false;
+ statics::collected.clear();
+ }
+ };
+ typedef test_group<apply_data> apply_group;
+ typedef apply_group::object object;
+ apply_group applygrp("apply");
+
+ template<> template<>
+ void object::test<1>()
+ {
+ set_test_name("apply(tuple)");
+ LL::apply(statics::various,
+ std::make_tuple(statics::b, statics::i, statics::f, statics::s,
+ statics::uu, statics::dt, statics::uri, statics::bin));
+ ensure("apply(tuple) failed", statics::called);
+ }
+
+ template<> template<>
+ void object::test<2>()
+ {
+ set_test_name("apply(array)");
+ LL::apply(statics::ints, statics::fibs);
+ ensure("apply(array) failed", statics::called);
+ }
+
+ template<> template<>
+ void object::test<3>()
+ {
+ set_test_name("apply(vector)");
+ LL::apply(statics::strings, statics::quick);
+ ensure("apply(vector) failed", statics::called);
+ }
+
+ // The various apply(LLSD) tests exercise only the success cases because
+ // the failure cases trigger assert() fail, which is hard to catch.
+ template<> template<>
+ void object::test<4>()
+ {
+ set_test_name("apply(LLSD())");
+ LL::apply(statics::voidfunc, LLSD());
+ ensure("apply(LLSD()) failed", statics::called);
+ }
+
+ template<> template<>
+ void object::test<5>()
+ {
+ set_test_name("apply(fn(int), LLSD scalar)");
+ LL::apply(statics::intfunc, LLSD(statics::i));
+ ensure("apply(fn(int), LLSD scalar) failed", statics::called);
+ }
+
+ template<> template<>
+ void object::test<6>()
+ {
+ set_test_name("apply(fn(LLSD), LLSD scalar)");
+ // This test verifies that LLSDParam<LLSD> doesn't send the compiler
+ // into infinite recursion when the target is itself LLSD.
+ LL::apply(statics::sdfunc, LLSD(statics::i));
+ ensure("apply(fn(LLSD), LLSD scalar) failed", statics::called);
+ }
+
+ template<> template<>
+ void object::test<7>()
+ {
+ set_test_name("apply(LLSD array)");
+ LL::apply(statics::various,
+ llsd::array(statics::b, statics::i, statics::f, statics::s,
+ statics::uu, statics::dt, statics::uri, statics::bin));
+ ensure("apply(LLSD array) failed", statics::called);
+ }
+
+ template<> template<>
+ void object::test<8>()
+ {
+ set_test_name("VAPPLY()");
+ // Make a std::array<std::string> from statics::quick. We can't call a
+ // variadic function with a data structure of dynamic length.
+ std::array<std::string, 5> strray;
+ for (size_t i = 0; i < strray.size(); ++i)
+ strray[i] = statics::quick[i];
+ // This doesn't work: the compiler doesn't know which overload of
+ // collect() to pass to LL::apply().
+ // LL::apply(statics::collect, strray);
+ // That's what VAPPLY() is for.
+ VAPPLY(statics::collect, strray);
+ ensure("VAPPLY() failed", statics::called);
+ ensure_equals("collected mismatch", statics::collected, statics::quick);
+ }
+} // namespace tut
diff --git a/indra/llcommon/tests/lazyeventapi_test.cpp b/indra/llcommon/tests/lazyeventapi_test.cpp
new file mode 100644
index 0000000000..31b2d6d17f
--- /dev/null
+++ b/indra/llcommon/tests/lazyeventapi_test.cpp
@@ -0,0 +1,136 @@
+/**
+ * @file lazyeventapi_test.cpp
+ * @author Nat Goodspeed
+ * @date 2022-06-18
+ * @brief Test for lazyeventapi.
+ *
+ * $LicenseInfo:firstyear=2022&license=viewerlgpl$
+ * Copyright (c) 2022, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "lazyeventapi.h"
+// STL headers
+// std headers
+// external library headers
+// other Linden headers
+#include "../test/lltut.h"
+#include "llevents.h"
+#include "llsdutil.h"
+
+// observable side effect, solely for testing
+static LLSD data;
+
+// LLEventAPI listener subclass
+class MyListener: public LLEventAPI
+{
+public:
+ // need this trivial forwarding constructor
+ // (of course do any other initialization your subclass requires)
+ MyListener(const LL::LazyEventAPIParams& params):
+ LLEventAPI(params)
+ {}
+
+ // example operation, registered by LazyEventAPI subclass below
+ void set_data(const LLSD& event)
+ {
+ data = event["data"];
+ }
+};
+
+// LazyEventAPI registrar subclass
+class MyRegistrar: public LL::LazyEventAPI<MyListener>
+{
+ using super = LL::LazyEventAPI<MyListener>;
+ using super::listener;
+public:
+ // LazyEventAPI subclass initializes like a classic LLEventAPI subclass
+ // constructor, with API name and desc plus add() calls for the defined
+ // operations
+ MyRegistrar():
+ super("Test", "This is a test LLEventAPI")
+ {
+ add("set", "This is a set operation", &listener::set_data);
+ }
+};
+// Normally we'd declare a static instance of MyRegistrar -- but because we
+// want to test both with and without, defer declaration to individual test
+// methods.
+
+/*****************************************************************************
+* TUT
+*****************************************************************************/
+namespace tut
+{
+ struct lazyeventapi_data
+ {
+ lazyeventapi_data()
+ {
+ // before every test, reset 'data'
+ data.clear();
+ }
+ ~lazyeventapi_data()
+ {
+ // after every test, reset LLEventPumps
+ LLEventPumps::deleteSingleton();
+ }
+ };
+ typedef test_group<lazyeventapi_data> lazyeventapi_group;
+ typedef lazyeventapi_group::object object;
+ lazyeventapi_group lazyeventapigrp("lazyeventapi");
+
+ template<> template<>
+ void object::test<1>()
+ {
+ set_test_name("LazyEventAPI");
+ // this is where the magic (should) happen
+ // 'register' still a keyword until C++17
+ MyRegistrar regster;
+ LLEventPumps::instance().obtain("Test").post(llsd::map("op", "set", "data", "hey"));
+ ensure_equals("failed to set data", data.asString(), "hey");
+ }
+
+ template<> template<>
+ void object::test<2>()
+ {
+ set_test_name("No LazyEventAPI");
+ // Because the MyRegistrar declaration in test<1>() is local, because
+ // it has been destroyed, we fully expect NOT to reach a MyListener
+ // instance with this post.
+ LLEventPumps::instance().obtain("Test").post(llsd::map("op", "set", "data", "moot"));
+ ensure("accidentally set data", ! data.isDefined());
+ }
+
+ template<> template<>
+ void object::test<3>()
+ {
+ set_test_name("LazyEventAPI metadata");
+ MyRegistrar regster;
+ // Of course we have 'regster' in hand; we don't need to search for
+ // it. But this next test verifies that we can find (all) LazyEventAPI
+ // instances using LazyEventAPIBase::instance_snapshot. Normally we
+ // wouldn't search; normally we'd just look at each instance in the
+ // loop body.
+ const MyRegistrar* found = nullptr;
+ for (const auto& registrar : LL::LazyEventAPIBase::instance_snapshot())
+ if ((found = dynamic_cast<const MyRegistrar*>(&registrar)))
+ break;
+ ensure("Failed to find MyRegistrar via LLInstanceTracker", found);
+
+ ensure_equals("wrong API name", found->getName(), "Test");
+ ensure_contains("wrong API desc", found->getDesc(), "test LLEventAPI");
+ ensure_equals("wrong API field", found->getDispatchKey(), "op");
+ // Normally we'd just iterate over *found. But for test purposes,
+ // actually capture the range of NameDesc pairs in a vector.
+ std::vector<LL::LazyEventAPIBase::NameDesc> ops{ found->begin(), found->end() };
+ ensure_equals("failed to find operations", ops.size(), 1);
+ ensure_equals("wrong operation name", ops[0].first, "set");
+ ensure_contains("wrong operation desc", ops[0].second, "set operation");
+ LLSD metadata{ found->getMetadata(ops[0].first) };
+ ensure_equals("bad metadata name", metadata["name"].asString(), ops[0].first);
+ ensure_equals("bad metadata desc", metadata["desc"].asString(), ops[0].second);
+ }
+} // namespace tut
diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp
index 466f11f52a..b0c532887c 100644
--- a/indra/llcommon/tests/lleventdispatcher_test.cpp
+++ b/indra/llcommon/tests/lleventdispatcher_test.cpp
@@ -18,9 +18,12 @@
// external library headers
// other Linden headers
#include "../test/lltut.h"
+#include "lleventfilter.h"
#include "llsd.h"
#include "llsdutil.h"
+#include "llevents.h"
#include "stringize.h"
+#include "StringVec.h"
#include "tests/wrapllerrs.h"
#include "../test/catch_and_store_what_in.h"
#include "../test/debug.h"
@@ -32,8 +35,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>
@@ -177,6 +178,7 @@ struct Vars
/*-------- Arbitrary-params (non-const, const, static) methods ---------*/
void methodna(NPARAMSa)
{
+ DEBUG;
// Because our const char* param cp might be NULL, and because we
// intend to capture the value in a std::string, have to distinguish
// between the NULL value and any non-NULL value. Use a convention
@@ -188,7 +190,7 @@ struct Vars
else
vcp = std::string("'") + cp + "'";
- debug()("methodna(", b,
+ this->debug()("methodna(", b,
", ", i,
", ", f,
", ", d,
@@ -205,7 +207,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);
}
@@ -226,7 +228,8 @@ struct Vars
void cmethodna(NPARAMSa) const
{
- debug()('c', NONL);
+ DEBUG;
+ this->debug()('c', NONL);
const_cast<Vars*>(this)->methodna(NARGSa);
}
@@ -315,6 +318,31 @@ void freenb(NPARAMSb)
*****************************************************************************/
namespace tut
{
+ void ensure_has(const std::string& outer, const std::string& inner)
+ {
+ ensure(stringize("'", outer, "' does not contain '", inner, "'"),
+ outer.find(inner) != std::string::npos);
+ }
+
+ template <typename CALLABLE>
+ std::string call_exc(CALLABLE&& func, const std::string& exc_frag)
+ {
+ std::string what =
+ catch_what<LLEventDispatcher::DispatchError>(std::forward<CALLABLE>(func));
+ ensure_has(what, exc_frag);
+ return what;
+ }
+
+ template <typename CALLABLE>
+ void call_logerr(CALLABLE&& func, const std::string& frag)
+ {
+ CaptureLog capture;
+ // the error should be logged; we just need to stop the exception
+ // propagating
+ catch_what<LLEventDispatcher::DispatchError>(std::forward<CALLABLE>(func));
+ capture.messageWith(frag);
+ }
+
struct lleventdispatcher_data
{
Debug debug{"test"};
@@ -397,9 +425,9 @@ namespace tut
work.add(name, desc, &Dispatcher::cmethod1, required);
// Non-subclass method with/out required params
addf("method1", "method1", &v);
- work.add(name, desc, boost::bind(&Vars::method1, boost::ref(v), _1));
+ work.add(name, desc, [this](const LLSD& args){ return v.method1(args); });
addf("method1_req", "method1", &v);
- work.add(name, desc, boost::bind(&Vars::method1, boost::ref(v), _1), required);
+ work.add(name, desc, [this](const LLSD& args){ return v.method1(args); }, required);
/*--------------- Arbitrary params, array style ----------------*/
@@ -461,7 +489,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] =
@@ -471,7 +499,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]);
@@ -583,6 +611,7 @@ namespace tut
void addf(const std::string& n, const std::string& d, Vars* v)
{
+ debug("addf('", n, "', '", d, "')");
// This method is to capture in our own DescMap the name and
// description of every registered function, for metadata query
// testing.
@@ -598,19 +627,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
@@ -621,41 +645,49 @@ 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)
+ std::string call_exc(const std::string& func, const LLSD& args, const std::string& exc_frag)
{
- ensure(STRINGIZE("'" << outer << "' does not contain '" << inner << "'").c_str(),
- outer.find(inner) != std::string::npos);
+ return tut::call_exc(
+ [this, func, args]()
+ {
+ if (func.empty())
+ {
+ work(args);
+ }
+ else
+ {
+ work(func, args);
+ }
+ },
+ exc_frag);
}
- void call_exc(const std::string& func, const LLSD& args, const std::string& exc_frag)
+ void call_logerr(const std::string& func, const LLSD& args, const std::string& frag)
{
- std::string threw = catch_what<std::runtime_error>([this, &func, &args](){
- work(func, args);
- });
- ensure_has(threw, exc_frag);
+ tut::call_logerr([this, func, args](){ work(func, args); }, frag);
}
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;
}
@@ -724,7 +756,7 @@ namespace tut
set_test_name("map-style registration with non-array params");
// Pass "param names" as scalar or as map
LLSD attempts(llsd::array(17, LLSDMap("pi", 3.14)("two", 2)));
- foreach(LLSD ae, inArray(attempts))
+ for (LLSD ae: inArray(attempts))
{
std::string threw = catch_what<std::exception>([this, &ae](){
work.add("freena_err", "freena", freena, ae);
@@ -799,7 +831,7 @@ namespace tut
{
set_test_name("query Callables with/out required params");
LLSD names(llsd::array("free1", "Dmethod1", "Dcmethod1", "method1"));
- foreach(LLSD nm, inArray(names))
+ for (LLSD nm: inArray(names))
{
LLSD metadata(getMetadata(nm));
ensure_equals("name mismatch", metadata["name"], nm);
@@ -828,19 +860,19 @@ namespace tut
(5, llsd::array("freena_array", "smethodna_array", "methodna_array")),
llsd::array
(5, llsd::array("freenb_array", "smethodnb_array", "methodnb_array"))));
- foreach(LLSD ae, inArray(expected))
+ for (LLSD ae: inArray(expected))
{
LLSD::Integer arity(ae[0].asInteger());
LLSD names(ae[1]);
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());
}
@@ -854,7 +886,7 @@ namespace tut
// - (Free function | non-static method), map style, no params (ergo
// no defaults)
LLSD names(llsd::array("free0_map", "smethod0_map", "method0_map"));
- foreach(LLSD nm, inArray(names))
+ for (LLSD nm: inArray(names))
{
LLSD metadata(getMetadata(nm));
ensure_equals("name mismatch", metadata["name"], nm);
@@ -884,7 +916,7 @@ namespace tut
llsd::array("smethodnb_map_adft", "smethodnb_map_mdft"),
llsd::array("methodna_map_adft", "methodna_map_mdft"),
llsd::array("methodnb_map_adft", "methodnb_map_mdft")));
- foreach(LLSD eq, inArray(equivalences))
+ for (LLSD eq: inArray(equivalences))
{
LLSD adft(eq[0]);
LLSD mdft(eq[1]);
@@ -898,8 +930,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);
}
}
@@ -915,7 +947,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
@@ -943,9 +975,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);
}
@@ -990,7 +1022,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]);
@@ -1003,14 +1035,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);
}
}
@@ -1031,13 +1063,7 @@ namespace tut
{
set_test_name("call with bad name");
call_exc("freek", LLSD(), "not found");
- // We don't have a comparable helper function for the one-arg
- // operator() method, and it's not worth building one just for this
- // case. Write it out.
- std::string threw = catch_what<std::runtime_error>([this](){
- work(LLSDMap("op", "freek"));
- });
- ensure_has(threw, "bad");
+ std::string threw = call_exc("", LLSDMap("op", "freek"), "bad");
ensure_has(threw, "op");
ensure_has(threw, "freek");
}
@@ -1079,7 +1105,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'.
@@ -1087,7 +1113,7 @@ namespace tut
ensure_equals("answer mismatch", tr.llsd, answer);
// Should NOT be able to pass 'answer' to Callables registered
// with 'required'.
- call_exc(tr.name_req, answer, "bad request");
+ call_logerr(tr.name_req, answer, "bad request");
// But SHOULD be able to pass 'matching' to Callables registered
// with 'required'.
work(tr.name_req, matching);
@@ -1101,17 +1127,20 @@ 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_exc("free0_array", 17, array_exc);
- call_exc("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_exc("free0_map", 17, map_exc);
+ call_logerr("free0_map", 17, map_exc);
// Passing an array to a map-style function works now! No longer an
// error case!
// call_exc("free0_map", llsd::array("a", "b"), map_exc);
@@ -1125,7 +1154,7 @@ namespace tut
("free0_array", "free0_map",
"smethod0_array", "smethod0_map",
"method0_array", "method0_map"));
- foreach(LLSD name, inArray(names))
+ for (LLSD name: inArray(names))
{
// Look up the Vars instance for this function.
Vars* vars(varsfor(name));
@@ -1150,15 +1179,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_exc(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");
+ }
}
}
}
@@ -1166,7 +1201,7 @@ namespace tut
template<> template<>
void object::test<20>()
{
- set_test_name("call array-style functions with (just right | too long) arrays");
+ set_test_name("call array-style functions with right-size arrays");
std::vector<U8> binary;
for (size_t h(0x01), i(0); i < 5; h+= 0x22, ++i)
{
@@ -1178,40 +1213,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
}
}
}
@@ -1239,7 +1259,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");
}
@@ -1253,7 +1273,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];
@@ -1294,21 +1314,360 @@ namespace tut
"freenb_map_mdft", "smethodnb_map_mdft", "methodnb_map_mdft")));
// Treat (full | overfull) (array | map) the same.
LLSD argssets(llsd::array(array_full, array_overfull, map_full, map_overfull));
- foreach(const LLSD& args, inArray(argssets))
+ for (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?
}
}
}
}
+
+ struct DispatchResult: public LLDispatchListener
+ {
+ using DR = DispatchResult;
+
+ DispatchResult(): LLDispatchListener("results", "op")
+ {
+ add("strfunc", "return string", &DR::strfunc);
+ add("voidfunc", "void function", &DR::voidfunc);
+ add("emptyfunc", "return empty LLSD", &DR::emptyfunc);
+ add("intfunc", "return Integer LLSD", &DR::intfunc);
+ add("llsdfunc", "return passed LLSD", &DR::llsdfunc);
+ add("mapfunc", "return map LLSD", &DR::mapfunc);
+ add("arrayfunc", "return array LLSD", &DR::arrayfunc);
+ }
+
+ std::string strfunc(const std::string& str) const { return "got " + str; }
+ void voidfunc() const {}
+ LLSD emptyfunc() const { return {}; }
+ int intfunc(int i) const { return -i; }
+ LLSD llsdfunc(const LLSD& event) const
+ {
+ LLSD result{ event };
+ result["with"] = "string";
+ return result;
+ }
+ LLSD mapfunc(int i, const std::string& str) const
+ {
+ return llsd::map("i", intfunc(i), "str", strfunc(str));
+ }
+ LLSD arrayfunc(int i, const std::string& str) const
+ {
+ return llsd::array(intfunc(i), strfunc(str));
+ }
+ };
+
+ template<> template<>
+ void object::test<23>()
+ {
+ set_test_name("string result");
+ DispatchResult service;
+ LLSD result{ service("strfunc", "a string") };
+ ensure_equals("strfunc() mismatch", result.asString(), "got a string");
+ }
+
+ template<> template<>
+ void object::test<24>()
+ {
+ set_test_name("void result");
+ DispatchResult service;
+ LLSD result{ service("voidfunc", LLSD()) };
+ ensure("voidfunc() returned defined", result.isUndefined());
+ }
+
+ template<> template<>
+ void object::test<25>()
+ {
+ set_test_name("Integer result");
+ DispatchResult service;
+ LLSD result{ service("intfunc", -17) };
+ ensure_equals("intfunc() mismatch", result.asInteger(), 17);
+ }
+
+ template<> template<>
+ void object::test<26>()
+ {
+ set_test_name("LLSD echo");
+ DispatchResult service;
+ LLSD result{ service("llsdfunc", llsd::map("op", "llsdfunc", "reqid", 17)) };
+ ensure_equals("llsdfunc() mismatch", result,
+ llsd::map("op", "llsdfunc", "reqid", 17, "with", "string"));
+ }
+
+ template<> template<>
+ void object::test<27>()
+ {
+ set_test_name("map LLSD result");
+ DispatchResult service;
+ LLSD result{ service("mapfunc", llsd::array(-12, "value")) };
+ ensure_equals("mapfunc() mismatch", result, llsd::map("i", 12, "str", "got value"));
+ }
+
+ template<> template<>
+ void object::test<28>()
+ {
+ set_test_name("array LLSD result");
+ DispatchResult service;
+ LLSD result{ service("arrayfunc", llsd::array(-8, "word")) };
+ ensure_equals("arrayfunc() mismatch", result, llsd::array(8, "got word"));
+ }
+
+ template<> template<>
+ void object::test<29>()
+ {
+ set_test_name("listener error, no reply");
+ DispatchResult service;
+ tut::call_exc(
+ [&service]()
+ { service.post(llsd::map("op", "nosuchfunc", "reqid", 17)); },
+ "nosuchfunc");
+ }
+
+ template<> template<>
+ void object::test<30>()
+ {
+ set_test_name("listener error with reply");
+ DispatchResult service;
+ LLCaptureListener<LLSD> result;
+ service.post(llsd::map("op", "nosuchfunc", "reqid", 17, "reply", result.getName()));
+ LLSD reply{ result.get() };
+ ensure("no reply", reply.isDefined());
+ ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
+ ensure_has(reply["error"].asString(), "nosuchfunc");
+ }
+
+ template<> template<>
+ void object::test<31>()
+ {
+ set_test_name("listener call to void function");
+ DispatchResult service;
+ LLCaptureListener<LLSD> result;
+ result.set("non-empty");
+ for (const auto& func: StringVec{ "voidfunc", "emptyfunc" })
+ {
+ service.post(llsd::map(
+ "op", func,
+ "reqid", 17,
+ "reply", result.getName()));
+ ensure_equals("reply from " + func, result.get().asString(), "non-empty");
+ }
+ }
+
+ template<> template<>
+ void object::test<32>()
+ {
+ set_test_name("listener call to string function");
+ DispatchResult service;
+ LLCaptureListener<LLSD> result;
+ service.post(llsd::map(
+ "op", "strfunc",
+ "args", llsd::array("a string"),
+ "reqid", 17,
+ "reply", result.getName()));
+ LLSD reply{ result.get() };
+ ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
+ ensure_equals("bad reply from strfunc", reply["data"].asString(), "got a string");
+ }
+
+ template<> template<>
+ void object::test<33>()
+ {
+ set_test_name("listener call to map function");
+ DispatchResult service;
+ LLCaptureListener<LLSD> result;
+ service.post(llsd::map(
+ "op", "mapfunc",
+ "args", llsd::array(-7, "value"),
+ "reqid", 17,
+ "reply", result.getName()));
+ LLSD reply{ result.get() };
+ ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
+ ensure_equals("bad i from mapfunc", reply["i"].asInteger(), 7);
+ ensure_equals("bad str from mapfunc", reply["str"], "got value");
+ }
+
+ template<> template<>
+ void object::test<34>()
+ {
+ set_test_name("batched map success");
+ DispatchResult service;
+ LLCaptureListener<LLSD> result;
+ service.post(llsd::map(
+ "op", llsd::map(
+ "strfunc", "some string",
+ "intfunc", 2,
+ "voidfunc", LLSD(),
+ "arrayfunc", llsd::array(-5, "other string")),
+ "reqid", 17,
+ "reply", result.getName()));
+ LLSD reply{ result.get() };
+ ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
+ reply.erase("reqid");
+ ensure_equals(
+ "bad map batch",
+ reply,
+ llsd::map(
+ "strfunc", "got some string",
+ "intfunc", -2,
+ "voidfunc", LLSD(),
+ "arrayfunc", llsd::array(5, "got other string")));
+ }
+
+ template<> template<>
+ void object::test<35>()
+ {
+ set_test_name("batched map error");
+ DispatchResult service;
+ LLCaptureListener<LLSD> result;
+ service.post(llsd::map(
+ "op", llsd::map(
+ "badfunc", 34, // !
+ "strfunc", "some string",
+ "intfunc", 2,
+ "missing", LLSD(), // !
+ "voidfunc", LLSD(),
+ "arrayfunc", llsd::array(-5, "other string")),
+ "reqid", 17,
+ "reply", result.getName()));
+ LLSD reply{ result.get() };
+ ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
+ reply.erase("reqid");
+ auto error{ reply["error"].asString() };
+ reply.erase("error");
+ ensure_has(error, "badfunc");
+ ensure_has(error, "missing");
+ ensure_equals(
+ "bad partial batch",
+ reply,
+ llsd::map(
+ "strfunc", "got some string",
+ "intfunc", -2,
+ "voidfunc", LLSD(),
+ "arrayfunc", llsd::array(5, "got other string")));
+ }
+
+ template<> template<>
+ void object::test<36>()
+ {
+ set_test_name("batched map exception");
+ DispatchResult service;
+ auto error = tut::call_exc(
+ [&service]()
+ {
+ service.post(llsd::map(
+ "op", llsd::map(
+ "badfunc", 34, // !
+ "strfunc", "some string",
+ "intfunc", 2,
+ "missing", LLSD(), // !
+ "voidfunc", LLSD(),
+ "arrayfunc", llsd::array(-5, "other string")),
+ "reqid", 17));
+ // no "reply"
+ },
+ "badfunc");
+ ensure_has(error, "missing");
+ }
+
+ template<> template<>
+ void object::test<37>()
+ {
+ set_test_name("batched array success");
+ DispatchResult service;
+ LLCaptureListener<LLSD> result;
+ service.post(llsd::map(
+ "op", llsd::array(
+ llsd::array("strfunc", "some string"),
+ llsd::array("intfunc", 2),
+ "arrayfunc",
+ "voidfunc"),
+ "args", llsd::array(
+ LLSD(),
+ LLSD(),
+ llsd::array(-5, "other string")),
+ // args array deliberately short, since the default
+ // [3] is undefined, which should work for voidfunc
+ "reqid", 17,
+ "reply", result.getName()));
+ LLSD reply{ result.get() };
+ ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
+ reply.erase("reqid");
+ ensure_equals(
+ "bad array batch",
+ reply,
+ llsd::map(
+ "data", llsd::array(
+ "got some string",
+ -2,
+ llsd::array(5, "got other string"),
+ LLSD())));
+ }
+
+ template<> template<>
+ void object::test<38>()
+ {
+ set_test_name("batched array error");
+ DispatchResult service;
+ LLCaptureListener<LLSD> result;
+ service.post(llsd::map(
+ "op", llsd::array(
+ llsd::array("strfunc", "some string"),
+ llsd::array("intfunc", 2, "whoops"), // bad form
+ "arrayfunc",
+ "voidfunc"),
+ "args", llsd::array(
+ LLSD(),
+ LLSD(),
+ llsd::array(-5, "other string")),
+ // args array deliberately short, since the default
+ // [3] is undefined, which should work for voidfunc
+ "reqid", 17,
+ "reply", result.getName()));
+ LLSD reply{ result.get() };
+ ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
+ reply.erase("reqid");
+ auto error{ reply["error"] };
+ reply.erase("error");
+ ensure_has(error, "[1]");
+ ensure_has(error, "unsupported");
+ ensure_equals("bad array batch", reply,
+ llsd::map("data", llsd::array("got some string")));
+ }
+
+ template<> template<>
+ void object::test<39>()
+ {
+ set_test_name("batched array exception");
+ DispatchResult service;
+ auto error = tut::call_exc(
+ [&service]()
+ {
+ service.post(llsd::map(
+ "op", llsd::array(
+ llsd::array("strfunc", "some string"),
+ llsd::array("intfunc", 2, "whoops"), // bad form
+ "arrayfunc",
+ "voidfunc"),
+ "args", llsd::array(
+ LLSD(),
+ LLSD(),
+ llsd::array(-5, "other string")),
+ // args array deliberately short, since the default
+ // [3] is undefined, which should work for voidfunc
+ "reqid", 17));
+ // no "reply"
+ },
+ "[1]");
+ ensure_has(error, "unsupported");
+ }
} // namespace tut
diff --git a/indra/llcommon/tests/wrapllerrs.h b/indra/llcommon/tests/wrapllerrs.h
index 3779fb41bc..d657b329bb 100644
--- a/indra/llcommon/tests/wrapllerrs.h
+++ b/indra/llcommon/tests/wrapllerrs.h
@@ -226,6 +226,11 @@ public:
return boost::dynamic_pointer_cast<CaptureLogRecorder>(mRecorder)->streamto(out);
}
+ friend inline std::ostream& operator<<(std::ostream& out, const CaptureLog& self)
+ {
+ return self.streamto(out);
+ }
+
private:
LLError::FatalFunction mFatalFunction;
LLError::SettingsStoragePtr mOldSettings;