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

OpenLightingProject / ola / 20179851591

12 Dec 2025 09:05PM UTC coverage: 45.048% (-0.7%) from 45.72%
20179851591

Pull #2027

github

web-flow
Bump actions/upload-artifact from 4 to 6

Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #2027: Bump actions/upload-artifact from 4 to 6

8554 of 19812 branches covered (43.18%)

22094 of 49046 relevant lines covered (45.05%)

50.63 hits per line

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

79.19
/olad/plugin_api/Universe.cpp
1
/*
2
 * This program is free software; you can redistribute it and/or modify
3
 * it under the terms of the GNU General Public License as published by
4
 * the Free Software Foundation; either version 2 of the License, or
5
 * (at your option) any later version.
6
 *
7
 * This program is distributed in the hope that it will be useful,
8
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 * GNU Library General Public License for more details.
11
 *
12
 * You should have received a copy of the GNU General Public License
13
 * along with this program; if not, write to the Free Software
14
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
15
 *
16
 * Universe.cpp
17
 * Represents a universe of DMX data.
18
 * Copyright (C) 2005 Simon Newton
19
 *
20
 * Each universe has the following:
21
 *   A human readable name
22
 *   A DmxBuffer with the current dmx data
23
 *   A MergeMode, either LTP (latest takes precedence) or HTP (highest takes
24
 *     precedence)
25
 *   A list of ports bound to this universe. If a port is an input, we use the
26
 *     data to update the DmxBuffer according to the MergeMode. If a port is an
27
 *     output, we notify the port whenever the DmxBuffer changes.
28
 *   A list of source clients. which provide us with data for updating the
29
 *     DmxBuffer per the merge mode.
30
 *   A list of sink clients, which we update whenever the DmxBuffer changes.
31
 */
32

33
#include <algorithm>
34
#include <iterator>
35
#include <map>
36
#include <memory>
37
#include <set>
38
#include <string>
39
#include <utility>
40
#include <vector>
41

42
#include "ola/base/Array.h"
43
#include "ola/Logging.h"
44
#include "ola/MultiCallback.h"
45
#include "ola/rdm/RDMCommand.h"
46
#include "ola/rdm/RDMEnums.h"
47
#include "ola/stl/STLUtils.h"
48
#include "ola/strings/Format.h"
49
#include "olad/Port.h"
50
#include "olad/Universe.h"
51
#include "olad/plugin_api/Client.h"
52
#include "olad/plugin_api/UniverseStore.h"
53

54
namespace ola {
55

56
using ola::rdm::RDMDiscoveryCallback;
57
using ola::rdm::RDMReply;
58
using ola::rdm::RDMRequest;
59
using ola::rdm::RunRDMCallback;
60
using ola::rdm::UID;
61
using ola::strings::ToHex;
62
using std::auto_ptr;
63
using std::map;
64
using std::ostringstream;
65
using std::set;
66
using std::string;
67
using std::vector;
68

69
const char Universe::K_UNIVERSE_UID_COUNT_VAR[] = "universe-uids";
70
const char Universe::K_FPS_VAR[] = "universe-dmx-frames";
71
const char Universe::K_MERGE_HTP_STR[] = "htp";
72
const char Universe::K_MERGE_LTP_STR[] = "ltp";
73
const char Universe::K_UNIVERSE_INPUT_PORT_VAR[] = "universe-input-ports";
74
const char Universe::K_UNIVERSE_MODE_VAR[] = "universe-mode";
75
const char Universe::K_UNIVERSE_NAME_VAR[] = "universe-name";
76
const char Universe::K_UNIVERSE_OUTPUT_PORT_VAR[] = "universe-output-ports";
77
const char Universe::K_UNIVERSE_RDM_REQUESTS[] = "universe-rdm-requests";
78
const char Universe::K_UNIVERSE_SINK_CLIENTS_VAR[] = "universe-sink-clients";
79
const char Universe::K_UNIVERSE_SOURCE_CLIENTS_VAR[] =
80
    "universe-source-clients";
81

82
/*
83
 * Create a new universe
84
 * @param uid  the universe id of this universe
85
 * @param store the store this universe came from
86
 * @param export_map the ExportMap that we update
87
 */
88
Universe::Universe(unsigned int universe_id, UniverseStore *store,
27✔
89
                   ExportMap *export_map,
90
                   Clock *clock)
27✔
91
    : m_universe_name(""),
27✔
92
      m_universe_id(universe_id),
27✔
93
      m_active_priority(ola::dmx::SOURCE_PRIORITY_MIN),
27✔
94
      m_merge_mode(Universe::MERGE_LTP),
27✔
95
      m_universe_store(store),
27✔
96
      m_export_map(export_map),
27✔
97
      m_clock(clock),
27✔
98
      m_rdm_discovery_interval(),
27✔
99
      m_last_discovery_time(),
27✔
100
      m_transaction_number_sequence() {
27✔
101
  ostringstream universe_id_str, universe_name_str;
27✔
102
  universe_id_str << universe_id;
27✔
103
  m_universe_id_str = universe_id_str.str();
27✔
104
  universe_name_str << "Universe " << universe_id;
27✔
105
  m_universe_name = universe_name_str.str();
27✔
106

107
  UpdateName();
27✔
108
  UpdateMode();
27✔
109

110
  const char *vars[] = {
27✔
111
    K_FPS_VAR,
112
    K_UNIVERSE_INPUT_PORT_VAR,
113
    K_UNIVERSE_OUTPUT_PORT_VAR,
114
    K_UNIVERSE_RDM_REQUESTS,
115
    K_UNIVERSE_SINK_CLIENTS_VAR,
116
    K_UNIVERSE_SOURCE_CLIENTS_VAR,
117
    K_UNIVERSE_UID_COUNT_VAR,
118
  };
119

120
  if (m_export_map) {
27✔
121
    for (unsigned int i = 0; i < arraysize(vars); ++i) {
×
122
      (*m_export_map->GetUIntMapVar(vars[i]))[m_universe_id_str] = 0;
×
123
    }
124
  }
125

126
  // We set the last discovery time to now, since most ports will trigger
127
  // discovery when they are patched.
128
  clock->CurrentMonotonicTime(&m_last_discovery_time);
27✔
129
}
27✔
130

131

132
/*
133
 * Delete this universe
134
 */
135
Universe::~Universe() {
27✔
136
  const char *string_vars[] = {
27✔
137
    K_UNIVERSE_NAME_VAR,
138
    K_UNIVERSE_MODE_VAR,
139
  };
140

141
  const char *uint_vars[] = {
27✔
142
    K_FPS_VAR,
143
    K_UNIVERSE_INPUT_PORT_VAR,
144
    K_UNIVERSE_OUTPUT_PORT_VAR,
145
    K_UNIVERSE_RDM_REQUESTS,
146
    K_UNIVERSE_SINK_CLIENTS_VAR,
147
    K_UNIVERSE_SOURCE_CLIENTS_VAR,
148
    K_UNIVERSE_UID_COUNT_VAR,
149
  };
150

151
  if (m_export_map) {
27✔
152
    for (unsigned int i = 0; i < arraysize(string_vars); ++i) {
×
153
      m_export_map->GetStringMapVar(string_vars[i])->Remove(m_universe_id_str);
×
154
    }
155
    for (unsigned int i = 0; i < arraysize(uint_vars); ++i) {
×
156
      m_export_map->GetUIntMapVar(uint_vars[i])->Remove(m_universe_id_str);
×
157
    }
158
  }
159
}
44✔
160

161

162
/*
163
 * Set the universe name
164
 * @param name the new universe name
165
 */
166
void Universe::SetName(const string &name) {
4✔
167
  m_universe_name = name;
4✔
168
  UpdateName();
4✔
169

170
  // notify ports
171
  vector<OutputPort*>::const_iterator iter;
4✔
172
  for (iter = m_output_ports.begin(); iter != m_output_ports.end(); ++iter) {
4✔
173
    (*iter)->UniverseNameChanged(name);
×
174
  }
175
}
4✔
176

177

178
/*
179
 * Set the universe merge mode
180
 * @param merge_mode the new merge_mode
181
 */
182
void Universe::SetMergeMode(enum merge_mode merge_mode) {
6✔
183
  m_merge_mode = merge_mode;
6✔
184
  UpdateMode();
6✔
185
}
6✔
186

187

188
/*
189
 * Add an InputPort to this universe.
190
 * @param port the port to add
191
 */
192
bool Universe::AddPort(InputPort *port) {
13✔
193
  return GenericAddPort(port, &m_input_ports);
13✔
194
}
195

196

197
/*
198
 * Add an OutputPort to this universe.
199
 * @param port the port to add
200
 */
201
bool Universe::AddPort(OutputPort *port) {
11✔
202
  return GenericAddPort(port, &m_output_ports);
11✔
203
}
204

205

206
/*
207
 * Remove a port from this universe.
208
 * @param port the port to remove
209
 * @return true if the port was removed, false if it didn't exist
210
 */
211
bool Universe::RemovePort(InputPort *port) {
9✔
212
  return GenericRemovePort(port, &m_input_ports);
9✔
213
}
214

215

216
/*
217
 * Remove a port from this universe.
218
 * @param port the port to remove
219
 * @return true if the port was removed, false if it didn't exist
220
 */
221
bool Universe::RemovePort(OutputPort *port) {
8✔
222
  bool ret = GenericRemovePort(port, &m_output_ports, &m_output_uids);
8✔
223

224
  if (m_export_map) {
8✔
225
    (*m_export_map->GetUIntMapVar(K_UNIVERSE_UID_COUNT_VAR))[m_universe_id_str]
×
226
        = m_output_uids.size();
×
227
  }
228
  return ret;
8✔
229
}
230

231

232
/*
233
 * Check if this port is bound to this universe
234
 * @param port the port to check for
235
 * @return true if the port exists in this universe, false otherwise
236
 */
237
bool Universe::ContainsPort(InputPort *port) const {
14✔
238
  return GenericContainsPort(port, m_input_ports);
14✔
239
}
240

241

242
/*
243
 * Check if this port is bound to this universe
244
 * @param port the port to check for
245
 * @return true if the port exists in this universe, false otherwise
246
 */
247
bool Universe::ContainsPort(OutputPort *port) const {
×
248
  return GenericContainsPort(port, m_output_ports);
×
249
}
250

251

252
/*
253
 * Get a list of input ports associated with this universe
254
 * @param ports, the vector to be populated
255
 */
256
void Universe::InputPorts(vector<InputPort*> *ports) const {
×
257
  ports->clear();
×
258
  std::copy(m_input_ports.begin(), m_input_ports.end(),
×
259
            std::back_inserter(*ports));
260
}
×
261

262

263
/*
264
 * Get a list of output ports associated with this universe
265
 * @param ports, the vector to be populated
266
 */
267
void Universe::OutputPorts(vector<OutputPort*> *ports) const {
×
268
  ports->clear();
×
269
  std::copy(m_output_ports.begin(), m_output_ports.end(),
×
270
            std::back_inserter(*ports));
271
}
×
272

273

274
/*
275
 * @brief Add a client as a source for this universe
276
 * @param client the client to add
277
 * @return true
278
 */
279
bool Universe::AddSourceClient(Client *client) {
6✔
280
  // Check to see if it exists already. It doesn't make sense to have multiple
281
  //  clients
282
  if (STLReplace(&m_source_clients, client, false)) {
6✔
283
    return true;
284
  }
285

286
  OLA_INFO << "Added source client, " << client << " to universe "
10✔
287
           << m_universe_id;
5✔
288

289
  SafeIncrement(K_UNIVERSE_SOURCE_CLIENTS_VAR);
5✔
290
  return true;
5✔
291
}
292

293

294
/*
295
 * @param Remove this client from the universe.
296
 * @note After the client is removed we internally check if this universe is
297
 * still in use, and if not delete it
298
 * @param client the client to remove
299
 * @return true is this client was removed, false if it didn't exist
300
 */
301
bool Universe::RemoveSourceClient(Client *client) {
4✔
302
  if (!STLRemove(&m_source_clients, client)) {
4✔
303
    return false;
304
  }
305

306
  SafeDecrement(K_UNIVERSE_SOURCE_CLIENTS_VAR);
3✔
307

308
  OLA_INFO << "Source client " << client << " has been removed from uni "
6✔
309
           << m_universe_id;
3✔
310

311
  if (!IsActive()) {
3✔
312
    m_universe_store->AddUniverseGarbageCollection(this);
1✔
313
  }
314
  return true;
315
}
316

317

318
/*
319
 * Check if this universe contains a client as a source
320
 * @param client the client to check for
321
 * @returns true if this universe contains the client, false otherwise
322
 */
323
bool Universe::ContainsSourceClient(Client *client) const {
6✔
324
  return STLContains(m_source_clients, client);
6✔
325
}
326

327

328
/*
329
 * @brief Add a client as a sink for this universe
330
 * @param client the client to add
331
 * @return true if client was added, and false if it was already a sink client
332
 */
333
bool Universe::AddSinkClient(Client *client) {
4✔
334
  if (!STLInsertIfNotPresent(&m_sink_clients, client)) {
4✔
335
    return false;
336
  }
337

338
  OLA_INFO << "Added sink client, " << client << " to universe "
6✔
339
           << m_universe_id;
3✔
340

341
  SafeIncrement(K_UNIVERSE_SINK_CLIENTS_VAR);
3✔
342
  return true;
3✔
343
}
344

345

346
/*
347
 * @param Remove this sink client from the universe.
348
 * @note After the client is removed we internally check if this universe is
349
 * still in use, and if not delete it
350
 * @param client the client to remove
351
 * @return true is this client was removed, false if it didn't exist
352
 */
353
bool Universe::RemoveSinkClient(Client *client) {
5✔
354
  if (!STLRemove(&m_sink_clients, client)) {
5✔
355
    return false;
356
  }
357

358
  SafeDecrement(K_UNIVERSE_SINK_CLIENTS_VAR);
3✔
359

360
  OLA_INFO << "Sink client " << client << " has been removed from uni "
6✔
361
           << m_universe_id;
3✔
362

363
  if (!IsActive()) {
3✔
364
    m_universe_store->AddUniverseGarbageCollection(this);
3✔
365
  }
366
  return true;
367
}
368

369
/*
370
 * Check if this universe contains a client as a sink
371
 * @param client the client to check for
372
 * @returns true if this universe contains the client, false otherwise
373
 */
374
bool Universe::ContainsSinkClient(Client *client) const {
12✔
375
  return STLContains(m_sink_clients, client);
12✔
376
}
377

378

379
/*
380
 * Set the dmx data for this universe, this overrides anything from the clients
381
 * or ports but will be overridden in the next update.
382
 * @param buffer the dmx buffer with the data
383
 * @return true is we updated all ports/clients, false otherwise
384
 */
385
bool Universe::SetDMX(const DmxBuffer &buffer) {
5✔
386
  if (!buffer.Size()) {
5✔
387
    OLA_INFO << "Trying to SetDMX with a 0 length dmx buffer, universe "
×
388
             << UniverseId();
×
389
    return true;
×
390
  }
391
  m_buffer.Set(buffer);
5✔
392
  return UpdateDependants();
5✔
393
}
394

395

396
/*
397
 * Call this when the dmx in a port that is part of this universe changes
398
 * @param port the port that has changed
399
 */
400
bool Universe::PortDataChanged(InputPort *port) {
14✔
401
  if (!ContainsPort(port)) {
14✔
402
    OLA_INFO << "Trying to update a port which isn't bound to universe: "
×
403
             << UniverseId();
×
404
    return false;
×
405
  }
406
  if (MergeAll(port, NULL)) {
14✔
407
    UpdateDependants();
14✔
408
  }
409
  return true;
410
}
411

412

413
/*
414
 * Called to indicate that data from a client has changed
415
 */
416
bool Universe::SourceClientDataChanged(Client *client) {
5✔
417
  if (!client) {
5✔
418
    return false;
419
  }
420

421
  AddSourceClient(client);   // always add since this may be the first call
5✔
422
  if (MergeAll(NULL, client)) {
5✔
423
    UpdateDependants();
4✔
424
  }
425
  return true;
426
}
427

428

429
/**
430
 * @brief Clean old source clients
431
 */
432
void Universe::CleanStaleSourceClients() {
×
433
  SourceClientMap::iterator iter = m_source_clients.begin();
×
434
  while (iter != m_source_clients.end()) {
×
435
    if (iter->second) {
×
436
      // if stale remove it
437
      m_source_clients.erase(iter++);
×
438
      SafeDecrement(K_UNIVERSE_SOURCE_CLIENTS_VAR);
×
439
      OLA_INFO << "Removed Stale Client";
×
440
      if (!IsActive()) {
×
441
        m_universe_store->AddUniverseGarbageCollection(this);
×
442
      }
443
    } else {
444
      // clear the stale flag
445
      iter->second = true;
×
446
      ++iter;
×
447
    }
448
  }
449
}
×
450

451

452
/*
453
 * Handle a RDM request for this universe, ownership of the request object is
454
 * transferred to this method.
455
 */
456
void Universe::SendRDMRequest(RDMRequest *request_ptr,
9✔
457
                              ola::rdm::RDMCallback *callback) {
458
  auto_ptr<RDMRequest> request(request_ptr);
9✔
459

460
  OLA_INFO << "Universe " << UniverseId() << ", RDM request to "
27✔
461
           << request->DestinationUID() << ", SD: " << request->SubDevice()
9✔
462
           << ", CC " << ToHex(request->CommandClass()) << ", TN "
9✔
463
           << static_cast<int>(request->TransactionNumber()) << ", PID "
9✔
464
           << ToHex(request->ParamId()) << ", PDL: "
18✔
465
           << request->ParamDataSize();
9✔
466

467
  SafeIncrement(K_UNIVERSE_RDM_REQUESTS);
9✔
468

469
  if (request->DestinationUID().IsBroadcast()) {
9✔
470
    if (m_output_ports.empty()) {
7✔
471
      RunRDMCallback(
×
472
          callback,
473
          request->IsDUB() ? ola::rdm::RDM_TIMEOUT :
×
474
              ola::rdm::RDM_WAS_BROADCAST);
475
      return;
×
476
    }
477

478
    // send this request to all ports
479
    broadcast_request_tracker *tracker = new broadcast_request_tracker;
7✔
480
    tracker->expected_count = m_output_ports.size();
7✔
481
    tracker->current_count = 0;
7✔
482
    tracker->status_code = (request->IsDUB() ?
7✔
483
        ola::rdm::RDM_PLUGIN_DISCOVERY_NOT_SUPPORTED :
484
        ola::rdm::RDM_WAS_BROADCAST);
485
    tracker->callback = callback;
7✔
486
    vector<OutputPort*>::iterator port_iter;
7✔
487

488
    for (port_iter = m_output_ports.begin(); port_iter != m_output_ports.end();
21✔
489
         ++port_iter) {
14✔
490
      // because each port deletes the request, we need to copy it here
491
      if (request->IsDUB()) {
14✔
492
        (*port_iter)->SendRDMRequest(
10✔
493
            request->Duplicate(),
10✔
494
            NewSingleCallback(this,
10✔
495
                              &Universe::HandleBroadcastDiscovery,
496
                              tracker));
497
      } else  {
498
        (*port_iter)->SendRDMRequest(
4✔
499
            request->Duplicate(),
4✔
500
            NewSingleCallback(this, &Universe::HandleBroadcastAck, tracker));
4✔
501
      }
502
    }
503
  } else {
504
    map<UID, OutputPort*>::iterator iter =
2✔
505
        m_output_uids.find(request->DestinationUID());
2✔
506

507
    if (iter == m_output_uids.end()) {
2✔
508
      OLA_WARN << "Can't find UID " << request->DestinationUID()
2✔
509
               << " in the output universe map, dropping request";
1✔
510
      RunRDMCallback(callback, ola::rdm::RDM_UNKNOWN_UID);
1✔
511
    } else {
512
      iter->second->SendRDMRequest(request.release(), callback);
1✔
513
    }
514
  }
515
}
9✔
516

517

518
/*
519
 * Trigger RDM discovery for this universe
520
 */
521
void Universe::RunRDMDiscovery(RDMDiscoveryCallback *on_complete, bool full) {
3✔
522
  if (full) {
3✔
523
    OLA_INFO << "Full RDM discovery triggered for universe " << m_universe_id;
3✔
524
  } else {
525
    OLA_INFO << "Incremental RDM discovery triggered for universe "
×
526
             << m_universe_id;
×
527
  }
528

529
  m_clock->CurrentMonotonicTime(&m_last_discovery_time);
3✔
530

531
  // we need to make a copy of the ports first, because the callback may run at
532
  // any time so we need to guard against the port list changing.
533
  vector<OutputPort*> output_ports(m_output_ports.size());
3✔
534
  copy(m_output_ports.begin(), m_output_ports.end(), output_ports.begin());
3✔
535

536
  // the multicallback that indicates when discovery is done
537
  BaseCallback0<void> *discovery_complete = NewMultiCallback(
6✔
538
      output_ports.size(),
3✔
539
      NewSingleCallback(this, &Universe::DiscoveryComplete, on_complete));
3✔
540

541
  // Send Discovery requests to all ports, as each of these return they'll
542
  // update the UID map. When all ports callbacks have run, the MultiCallback
543
  // will trigger, running the DiscoveryCallback.
544
  vector<OutputPort*>::iterator iter;
545
  for (iter = output_ports.begin(); iter != output_ports.end(); ++iter) {
8✔
546
    if (full) {
5✔
547
      (*iter)->RunFullDiscovery(
10✔
548
          NewSingleCallback(this,
5✔
549
                            &Universe::PortDiscoveryComplete,
550
                            discovery_complete,
551
                            *iter));
5✔
552
    } else {
553
      (*iter)->RunIncrementalDiscovery(
×
554
          NewSingleCallback(this,
×
555
                            &Universe::PortDiscoveryComplete,
556
                            discovery_complete,
557
                            *iter));
×
558
    }
559
  }
560
}
3✔
561

562

563
/*
564
 * Update the UID : port mapping with this new data
565
 */
566
void Universe::NewUIDList(OutputPort *port, const ola::rdm::UIDSet &uids) {
8✔
567
  map<UID, OutputPort*>::iterator iter = m_output_uids.begin();
8✔
568
  while (iter != m_output_uids.end()) {
17✔
569
    if (iter->second == port && !uids.Contains(iter->first)) {
9✔
570
      m_output_uids.erase(iter++);
1✔
571
    } else {
572
      ++iter;
8✔
573
    }
574
  }
575

576
  ola::rdm::UIDSet::Iterator set_iter = uids.Begin();
8✔
577
  for (; set_iter != uids.End(); ++set_iter) {
15✔
578
    iter = m_output_uids.find(*set_iter);
7✔
579
    if (iter == m_output_uids.end()) {
7✔
580
      m_output_uids[*set_iter] = port;
5✔
581
    } else if (iter->second != port) {
2✔
582
      OLA_WARN << "UID " << *set_iter << " seen on more than one port";
×
583
    }
584
  }
585

586
  if (m_export_map) {
8✔
587
    (*m_export_map->GetUIntMapVar(K_UNIVERSE_UID_COUNT_VAR))[m_universe_id_str]
×
588
        = m_output_uids.size();
×
589
  }
590
}
8✔
591

592

593
/*
594
 * Returns the complete UIDSet for this universe
595
 */
596
void Universe::GetUIDs(ola::rdm::UIDSet *uids) const {
6✔
597
  map<UID, OutputPort*>::const_iterator iter = m_output_uids.begin();
6✔
598
  for (; iter != m_output_uids.end(); ++iter) {
11✔
599
    uids->AddUID(iter->first);
5✔
600
  }
601
}
6✔
602

603

604
/**
605
 * Return the number of uids in the universe
606
 */
607
unsigned int Universe::UIDCount() const {
×
608
  return m_output_uids.size();
×
609
}
610

611
/**
612
 * Return the RDM transaction number to use
613
 */
614
uint8_t Universe::GetRDMTransactionNumber() {
×
615
  return m_transaction_number_sequence.Next();
×
616
}
617

618
/*
619
 * Return true if this universe is in use (has at least one port or client).
620
 */
621
bool Universe::IsActive() const {
42✔
622
  // any of the following means the port is active
623
  return !(m_output_ports.empty() && m_input_ports.empty() &&
42✔
624
           m_source_clients.empty() && m_sink_clients.empty());
29✔
625
}
626

627

628
// Private Methods
629
//-----------------------------------------------------------------------------
630

631

632
/*
633
 * Called when the dmx data for this universe changes,
634
 * updates everyone who needs to know (patched ports and network clients)
635
 */
636
bool Universe::UpdateDependants() {
23✔
637
  vector<OutputPort*>::const_iterator iter;
23✔
638
  set<Client*>::const_iterator client_iter;
23✔
639

640
  // write to all ports assigned to this universe
641
  for (iter = m_output_ports.begin(); iter != m_output_ports.end(); ++iter) {
24✔
642
    (*iter)->WriteDMX(m_buffer, m_active_priority);
1✔
643
  }
644

645
  // write to all clients
646
  for (client_iter = m_sink_clients.begin();
24✔
647
       client_iter != m_sink_clients.end();
24✔
648
       ++client_iter) {
1✔
649
    (*client_iter)->SendDMX(m_universe_id, m_active_priority, m_buffer);
1✔
650
  }
651

652
  SafeIncrement(K_FPS_VAR);
23✔
653
  return true;
23✔
654
}
655

656

657
/*
658
 * Update the name in the export map.
659
 */
660
void Universe::UpdateName() {
31✔
661
  if (!m_export_map) {
31✔
662
    return;
663
  }
664
  StringMap *name_map = m_export_map->GetStringMapVar(K_UNIVERSE_NAME_VAR);
×
665
  (*name_map)[m_universe_id_str] = m_universe_name;
×
666
}
667

668

669
/*
670
 * Update the mode in the export map.
671
 */
672
void Universe::UpdateMode() {
33✔
673
  if (!m_export_map) {
33✔
674
    return;
675
  }
676
  StringMap *mode_map = m_export_map->GetStringMapVar(K_UNIVERSE_MODE_VAR);
×
677
  (*mode_map)[m_universe_id_str] = (m_merge_mode == Universe::MERGE_LTP ?
×
678
                                    K_MERGE_LTP_STR : K_MERGE_HTP_STR);
×
679
}
680

681

682
/*
683
 * HTP Merge all sources (clients/ports)
684
 * @pre sources.size >= 2
685
 * @param sources the list of DmxSources to merge
686
 */
687
void Universe::HTPMergeSources(const vector<DmxSource> &sources) {
3✔
688
  vector<DmxSource>::const_iterator iter;
3✔
689
  m_buffer.Reset();
3✔
690

691
  for (iter = sources.begin(); iter != sources.end(); ++iter) {
10✔
692
    m_buffer.HTPMerge(iter->Data());
7✔
693
  }
694
}
3✔
695

696

697
/*
698
 * Merge all port/client sources.
699
 * This does a priority based merge as documented at:
700
 * https://wiki.openlighting.org/index.php/OLA_Merging_Algorithms
701
 * @param port the input port that changed or NULL
702
 * @param client the client that changed or NULL
703
 * @returns true if the data for this universe changed, false otherwise
704
 */
705
bool Universe::MergeAll(const InputPort *port, const Client *client) {
19✔
706
  vector<DmxSource> active_sources;
19✔
707

708
  vector<InputPort*>::const_iterator iter;
19✔
709
  SourceClientMap::const_iterator client_iter;
19✔
710

711
  m_active_priority = ola::dmx::SOURCE_PRIORITY_MIN;
19✔
712
  TimeStamp now;
19✔
713
  m_clock->CurrentMonotonicTime(&now);
19✔
714
  bool changed_source_is_active = false;
19✔
715

716
  // Find the highest active ports
717
  for (iter = m_input_ports.begin(); iter != m_input_ports.end(); ++iter) {
44✔
718
    DmxSource source = (*iter)->SourceData();
25✔
719
    if (!source.IsSet() || !source.IsActive(now) || !source.Data().Size()) {
25✔
720
      continue;
2✔
721
    }
722

723
    if (source.Priority() > m_active_priority) {
23✔
724
      changed_source_is_active = false;
16✔
725
      active_sources.clear();
16✔
726
      m_active_priority = source.Priority();
16✔
727
    }
728

729
    if (source.Priority() == m_active_priority) {
23✔
730
      active_sources.push_back(source);
23✔
731
      if (*iter == port) {
23✔
732
        changed_source_is_active = true;
23✔
733
      }
734
    }
735
  }
25✔
736

737
  // find the highest priority active clients
738
  for (client_iter = m_source_clients.begin();
26✔
739
       client_iter != m_source_clients.end();
26✔
740
       ++client_iter) {
7✔
741
    const DmxSource &source = client_iter->first->SourceData(UniverseId());
7✔
742

743
    if (!source.IsSet() || !source.IsActive(now) || !source.Data().Size()) {
7✔
744
      continue;
×
745
    }
746

747
    if (source.Priority() > m_active_priority) {
7✔
748
      changed_source_is_active = false;
3✔
749
      active_sources.clear();
3✔
750
      m_active_priority = source.Priority();
3✔
751
    }
752

753
    if (source.Priority() == m_active_priority) {
7✔
754
      active_sources.push_back(source);
7✔
755
      if (client_iter->first == client) {
7✔
756
        changed_source_is_active = true;
7✔
757
      }
758
    }
759
  }
7✔
760

761
  if (active_sources.empty()) {
19✔
762
    OLA_WARN << "Something changed but we didn't find any active sources "
×
763
             << " for universe " << UniverseId();
×
764
    return false;
×
765
  }
766

767
  if (!changed_source_is_active) {
19✔
768
    // this source didn't have any effect, skip
769
    return false;
770
  }
771

772
  // only one source at the active priority
773
  if (active_sources.size() == 1) {
19✔
774
    m_buffer.Set(active_sources[0].Data());
11✔
775
  } else {
776
    // multi source merge
777
    if (m_merge_mode == Universe::MERGE_LTP) {
8✔
778
      vector<DmxSource>::const_iterator source_iter = active_sources.begin();
5✔
779
      DmxSource changed_source;
5✔
780
      if (port) {
5✔
781
        changed_source = port->SourceData();
2✔
782
      } else {
783
        changed_source = client->SourceData(UniverseId());
6✔
784
      }
785

786
      // check that the current port/client is newer than all other active
787
      // sources
788
      for (; source_iter != active_sources.end(); source_iter++) {
14✔
789
        if (changed_source.Timestamp() < source_iter->Timestamp()) {
10✔
790
          return false;
1✔
791
        }
792
      }
793
      // if we made it to here this is the newest source
794
      m_buffer.Set(changed_source.Data());
4✔
795
    } else {
5✔
796
      HTPMergeSources(active_sources);
3✔
797
    }
798
  }
799
  return true;
800
}
19✔
801

802

803
/**
804
 * Called when discovery completes on a single ports.
805
 */
806
void Universe::PortDiscoveryComplete(BaseCallback0<void> *on_complete,
5✔
807
                                     OutputPort *output_port,
808
                                     const ola::rdm::UIDSet &uids) {
809
  NewUIDList(output_port, uids);
5✔
810
  on_complete->Run();
5✔
811
}
5✔
812

813

814
/**
815
 * Called when discovery completes on all ports.
816
 */
817
void Universe::DiscoveryComplete(RDMDiscoveryCallback *on_complete) {
3✔
818
  ola::rdm::UIDSet uids;
3✔
819
  GetUIDs(&uids);
3✔
820
  if (on_complete) {
3✔
821
    on_complete->Run(uids);
3✔
822
  }
823
}
3✔
824

825

826
/**
827
 * Track fan-out responses for a broadcast request.
828
 * This increments the port counter until we reach the expected value, and
829
 * which point we run the callback for the client.
830
 */
831
void Universe::HandleBroadcastAck(broadcast_request_tracker *tracker,
4✔
832
                                  RDMReply *reply) {
833
  tracker->current_count++;
4✔
834
  if (reply->StatusCode() != ola::rdm::RDM_WAS_BROADCAST) {
4✔
835
    // propagate errors though
836
    tracker->status_code = reply->StatusCode();
1✔
837
  }
838

839
  if (tracker->current_count == tracker->expected_count) {
4✔
840
    // all ports have completed
841
    RunRDMCallback(tracker->callback, tracker->status_code);
2✔
842
    delete tracker;
2✔
843
  }
844
}
4✔
845

846

847
/**
848
 * Handle the DUB responses. This is unique because unlike an RDM splitter can
849
 * can return the DUB responses from each port (485 line in the splitter
850
 * world). We do this by concatenating the vectors together.
851
 *
852
 * The response codes should be one of:
853
 *   RDM_DUB_RESPONSE - got a DUB response
854
 *   RDM_TIMEOUT - no response received
855
 *   RDM_PLUGIN_DISCOVERY_NOT_SUPPORTED - the port doesn't support DUB
856
 *
857
 * The above list is ordered in highest to lowest precedence, i.e. if we get
858
 * any port with a RDM_DUB_RESPONSE, this overrides any other message.
859
 */
860
void Universe::HandleBroadcastDiscovery(
10✔
861
    broadcast_request_tracker *tracker,
862
    RDMReply *reply) {
863
  tracker->current_count++;
10✔
864

865
  if (reply->StatusCode() == ola::rdm::RDM_DUB_RESPONSE) {
10✔
866
    // RDM_DUB_RESPONSE is the highest priority
867
    tracker->status_code = ola::rdm::RDM_DUB_RESPONSE;
4✔
868
  } else if (reply->StatusCode() == ola::rdm::RDM_TIMEOUT &&
6✔
869
             tracker->status_code != ola::rdm::RDM_DUB_RESPONSE) {
2✔
870
    // RDM_TIMEOUT is the second highest
871
    tracker->status_code = reply->StatusCode();
1✔
872
  } else if (tracker->status_code != ola::rdm::RDM_DUB_RESPONSE &&
5✔
873
             tracker->status_code != ola::rdm::RDM_TIMEOUT) {
874
    // everything else follows
875
    tracker->status_code = reply->StatusCode();
2✔
876
  }
877

878
  // append any packets to the main packets vector
879
  tracker->frames.insert(tracker->frames.end(),
10✔
880
                         reply->Frames().begin(),
10✔
881
                         reply->Frames().end());
10✔
882

883
  if (tracker->current_count == tracker->expected_count) {
10✔
884
    // all ports have completed
885
    RDMReply reply(tracker->status_code, NULL, tracker->frames);
5✔
886
    tracker->callback->Run(&reply);
5✔
887
    delete tracker;
5✔
888
  }
5✔
889
}
10✔
890

891

892
/*
893
 * Helper function to increment an Export Map variable
894
 */
895
void Universe::SafeIncrement(const string &name) {
40✔
896
  if (m_export_map) {
40✔
897
    (*m_export_map->GetUIntMapVar(name))[m_universe_id_str]++;
×
898
  }
899
}
40✔
900

901
/*
902
 * Helper function to decrement an Export Map variable
903
 */
904
void Universe::SafeDecrement(const string &name) {
6✔
905
  if (m_export_map) {
6✔
906
    (*m_export_map->GetUIntMapVar(name))[m_universe_id_str]--;
×
907
  }
908
}
6✔
909

910

911
/*
912
 * Add an Input or Output port to this universe.
913
 * @param port, the port to add
914
 * @param ports, the vector of ports to add to
915
 */
916
template<class PortClass>
917
bool Universe::GenericAddPort(PortClass *port, vector<PortClass*> *ports) {
13✔
918
  if (find(ports->begin(), ports->end(), port) != ports->end()) {
13✔
919
    return true;
920
  }
921

922
  ports->push_back(port);
13✔
923
  if (m_export_map) {
13✔
924
    UIntMap *map = m_export_map->GetUIntMapVar(
×
925
        IsInputPort<PortClass>() ? K_UNIVERSE_INPUT_PORT_VAR :
×
926
        K_UNIVERSE_OUTPUT_PORT_VAR);
927
    (*map)[m_universe_id_str]++;
×
928
  }
929
  return true;
930
}
931

932

933
/*
934
 * Remove an Input or Output port from this universe.
935
 * @param port, the port to add
936
 * @param ports, the vector of ports to remove from
937
 */
938
template<class PortClass>
939
bool Universe::GenericRemovePort(PortClass *port,
9✔
940
                                 vector<PortClass*> *ports,
941
                                 map<UID, PortClass*> *uid_map) {
942
  typename vector<PortClass*>::iterator iter =
943
    find(ports->begin(), ports->end(), port);
9✔
944

945
  if (iter == ports->end()) {
9✔
946
    OLA_DEBUG << "Could not find port " << port->UniqueId() << " in universe "
×
947
      << UniverseId();
×
948
    return true;
×
949
  }
950

951
  ports->erase(iter);
9✔
952
  if (m_export_map) {
9✔
953
    UIntMap *map = m_export_map->GetUIntMapVar(
×
954
        IsInputPort<PortClass>() ? K_UNIVERSE_INPUT_PORT_VAR :
×
955
        K_UNIVERSE_OUTPUT_PORT_VAR);
956
    (*map)[m_universe_id_str]--;
×
957
  }
958

959
  if (!IsActive()) {
9✔
960
    m_universe_store->AddUniverseGarbageCollection(this);
6✔
961
  }
962

963
  // Remove any uids that mapped to this port
964
  if (uid_map) {
9✔
965
    typename map<UID, PortClass*>::iterator uid_iter = uid_map->begin();
×
966
    while (uid_iter != uid_map->end()) {
×
967
      if (uid_iter->second == port) {
×
968
        uid_map->erase(uid_iter++);
×
969
      } else {
970
        ++uid_iter;
×
971
      }
972
    }
973
  }
974
  return true;
975
}
976

977

978
/*
979
 * Check if this universe contains a particular port.
980
 * @param port, the port to add
981
 * @param ports, the vector of ports to remove from
982
 */
983
template<class PortClass>
984
bool Universe::GenericContainsPort(PortClass *port,
14✔
985
                                   const vector<PortClass*> &ports) const {
986
  return find(ports.begin(), ports.end(), port) != ports.end();
14✔
987
}
988
}  // namespace  ola
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

© 2025 Coveralls, Inc