summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNat Goodspeed <nat@lindenlab.com>2023-07-12 16:47:11 -0400
committerNat Goodspeed <nat@lindenlab.com>2023-07-12 16:47:11 -0400
commit11d22f3cf8a11183c871c92b4297dc399dc40f04 (patch)
tree65dd792081f00bc79da0d6a4e323c2a8e722f70b
parente80f0f331dbfca686e0a80c557e9b1e455c8d167 (diff)
parent6b53036f7499a4e42813378009050eaf02c0b69d (diff)
SL-18330: Merge commit '6b53036' into DRTVWR-587-maint-V
Bring over part of the LLEventDispatcher work inspired by DRTVWR-558.
-rw-r--r--indra/llcommon/CMakeLists.txt5
-rw-r--r--indra/llcommon/lazyeventapi.cpp72
-rw-r--r--indra/llcommon/lazyeventapi.h204
-rw-r--r--indra/llcommon/lleventapi.cpp8
-rw-r--r--indra/llcommon/lleventapi.h32
-rw-r--r--indra/llcommon/lleventdispatcher.cpp147
-rw-r--r--indra/llcommon/lleventdispatcher.h155
-rw-r--r--indra/llcommon/llevents.cpp71
-rw-r--r--indra/llcommon/llevents.h43
-rw-r--r--indra/llcommon/llleaplistener.cpp70
-rw-r--r--indra/llcommon/tests/lazyeventapi_test.cpp136
-rw-r--r--indra/llcommon/tests/lleventdispatcher_test.cpp62
12 files changed, 828 insertions, 177 deletions
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index 54020a4231..91ae2440fa 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -17,6 +17,7 @@ include(Tracy)
set(llcommon_SOURCE_FILES
indra_constants.cpp
+ lazyeventapi.cpp
llallocator.cpp
llallocator_heap_profile.cpp
llapp.cpp
@@ -115,11 +116,13 @@ set(llcommon_SOURCE_FILES
set(llcommon_HEADER_FILES
CMakeLists.txt
+ apply.h
chrono.h
classic_callback.h
ctype_workaround.h
fix_macros.h
indra_constants.h
+ lazyeventapi.h
linden_common.h
llalignedarray.h
llallocator.h
@@ -290,9 +293,9 @@ if (LL_TESTS)
#set(TEST_DEBUG on)
set(test_libs llcommon)
- 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/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..a815b119f0
--- /dev/null
+++ b/indra/llcommon/lazyeventapi.h
@@ -0,0 +1,204 @@
+/**
+ * @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.
+ // We can't bind an unexpanded parameter pack into a lambda --
+ // shame really. Instead, capture it as a std::tuple and then, in
+ // the lambda, use apply() to convert back to function args.
+ mParams.init.connect_extended(
+ [name, desc, rest = std::make_tuple(std::forward<ARGS>(rest)...)]
+ (const boost::signals2::connection& conn, LLEventAPI* instance)
+ {
+ // we only need this connection once
+ conn.disconnect();
+ // Our add() method distinguishes name and desc because we
+ // capture them separately. But now, because apply()
+ // expects a tuple specifying ALL the arguments, expand to
+ // a tuple including add_trampoline() arguments: instance,
+ // name, desc, rest.
+ // apply() can't accept a template per se; it needs a
+ // particular specialization.
+ apply(&LazyEventAPIBase::add_trampoline<const std::string&, const std::string&, ARGS...>,
+ std::tuple_cat(std::make_tuple(instance, name, desc),
+ rest));
+ });
+ }
+
+ // 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/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..0c3bb35cfe 100644
--- a/indra/llcommon/lleventdispatcher.cpp
+++ b/indra/llcommon/lleventdispatcher.cpp
@@ -40,15 +40,24 @@
// other Linden headers
#include "llevents.h"
#include "llerror.h"
+#include "llexception.h"
#include "llsdutil.h"
#include "stringize.h"
#include <memory> // std::auto_ptr
/*****************************************************************************
+* DispatchError
+*****************************************************************************/
+struct DispatchError: public LLException
+{
+ DispatchError(const std::string& what): LLException(what) {}
+};
+
+/*****************************************************************************
* LLSDArgsSource
*****************************************************************************/
/**
- * Store an LLSD array, producing its elements one at a time. Die with LL_ERRS
+ * Store an LLSD array, producing its elements one at a time. It is an error
* if the consumer requests more elements than the array contains.
*/
class LL_COMMON_API LLSDArgsSource
@@ -74,8 +83,7 @@ LLSDArgsSource::LLSDArgsSource(const std::string function, const LLSD& args):
{
if (! (_args.isUndefined() || _args.isArray()))
{
- LL_ERRS("LLSDArgsSource") << _function << " needs an args array instead of "
- << _args << LL_ENDL;
+ LLTHROW(DispatchError(stringize(_function, " needs an args array instead of ", _args)));
}
}
@@ -88,8 +96,8 @@ LLSD LLSDArgsSource::next()
{
if (_index >= _args.size())
{
- LL_ERRS("LLSDArgsSource") << _function << " requires more arguments than the "
- << _args.size() << " provided: " << _args << LL_ENDL;
+ LLTHROW(DispatchError(stringize(_function, " requires more arguments than the ",
+ _args.size(), " provided: ", _args)));
}
return _args[_index++];
}
@@ -163,7 +171,8 @@ public:
/// default values
LLSDArgsMapper(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:
@@ -195,7 +204,7 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function,
{
if (! (_names.isUndefined() || _names.isArray()))
{
- LL_ERRS("LLSDArgsMapper") << function << " names must be an array, not " << names << LL_ENDL;
+ LLTHROW(DispatchError(stringize(function, " names must be an array, not ", names)));
}
auto nparams(_names.size());
// From _names generate _indexes.
@@ -218,8 +227,8 @@ 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;
+ LLTHROW(DispatchError(stringize(function, " names array ", names,
+ " shorter than defaults array ", defaults)));
}
// Offset by which we slide defaults array right to right-align with
@@ -256,14 +265,14 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function,
}
if (bogus.size())
{
- LL_ERRS("LLSDArgsMapper") << function << " defaults specified for nonexistent params "
- << formatlist(bogus) << LL_ENDL;
+ LLTHROW(DispatchError(stringize(function, " defaults specified for nonexistent params ",
+ formatlist(bogus))));
}
}
else
{
- LL_ERRS("LLSDArgsMapper") << function << " defaults must be a map or an array, not "
- << defaults << LL_ENDL;
+ LLTHROW(DispatchError(stringize(function, " defaults must be a map or an array, not ",
+ defaults)));
}
}
@@ -271,8 +280,8 @@ LLSD 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;
+ LLTHROW(DispatchError(stringize(_function, " map() needs a map or array, not ",
+ argsmap)));
}
// Initialize the args array. Indexing a non-const LLSD array grows it
// to appropriate size, but we don't want to resize this one on each
@@ -369,8 +378,8 @@ 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;
+ LLTHROW(DispatchError(stringize(_function, " missing required arguments ",
+ formatlist(unfilled), " from ", argsmap)));
}
// done
@@ -420,7 +429,7 @@ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchE
std::string mismatch(llsd_matches(mRequired, event));
if (! mismatch.empty())
{
- LL_ERRS("LLEventDispatcher") << desc << ": bad request: " << mismatch << LL_ENDL;
+ LLTHROW(DispatchError(stringize(desc, ": bad request: ", mismatch)));
}
// Event syntax looks good, go for it!
mFunc(event);
@@ -577,13 +586,6 @@ void LLEventDispatcher::add(const std::string& name, const std::string& desc,
new LLSDDispatchEntry(desc, callable, required))));
}
-void LLEventDispatcher::addFail(const std::string& name, const std::string& classname) const
-{
- LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << ")::add(" << name
- << "): " << classname << " is not a subclass "
- << "of LLEventDispatcher" << LL_ENDL;
-}
-
/// Unregister a callable
bool LLEventDispatcher::remove(const std::string& name)
{
@@ -596,48 +598,90 @@ 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.
+/// Call a registered callable with an explicitly-specified name. It is an
+/// error if no such callable exists.
void LLEventDispatcher::operator()(const std::string& name, const LLSD& event) const
{
- if (! try_call(name, event))
+ std::string error{ try_call_log(std::string(), name, event) };
+ if (! error.empty())
{
- LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): '" << name
- << "' not found" << LL_ENDL;
+ callFail(event, error);
}
}
-/// 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.
+/// 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.
void LLEventDispatcher::operator()(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))
+ std::string error{ try_call_log(mKey, event[mKey], event) };
+ if (! error.empty())
{
- LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): bad " << mKey
- << " value '" << name << "'" << LL_ENDL;
+ callFail(event, error);
+ }
+}
+
+void LLEventDispatcher::callFail(const LLSD& event, const std::string& msg) const
+{
+ static LLSD::String key{ "reply" };
+ if (event.has(key))
+ {
+ // Oh good, the incoming event specifies a reply pump -- pass back a
+ // response that includes an "error" key with the message.
+ sendReply(llsd::map("error", msg), event, key);
}
}
bool LLEventDispatcher::try_call(const LLSD& event) const
{
- return try_call(event[mKey], event);
+ return try_call_log(mKey, event[mKey], event).empty();
}
bool LLEventDispatcher::try_call(const std::string& name, const LLSD& event) const
{
+ return try_call_log(std::string(), name, event).empty();
+}
+
+std::string LLEventDispatcher::try_call_log(const std::string& key, const std::string& name,
+ const LLSD& event) const
+{
+ std::string error{ try_call(key, name, event) };
+ if (! error.empty())
+ {
+ LL_WARNS("LLEventDispatcher") << error << LL_ENDL;
+ }
+ return error;
+}
+
+// This internal method returns empty string if the call succeeded, else
+// non-empty error message.
+std::string LLEventDispatcher::try_call(const std::string& key, const std::string& name,
+ const LLSD& event) const
+{
DispatchMap::const_iterator found = mDispatch.find(name);
if (found == mDispatch.end())
{
- return false;
+ if (key.empty())
+ {
+ return stringize("LLEventDispatcher(", mDesc, "): '", name, "' not found");
+ }
+ else
+ {
+ return stringize("LLEventDispatcher(", mDesc, "): bad ", key, " value '", name, "'");
+ }
+ }
+
+ try
+ {
+ // Found the name, so it's plausible to even attempt the call.
+ found->second->call(stringize("LLEventDispatcher(", mDesc, ") calling '", name, "'"),
+ event);
+ }
+ catch (const DispatchError& err)
+ {
+ return err.what();
}
- // 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
+ return {}; // tell caller we were able to call
}
LLSD LLEventDispatcher::getMetadata(const std::string& name) const
@@ -655,8 +699,17 @@ LLSD LLEventDispatcher::getMetadata(const std::string& name) const
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)))
+ // 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); }))
{
}
diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h
index 9e1244ef5b..6d1df86fea 100644
--- a/indra/llcommon/lleventdispatcher.h
+++ b/indra/llcommon/lleventdispatcher.h
@@ -61,7 +61,6 @@ static const auto& nil(nil_);
#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <boost/iterator/transform_iterator.hpp>
-#include <boost/utility/enable_if.hpp>
#include <boost/function_types/is_nonmember_callable_builtin.hpp>
#include <boost/function_types/parameter_types.hpp>
#include <boost/function_types/function_arity.hpp>
@@ -166,11 +165,12 @@ public:
* 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);
+ // enable_if usage per https://stackoverflow.com/a/39913395/5533635
+ template<typename Function,
+ typename = typename std::enable_if<
+ boost::function_types::is_nonmember_callable_builtin<Function>::value
+ >::type>
+ void add(const std::string& name, const std::string& desc, Function f);
/**
* Register a nonstatic class method with arbitrary parameters.
@@ -189,12 +189,13 @@ public:
* 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
@@ -211,13 +212,12 @@ 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
+ >::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.
@@ -240,42 +240,42 @@ 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.
+ /// Call a registered callable with an explicitly-specified name. 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.
void 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.
+ /// <tt>false</tt>. It is an error if the @a event fails to match the @a
+ /// required prototype specified at add() time.
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.
+ /// callable whose name is specified by that map @a key. 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.
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.
+ /// 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.
bool try_call(const LLSD& event) const;
/// @name Iterate over defined names
@@ -329,17 +329,16 @@ private:
void addMethod(const std::string& name, const std::string& desc,
const METHOD& method, const LLSD& required)
{
- CLASS* downcast = dynamic_cast<CLASS*>(this);
- if (! downcast)
- {
- addFail(name, typeid(CLASS).name());
- }
- else
- {
- add(name, desc, boost::bind(method, downcast, _1), required);
- }
+ CLASS* downcast = static_cast<CLASS*>(this);
+ add(name, desc, boost::bind(method, downcast, _1), required);
}
- void addFail(const std::string& name, const std::string& classname) const;
+ std::string try_call_log(const std::string& key, const std::string& name,
+ const LLSD& event) const;
+ std::string try_call(const std::string& key, const std::string& name,
+ const LLSD& event) const;
+ // Implement "it is an error" semantics for attempted call operations: if
+ // the incoming event includes a "reply" key, log and send an error reply.
+ void callFail(const LLSD& event, const std::string& msg) const;
std::string mDesc, mKey;
DispatchMap mDispatch;
@@ -427,7 +426,25 @@ struct LLEventDispatcher::invoker
// 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())));
+ ( func, argsrc, boost::fusion::push_back(boost::fusion::nil(), bindable(getter())));
+ }
+
+ template <typename T>
+ static inline
+ auto bindable(T&& value,
+ typename std::enable_if<std::is_pointer<T>::value, bool>::type=true)
+ {
+ // if passed a pointer, just return that pointer
+ return std::forward<T>(value);
+ }
+
+ template <typename T>
+ static inline
+ auto bindable(T&& value,
+ typename std::enable_if<! std::is_pointer<T>::value, bool>::type=true)
+ {
+ // if passed a reference, wrap it for binding
+ return std::ref(std::forward<T>(value));
}
};
@@ -446,9 +463,8 @@ struct LLEventDispatcher::invoker<Function,To,To>
}
};
-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, typename>
+void LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f)
{
// Construct an invoker_function, a callable accepting const args_source&.
// Add to DispatchMap an ArrayParamsDispatchEntry that will handle the
@@ -457,10 +473,9 @@ LLEventDispatcher::add(const std::string& name, const std::string& desc, Functio
boost::function_types::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.
@@ -468,20 +483,18 @@ LLEventDispatcher::add(const std::string& name, const std::string& desc, Method
boost::function_types::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);
}
@@ -524,17 +537,21 @@ LLEventDispatcher::make_invoker(Method f, const InstanceGetter& getter)
* LLEventPump name and dispatch key, and add() its methods. Incoming events
* will automatically be dispatched.
*/
-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(); }
+ virtual ~LLDispatchListener() {}
private:
bool process(const LLSD& event);
- LLEventStream mPump;
LLTempBoundListener mBoundListener;
};
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/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/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..b38e47a773 100644
--- a/indra/llcommon/tests/lleventdispatcher_test.cpp
+++ b/indra/llcommon/tests/lleventdispatcher_test.cpp
@@ -20,6 +20,7 @@
#include "../test/lltut.h"
#include "llsd.h"
#include "llsdutil.h"
+#include "llevents.h"
#include "stringize.h"
#include "tests/wrapllerrs.h"
#include "../test/catch_and_store_what_in.h"
@@ -644,12 +645,45 @@ namespace tut
outer.find(inner) != std::string::npos);
}
- void call_exc(const std::string& func, const LLSD& args, const std::string& exc_frag)
+ std::string call_exc(const std::string& func, const LLSD& args, const std::string& exc_frag)
{
- std::string threw = catch_what<std::runtime_error>([this, &func, &args](){
- work(func, args);
- });
- ensure_has(threw, exc_frag);
+ // This method was written when LLEventDispatcher responded to
+ // name or argument errors with LL_ERRS, hence the name: we used
+ // to have to intercept LL_ERRS by making it throw. Now we set up
+ // to catch an error response instead. But -- for that we need to
+ // be able to sneak a "reply" key into args, which must be a Map.
+ if (! (args.isUndefined() or args.isMap()))
+ fail(stringize("can't test call_exc() with ", args));
+ LLEventStream replypump("reply");
+ LLSD reply;
+ LLTempBoundListener bound{
+ replypump.listen(
+ "listener",
+ [&reply](const LLSD& event)
+ {
+ reply = event;
+ return false;
+ }) };
+ LLSD modargs{ args };
+ modargs["reply"] = replypump.getName();
+ if (func.empty())
+ {
+ work(modargs);
+ }
+ else
+ {
+ work(func, modargs);
+ }
+ ensure("no error response", reply.has("error"));
+ ensure_has(reply["error"], exc_frag);
+ return reply["error"];
+ }
+
+ void call_logerr(const std::string& func, const LLSD& args, const std::string& frag)
+ {
+ CaptureLog capture;
+ work(func, args);
+ capture.messageWith(frag);
}
LLSD getMetadata(const std::string& name)
@@ -1031,13 +1065,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");
}
@@ -1087,7 +1115,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);
@@ -1107,11 +1135,11 @@ namespace tut
// 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);
+ call_logerr("free0_array", 17, array_exc);
+ call_logerr("free0_array", LLSDMap("pi", 3.14), array_exc);
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);
@@ -1158,7 +1186,7 @@ namespace tut
{
foreach(const llsd::MapEntry& e, inMap(funcsab))
{
- call_exc(e.second, tooshort, "requires more arguments");
+ call_logerr(e.second, tooshort, "requires more arguments");
}
}
}