SL-18330: Merge commit '6b53036' into DRTVWR-587-maint-V
Bring over part of the LLEventDispatcher work inspired by DRTVWR-558.master
commit
11d22f3cf8
|
|
@ -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}")
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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) */
|
||||
|
|
@ -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()
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -64,19 +69,6 @@ public:
|
|||
/// Get the documentation string
|
||||
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
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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, "'");
|
||||
}
|
||||
}
|
||||
// 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
|
||||
|
||||
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();
|
||||
}
|
||||
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); }))
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
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)
|
||||
reportAPI(reply, *foundea);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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*>(®istrar)))
|
||||
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
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue