summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--indra/llcommon/CMakeLists.txt4
-rw-r--r--indra/llcommon/lazyeventapi.cpp53
-rw-r--r--indra/llcommon/lazyeventapi.h204
-rw-r--r--indra/llcommon/lleventapi.cpp8
-rw-r--r--indra/llcommon/lleventapi.h23
-rw-r--r--indra/llcommon/lleventdispatcher.cpp13
-rw-r--r--indra/llcommon/lleventdispatcher.h105
-rw-r--r--indra/llcommon/llevents.cpp29
-rw-r--r--indra/llcommon/llevents.h6
-rw-r--r--indra/llcommon/tests/lazyeventapi_test.cpp89
10 files changed, 466 insertions, 68 deletions
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index ca8b5e946f..36b2e09dc5 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -30,6 +30,7 @@ include_directories(
set(llcommon_SOURCE_FILES
indra_constants.cpp
+ lazyeventapi.cpp
llallocator.cpp
llallocator_heap_profile.cpp
llapp.cpp
@@ -128,10 +129,12 @@ set(llcommon_SOURCE_FILES
set(llcommon_HEADER_FILES
CMakeLists.txt
+ apply.h
chrono.h
ctype_workaround.h
fix_macros.h
indra_constants.h
+ lazyeventapi.h
linden_common.h
llalignedarray.h
llallocator.h
@@ -338,6 +341,7 @@ if (LL_TESTS)
${BOOST_SYSTEM_LIBRARY})
LL_ADD_INTEGRATION_TEST(commonmisc "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(bitpack "" "${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..aefc2db6da
--- /dev/null
+++ b/indra/llcommon/lazyeventapi.cpp
@@ -0,0 +1,53 @@
+/**
+ * @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
+// external library headers
+// other Linden headers
+#include "llevents.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);
+ }
+}
diff --git a/indra/llcommon/lazyeventapi.h b/indra/llcommon/lazyeventapi.h
new file mode 100644
index 0000000000..2e947899dc
--- /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;
+ };
+
+ // The tricky part is: can we capture a sequence of add() calls in the
+ // LazyEventAPI subclass constructor and then, in effect, replay those
+ // add() calls on instantiation of the registered LLEventAPI subclass? so
+ // we don't have to duplicate the add() calls in both constructors?
+
+ // Derive a subclass from LazyEventAPI. Its constructor must pass
+ // LazyEventAPI's constructor the name, desc, field params. Moreover the
+ // constructor body must call add(name, desc, *args) for any of the
+ // LLEventDispatcher add() methods, referencing the LLEventAPI subclass
+ // methods.
+
+ // LazyEventAPI will store the name, desc, field params for the overall
+ // LLEventAPI. It will support a single generic add() call accepting name,
+ // desc, parameter pack.
+
+ // It will hold a std::vector<std::pair<name, desc>> for each operation.
+ // It will make all these strings available to LLLeapListener.
+
+ // Maybe what we want is to store a vector of callables (a
+ // boost::signals2!) and populate it with lambdas, each of which accepts
+ // LLEventAPI* and calls the relevant add() method by forwarding exactly
+ // the name, desc and parameter pack. Then, on constructing the target
+ // LLEventAPI, we just fire the signal, passing the new instance pointer.
+
+ /**
+ * 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;
+
+ // actually instantiate the companion LLEventAPI subclass
+ virtual LLEventPump* construct(const std::string& name) = 0;
+
+ // 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));
+ });
+ }
+
+ // metadata that might be queried by LLLeapListener
+ std::vector<std::pair<std::string, std::string>> mOperations;
+ // Params with which to instantiate the companion LLEventAPI subclass
+ LazyEventAPIParams mParams;
+
+ private:
+ // 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)...);
+ }
+
+ bool mRegistered;
+ };
+
+ /**
+ * 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)
+ {}
+
+ 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 ed62fa064a..a019458553 100644
--- a/indra/llcommon/lleventapi.h
+++ b/indra/llcommon/lleventapi.h
@@ -35,6 +35,13 @@
#include "llinstancetracker.h"
#include <string>
+namespace LL
+{
+ template <class EVENTAPI>
+ class LazyEventAPI;
+ class LazyEventAPIParams;
+}
+
/**
* LLEventAPI not only provides operation dispatch functionality, inherited
* from LLDispatchListener -- it also gives us event API introspection.
@@ -45,6 +52,8 @@ class LL_COMMON_API LLEventAPI: public LLDispatchListener,
{
typedef LLDispatchListener lbase;
typedef LLInstanceTracker<LLEventAPI, std::string> ibase;
+ template <class EVENTAPI>
+ friend class LL::LazyEventAPI;
public:
@@ -137,16 +146,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 742d6cf51f..bc53ec3da0 100644
--- a/indra/llcommon/lleventdispatcher.cpp
+++ b/indra/llcommon/lleventdispatcher.cpp
@@ -706,8 +706,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 1b3e834aeb..ce9d3775cc 100644
--- a/indra/llcommon/lleventdispatcher.h
+++ b/indra/llcommon/lleventdispatcher.h
@@ -165,12 +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 std::enable_if<
- boost::function_types::is_nonmember_callable_builtin<Function>::value
- >::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,14 +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 std::enable_if<
- boost::function_types::is_member_function_pointer<Method>::value &&
- ! std::is_convertible<InstanceGetter, LLSD>::value
- >::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
@@ -213,14 +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 std::enable_if<
- boost::function_types::is_nonmember_callable_builtin<Function>::value
- >::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.
@@ -243,16 +240,14 @@ 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 std::enable_if<
- boost::function_types::is_member_function_pointer<Method>::value &&
- ! std::is_convertible<InstanceGetter, LLSD>::value
- >::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());
//@}
@@ -476,9 +471,8 @@ struct LLEventDispatcher::invoker<Function,To,To>
}
};
-template<typename Function>
-typename std::enable_if< boost::function_types::is_nonmember_callable_builtin<Function>::value >::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
@@ -487,13 +481,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 std::enable_if<
- boost::function_types::is_member_function_pointer<Method>::value &&
- ! std::is_convertible<InstanceGetter, LLSD>::value
->::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.
@@ -501,23 +491,18 @@ LLEventDispatcher::add(const std::string& name, const std::string& desc, Method
boost::function_types::function_arity<Method>::value - 1);
}
-template<typename Function>
-typename std::enable_if< boost::function_types::is_nonmember_callable_builtin<Function>::value >::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 std::enable_if<
- boost::function_types::is_member_function_pointer<Method>::value &&
- ! std::is_convertible<InstanceGetter, LLSD>::value
->::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);
}
@@ -560,17 +545,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 5725dad9cc..1a305ec3dc 100644
--- a/indra/llcommon/llevents.cpp
+++ b/indra/llcommon/llevents.cpp
@@ -90,6 +90,13 @@ bool LLEventPumps::registerTypeFactory(const std::string& type, const TypeFactor
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?
@@ -109,10 +116,30 @@ bool LLEventPumps::registerPumpFactory(const std::string& name, const PumpFactor
static std::string nul(1, '\0');
std::string type_name{ nul + name };
mTypes[name] = type_name;
- mFactories[type_name] = factory;
+ // 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);
diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h
index 38adc31121..c1dbf4392f 100644
--- a/indra/llcommon/llevents.h
+++ b/indra/llcommon/llevents.h
@@ -280,6 +280,7 @@ public:
* 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;
@@ -304,6 +305,7 @@ public:
* 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.
@@ -362,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, PumpFactory> 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/tests/lazyeventapi_test.cpp b/indra/llcommon/tests/lazyeventapi_test.cpp
new file mode 100644
index 0000000000..6639c5e540
--- /dev/null
+++ b/indra/llcommon/tests/lazyeventapi_test.cpp
@@ -0,0 +1,89 @@
+/**
+ * @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"
+
+// LLEventAPI listener subclass
+class MyListener: public LLEventAPI
+{
+public:
+ MyListener(const LL::LazyEventAPIParams& params):
+ LLEventAPI(params)
+ {}
+
+ void get(const LLSD& event)
+ {
+ std::cout << "MyListener::get() got " << event << std::endl;
+ }
+};
+
+// LazyEventAPI registrar subclass
+class MyRegistrar: public LL::LazyEventAPI<MyListener>
+{
+ using super = LL::LazyEventAPI<MyListener>;
+ using super::listener;
+public:
+ MyRegistrar():
+ super("Test", "This is a test LLEventAPI")
+ {
+ add("get", "This is a get operation", &listener::get);
+ }
+};
+// Normally we'd declare a static instance of MyRegistrar -- but because we
+// may want to test with and without, defer declaration to individual test
+// methods.
+
+/*****************************************************************************
+* TUT
+*****************************************************************************/
+namespace tut
+{
+ struct lazyeventapi_data
+ {
+ ~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("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("moot");
+ }
+} // namespace tut