• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mcallegari / qlcplus / 22149820832

18 Feb 2026 05:11PM UTC coverage: 34.067% (+0.08%) from 33.99%
22149820832

push

github

mcallegari
engine: improve universe composition efficiency

- Universe::tick() now releases the semaphore only if no pending token exists.
- Elapsed-time aware fading: improves fade timing accuracy under variable CPU load and scheduler jitter; fades progress according to real time
instead of assuming perfect 20 ms cadence
- Reduced lock hold in Universe fader processing to avoid holding the universe fader list lock during potentially expensive per-fader channel writes, reducing contention
and improving scalability with many faders/universes
- Lower allocation/copy pressure in output emission: avoids per-tick temporary copy for plugin output write path while preserving safe ownership semantics for queued
signal consumers
- GenericFader channel lookup/update restructuring: reduces lock churn and repeated channel metadata setup overhead in high-channel scenes
- Locking correctness improvements in GenericFader: fixes data-race risk and ensures thread-safe mutation behavior
- Additional performance validation is done through engine/test/universeperf

122 of 202 new or added lines in 9 files covered. (60.4%)

2 existing lines in 2 files now uncovered.

17708 of 51980 relevant lines covered (34.07%)

41221.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

63.58
/engine/src/universe.cpp
1
/*
2
  Q Light Controller Plus
3
  universe.cpp
4

5
  Copyright (c) Massimo Callegari
6

7
  Licensed under the Apache License, Version 2.0 (the "License");
8
  you may not use this file except in compliance with the License.
9
  You may obtain a copy of the License at
10

11
      http://www.apache.org/licenses/LICENSE-2.0.txt
12

13
  Unless required by applicable law or agreed to in writing, software
14
  distributed under the License is distributed on an "AS IS" BASIS,
15
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
  See the License for the specific language governing permissions and
17
  limitations under the License.
18
*/
19

20
#include <QXmlStreamReader>
21
#include <QXmlStreamWriter>
22
#include <QElapsedTimer>
23
#include <QDebug>
24
#include <math.h>
25

26
#include "channelmodifier.h"
27
#include "inputoutputmap.h"
28
#include "genericfader.h"
29
#include "qlcioplugin.h"
30
#include "outputpatch.h"
31
#include "grandmaster.h"
32
#include "mastertimer.h"
33
#include "inputpatch.h"
34
#include "qlcmacros.h"
35
#include "universe.h"
36
#include "function.h"
37
#include "qlcfile.h"
38
#include "utils.h"
39

40
#define RELATIVE_ZERO_8BIT   0x7F
41
#define RELATIVE_ZERO_16BIT  0x7F00
42

43
#define KXMLUniverseNormalBlend      QStringLiteral("Normal")
44
#define KXMLUniverseMaskBlend        QStringLiteral("Mask")
45
#define KXMLUniverseAdditiveBlend    QStringLiteral("Additive")
46
#define KXMLUniverseSubtractiveBlend QStringLiteral("Subtractive")
47

48
Universe::Universe(quint32 id, GrandMaster *gm, QObject *parent)
1,076✔
49
    : QThread(parent)
50
    , m_id(id)
1,076✔
51
    , m_name(QString("Universe %1").arg(id + 1))
1,076✔
52
    , m_grandMaster(gm)
1,076✔
53
    , m_passthrough(false)
1,076✔
54
    , m_monitor(false)
1,076✔
55
    , m_inputPatch(NULL)
1,076✔
56
    , m_fbPatch(NULL)
1,076✔
57
    , m_channelsMask(new QByteArray(UNIVERSE_SIZE, char(0)))
1,076✔
58
    , m_modifiedZeroValues(new QByteArray(UNIVERSE_SIZE, char(0)))
1,076✔
59
    , m_running(false)
1,076✔
60
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
61
    , m_fadersMutex(QMutex::Recursive)
62
#endif
63
    , m_usedChannels(0)
1,076✔
64
    , m_totalChannels(0)
1,076✔
65
    , m_totalChannelsChanged(false)
1,076✔
66
    , m_intensityChannelsChanged(false)
1,076✔
67
    , m_preGMValues(new QByteArray(UNIVERSE_SIZE, char(0)))
1,076✔
68
    , m_postGMValues(new QByteArray(UNIVERSE_SIZE, char(0)))
1,076✔
69
    , m_lastPostGMValues(new QByteArray(UNIVERSE_SIZE, char(0)))
1,076✔
70
    , m_blackoutValues(new QByteArray(UNIVERSE_SIZE, char(0)))
1,076✔
71
    , m_passthroughValues()
5,380✔
72
{
73
    m_modifiers.fill(NULL, UNIVERSE_SIZE);
1,076✔
74

75
    connect(m_grandMaster, SIGNAL(valueChanged(uchar)),
1,076✔
76
            this, SLOT(slotGMValueChanged()));
77
}
1,076✔
78

79
Universe::~Universe()
1,972✔
80
{
81
    if (isRunning() == true)
1,061✔
82
    {
83
        // isRunning is inconsistent with m_running,
84
        // so double check if the thread is really in the run loop
85
        while (m_running == false)
×
86
            usleep(10000);
×
87

88
        m_running = false;
×
89
        wait(1000);
×
90
    }
91

92
    delete m_inputPatch;
1,061✔
93
    int opCount = m_outputPatchList.count();
1,061✔
94
    for (int i = 0; i < opCount; i++)
1,225✔
95
    {
96
        OutputPatch *patch = m_outputPatchList.takeLast();
164✔
97
        delete patch;
164✔
98
    }
99
    delete m_fbPatch;
1,061✔
100
}
1,972✔
101

102
void Universe::setName(QString name)
7✔
103
{
104
    if (name.isEmpty())
7✔
105
        m_name = QString("Universe %1").arg(m_id + 1);
×
106
    else
107
        m_name = name;
7✔
108
    emit nameChanged();
7✔
109
}
7✔
110

111
QString Universe::name() const
107✔
112
{
113
    return m_name;
107✔
114
}
115

116
void Universe::setID(quint32 id)
2✔
117
{
118
    m_id = id;
2✔
119
}
2✔
120

121
quint32 Universe::id() const
160,893✔
122
{
123
    return m_id;
160,893✔
124
}
125

126
ushort Universe::usedChannels()
223✔
127
{
128
    return m_usedChannels;
223✔
129
}
130

131
ushort Universe::totalChannels()
6✔
132
{
133
    return m_totalChannels;
6✔
134
}
135

136
bool Universe::hasChanged()
5,411,659✔
137
{
138
    bool changed =
139
        memcmp(m_lastPostGMValues->constData(), m_postGMValues->constData(), m_usedChannels) != 0;
5,411,659✔
140
    if (changed)
5,411,659✔
141
        memcpy(m_lastPostGMValues->data(), m_postGMValues->constData(), m_usedChannels);
158,717✔
142
    return changed;
5,411,659✔
143
}
144

145
void Universe::setPassthrough(bool enable)
6✔
146
{
147
    if (enable == m_passthrough)
6✔
148
        return;
2✔
149

150
    qDebug() << "Set universe" << id() << "passthrough to" << enable;
4✔
151

152
    disconnectInputPatch();
4✔
153

154
    if (enable && m_passthroughValues.isNull())
4✔
155
    {
156
        // When passthrough is disabled, we don't release the array, since it's only ~512 B and
157
        // we would have to synchronize with other threads
158

159
        // When enabling passthrough, make sure the array is allocated BEFORE m_passthrough is set to
160
        // true. That way we only have to check for m_passthrough, and do not need to check
161
        // m_passthroughValues.isNull()
162
        m_passthroughValues.reset(new QByteArray(UNIVERSE_SIZE, char(0)));
4✔
163
    }
164

165
    m_passthrough = enable;
4✔
166

167
    connectInputPatch();
4✔
168

169
    emit passthroughChanged();
4✔
170
}
171

172
bool Universe::passthrough() const
12✔
173
{
174
    return m_passthrough;
12✔
175
}
176

177
void Universe::setMonitor(bool enable)
1✔
178
{
179
    m_monitor = enable;
1✔
180
}
1✔
181

182
bool Universe::monitor() const
1✔
183
{
184
    return m_monitor;
1✔
185
}
186

187
void Universe::slotGMValueChanged()
5,033✔
188
{
189
    {
190
        for (int i = 0; i < m_intensityChannels.size(); ++i)
2,565,584✔
191
        {
192
            int channel = m_intensityChannels.at(i);
2,560,551✔
193
            updatePostGMValue(channel);
2,560,551✔
194
        }
195
    }
196

197
    if (m_grandMaster->channelMode() == GrandMaster::AllChannels)
5,033✔
198
    {
199
        for (int i = 0; i < m_nonIntensityChannels.size(); ++i)
32✔
200
        {
201
            int channel = m_nonIntensityChannels.at(i);
15✔
202
            updatePostGMValue(channel);
15✔
203
        }
204
    }
205
}
5,033✔
206

207
/************************************************************************
208
 * Faders
209
 ************************************************************************/
210

211
QSharedPointer<GenericFader> Universe::requestFader(Universe::FaderPriority priority)
2,551✔
212
{
213
    int insertPos = 0;
2,551✔
214
    QSharedPointer<GenericFader> fader = QSharedPointer<GenericFader>(new GenericFader());
2,551✔
215
    fader->setPriority(priority);
2,551✔
216

217
    {
218
        QMutexLocker fadersLocker(&m_fadersMutex);
2,551✔
219
        {
220
            for (int i = m_faders.count() - 1; i >= 0; i--)
7,951✔
221
            {
222
                const QSharedPointer<GenericFader>& f = m_faders.at(i);
7,779✔
223
                if (!f.isNull() && f->priority() <= fader->priority())
7,779✔
224
                {
225
                    insertPos = i + 1;
2,379✔
226
                    break;
2,379✔
227
                }
228
            }
229
        }
230

231
        m_faders.insert(insertPos, fader);
2,551✔
232

233
        qDebug() << "[Universe]" << id() << ": Generic fader with priority" << fader->priority()
5,102✔
234
                 << "registered at pos" << insertPos << ", count" << m_faders.count();
2,551✔
235
    }
2,551✔
236
    return fader;
2,551✔
237
}
×
238

239
void Universe::dismissFader(QSharedPointer<GenericFader> fader)
3✔
240
{
241
    QMutexLocker fadersLocker(&m_fadersMutex);
3✔
242
    int index = m_faders.indexOf(fader);
3✔
243
    if (index >= 0)
3✔
244
    {
245
        m_faders.takeAt(index);
3✔
246
        fader.clear();
3✔
247
    }
248
}
3✔
249

250
void Universe::requestFaderPriority(QSharedPointer<GenericFader> fader, Universe::FaderPriority priority)
×
251
{
252
    QMutexLocker fadersLocker(&m_fadersMutex);
×
253
    int pos = m_faders.indexOf(fader);
×
254
    int newPos = 0;
×
255

256
    if (pos == -1)
×
257
        return;
×
258

259
    for (int i = m_faders.count() - 1; i >= 0; i--)
×
260
    {
261
        QSharedPointer<GenericFader> f = m_faders.at(i);
×
262
        if (!f.isNull() && f->priority() <= priority)
×
263
        {
264
            newPos = i;
×
265
            fader->setPriority(priority);
×
266
            break;
×
267
        }
268
    }
×
269

270
    if (newPos != pos)
×
271
    {
272
        m_faders.move(pos, newPos);
×
273
        qDebug() << "[Universe]" << id() << ": Generic fader moved from" << pos
×
274
                 << "to" << m_faders.indexOf(fader) << ". Count:" << m_faders.count();
×
275
    }
276
}
×
277

278
QList<QSharedPointer<GenericFader> > Universe::faders()
×
279
{
280
    return m_faders;
×
281
}
282

283
void Universe::setFaderPause(quint32 functionID, bool enable)
8✔
284
{
285
    QMutexLocker fadersLocker(&m_fadersMutex);
8✔
286
    QMutableListIterator<QSharedPointer<GenericFader> > it(m_faders);
8✔
287
    while (it.hasNext())
17✔
288
    {
289
        QSharedPointer<GenericFader> fader = it.next();
9✔
290
        if (fader.isNull() || fader->parentFunctionID() != functionID)
9✔
291
            continue;
6✔
292

293
        fader->setPaused(enable);
3✔
294
    }
9✔
295
}
8✔
296

297
void Universe::setFaderFadeOut(int fadeTime)
×
298
{
299
    QMutexLocker fadersLocker(&m_fadersMutex);
×
300
    foreach (QSharedPointer<GenericFader> fader, m_faders)
×
301
    {
302
        if (!fader.isNull() && fader->parentFunctionID() != Function::invalidId())
×
303
            fader->setFadeOut(true, uint(fadeTime));
×
304
    }
×
305
}
×
306

307
void Universe::tick()
688✔
308
{
309
    // Keep at most one pending tick to avoid queueing stale work when running late.
310
    if (m_semaphore.available() == 0)
688✔
311
        m_semaphore.release(1);
12✔
312
}
688✔
313

314
void Universe::processFaders(uint elapsedMs)
289,606✔
315
{
316
    flushInput();
289,606✔
317
    zeroIntensityChannels();
289,606✔
318

319
    QList<QSharedPointer<GenericFader>> activeFaders;
289,606✔
320
    {
321
        QMutexLocker fadersLocker(&m_fadersMutex);
289,606✔
322
        activeFaders.reserve(m_faders.size());
289,606✔
323
        QMutableListIterator<QSharedPointer<GenericFader> > it(m_faders);
289,606✔
324
        while (it.hasNext())
1,612,707✔
325
        {
326
            QSharedPointer<GenericFader> fader = it.next(); //m_faders.at(i);
1,323,101✔
327
            if (fader.isNull())
1,323,101✔
328
                continue;
×
329

330
            // destroy a fader if it's been requested
331
            // and it's not fading out
332
            if (fader->deleteRequested() && !fader->isFadingOut())
1,323,101✔
333
            {
334
                fader->removeAll();
4✔
335
                it.remove();
4✔
336
                fader.clear();
4✔
337
                continue;
4✔
338
            }
339

340
            if (fader->isEnabled() == false)
1,323,097✔
341
                continue;
×
342

343
            activeFaders.append(fader);
1,323,097✔
344
        }
1,323,101✔
345
    }
289,606✔
346

347
    foreach (const QSharedPointer<GenericFader> &fader, activeFaders)
1,612,703✔
348
        fader->write(this, elapsedMs);
1,612,703✔
349

350
    bool dataChanged = hasChanged();
289,606✔
351
    const QByteArray postGM = QByteArray::fromRawData(m_postGMValues->constData(), m_usedChannels);
289,606✔
352
    dumpOutput(postGM, dataChanged);
289,606✔
353

354
    if (dataChanged)
289,606✔
355
        emit universeWritten(id(), QByteArray(postGM.constData(), postGM.size()));
157,691✔
356
}
289,606✔
357

358
void Universe::run()
×
359
{
360
    m_running = true;
×
361
    int timeout = int(MasterTimer::tick()) * 2;
×
NEW
362
    QElapsedTimer elapsedTimer;
×
NEW
363
    elapsedTimer.start();
×
364

365
    qDebug() << "Universe thread started" << id();
×
366

367
    while (m_running)
×
368
    {
369
        if (m_semaphore.tryAcquire(1, timeout) == false)
×
370
        {
371
            //qWarning() << "Semaphore not acquired on universe" << id();
372
            continue;
×
373
        }
374
#if 0
375
        if (m_faders.count())
376
            qDebug() << "<<<<<<<< UNIVERSE TICK - id" << id() << "faders:" << m_faders.count();
377
#endif
NEW
378
        const qint64 elapsedNs = elapsedTimer.nsecsElapsed();
×
NEW
379
        elapsedTimer.restart();
×
NEW
380
        uint elapsedMs = uint((elapsedNs + 500000) / 1000000);
×
NEW
381
        if (elapsedMs == 0)
×
NEW
382
            elapsedMs = 1;
×
383

NEW
384
        processFaders(elapsedMs);
×
385
    }
386

387
    qDebug() << "Universe thread stopped" << id();
×
388
}
×
389

390
/************************************************************************
391
 * Values
392
 ************************************************************************/
393

394
void Universe::reset()
9✔
395
{
396
    m_preGMValues->fill(0);
9✔
397
    m_blackoutValues->fill(0);
9✔
398

399
    if (m_passthrough)
9✔
400
        (*m_postGMValues) = (*m_passthroughValues);
×
401
    else
402
        m_postGMValues->fill(0);
9✔
403

404
    m_modifiers.fill(NULL, UNIVERSE_SIZE);
9✔
405
    m_passthrough = false; // not releasing m_passthroughValues, see comment in setPassthrough
9✔
406
}
9✔
407

408
void Universe::reset(int address, int range)
5,106,777✔
409
{
410
    if (address >= UNIVERSE_SIZE)
5,106,777✔
411
        return;
×
412

413
    if (address + range > UNIVERSE_SIZE)
5,106,777✔
414
       range = UNIVERSE_SIZE - address;
1✔
415

416
    memset(m_preGMValues->data() + address, 0, range * sizeof(*m_preGMValues->data()));
5,106,777✔
417
    memset(m_blackoutValues->data() + address, 0, range * sizeof(*m_blackoutValues->data()));
5,106,777✔
418
    memcpy(m_postGMValues->data() + address, m_modifiedZeroValues->data() + address, range * sizeof(*m_postGMValues->data()));
5,106,777✔
419

420
    applyPassthroughValues(address, range);
5,106,777✔
421
}
422

423
void Universe::applyPassthroughValues(int address, int range)
5,106,777✔
424
{
425
    if (!m_passthrough)
5,106,777✔
426
        return;
5,106,777✔
427

428
    for (int i = address; i < address + range && i < UNIVERSE_SIZE; i++)
×
429
    {
430
        if (static_cast<uchar>(m_postGMValues->at(i)) < static_cast<uchar>(m_passthroughValues->at(i))) // HTP merge
×
431
        {
432
            (*m_postGMValues)[i] = (*m_passthroughValues)[i];
×
433
        }
434
    }
435
}
436

437
void Universe::zeroIntensityChannels()
299,708✔
438
{
439
    updateIntensityChannelsRanges();
299,708✔
440
    int const* channels = m_intensityChannelsRanges.constData();
299,708✔
441
    for (int i = 0; i < m_intensityChannelsRanges.size(); ++i)
5,406,484✔
442
    {
443
        short channel = channels[i] >> 16;
5,106,776✔
444
        short size = channels[i] & 0xffff;
5,106,776✔
445

446
        reset(channel, size);
5,106,776✔
447
    }
448
}
299,708✔
449

450
QHash<int, uchar> Universe::intensityChannels()
1✔
451
{
452
    QHash <int, uchar> intensityList;
1✔
453
    for (int i = 0; i < m_intensityChannels.size(); ++i)
1✔
454
    {
455
        int channel = m_intensityChannels.at(i);
×
456
        intensityList[channel] = m_preGMValues->at(channel);
×
457
    }
458
    return intensityList;
1✔
459
}
×
460

461
uchar Universe::postGMValue(int address) const
×
462
{
463
    if (address >= m_postGMValues->size())
×
464
        return 0;
×
465

466
    return uchar(m_postGMValues->at(address));
×
467
}
468

469
const QByteArray* Universe::postGMValues() const
4,633✔
470
{
471
    return m_postGMValues.data();
4,633✔
472
}
473

474
Universe::BlendMode Universe::stringToBlendMode(QString mode)
5✔
475
{
476
    if (mode == KXMLUniverseNormalBlend)
5✔
477
        return NormalBlend;
1✔
478
    else if (mode == KXMLUniverseMaskBlend)
4✔
479
        return MaskBlend;
1✔
480
    else if (mode == KXMLUniverseAdditiveBlend)
3✔
481
        return AdditiveBlend;
1✔
482
    else if (mode == KXMLUniverseSubtractiveBlend)
2✔
483
        return SubtractiveBlend;
1✔
484

485
    return NormalBlend;
1✔
486
}
487

488
QString Universe::blendModeToString(Universe::BlendMode mode)
7✔
489
{
490
    switch(mode)
7✔
491
    {
492
        default:
1✔
493
        case NormalBlend:
494
            return QString(KXMLUniverseNormalBlend);
1✔
495
        break;
496
        case MaskBlend:
1✔
497
            return QString(KXMLUniverseMaskBlend);
1✔
498
        break;
499
        case AdditiveBlend:
4✔
500
            return QString(KXMLUniverseAdditiveBlend);
4✔
501
        break;
502
        case SubtractiveBlend:
1✔
503
            return QString(KXMLUniverseSubtractiveBlend);
1✔
504
        break;
505
    }
506
}
507

508
const QByteArray Universe::preGMValues() const
1,326,130✔
509
{
510
    return *m_preGMValues;
1,326,130✔
511
}
512

513
uchar Universe::preGMValue(int address) const
19,376,027✔
514
{
515
    if (address < 0 || address >= m_preGMValues->size())
19,376,027✔
516
        return 0U;
×
517

518
    return static_cast<uchar>(m_preGMValues->at(address));
19,376,027✔
519
}
520

521
uchar Universe::applyGM(int channel, uchar value)
14,591,618✔
522
{
523
    if ((m_grandMaster->channelMode() == GrandMaster::Intensity && m_channelsMask->at(channel) & Intensity) ||
20,353,604✔
524
        (m_grandMaster->channelMode() == GrandMaster::AllChannels))
5,761,986✔
525
    {
526
        if (m_grandMaster->valueMode() == GrandMaster::Limit)
8,830,686✔
527
            value = MIN(value, m_grandMaster->value());
792✔
528
        else
529
            value = char(floor((double(value) * m_grandMaster->fraction()) + 0.5));
8,829,894✔
530
    }
531

532
    return value;
14,591,618✔
533
}
534

535
uchar Universe::applyModifiers(int channel, uchar value)
19,315,423✔
536
{
537
    if (m_modifiers.at(channel) != NULL)
19,315,423✔
538
        return m_modifiers.at(channel)->getValue(value);
×
539

540
    return value;
19,315,423✔
541
}
542

543
uchar Universe::applyPassthrough(int channel, uchar value)
19,315,423✔
544
{
545
    if (m_passthrough)
19,315,423✔
546
    {
547
        const uchar passthroughValue = static_cast<uchar>(m_passthroughValues->at(channel));
×
548
        if (value < passthroughValue) // HTP merge
×
549
        {
550
            return passthroughValue;
×
551
        }
552
    }
553

554
    return value;
19,315,423✔
555
}
556

557
void Universe::updatePostGMValue(int channel)
19,315,423✔
558
{
559
    uchar value = preGMValue(channel);
19,315,423✔
560

561
    if (value != 0)
19,315,423✔
562
        value = applyGM(channel, value);
14,589,058✔
563

564
    value = applyModifiers(channel, value);
19,315,423✔
565
    value = applyPassthrough(channel, value);
19,315,423✔
566

567
    (*m_postGMValues)[channel] = static_cast<char>(value);
19,315,423✔
568
}
19,315,423✔
569

570
/************************************************************************
571
 * Patches
572
 ************************************************************************/
573

574
bool Universe::isPatched()
2✔
575
{
576
    if (m_inputPatch != NULL || m_outputPatchList.count() || m_fbPatch != NULL)
2✔
577
        return true;
1✔
578

579
    return false;
1✔
580
}
581

582
bool Universe::setInputPatch(QLCIOPlugin *plugin,
6✔
583
                             quint32 input, QLCInputProfile *profile)
584
{
585
    qDebug() << "[Universe] setInputPatch - ID:" << m_id << ", plugin:" << ((plugin == NULL)?"None":plugin->name())
12✔
586
             << ", input:" << input << ", profile:" << ((profile == NULL)?"None":profile->name());
6✔
587
    if (m_inputPatch == NULL)
6✔
588
    {
589
        if (plugin == NULL || input == QLCIOPlugin::invalidLine())
5✔
590
            return true;
1✔
591

592
        m_inputPatch = new InputPatch(m_id, this);
4✔
593
        connectInputPatch();
4✔
594
    }
595
    else
596
    {
597
        if (input == QLCIOPlugin::invalidLine())
1✔
598
        {
599
            disconnectInputPatch();
×
600
            delete m_inputPatch;
×
601
            m_inputPatch = NULL;
×
602
            emit inputPatchChanged();
×
603
            return true;
×
604
        }
605
    }
606

607
    if (m_inputPatch != NULL)
5✔
608
    {
609
        bool result = m_inputPatch->set(plugin, input, profile);
5✔
610
        emit inputPatchChanged();
5✔
611
        return result;
5✔
612
    }
613

614
    return true;
×
615
}
616

617
bool Universe::setOutputPatch(QLCIOPlugin *plugin, quint32 output, int index)
169✔
618
{
619
    if (index < 0)
169✔
620
        return false;
×
621

622
    qDebug() << "[Universe] setOutputPatch - ID:" << m_id
169✔
623
             << ", plugin:" << ((plugin == NULL) ? "None" : plugin->name()) << ", output:" << output;
169✔
624

625
    // replace or delete an existing patch
626
    if (index < m_outputPatchList.count())
169✔
627
    {
628
        if (plugin == NULL || output == QLCIOPlugin::invalidLine())
2✔
629
        {
630
            // need to delete an existing patch
631
            OutputPatch *patch = m_outputPatchList.takeAt(index);
2✔
632
            delete patch;
2✔
633
            emit outputPatchesCountChanged();
2✔
634
            return true;
2✔
635
        }
636

637
        OutputPatch *patch = m_outputPatchList.at(index);
×
638
        bool result = patch->set(plugin, output);
×
639
        emit outputPatchChanged();
×
640
        return result;
×
641
    }
642
    else
643
    {
644
        if (plugin == NULL || output == QLCIOPlugin::invalidLine())
167✔
645
            return false;
1✔
646

647
        // add a new patch
648
        OutputPatch *patch = new OutputPatch(m_id, this);
166✔
649
        bool result = patch->set(plugin, output);
166✔
650
        m_outputPatchList.append(patch);
166✔
651
        emit outputPatchesCountChanged();
166✔
652
        return result;
166✔
653
    }
654

655
    return false;
656
}
657

658
bool Universe::setFeedbackPatch(QLCIOPlugin *plugin, quint32 output)
×
659
{
660
    qDebug() << Q_FUNC_INFO << "plugin:" << plugin << "output:" << output;
×
661
    if (m_fbPatch == NULL)
×
662
    {
663
        if (plugin == NULL || output == QLCIOPlugin::invalidLine())
×
664
            return false;
×
665

666
        m_fbPatch = new OutputPatch(m_id, this);
×
667
    }
668
    else
669
    {
670
        if (plugin == NULL || output == QLCIOPlugin::invalidLine())
×
671
        {
672
            delete m_fbPatch;
×
673
            m_fbPatch = NULL;
×
674
            emit hasFeedbackChanged();
×
675
            return true;
×
676
        }
677
    }
678
    if (m_fbPatch != NULL)
×
679
    {
680
        bool result = m_fbPatch->set(plugin, output);
×
681
        emit hasFeedbackChanged();
×
682
        return result;
×
683
    }
684

685
    return false;
×
686
}
687

688
bool Universe::hasFeedback() const
×
689
{
690
    return m_fbPatch != NULL ? true : false;
×
691
}
692

693
InputPatch *Universe::inputPatch() const
161✔
694
{
695
    return m_inputPatch;
161✔
696
}
697

698
OutputPatch *Universe::outputPatch(int index) const
498✔
699
{
700
    if (index < 0 || index >= m_outputPatchList.count())
498✔
701
        return NULL;
14✔
702

703
    return m_outputPatchList.at(index);
484✔
704
}
705

706
int Universe::outputPatchesCount() const
83✔
707
{
708
    return m_outputPatchList.count();
83✔
709
}
710

711
OutputPatch *Universe::feedbackPatch() const
49✔
712
{
713
    return m_fbPatch;
49✔
714
}
715

716
void Universe::dumpOutput(const QByteArray &data, bool dataChanged)
289,654✔
717
{
718
    if (m_outputPatchList.count() == 0)
289,654✔
719
        return;
264,864✔
720

721
    foreach (OutputPatch *op, m_outputPatchList)
49,580✔
722
    {
723
        if (m_totalChannelsChanged == true)
24,790✔
724
            op->setPluginParameter(PLUGIN_UNIVERSECHANNELS, m_totalChannels);
154✔
725

726
        if (op->blackout())
24,790✔
727
            op->dump(m_id, *m_blackoutValues, dataChanged);
20✔
728
        else
729
            op->dump(m_id, data, dataChanged);
24,770✔
730
    }
24,790✔
731
    m_totalChannelsChanged = false;
24,790✔
732
}
733

734
void Universe::flushInput()
289,626✔
735
{
736
    if (m_inputPatch == NULL)
289,626✔
737
        return;
289,621✔
738

739
    m_inputPatch->flush(m_id);
5✔
740
}
741

742
void Universe::slotInputValueChanged(quint32 universe, quint32 channel, uchar value, const QString &key)
×
743
{
744
    if (m_passthrough)
×
745
    {
746
        if (universe == m_id)
×
747
        {
748
            if (channel >= UNIVERSE_SIZE)
×
UNCOV
749
                return;
×
750

751
            if (channel >= m_usedChannels)
×
752
                m_usedChannels = channel + 1;
×
753

754
            (*m_passthroughValues)[channel] = value;
×
755

756
            updatePostGMValue(channel);
×
757
        }
758
    }
759
    else
760
        emit inputValueChanged(universe, channel, value, key);
×
761
}
762

763
void Universe::connectInputPatch()
8✔
764
{
765
    if (m_inputPatch == NULL)
8✔
766
        return;
4✔
767

768
    if (!m_passthrough)
4✔
769
        connect(m_inputPatch, SIGNAL(inputValueChanged(quint32,quint32,uchar,const QString&)),
4✔
770
                this, SIGNAL(inputValueChanged(quint32,quint32,uchar,QString)));
771
    else
772
        connect(m_inputPatch, SIGNAL(inputValueChanged(quint32,quint32,uchar,const QString&)),
×
773
                this, SLOT(slotInputValueChanged(quint32,quint32,uchar,const QString&)));
774
}
775

776
void Universe::disconnectInputPatch()
4✔
777
{
778
    if (m_inputPatch == NULL)
4✔
779
        return;
4✔
780

781
    if (!m_passthrough)
×
782
        disconnect(m_inputPatch, SIGNAL(inputValueChanged(quint32,quint32,uchar,const QString&)),
×
783
                this, SIGNAL(inputValueChanged(quint32,quint32,uchar,QString)));
784
    else
785
        disconnect(m_inputPatch, SIGNAL(inputValueChanged(quint32,quint32,uchar,const QString&)),
×
786
                this, SLOT(slotInputValueChanged(quint32,quint32,uchar,const QString&)));
787
}
788

789
/************************************************************************
790
 * Channels capabilities
791
 ************************************************************************/
792

793
void Universe::setChannelCapability(ushort channel, QLCChannel::Group group, ChannelType forcedType)
67,241✔
794
{
795
    if (channel >= (ushort)m_channelsMask->length())
67,241✔
796
        return;
×
797

798
    if (Utils::vectorRemove(m_intensityChannels, channel))
67,241✔
799
        m_intensityChannelsChanged = true;
830✔
800
    Utils::vectorRemove(m_nonIntensityChannels, channel);
67,241✔
801

802
    if (forcedType != Undefined)
67,241✔
803
    {
804
        (*m_channelsMask)[channel] = char(forcedType);
5✔
805
        if ((forcedType & HTP) == HTP)
5✔
806
        {
807
            //qDebug() << "--- Channel" << channel << "forced type HTP";
808
            Utils::vectorSortedAddUnique(m_intensityChannels, channel);
3✔
809
            m_intensityChannelsChanged = true;
3✔
810
            if (group == QLCChannel::Intensity)
3✔
811
            {
812
                //qDebug() << "--- Channel" << channel << "Intensity + HTP";
813
                (*m_channelsMask)[channel] = char(HTP | Intensity);
×
814
            }
815
        }
816
        else if ((forcedType & LTP) == LTP)
2✔
817
        {
818
            //qDebug() << "--- Channel" << channel << "forced type LTP";
819
            Utils::vectorSortedAddUnique(m_nonIntensityChannels, channel);
2✔
820
        }
821
    }
822
    else
823
    {
824
        if (group == QLCChannel::Intensity)
67,236✔
825
        {
826
            //qDebug() << "--- Channel" << channel << "Intensity + HTP";
827
            (*m_channelsMask)[channel] = char(HTP | Intensity);
45,576✔
828
            Utils::vectorSortedAddUnique(m_intensityChannels, channel);
45,576✔
829
            m_intensityChannelsChanged = true;
45,576✔
830
        }
831
        else
832
        {
833
            //qDebug() << "--- Channel" << channel << "LTP";
834
            (*m_channelsMask)[channel] = char(LTP);
21,660✔
835
            Utils::vectorSortedAddUnique(m_nonIntensityChannels, channel);
21,660✔
836
        }
837
    }
838

839
    // qDebug() << Q_FUNC_INFO << "Channel:" << channel << "mask:" << QString::number(m_channelsMask->at(channel), 16);
840
    if (channel >= m_totalChannels)
67,241✔
841
    {
842
        m_totalChannels = channel + 1;
65,648✔
843
        m_totalChannelsChanged = true;
65,648✔
844
    }
845
}
846

847
uchar Universe::channelCapabilities(ushort channel)
517✔
848
{
849
    if (channel >= (ushort)m_channelsMask->length())
517✔
850
        return Undefined;
×
851

852
    return m_channelsMask->at(channel);
517✔
853
}
854

855
void Universe::setChannelDefaultValue(ushort channel, uchar value)
2,592✔
856
{
857
    if (channel >= m_totalChannels)
2,592✔
858
    {
859
        m_totalChannels = channel + 1;
×
860
        m_totalChannelsChanged = true;
×
861
    }
862

863
    if (channel >= m_usedChannels)
2,592✔
864
        m_usedChannels = channel + 1;
999✔
865

866
    (*m_preGMValues)[channel] = value;
2,592✔
867
    updatePostGMValue(channel);
2,592✔
868
}
2,592✔
869

870
void Universe::setChannelModifier(ushort channel, ChannelModifier *modifier)
2,592✔
871
{
872
    if (channel >= (ushort)m_modifiers.count())
2,592✔
873
        return;
×
874

875
    m_modifiers[channel] = modifier;
2,592✔
876

877
    if (modifier != NULL)
2,592✔
878
    {
879
        (*m_modifiedZeroValues)[channel] = modifier->getValue(0);
×
880

881
        if (channel >= m_totalChannels)
×
882
        {
883
            m_totalChannels = channel + 1;
×
884
            m_totalChannelsChanged = true;
×
885
        }
886

887
        if (channel >= m_usedChannels)
×
888
            m_usedChannels = channel + 1;
×
889
    }
890

891
    updatePostGMValue(channel);
2,592✔
892
}
893

894
ChannelModifier *Universe::channelModifier(ushort channel)
×
895
{
896
    if (channel >= (ushort)m_modifiers.count())
×
897
        return NULL;
×
898

899
    return m_modifiers.at(channel);
×
900
}
901

902
void Universe::updateIntensityChannelsRanges()
299,708✔
903
{
904
    if (!m_intensityChannelsChanged)
299,708✔
905
        return;
299,520✔
906

907
    m_intensityChannelsChanged = false;
188✔
908

909
    m_intensityChannelsRanges.clear();
188✔
910
    short currentPos = -1;
188✔
911
    short currentSize = 0;
188✔
912

913
    for (int i = 0; i < m_intensityChannels.size(); ++i)
41,849✔
914
    {
915
        int channel = m_intensityChannels.at(i);
41,661✔
916
        if (currentPos + currentSize == channel)
41,661✔
917
            ++currentSize;
21,129✔
918
        else
919
        {
920
            if (currentPos != -1)
20,532✔
921
                m_intensityChannelsRanges.append((currentPos << 16) | currentSize);
20,344✔
922
            currentPos = channel;
20,532✔
923
            currentSize = 1;
20,532✔
924
        }
925
    }
926
    if (currentPos != -1)
188✔
927
        m_intensityChannelsRanges.append((currentPos << 16) | currentSize);
188✔
928

929
    qDebug() << Q_FUNC_INFO << ":" << m_intensityChannelsRanges.size() << "ranges";
188✔
930
}
931

932
/****************************************************************************
933
 * Writing
934
 ****************************************************************************/
935

936
bool Universe::write(int address, uchar value, bool forceLTP)
6,069,911✔
937
{
938
    Q_ASSERT(address < UNIVERSE_SIZE);
6,069,911✔
939

940
    //qDebug() << "[Universe]" << id() << ": write channel" << address << ", value:" << value;
941

942
    if (address >= m_usedChannels)
6,069,911✔
943
        m_usedChannels = address + 1;
9,372✔
944

945
    if (m_channelsMask->at(address) & HTP)
6,069,911✔
946
    {
947
        if (forceLTP == false && value < (uchar)m_preGMValues->at(address))
3,503,733✔
948
        {
949
            return false;
×
950
        }
951
    }
952
    else
953
    {
954
        // preserve non HTP channels for blackout
955
        (*m_blackoutValues)[address] = char(value);
2,566,178✔
956
    }
957

958
    (*m_preGMValues)[address] = char(value);
6,069,911✔
959

960
    updatePostGMValue(address);
6,069,911✔
961

962
    return true;
6,069,911✔
963
}
964

965
bool Universe::writeMultiple(int address, quint32 value, int channelCount)
10,184,750✔
966
{
967
    for (int i = 0; i < channelCount; i++)
20,369,500✔
968
    {
969
        //qDebug() << "[Universe]" << id() << ": write channel" << (address + i) << ", value:" << QString::number(((uchar *)&value)[channelCount - 1 - i]);
970

971
        // preserve non HTP channels for blackout
972
        if ((m_channelsMask->at(address + i) & HTP) == 0)
10,184,750✔
973
            (*m_blackoutValues)[address + i] = ((uchar *)&value)[channelCount - 1 - i];
4,543,775✔
974

975
        (*m_preGMValues)[address + i] = ((uchar *)&value)[channelCount - 1 - i];
10,184,750✔
976

977
        updatePostGMValue(address + i);
10,184,750✔
978
    }
979

980
    return true;
10,184,750✔
981
}
982

983
bool Universe::writeRelative(int address, quint32 value, int channelCount)
495,009✔
984
{
985
    Q_ASSERT(address < UNIVERSE_SIZE);
495,009✔
986

987
    //qDebug() << "Write relative channel" << address << "value" << value;
988

989
    if (address + channelCount >= m_usedChannels)
495,009✔
990
        m_usedChannels = address + channelCount;
24,759✔
991

992
    if (channelCount == 1)
495,009✔
993
    {
994
        short newVal = uchar((*m_preGMValues)[address]);
495,006✔
995
        newVal += short(value) - RELATIVE_ZERO_8BIT;
495,006✔
996
        (*m_preGMValues)[address] = char(CLAMP(newVal, 0, UCHAR_MAX));
495,006✔
997
        (*m_blackoutValues)[address] = char(CLAMP(newVal, 0, UCHAR_MAX));
495,006✔
998
        updatePostGMValue(address);
495,006✔
999
    }
1000
    else
1001
    {
1002
        quint32 currentValue = 0;
3✔
1003
        for (int i = 0; i < channelCount; i++)
9✔
1004
            currentValue = (currentValue << 8) + uchar(m_preGMValues->at(address + i));
6✔
1005

1006
        currentValue = qint32(CLAMP((qint32)currentValue + (qint32)value - RELATIVE_ZERO_16BIT, 0, 0xFFFF));
3✔
1007

1008
        for (int i = 0; i < channelCount; i++)
9✔
1009
        {
1010
            (*m_preGMValues)[address + i] = ((uchar *)&currentValue)[channelCount - 1 - i];
6✔
1011
            (*m_blackoutValues)[address + i] = ((uchar *)&currentValue)[channelCount - 1 - i];
6✔
1012
            updatePostGMValue(address + i);
6✔
1013
        }
1014
    }
1015

1016
    return true;
495,009✔
1017
}
1018

1019
bool Universe::writeBlended(int address, quint32 value, int channelCount, Universe::BlendMode blend)
10,184,751✔
1020
{
1021
    if (address + channelCount >= m_usedChannels)
10,184,751✔
1022
        m_usedChannels = address + channelCount;
1,316✔
1023

1024
    quint32 currentValue = 0;
10,184,751✔
1025
    for (int i = 0; i < channelCount; i++)
20,369,502✔
1026
        currentValue = (currentValue << 8) + uchar(m_preGMValues->at(address + i));
10,184,751✔
1027

1028
    switch (blend)
10,184,751✔
1029
    {
1030
        case NormalBlend:
3,848,745✔
1031
        {
1032
            if ((m_channelsMask->at(address) & HTP) && value < currentValue)
3,848,745✔
1033
            {
1034
                return false;
×
1035
            }
1036
        }
1037
        break;
3,848,745✔
1038
        case MaskBlend:
2,128,502✔
1039
        {
1040
            const float maxValue = (channelCount == 1)
1041
                                   ? 255.0f
2,128,502✔
NEW
1042
                                   : (channelCount == 2 ? 65535.0f : float(pow(255.0f, channelCount)));
×
1043
            if (value)
2,128,502✔
1044
            {
1045
                if (currentValue)
2,095,214✔
1046
                    value = float(currentValue) * (float(value) / maxValue);
1✔
1047
                else
1048
                    value = 0;
2,095,213✔
1049
            }
1050
        }
1051
        break;
2,128,502✔
1052
        case AdditiveBlend:
2,103,751✔
1053
        {
1054
            const float maxValue = (channelCount == 1)
1055
                                   ? 255.0f
2,103,751✔
NEW
1056
                                   : (channelCount == 2 ? 65535.0f : float(pow(255.0f, channelCount)));
×
1057
            //qDebug() << "Universe write additive channel" << channel << ", value:" << currVal << "+" << value;
1058
            value = fmin(float(currentValue + value), maxValue);
2,103,751✔
1059
        }
1060
        break;
2,103,751✔
1061
        case SubtractiveBlend:
2,103,752✔
1062
        {
1063
            if (value >= currentValue)
2,103,752✔
1064
                value = 0;
2,103,751✔
1065
            else
1066
                value = currentValue - value;
1✔
1067
        }
1068
        break;
2,103,752✔
1069
        default:
1✔
1070
            qDebug() << "[Universe] Blend mode not handled. Implement me!" << blend;
1✔
1071
            return false;
1✔
1072
        break;
1073
    }
1074

1075
    writeMultiple(address, value, channelCount);
10,184,750✔
1076

1077
    return true;
10,184,750✔
1078
}
1079

1080
/*********************************************************************
1081
 * Load & Save
1082
 *********************************************************************/
1083

1084
bool Universe::loadXML(QXmlStreamReader &root, int index, InputOutputMap *ioMap)
5✔
1085
{
1086
    if (root.name() != KXMLQLCUniverse)
5✔
1087
    {
1088
        qWarning() << Q_FUNC_INFO << "Universe node not found";
1✔
1089
        return false;
1✔
1090
    }
1091

1092
    int outputIndex = 0;
4✔
1093

1094
    QXmlStreamAttributes attrs = root.attributes();
4✔
1095

1096
    if (attrs.hasAttribute(KXMLQLCUniverseName))
4✔
1097
        setName(attrs.value(KXMLQLCUniverseName).toString());
4✔
1098

1099
    if (attrs.hasAttribute(KXMLQLCUniversePassthrough))
4✔
1100
    {
1101
        if (attrs.value(KXMLQLCUniversePassthrough).toString() == KXMLQLCTrue ||
14✔
1102
            attrs.value(KXMLQLCUniversePassthrough).toString() == "1")
7✔
1103
            setPassthrough(true);
2✔
1104
        else
1105
            setPassthrough(false);
1✔
1106
    }
1107
    else
1108
    {
1109
        setPassthrough(false);
1✔
1110
    }
1111

1112
    while (root.readNextStartElement())
4✔
1113
    {
1114
        QXmlStreamAttributes pAttrs = root.attributes();
×
1115

1116
        if (root.name() == KXMLQLCUniverseInputPatch)
×
1117
        {
1118
            QString plugin = KInputNone;
×
1119
            quint32 inputLine = QLCIOPlugin::invalidLine();
×
1120
            QString inputUID;
×
1121
            QString profile = KInputNone;
×
1122

1123
            if (pAttrs.hasAttribute(KXMLQLCUniversePlugin))
×
1124
                plugin = pAttrs.value(KXMLQLCUniversePlugin).toString();
×
1125
            if (pAttrs.hasAttribute(KXMLQLCUniverseLineUID))
×
1126
                inputUID = pAttrs.value(KXMLQLCUniverseLineUID).toString();
×
1127
            if (pAttrs.hasAttribute(KXMLQLCUniverseLine))
×
1128
                inputLine = pAttrs.value(KXMLQLCUniverseLine).toString().toUInt();
×
1129
            if (pAttrs.hasAttribute(KXMLQLCUniverseProfileName))
×
1130
                profile = pAttrs.value(KXMLQLCUniverseProfileName).toString();
×
1131

1132
            // apply the parameters just loaded
1133
            ioMap->setInputPatch(index, plugin, inputUID, inputLine, profile);
×
1134

1135
            QXmlStreamReader::TokenType tType = root.readNext();
×
1136
            if (tType == QXmlStreamReader::Characters)
×
1137
                tType = root.readNext();
×
1138

1139
            // check if there is a PluginParameters tag defined
1140
            if (tType == QXmlStreamReader::StartElement)
×
1141
            {
1142
                if (root.name() == KXMLQLCUniversePluginParameters)
×
1143
                    loadXMLPluginParameters(root, InputPatchTag, 0);
×
1144
                root.skipCurrentElement();
×
1145
            }
1146
        }
×
1147
        else if (root.name() == KXMLQLCUniverseOutputPatch)
×
1148
        {
1149
            QString plugin = KOutputNone;
×
1150
            QString outputUID;
×
1151
            quint32 outputLine = QLCIOPlugin::invalidLine();
×
1152

1153
            if (pAttrs.hasAttribute(KXMLQLCUniversePlugin))
×
1154
                plugin = pAttrs.value(KXMLQLCUniversePlugin).toString();
×
1155
            if (pAttrs.hasAttribute(KXMLQLCUniverseLineUID))
×
1156
                outputUID = pAttrs.value(KXMLQLCUniverseLineUID).toString();
×
1157
            if (pAttrs.hasAttribute(KXMLQLCUniverseLine))
×
1158
                outputLine = pAttrs.value(KXMLQLCUniverseLine).toString().toUInt();
×
1159

1160
            // apply the parameters just loaded
1161
            ioMap->setOutputPatch(index, plugin, outputUID, outputLine, false, outputIndex);
×
1162

1163
            QXmlStreamReader::TokenType tType = root.readNext();
×
1164
            if (tType == QXmlStreamReader::Characters)
×
1165
                tType = root.readNext();
×
1166

1167
            // check if there is a PluginParameters tag defined
1168
            if (tType == QXmlStreamReader::StartElement)
×
1169
            {
1170
                if (root.name() == KXMLQLCUniversePluginParameters)
×
1171
                    loadXMLPluginParameters(root, OutputPatchTag, outputIndex);
×
1172
                root.skipCurrentElement();
×
1173
            }
1174

1175
            outputIndex++;
×
1176
        }
×
1177
        else if (root.name() == KXMLQLCUniverseFeedbackPatch)
×
1178
        {
1179
            QString plugin = KOutputNone;
×
1180
            QString outputUID;
×
1181
            quint32 output = QLCIOPlugin::invalidLine();
×
1182

1183
            if (pAttrs.hasAttribute(KXMLQLCUniversePlugin))
×
1184
                plugin = pAttrs.value(KXMLQLCUniversePlugin).toString();
×
1185
            if (pAttrs.hasAttribute(KXMLQLCUniverseLineUID))
×
1186
                outputUID = pAttrs.value(KXMLQLCUniverseLineUID).toString();
×
1187
            if (pAttrs.hasAttribute(KXMLQLCUniverseLine))
×
1188
                output = pAttrs.value(KXMLQLCUniverseLine).toString().toUInt();
×
1189

1190
            // apply the parameters just loaded
1191
            ioMap->setOutputPatch(index, plugin, outputUID, output, true);
×
1192

1193
            QXmlStreamReader::TokenType tType = root.readNext();
×
1194
            if (tType == QXmlStreamReader::Characters)
×
1195
                tType = root.readNext();
×
1196

1197
            // check if there is a PluginParameters tag defined
1198
            if (tType == QXmlStreamReader::StartElement)
×
1199
            {
1200
                if (root.name() == KXMLQLCUniversePluginParameters)
×
1201
                    loadXMLPluginParameters(root, FeedbackPatchTag, 0);
×
1202
                root.skipCurrentElement();
×
1203
            }
1204
        }
×
1205
        else
1206
        {
1207
            qWarning() << Q_FUNC_INFO << "Unknown Universe tag:" << root.name();
×
1208
            root.skipCurrentElement();
×
1209
        }
1210
    }
×
1211

1212
    return true;
4✔
1213
}
4✔
1214

1215
bool Universe::loadXMLPluginParameters(QXmlStreamReader &root, PatchTagType currentTag, int patchIndex)
×
1216
{
1217
    if (root.name() != KXMLQLCUniversePluginParameters)
×
1218
    {
1219
        qWarning() << Q_FUNC_INFO << "PluginParameters node not found";
×
1220
        return false;
×
1221
    }
1222

1223
    QXmlStreamAttributes pluginAttrs = root.attributes();
×
1224
    for (int i = 0; i < pluginAttrs.count(); i++)
×
1225
    {
1226
        QXmlStreamAttribute attr = pluginAttrs.at(i);
×
1227
        if (currentTag == InputPatchTag)
×
1228
        {
1229
            InputPatch *ip = inputPatch();
×
1230
            if (ip != NULL)
×
1231
                ip->setPluginParameter(attr.name().toString(), attr.value().toString());
×
1232
        }
1233
        else if (currentTag == OutputPatchTag)
×
1234
        {
1235
            OutputPatch *op = outputPatch(patchIndex);
×
1236
            if (op != NULL)
×
1237
                op->setPluginParameter(attr.name().toString(), attr.value().toString());
×
1238
        }
1239
        else if (currentTag == FeedbackPatchTag)
×
1240
        {
1241
            OutputPatch *fbp = feedbackPatch();
×
1242
            if (fbp != NULL)
×
1243
                fbp->setPluginParameter(attr.name().toString(), attr.value().toString());
×
1244
        }
1245
    }
×
1246
    root.skipCurrentElement();
×
1247

1248
    return true;
×
1249
}
×
1250

1251
bool Universe::saveXML(QXmlStreamWriter *doc) const
6✔
1252
{
1253
    Q_ASSERT(doc != NULL);
6✔
1254

1255
    doc->writeStartElement(KXMLQLCUniverse);
12✔
1256
    doc->writeAttribute(KXMLQLCUniverseName, name());
12✔
1257
    doc->writeAttribute(KXMLQLCUniverseID, QString::number(id()));
12✔
1258

1259
    if (passthrough() == true)
6✔
1260
        doc->writeAttribute(KXMLQLCUniversePassthrough, KXMLQLCTrue);
3✔
1261

1262
    if (inputPatch() != NULL)
6✔
1263
    {
1264
        savePatchXML(doc, KXMLQLCUniverseInputPatch, inputPatch()->pluginName(), inputPatch()->inputName(),
×
1265
            inputPatch()->input(), inputPatch()->profileName(), inputPatch()->getPluginParameters());
×
1266
    }
1267
    foreach (OutputPatch *op, m_outputPatchList)
6✔
1268
    {
1269
        savePatchXML(doc, KXMLQLCUniverseOutputPatch, op->pluginName(), op->outputName(),
×
1270
            op->output(), "", op->getPluginParameters());
×
1271
    }
6✔
1272
    if (feedbackPatch() != NULL)
6✔
1273
    {
1274
        savePatchXML(doc, KXMLQLCUniverseFeedbackPatch, feedbackPatch()->pluginName(), feedbackPatch()->outputName(),
×
1275
            feedbackPatch()->output(), "", feedbackPatch()->getPluginParameters());
×
1276
    }
1277

1278
    /* End the <Universe> tag */
1279
    doc->writeEndElement();
6✔
1280

1281
    return true;
6✔
1282
}
1283

1284
void Universe::savePatchXML(
×
1285
    QXmlStreamWriter *doc,
1286
    const QString &tag,
1287
    const QString &pluginName,
1288
    const QString &lineName,
1289
    quint32 line,
1290
    QString profileName,
1291
    QMap<QString, QVariant> parameters) const
1292
{
1293
    // sanity check: don't save invalid data
1294
    if (pluginName.isEmpty() || pluginName == KInputNone || line == QLCIOPlugin::invalidLine())
×
1295
        return;
×
1296

1297
    doc->writeStartElement(tag);
×
1298
    doc->writeAttribute(KXMLQLCUniversePlugin, pluginName);
×
1299
    doc->writeAttribute(KXMLQLCUniverseLineUID, lineName);
×
1300
    doc->writeAttribute(KXMLQLCUniverseLine, QString::number(line));
×
1301
    if (!profileName.isEmpty() && profileName != KInputNone)
×
1302
        doc->writeAttribute(KXMLQLCUniverseProfileName, profileName);
×
1303

1304
    savePluginParametersXML(doc, parameters);
×
1305
    doc->writeEndElement();
×
1306
}
1307

1308
bool Universe::savePluginParametersXML(QXmlStreamWriter *doc,
×
1309
                                       QMap<QString, QVariant> parameters) const
1310
{
1311
    Q_ASSERT(doc != NULL);
×
1312

1313
    if (parameters.isEmpty())
×
1314
        return false;
×
1315

1316
    doc->writeStartElement(KXMLQLCUniversePluginParameters);
×
1317
    QMapIterator<QString, QVariant> it(parameters);
×
1318
    while (it.hasNext())
×
1319
    {
1320
        it.next();
×
1321
        QString pName = it.key();
×
1322
        QVariant pValue = it.value();
×
1323
        doc->writeAttribute(pName, pValue.toString());
×
1324
    }
×
1325
    doc->writeEndElement();
×
1326

1327
    return true;
×
1328
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc