206 lines
8.4 KiB
C++
206 lines
8.4 KiB
C++
/**
|
|
* @file lazyeventapi.h
|
|
* @author Nat Goodspeed
|
|
* @date 2022-06-16
|
|
* @brief Declaring a static module-scope LazyEventAPI registers a specific
|
|
* LLEventAPI for future on-demand instantiation.
|
|
*
|
|
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
|
|
* Copyright (c) 2022, Linden Research, Inc.
|
|
* $/LicenseInfo$
|
|
*/
|
|
|
|
#if ! defined(LL_LAZYEVENTAPI_H)
|
|
#define LL_LAZYEVENTAPI_H
|
|
|
|
#include "apply.h"
|
|
#include "lleventapi.h"
|
|
#include "llinstancetracker.h"
|
|
#include <boost/signals2/signal.hpp>
|
|
#include <string>
|
|
#include <tuple>
|
|
#include <utility> // std::pair
|
|
#include <vector>
|
|
|
|
namespace LL
|
|
{
|
|
/**
|
|
* Bundle params we want to pass to LLEventAPI's protected constructor. We
|
|
* package them this way so a subclass constructor can simply forward an
|
|
* opaque reference to the LLEventAPI constructor.
|
|
*/
|
|
// This is a class instead of a plain struct mostly so when we forward-
|
|
// declare it we don't have to remember the distinction.
|
|
class LazyEventAPIParams
|
|
{
|
|
public:
|
|
// package the parameters used by the normal LLEventAPI constructor
|
|
std::string name, desc, field;
|
|
// bundle LLEventAPI::add() calls collected by LazyEventAPI::add(), so
|
|
// the special LLEventAPI constructor we engage can "play back" those
|
|
// add() calls
|
|
boost::signals2::signal<void(LLEventAPI*)> init;
|
|
};
|
|
|
|
/**
|
|
* LazyEventAPIBase implements most of the functionality of LazyEventAPI
|
|
* (q.v.), but we need the LazyEventAPI template subclass so we can accept
|
|
* the specific LLEventAPI subclass type.
|
|
*/
|
|
// No LLInstanceTracker key: we don't need to find a specific instance,
|
|
// LLLeapListener just needs to be able to enumerate all instances.
|
|
class LazyEventAPIBase: public LLInstanceTracker<LazyEventAPIBase>
|
|
{
|
|
public:
|
|
LazyEventAPIBase(const std::string& name, const std::string& desc,
|
|
const std::string& field);
|
|
virtual ~LazyEventAPIBase();
|
|
|
|
// Do not copy or move: once constructed, LazyEventAPIBase must stay
|
|
// put: we bind its instance pointer into a callback.
|
|
LazyEventAPIBase(const LazyEventAPIBase&) = delete;
|
|
LazyEventAPIBase(LazyEventAPIBase&&) = delete;
|
|
LazyEventAPIBase& operator=(const LazyEventAPIBase&) = delete;
|
|
LazyEventAPIBase& operator=(LazyEventAPIBase&&) = delete;
|
|
|
|
// capture add() calls we want to play back on LLEventAPI construction
|
|
template <typename... ARGS>
|
|
void add(const std::string& name, const std::string& desc, ARGS&&... rest)
|
|
{
|
|
// capture the metadata separately
|
|
mOperations.push_back(std::make_pair(name, desc));
|
|
// Use connect_extended() so the lambda is passed its own
|
|
// connection.
|
|
|
|
// apply() can't accept a template per se; it needs a particular
|
|
// specialization. Specialize out here to work around a clang bug:
|
|
// https://github.com/llvm/llvm-project/issues/41999
|
|
auto func{ &LazyEventAPIBase::add_trampoline
|
|
<const std::string&, const std::string&, ARGS...> };
|
|
// We can't bind an unexpanded parameter pack into a lambda --
|
|
// shame really. Instead, capture all our args as a std::tuple and
|
|
// then, in the lambda, use apply() to pass to add_trampoline().
|
|
auto args{ std::make_tuple(name, desc, std::forward<ARGS>(rest)...) };
|
|
|
|
mParams.init.connect_extended(
|
|
[func, args]
|
|
(const boost::signals2::connection& conn, LLEventAPI* instance)
|
|
{
|
|
// we only need this connection once
|
|
conn.disconnect();
|
|
// apply() expects a tuple specifying ALL the arguments,
|
|
// so prepend instance.
|
|
apply(func, std::tuple_cat(std::make_tuple(instance), args));
|
|
});
|
|
}
|
|
|
|
// The following queries mimic the LLEventAPI / LLEventDispatcher
|
|
// query API.
|
|
|
|
// Get the string name of the subject LLEventAPI
|
|
std::string getName() const { return mParams.name; }
|
|
// Get the documentation string
|
|
std::string getDesc() const { return mParams.desc; }
|
|
// Retrieve the LLSD key we use for dispatching
|
|
std::string getDispatchKey() const { return mParams.field; }
|
|
|
|
// operations
|
|
using NameDesc = std::pair<std::string, std::string>;
|
|
|
|
private:
|
|
// metadata that might be queried by LLLeapListener
|
|
std::vector<NameDesc> mOperations;
|
|
|
|
public:
|
|
using const_iterator = decltype(mOperations)::const_iterator;
|
|
const_iterator begin() const { return mOperations.begin(); }
|
|
const_iterator end() const { return mOperations.end(); }
|
|
LLSD getMetadata(const std::string& name) const;
|
|
|
|
protected:
|
|
// Params with which to instantiate the companion LLEventAPI subclass
|
|
LazyEventAPIParams mParams;
|
|
|
|
private:
|
|
// true if we successfully registered our LLEventAPI on construction
|
|
bool mRegistered;
|
|
|
|
// actually instantiate the companion LLEventAPI subclass
|
|
virtual LLEventPump* construct(const std::string& name) = 0;
|
|
|
|
// Passing an overloaded function to any function that accepts an
|
|
// arbitrary callable is a PITB because you have to specify the
|
|
// correct overload. What we want is for the compiler to select the
|
|
// correct overload, based on the carefully-wrought enable_ifs in
|
|
// LLEventDispatcher. This (one and only) add_trampoline() method
|
|
// exists solely to pass to LL::apply(). Once add_trampoline() is
|
|
// called with the expanded arguments, we hope the compiler will Do
|
|
// The Right Thing in selecting the correct LLEventAPI::add()
|
|
// overload.
|
|
template <typename... ARGS>
|
|
static
|
|
void add_trampoline(LLEventAPI* instance, ARGS&&... args)
|
|
{
|
|
instance->add(std::forward<ARGS>(args)...);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* LazyEventAPI provides a way to register a particular LLEventAPI to be
|
|
* instantiated on demand, that is, when its name is passed to
|
|
* LLEventPumps::obtain().
|
|
*
|
|
* Derive your listener from LLEventAPI as usual, with its various
|
|
* operation methods, but code your constructor to accept
|
|
* <tt>(const LL::LazyEventAPIParams& params)</tt>
|
|
* and forward that reference to (the protected)
|
|
* <tt>LLEventAPI(const LL::LazyEventAPIParams&)</tt> constructor.
|
|
*
|
|
* Then derive your listener registrar from
|
|
* <tt>LazyEventAPI<your LLEventAPI subclass></tt>. The constructor should
|
|
* look very like a traditional LLEventAPI constructor:
|
|
*
|
|
* * pass (name, desc [, field]) to LazyEventAPI's constructor
|
|
* * in the body, make a series of add() calls referencing your LLEventAPI
|
|
* subclass methods.
|
|
*
|
|
* You may use any LLEventAPI::add() methods, that is, any
|
|
* LLEventDispatcher::add() methods. But the target methods you pass to
|
|
* add() must belong to your LLEventAPI subclass, not the LazyEventAPI
|
|
* subclass.
|
|
*
|
|
* Declare a static instance of your LazyEventAPI listener registrar
|
|
* class. When it's constructed at static initialization time, it will
|
|
* register your LLEventAPI subclass with LLEventPumps. It will also
|
|
* collect metadata for the LLEventAPI and its operations to provide to
|
|
* LLLeapListener's introspection queries.
|
|
*
|
|
* When someone later calls LLEventPumps::obtain() to post an event to
|
|
* your LLEventAPI subclass, obtain() will instantiate it using
|
|
* LazyEventAPI's name, desc, field and add() calls.
|
|
*/
|
|
template <class EVENTAPI>
|
|
class LazyEventAPI: public LazyEventAPIBase
|
|
{
|
|
public:
|
|
// for subclass constructor to reference handler methods
|
|
using listener = EVENTAPI;
|
|
|
|
LazyEventAPI(const std::string& name, const std::string& desc,
|
|
const std::string& field="op"):
|
|
// Forward ctor params to LazyEventAPIBase
|
|
LazyEventAPIBase(name, desc, field)
|
|
{}
|
|
|
|
private:
|
|
LLEventPump* construct(const std::string& /*name*/) override
|
|
{
|
|
// base class has carefully assembled LazyEventAPIParams embedded
|
|
// in this instance, just pass to LLEventAPI subclass constructor
|
|
return new EVENTAPI(mParams);
|
|
}
|
|
};
|
|
} // namespace LL
|
|
|
|
#endif /* ! defined(LL_LAZYEVENTAPI_H) */
|