summaryrefslogtreecommitdiff
path: root/indra/llcommon/llpounceable.h
blob: 77b711bdc6d1cca135cbc59affdab6f8d91505b9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
/**
 * @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> >
{
private:
    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) */