phoenix-firestorm/indra/llcommon/llpounceable.h

217 lines
7.9 KiB
C++

/**
* @file llpounceable.h
* @author Nat Goodspeed
* @date 2015-05-22
* @brief LLPounceable is tangentially related to a future: it's a holder for
* a value that may or may not exist yet. Unlike a future, though,
* LLPounceable freely allows reading the held value. (If the held
* type T does not have a distinguished "empty" value, consider using
* LLPounceable<boost::optional<T>>.)
*
* LLPounceable::callWhenReady() is this template's claim to fame. It
* allows its caller to "pounce" on the held value as soon as it
* becomes non-empty. Call callWhenReady() with any C++ callable
* accepting T. If the held value is already non-empty, callWhenReady()
* will immediately call the callable with the held value. If the held
* value is empty, though, callWhenReady() will enqueue the callable
* for later. As soon as LLPounceable is assigned a non-empty held
* value, it will flush the queue of deferred callables.
*
* Consider a global LLMessageSystem* gMessageSystem. Message system
* initialization happens at a very specific point during viewer
* initialization. Other subsystems want to register callbacks on the
* LLMessageSystem instance as soon as it's initialized, but their own
* initialization may precede that. If we define gMessageSystem to be
* an LLPounceable<LLMessageSystem*>, a subsystem can use
* callWhenReady() to either register immediately (if gMessageSystem
* is already up and runnning) or register as soon as gMessageSystem
* is set with a new, initialized instance.
*
* $LicenseInfo:firstyear=2015&license=viewerlgpl$
* Copyright (c) 2015, Linden Research, Inc.
* $/LicenseInfo$
*/
#if ! defined(LL_LLPOUNCEABLE_H)
#define LL_LLPOUNCEABLE_H
#include "llsingleton.h"
#include <boost/noncopyable.hpp>
#include <boost/call_traits.hpp>
#include <boost/type_traits/remove_pointer.hpp>
#include <boost/utility/value_init.hpp>
#include <boost/unordered_map.hpp>
#include <boost/signals2/signal.hpp>
// Forward declare the user template, since we want to be able to point to it
// in some of its implementation classes.
template <typename T, class TAG>
class LLPounceable;
template <typename T, typename TAG>
struct LLPounceableTraits
{
// Our "queue" is a signal object with correct signature.
typedef boost::signals2::signal<void (typename boost::call_traits<T>::param_type)> signal_t;
// Call callWhenReady() with any callable accepting T.
typedef typename signal_t::slot_type func_t;
// owner pointer type
typedef LLPounceable<T, TAG>* owner_ptr;
};
// Tag types distinguish the two different implementations of LLPounceable's
// queue.
struct LLPounceableQueue {};
struct LLPounceableStatic {};
// generic LLPounceableQueueImpl deliberately omitted: only the above tags are
// legal
template <typename T, class TAG>
class LLPounceableQueueImpl;
// The implementation selected by LLPounceableStatic uses an LLSingleton
// because we can't count on a data member queue being initialized at the time
// we start getting callWhenReady() calls. This is that LLSingleton.
template <typename T>
class LLPounceableQueueSingleton:
public LLSingleton<LLPounceableQueueSingleton<T> >
{
LLSINGLETON_EMPTY_CTOR(LLPounceableQueueSingleton);
typedef LLPounceableTraits<T, LLPounceableStatic> traits;
typedef typename traits::owner_ptr owner_ptr;
typedef typename traits::signal_t signal_t;
// For a given held type T, every LLPounceable<T, LLPounceableStatic>
// instance will call on the SAME LLPounceableQueueSingleton instance --
// given how class statics work. We must keep a separate queue for each
// LLPounceable instance. Use a hash map for that.
typedef boost::unordered_map<owner_ptr, signal_t> map_t;
public:
// Disambiguate queues belonging to different LLPounceables.
signal_t& get(owner_ptr owner)
{
// operator[] has find-or-create semantics -- just what we want!
return mMap[owner];
}
private:
map_t mMap;
};
// LLPounceableQueueImpl that uses the above LLSingleton
template <typename T>
class LLPounceableQueueImpl<T, LLPounceableStatic>
{
public:
typedef LLPounceableTraits<T, LLPounceableStatic> traits;
typedef typename traits::owner_ptr owner_ptr;
typedef typename traits::signal_t signal_t;
signal_t& get(owner_ptr owner) const
{
// this Impl contains nothing; it delegates to the Singleton
return LLPounceableQueueSingleton<T>::instance().get(owner);
}
};
// The implementation selected by LLPounceableQueue directly contains the
// queue of interest, suitable for an LLPounceable we can trust to be fully
// initialized when it starts getting callWhenReady() calls.
template <typename T>
class LLPounceableQueueImpl<T, LLPounceableQueue>
{
public:
typedef LLPounceableTraits<T, LLPounceableQueue> traits;
typedef typename traits::owner_ptr owner_ptr;
typedef typename traits::signal_t signal_t;
signal_t& get(owner_ptr)
{
return mQueue;
}
private:
signal_t mQueue;
};
// LLPounceable<T> is for an LLPounceable instance on the heap or the stack.
// LLPounceable<T, LLPounceableStatic> is for a static LLPounceable instance.
template <typename T, class TAG=LLPounceableQueue>
class LLPounceable: public boost::noncopyable
{
private:
typedef LLPounceableTraits<T, TAG> traits;
typedef typename traits::owner_ptr owner_ptr;
typedef typename traits::signal_t signal_t;
public:
typedef typename traits::func_t func_t;
// By default, both the initial value and the distinguished empty value
// are a default-constructed T instance. However you can explicitly
// specify each.
LLPounceable(typename boost::call_traits<T>::value_type init =boost::value_initialized<T>(),
typename boost::call_traits<T>::param_type empty=boost::value_initialized<T>()):
mHeld(init),
mEmpty(empty)
{}
// make read access to mHeld as cheap and transparent as possible
operator T () const { return mHeld; }
typename boost::remove_pointer<T>::type operator*() const { return *mHeld; }
typename boost::call_traits<T>::value_type operator->() const { return mHeld; }
// uncomment 'explicit' as soon as we allow C++11 compilation
/*explicit*/ operator bool() const { return bool(mHeld); }
bool operator!() const { return ! mHeld; }
// support both assignment (dumb ptr idiom) and reset() (smart ptr)
void operator=(typename boost::call_traits<T>::param_type value)
{
reset(value);
}
void reset(typename boost::call_traits<T>::param_type value)
{
mHeld = value;
// If this new value is non-empty, flush anything pending in the queue.
if (mHeld != mEmpty)
{
signal_t& signal(get_signal());
signal(mHeld);
signal.disconnect_all_slots();
}
}
// our claim to fame
void callWhenReady(const func_t& func)
{
if (mHeld != mEmpty)
{
// If the held value is already non-empty, immediately call func()
func(mHeld);
}
else
{
// Held value still empty, queue func() for later. By default,
// connect() enqueues slots in FIFO order.
get_signal().connect(func);
}
}
private:
signal_t& get_signal() { return mQueue.get(this); }
// Store both the current and the empty value.
// MAYBE: Might be useful to delegate to LLPounceableTraits the meaning of
// testing for "empty." For some types we want operator!(); for others we
// want to compare to a distinguished value.
typename boost::call_traits<T>::value_type mHeld, mEmpty;
// This might either contain the queue (LLPounceableQueue) or delegate to
// an LLSingleton (LLPounceableStatic).
LLPounceableQueueImpl<T, TAG> mQueue;
};
#endif /* ! defined(LL_LLPOUNCEABLE_H) */