DRTVWR-564: WIP: Add LazyEventAPI and tests. Tests don't yet pass.

LazyEventAPI is a registrar that implicitly instantiates some particular
LLEventAPI subclass on demand: that is, when LLEventPumps::obtain() tries to
find an LLEventPump by the registered name.

This leverages the new LLEventPumps::registerPumpFactory() machinery. Fix
registerPumpFactory() to adapt the passed PumpFactory to accept TypeFactory
parameters (two of which it ignores). Supplement it with
unregisterPumpFactory() to support LazyEventAPI instances with lifespans
shorter than the process -- which may be mostly test programs, but still a
hole worth closing. Similarly, add unregisterTypeFactory().

A LazyEventAPI subclass takes over responsibility for specifying the
LLEventAPI's name, desc, field, plus whatever add() calls will be needed to
register the LLEventAPI's operations. This is so we can (later) enhance
LLLeapListener to consult LazyEventAPI instances for not-yet-instantiated
LLEventAPI metadata, as well as enumerating existing LLEventAPI instances.

The trickiest part of this is capturing calls to the various
LLEventDispatcher::add() overloads in such a way that, when the LLEventAPI
subclass is eventually instantiated, we can replay them in the new instance.

LLEventAPI acquires a new protected constructor specifically for use by a
subclass registered by a companion LazyEventAPI. It accepts a const reference
to LazyEventAPIParams, intended to be opaque to the LLEventAPI subclass; the
subclass must declare a constructor that accepts and forwards the parameter
block to the new LLEventAPI constructor. The implementation delegates to the
existing LLEventAPI constructor, plus it runs deferred add() calls.

LLDispatchListener now derives from LLEventStream instead of containing it as
a data member. The reason is that if LLEventPumps::obtain() implicitly
instantiates it, LLEventPumps's destructor will try to destroy it by deleting
the LLEventPump*. If the LLEventPump returned by the factory function is a
data member of an outer class, that won't work so well. But if
LLDispatchListener (and by implication, LLEventAPI and any subclass) is
derived from LLEventPump, then the virtual destructor will Do The Right Thing.

Change LLDispatchListener to *not* allow tweaking the LLEventPump name. Since
the overwhelming use case for LLDispatchListener is LLEventAPI, accepting but
silently renaming an LLEventAPI subclass would ensure nobody could reach it.

Change LLEventDispatcher's use of std::enable_if to control the set of add()
overloads available for the intended use cases. Apparently this formulation is
just as functional at the method declaration point, while avoiding the need to
restate the whole enable_if expression at the method definition point.

Add lazyeventapi_test.cpp to exercise.
master
Nat Goodspeed 2022-06-18 11:57:10 -04:00
parent dc2e2cd76f
commit af4fbc1f8a
10 changed files with 466 additions and 68 deletions

View File

@ -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}")

View File

@ -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);
}
}

View File

@ -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) */

View File

@ -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()
{
}

View File

@ -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;
};

View File

@ -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); }))
{
}

View File

@ -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;
};

View File

@ -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);

View File

@ -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

View File

@ -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