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

mcallegari / qlcplus / 23089271999

14 Mar 2026 01:51PM UTC coverage: 34.05% (+0.08%) from 33.973%
23089271999

push

github

mcallegari
Enter 5.2.1 release

15814 of 46444 relevant lines covered (34.05%)

22891.17 hits per line

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

41.35
/engine/src/showrunner.cpp
1
/*
2
  Q Light Controller
3
  showrunner.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 <QMutex>
21
#include <QDebug>
22

23
#include "showrunner.h"
24
#include "function.h"
25
#include "track.h"
26
#include "show.h"
27

28
#define TIMER_INTERVAL 50
29

30
static bool compareShowFunctions(const ShowFunction *sf1, const ShowFunction *sf2)
×
31
{
32
    if (sf1->startTime() < sf2->startTime())
×
33
        return true;
×
34
    return false;
35
}
36

37
ShowRunner::ShowRunner(const Doc* doc, quint32 showID, quint32 startTime)
3✔
38
    : QObject(NULL)
39
    , m_doc(doc)
3✔
40
    , m_currentTimeFunctionIndex(0)
3✔
41
    , m_elapsedTime(startTime)
3✔
42
    , m_currentBeatFunctionIndex(0)
3✔
43
    , m_elapsedBeats(0)
3✔
44
    , beatSynced(false)
3✔
45
    , m_totalRunTime(0)
3✔
46
{
47
    Q_ASSERT(m_doc != NULL);
48
    Q_ASSERT(showID != Show::invalidId());
49

50
    m_show = qobject_cast<Show*>(m_doc->function(showID));
3✔
51
    if (m_show == NULL)
3✔
52
        return;
53

54
    foreach (Track *track, m_show->tracks())
9✔
55
    {
56
        // some sanity checks
57
        if (track == NULL ||
6✔
58
            track->id() == Track::invalidId())
3✔
59
                continue;
×
60

61
        if (track->isMute())
3✔
62
            continue;
×
63

64
        // get all the functions of the track and append them to the runner queue
65
        foreach (ShowFunction *sfunc, track->showFunctions())
9✔
66
        {
67
            if (sfunc->startTime() + sfunc->duration(m_doc) <= startTime)
3✔
68
                continue;
×
69

70
            Function *f = m_doc->function(sfunc->functionID());
3✔
71
            if (f == NULL)
3✔
72
                continue;
×
73

74
            if (f->tempoType() == Function::Time)
3✔
75
                m_timeFunctions.append(sfunc);
3✔
76
            else
77
                m_beatFunctions.append(sfunc);
×
78

79
            if (sfunc->startTime() + sfunc->duration(m_doc) > m_totalRunTime)
3✔
80
                m_totalRunTime = sfunc->startTime() + sfunc->duration(m_doc);
3✔
81
        }
82

83
        // Initialize the intensity map
84
        m_intensityMap[track->id()] = 1.0;
3✔
85
    }
86

87
    std::sort(m_timeFunctions.begin(), m_timeFunctions.end(), compareShowFunctions);
3✔
88
    std::sort(m_beatFunctions.begin(), m_beatFunctions.end(), compareShowFunctions);
3✔
89

90
#if 1
91
    qDebug() << "Ordered list of ShowFunctions (time):";
3✔
92
    foreach (ShowFunction *sfunc, m_timeFunctions)
6✔
93
        qDebug() << "[Show] Function ID:" << sfunc->functionID() << "start time:" << sfunc->startTime() << "duration:" << sfunc->duration(m_doc);
3✔
94

95
    qDebug() << "Ordered list of ShowFunctions (beats):";
3✔
96
    foreach (ShowFunction *sfunc, m_beatFunctions)
3✔
97
        qDebug() << "[Show] Function ID:" << sfunc->functionID() << "start time:" << sfunc->startTime() << "duration:" << sfunc->duration(m_doc);
×
98
#endif
99
    m_runningQueue.clear();
3✔
100

101
    qDebug() << "ShowRunner created";
3✔
102
}
×
103

104
ShowRunner::~ShowRunner()
3✔
105
{
106
}
3✔
107

108
void ShowRunner::start()
×
109
{
110
    qDebug() << "ShowRunner started";
×
111
}
×
112

113
void ShowRunner::setPause(bool enable)
×
114
{
115
    for (int i = 0; i < m_runningQueue.count(); i++)
×
116
    {
117
        Function *f = m_runningQueue.at(i).first;
×
118
        f->setPause(enable);
×
119
    }
120
}
×
121

122
void ShowRunner::stop()
1✔
123
{
124
    m_elapsedTime = 0;
1✔
125
    m_elapsedBeats = 0;
1✔
126
    m_currentTimeFunctionIndex = 0;
1✔
127
    m_currentBeatFunctionIndex = 0;
1✔
128

129
    for (int i = 0; i < m_runningQueue.count(); i++)
2✔
130
    {
131
        Function *f = m_runningQueue.at(i).first;
1✔
132
        f->stop(functionParent());
1✔
133
    }
134

135
    m_runningQueue.clear();
1✔
136
    qDebug() << "ShowRunner stopped";
1✔
137
}
1✔
138

139
FunctionParent ShowRunner::functionParent() const
1✔
140
{
141
    return FunctionParent(FunctionParent::Function, m_show->id());
1✔
142
}
143

144
void ShowRunner::write(MasterTimer *timer)
×
145
{
146
    //qDebug() << Q_FUNC_INFO << "elapsed:" << m_elapsedTime << ", total:" << m_totalRunTime;
147

148
    // Phase 1. Check all the Functions that need to be started
149
    // m_timeFunctions is ordered by startup time, so when we found an entry
150
    // with start time greater than m_elapsed, this phase is over
151
    bool startFunctionsDone = false;
152

153
    // check synchronization to beats (if show is beat-based)
154
    if (m_show->tempoType() == Function::Beats)
×
155
    {
156
        //qDebug() << Q_FUNC_INFO << "isBeat:" << timer->isBeat() << ", elapsed beats:" << m_elapsedBeats;
157

158
        if (timer->isBeat())
×
159
        {
160
            if (beatSynced == false)
×
161
            {
162
                beatSynced = true;
×
163
                qDebug() << "Beat synced";
×
164
            }
165
            else
166
                m_elapsedBeats += 1000;
×
167
        }
168

169
        if (beatSynced == false)
×
170
            return;
171
    }
172

173
    // check if there are time-based functions to start
174
    while (startFunctionsDone == false)
175
    {
176
        if (m_currentTimeFunctionIndex == m_timeFunctions.count())
×
177
            break;
178

179
        ShowFunction *sf = m_timeFunctions.at(m_currentTimeFunctionIndex);
×
180
        quint32 funcStartTime = sf->startTime();
×
181
        quint32 functionTimeOffset = 0;
182
        Function *f = m_doc->function(sf->functionID());
×
183
        if (f == nullptr)
×
184
        {
185
            m_currentTimeFunctionIndex++;
×
186
            continue;
×
187
        }
188

189
        // this should happen only when a Show is not started from 0
190
        if (m_elapsedTime > funcStartTime)
×
191
        {
192
            functionTimeOffset = m_elapsedTime - funcStartTime;
×
193
            funcStartTime = m_elapsedTime;
194
        }
195
        if (m_elapsedTime >= funcStartTime)
×
196
        {
197
            foreach (Track *track, m_show->tracks())
×
198
            {
199
                if (track->showFunctions().contains(sf))
×
200
                {
201
                    int intOverrideId = f->requestAttributeOverride(Function::Intensity, m_intensityMap[track->id()]);
×
202
                    //f->adjustAttribute(m_intensityMap[track->id()], Function::Intensity);
203
                    sf->setIntensityOverrideId(intOverrideId);
×
204
                    break;
205
                }
206
            }
207

208
            f->start(m_doc->masterTimer(), functionParent(), functionTimeOffset);
×
209
            m_runningQueue.append(QPair<Function *, quint32>(f, sf->startTime() + sf->duration(m_doc)));
×
210
            m_currentTimeFunctionIndex++;
×
211
        }
212
        else
213
            startFunctionsDone = true;
214
    }
215

216
    startFunctionsDone = false;
217

218
    // check if there are beat-based functions to start
219
    while (startFunctionsDone == false)
220
    {
221
        if (m_currentBeatFunctionIndex == m_beatFunctions.count())
×
222
            break;
223

224
        ShowFunction *sf = m_beatFunctions.at(m_currentBeatFunctionIndex);
×
225
        quint32 funcStartTime = sf->startTime();
×
226
        quint32 functionTimeOffset = 0;
227
        Function *f = m_doc->function(sf->functionID());
×
228
        if (f == nullptr)
×
229
        {
230
            m_currentBeatFunctionIndex++;
×
231
            continue;
×
232
        }
233

234
        // this should happen only when a Show is not started from 0
235
        if (m_elapsedBeats > funcStartTime)
×
236
        {
237
            functionTimeOffset = m_elapsedBeats - funcStartTime;
×
238
            funcStartTime = m_elapsedBeats;
239
        }
240
        if (m_elapsedBeats >= funcStartTime)
×
241
        {
242
            foreach (Track *track, m_show->tracks())
×
243
            {
244
                if (track->showFunctions().contains(sf))
×
245
                {
246
                    int intOverrideId = f->requestAttributeOverride(Function::Intensity, m_intensityMap[track->id()]);
×
247
                    //f->adjustAttribute(m_intensityMap[track->id()], Function::Intensity);
248
                    sf->setIntensityOverrideId(intOverrideId);
×
249
                    break;
250
                }
251
            }
252

253
            f->start(m_doc->masterTimer(), functionParent(), functionTimeOffset);
×
254
            m_runningQueue.append(QPair<Function *, quint32>(f, sf->startTime() + sf->duration(m_doc)));
×
255
            m_currentBeatFunctionIndex++;
×
256
        }
257
        else
258
            startFunctionsDone = true;
259
    }
260

261
    // Phase 2. Check if we need to stop some running Functions
262
    // It is done in reverse order for two reasons:
263
    // 1- m_runningQueue is not ordered by stop time
264
    // 2- to avoid messing up with indices when an entry is removed
265
    for (int i = m_runningQueue.count() - 1; i >= 0; i--)
×
266
    {
267
        Function *func = m_runningQueue.at(i).first;
×
268
        quint32 stopTime = m_runningQueue.at(i).second;
×
269
        quint32 currTime = func->tempoType() == Function::Time ? m_elapsedTime : m_elapsedBeats;
×
270

271
        // if we passed the function stop time
272
        if (currTime >= stopTime)
×
273
        {
274
            // stop the function
275
            func->stop(functionParent());
×
276
            // remove it from the running queue
277
            m_runningQueue.removeAt(i);
×
278
        }
279
    }
280

281
    // Phase 3. Check if this is the end of the Show
282
    if (m_elapsedTime >= m_totalRunTime)
×
283
    {
284
        if (m_show != NULL)
×
285
            m_show->stop(functionParent());
×
286
        emit showFinished();
×
287
        return;
×
288
    }
289

290
    m_elapsedTime += MasterTimer::tick();
×
291
    emit timeChanged(m_elapsedTime);
×
292
}
293

294
/************************************************************************
295
 * Intensity
296
 ************************************************************************/
297

298
void ShowRunner::adjustIntensity(qreal fraction, const Track *track)
1✔
299
{
300
    if (track == NULL)
1✔
301
        return;
302

303
    qDebug() << Q_FUNC_INFO << "Track ID: " << track->id() << ", val:" << fraction;
1✔
304
    m_intensityMap[track->id()] = fraction;
1✔
305

306
    foreach (ShowFunction *sf, track->showFunctions())
3✔
307
    {
308
        Function *f = m_doc->function(sf->functionID());
1✔
309
        if (f == NULL)
1✔
310
            continue;
×
311

312
        for (int i = 0; i < m_runningQueue.count(); i++)
1✔
313
        {
314
            Function *rf = m_runningQueue.at(i).first;
×
315
            if (f == rf)
×
316
                f->adjustAttribute(fraction, sf->intensityOverrideId());
×
317
        }
318
    }
319
}
320

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