summaryrefslogtreecommitdiff
path: root/indra/llcommon/llpounceable.h
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llcommon/llpounceable.h')
-rw-r--r--indra/llcommon/llpounceable.h216
1 files changed, 216 insertions, 0 deletions
diff --git a/indra/llcommon/llpounceable.h b/indra/llcommon/llpounceable.h
new file mode 100644
index 0000000000..0421ce966a
--- /dev/null
+++ b/indra/llcommon/llpounceable.h
@@ -0,0 +1,216 @@
+/**
+ * @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) */