summaryrefslogtreecommitdiffstats
path: root/src/control/control.cpp
blob: ce902b34b69b27ad612d2d024b682e5cd27ef359 (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
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
#include "control/control.h"

#include "control/controlobject.h"
#include "moc_control.cpp"
#include "util/stat.h"

namespace {
/// Hack to implement persistent controls. This is a pointer to the current
/// user configuration object (if one exists). In general, we do not want the
/// user configuration to be a singleton -- objects that need access to it
/// should be passed it explicitly. However, the Control system is so
/// pervasive that updating every control creation to include the
/// configuration object would be arduous.
UserSettingsPointer s_pUserConfig;

/// Mutex guarding access to s_qCOHash and s_qCOAliasHash.
MMutex s_qCOHashMutex;

/// Hash of ControlDoublePrivate instantiations.
QHash<ConfigKey, QWeakPointer<ControlDoublePrivate>> s_qCOHash
        GUARDED_BY(s_qCOHashMutex);

/// Hash of aliases between ConfigKeys. Solely used for looking up the first
/// alias associated with a key.
QHash<ConfigKey, ConfigKey> s_qCOAliasHash
        GUARDED_BY(s_qCOHashMutex);

/// is used instead of a nullptr, helps to omit null checks everywhere
QWeakPointer<ControlDoublePrivate> s_pDefaultCO;
} // namespace

ControlDoublePrivate::ControlDoublePrivate()
        : m_bPersistInConfiguration(false),
          m_bIgnoreNops(true),
          m_bTrack(false),
          m_trackType(Stat::UNSPECIFIED),
          m_trackFlags(Stat::COUNT | Stat::SUM | Stat::AVERAGE |
                  Stat::SAMPLE_VARIANCE | Stat::MIN | Stat::MAX),
          // default CO is read only
          m_confirmRequired(true) {
    m_value.setValue(0.0);
}

ControlDoublePrivate::ControlDoublePrivate(
        const ConfigKey& key,
        ControlObject* pCreatorCO,
        bool bIgnoreNops,
        bool bTrack,
        bool bPersist,
        double defaultValue)
        : m_key(key),
          m_pCreatorCO(pCreatorCO),
          m_bPersistInConfiguration(bPersist),
          m_bIgnoreNops(bIgnoreNops),
          m_bTrack(bTrack),
          m_trackType(Stat::UNSPECIFIED),
          m_trackFlags(Stat::COUNT | Stat::SUM | Stat::AVERAGE |
                  Stat::SAMPLE_VARIANCE | Stat::MIN | Stat::MAX),
          m_confirmRequired(false) {
    initialize(defaultValue);
}

void ControlDoublePrivate::initialize(double defaultValue) {
    double value = defaultValue;
    if (m_bPersistInConfiguration) {
        UserSettingsPointer pConfig = s_pUserConfig;
        if (pConfig) {
            value = pConfig->getValue(m_key, defaultValue);
        } else {
            DEBUG_ASSERT(!"Can't load persistent value s_pUserConfig is null");
        }
    }
    m_defaultValue.setValue(defaultValue);
    m_value.setValue(value);

    //qDebug() << "Creating:" << m_trackKey << "at" << &m_value << sizeof(m_value);

    if (m_bTrack) {
        // TODO(rryan): Make configurable.
        m_trackKey = "control " + m_key.group + "," + m_key.item;
        Stat::track(m_trackKey, static_cast<Stat::StatType>(m_trackType),
                    static_cast<Stat::ComputeFlags>(m_trackFlags),
                    m_value.getValue());
    }
}

ControlDoublePrivate::~ControlDoublePrivate() {
    s_qCOHashMutex.lock();
    //qDebug() << "ControlDoublePrivate::s_qCOHash.remove(" << m_key.group << "," << m_key.item << ")";
    s_qCOHash.remove(m_key);
    s_qCOHashMutex.unlock();

    if (m_bPersistInConfiguration) {
        UserSettingsPointer pConfig = s_pUserConfig;
        VERIFY_OR_DEBUG_ASSERT(pConfig) {
            return;
        }
        pConfig->set(m_key, QString::number(get()));
    }
}

//static
void ControlDoublePrivate::setUserConfig(const UserSettingsPointer& pConfig) {
    DEBUG_ASSERT(pConfig != s_pUserConfig);
    s_pUserConfig = pConfig;
}

// static
void ControlDoublePrivate::insertAlias(const ConfigKey& alias, const ConfigKey& key) {
    MMutexLocker locker(&s_qCOHashMutex);

    auto it = s_qCOHash.constFind(key);
    VERIFY_OR_DEBUG_ASSERT(it != s_qCOHash.constEnd()) {
        qWarning() << "cannot create alias for null control" << key;
        return;
    }

    QSharedPointer<ControlDoublePrivate> pControl = it.value();
    VERIFY_OR_DEBUG_ASSERT(!pControl.isNull()) {
        qWarning() << "cannot create alias for expired control" << key;
        return;
    }

    s_qCOAliasHash.insert(key, alias);
    s_qCOHash.insert(alias, pControl);
}

// static
QSharedPointer<ControlDoublePrivate> ControlDoublePrivate::getControl(
        const ConfigKey& key,
        ControlFlags flags,
        ControlObject* pCreatorCO,
        bool bIgnoreNops,
        bool bTrack,
        bool bPersist,
        double defaultValue) {
    if (!key.isValid()) {
        if (!flags.testFlag(ControlFlag::AllowInvalidKey)) {
            qWarning() << "ControlDoublePrivate::getControl returning nullptr"
                       << "for invalid ConfigKey" << key;
            DEBUG_ASSERT(!"Unexpected invalid key");
        }
        return nullptr;
    }

    // Scope for MMutexLocker.
    {
        const MMutexLocker locker(&s_qCOHashMutex);
        const auto it = s_qCOHash.find(key);
        if (it != s_qCOHash.end()) {
            auto pControl = it.value().lock();
            if (pControl) {
                // Control object already exists
                if (pCreatorCO) {
                    qWarning()
                            << "ControlObject"
                            << key.group << key.item
                            << "already created";
                    DEBUG_ASSERT(!"pCreatorCO != nullptr, ControlObject already created");
                    return nullptr;
                }
                return pControl;
            } else {
                // The weak pointer has become invalid and can be cleaned up
                s_qCOHash.erase(it);
            }
        }
    }

    if (pCreatorCO) {
        auto pControl = QSharedPointer<ControlDoublePrivate>(
                new ControlDoublePrivate(key,
                        pCreatorCO,
                        bIgnoreNops,
                        bTrack,
                        bPersist,
                        defaultValue));
        const MMutexLocker locker(&s_qCOHashMutex);
        //qDebug() << "ControlDoublePrivate::s_qCOHash.insert(" << key.group << "," << key.item << ")";
        s_qCOHash.insert(key, pControl);
        return pControl;
    }

    if (!flags.testFlag(ControlFlag::NoWarnIfMissing)) {
        qWarning() << "ControlDoublePrivate::getControl returning NULL for ("
                   << key.group << "," << key.item << ")";
        DEBUG_ASSERT(flags.testFlag(ControlFlag::NoAssertIfMissing));
    }
    return nullptr;
}

//static
QSharedPointer<ControlDoublePrivate> ControlDoublePrivate::getDefaultControl() {
    auto defaultCO = s_pDefaultCO.lock();
    if (!defaultCO) {
        // Try again with the mutex locked to protect against creating two
        // ControlDoublePrivateConst objects. Access to s_defaultCO itself is
        // thread save.
        MMutexLocker locker(&s_qCOHashMutex);
        defaultCO = s_pDefaultCO.lock();
        if (!defaultCO) {
            defaultCO = QSharedPointer<ControlDoublePrivate>(new ControlDoublePrivateConst());
            s_pDefaultCO = defaultCO;
        }
    }
    return defaultCO;
}

// static
QList<QSharedPointer<ControlDoublePrivate>> ControlDoublePrivate::getAllInstances() {
    QList<QSharedPointer<ControlDoublePrivate>> result;
    MMutexLocker locker(&s_qCOHashMutex);
    result.reserve(s_qCOHash.size());
    for (auto it = s_qCOHash.begin(); it != s_qCOHash.end(); ++it) {
        auto pControl = it.value().lock();
        if (pControl) {
            result.append(std::move(pControl));
        } else {
            // The weak pointer has become invalid and can be cleaned up
            s_qCOHash.erase(it);
        }
    }
    return result;
}

// static
QList<QSharedPointer<ControlDoublePrivate>> ControlDoublePrivate::takeAllInstances() {
    QList<QSharedPointer<ControlDoublePrivate>> result;
    MMutexLocker locker(&s_qCOHashMutex);
    result.reserve(s_qCOHash.size());
    for (auto it = s_qCOHash.begin(); it != s_qCOHash.end(); ++it) {
        auto pControl = it.value().lock();
        if (pControl) {
            result.append(std::move(pControl));
        }
    }
    s_qCOHash.clear();
    return result;
}

//static
QHash<ConfigKey, ConfigKey> ControlDoublePrivate::getControlAliases() {
    MMutexLocker locker(&s_qCOHashMutex);
    // lock thread-unsafe copy constructors of QHash
    return s_qCOAliasHash;
}

void ControlDoublePrivate::deleteCreatorCO() {
    delete m_pCreatorCO.fetchAndStoreOrdered(nullptr);
}

void ControlDoublePrivate::reset() {
    double defaultValue = m_defaultValue.getValue();
    // NOTE: pSender = NULL is important. The originator of this action does
    // not know the resulting value so it makes sense that we should emit a
    // general valueChanged() signal even though we know the originator.
    set(defaultValue, nullptr);
}

void ControlDoublePrivate::set(double value, QObject* pSender) {
    // If the behavior says to ignore the set, ignore it.
    QSharedPointer<ControlNumericBehavior> pBehavior = m_pBehavior;
    if (!