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

ossia / score / 36105248298

25 Sep 2026 06:57AM UTC coverage: 42.611% (+0.4%) from 42.23%
36105248298

Pull #2308

github

web-flow
Merge 07da0e711 into c0c64ec39
Pull Request #2308: tests: a green test suite, and the sanitizer findings in score fixed

511 of 687 new or added lines in 50 files covered. (74.38%)

184 existing lines in 23 files now uncovered.

127081 of 298233 relevant lines covered (42.61%)

118531.04 hits per line

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

78.65
/src/plugins/score-plugin-engine/Execution/DocumentPlugin.cpp
1
// This is an open source non-commercial project. Dear PVS-Studio, please check
2
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
3
#include "DocumentPlugin.hpp"
4

5
#include "BaseScenarioComponent.hpp"
6

7
#include <Explorer/DocumentPlugin/DeviceDocumentPlugin.hpp>
8

9
#include <Scenario/Application/ScenarioActions.hpp>
10
#include <Scenario/Document/BaseScenario/BaseScenario.hpp>
11
#include <Scenario/Document/Interval/IntervalExecution.hpp>
12
#include <Scenario/Document/ScenarioDocument/ScenarioDocumentModel.hpp>
13
#include <Scenario/Document/State/StateExecution.hpp>
14
#include <Scenario/Execution/score2OSSIA.hpp>
15

16
#include <Audio/AudioApplicationPlugin.hpp>
17
#include <Audio/AudioDevice.hpp>
18
#include <Audio/AudioTick.hpp>
19
#include <Audio/Settings/Model.hpp>
20
#include <Engine/ApplicationPlugin.hpp>
21
#include <Execution/Settings/ExecutorModel.hpp>
22

23
#include <score/actions/ActionManager.hpp>
24
#include <score/model/ComponentUtils.hpp>
25
#include <score/plugins/documentdelegate/plugin/DocumentPlugin.hpp>
26
#include <score/tools/Bind.hpp>
27

28
#include <core/application/ApplicationSettings.hpp>
29
#include <core/document/Document.hpp>
30
#include <core/document/DocumentModel.hpp>
31

32
#include <ossia/audio/audio_protocol.hpp>
33
#include <ossia/dataflow/bench_map.hpp>
34
#include <ossia/dataflow/execution_state.hpp>
35
#include <ossia/dataflow/for_each_port.hpp>
36
#include <ossia/dataflow/graph/graph_interface.hpp>
37
#include <ossia/dataflow/graph_edge.hpp>
38
#include <ossia/dataflow/port.hpp>
39
#include <ossia/detail/flicks.hpp>
40
#include <ossia/detail/logger.hpp>
41
#include <ossia/editor/scenario/time_interval.hpp>
42
#include <ossia/network/common/path.hpp>
43

44
#include <QCoreApplication>
45

46
#include <wobjectimpl.h>
47
W_REGISTER_ARGTYPE(ossia::bench_map)
48
W_OBJECT_IMPL(Execution::DocumentPlugin)
7,787 ✔
49
namespace Execution
50
{
51
DocumentPlugin::ContextData::ContextData(const score::DocumentContext& ctx)
2,175 ✔
52
    : setupContext{context}
53
    , context
54
{
55
  {}, ctx, m_created, {}, {}, m_execQueue, m_editionQueue, m_gcQueue, setupContext,
56
      execGraph, execState
57
#if(__cplusplus > 201703L) && !defined(_MSC_VER)
58
      ,
59
  {
60
    ossia::disable_init
61
  }
62
#endif
63
}
64
{
65
}
×
66
DocumentPlugin::DocumentPlugin(const score::DocumentContext& ctx, QObject* parent)
657 ✔
67
    : score::DocumentPlugin{ctx, "OSSIADocumentPlugin", parent}
657 ✔
68
    , settings{ctx.app.settings<Execution::Settings::Model>()}
657 ✔
69
    , m_ctxData{std::make_shared<ContextData>(ctx)}
70
{
657 ✔
71
  m_ctxData->context.alias = m_ctxData;
657 ✔
72
  makeGraph();
657 ✔
73
  auto& devs = ctx.plugin<Explorer::DeviceDocumentPlugin>();
657 ✔
74
  local_device = devs.list().localDevice();
657 ✔
75
  if(auto dev = devs.list().audioDevice())
657 ✔
76
  {
77
    audio_device = static_cast<Dataflow::AudioDevice*>(dev);
×
78
  }
×
79
  else
80
  {
81
    audio_device = new Dataflow::AudioDevice(
1,314 ✔
82
        {Dataflow::AudioProtocolFactory::static_concreteKey(), "audio", {}});
657 ✔
83
    ctx.plugin<Explorer::DeviceDocumentPlugin>().list().setAudioDevice(audio_device);
657 ✔
84
  }
85

86
  devs.list().apply([this](auto& d) { on_deviceAdded(&d); });
1,971 ✔
87
  con(devs.list(), &Device::DeviceList::deviceAdded, this,
657 ✔
88
      &DocumentPlugin::on_deviceAdded);
657 ✔
89
  con(devs.list(), &Device::DeviceList::deviceRemoved, this, [this](auto* dev) {
691 ✔
90
    if(auto d = dev->getDevice())
34 ✔
91
      unregisterDevice(d);
26 ✔
92
  });
34 ✔
93

94
  connect(
657 ✔
95
      this, &DocumentPlugin::finished, this, &DocumentPlugin::on_finished,
96
      Qt::DirectConnection);
97
}
×
98

99
void DocumentPlugin::recreateBase()
51 ✔
100
{
101
  m_base = std::make_shared<BaseScenarioElement>(m_ctxData->context, this);
51 ✔
102
  connect(
51 ✔
103
      m_base.get(), &Execution::BaseScenarioElement::finished, this,
51 ✔
104
      [this] {
51 ✔
NEW
105
    auto& app = context().doc.app;
×
106
    // Without a GUI the transport actions are never registered: stop through
107
    // the same path as the OSC /stop message.
NEW
108
    if(app.applicationSettings.gui)
×
NEW
109
      app.actions.action<Actions::Stop>().action()->trigger();
×
110
    else
NEW
111
      app.guiApplicationPlugin<Engine::ApplicationPlugin>()
×
NEW
112
          .execution()
×
NEW
113
          .request_stop_from_localtree();
×
UNCOV
114
      },
×
115
      Qt::QueuedConnection);
116
}
51 ✔
117

118
DocumentPlugin::~DocumentPlugin()
1,314 ✔
119
{
657 ✔
120
  if(m_base)
657 ✔
121
  {
UNCOV
122
    if(m_base->active())
×
123
    {
124
      m_base->baseInterval().stop();
×
125
      clear();
×
126
    }
×
UNCOV
127
  }
×
128

129
  if(audio_device)
657 ✔
130
  {
131
    if(auto devs = context().doc.findPlugin<Explorer::DeviceDocumentPlugin>())
657 ✔
132
    {
133
      const auto& rootNode = devs->explorer().rootNode();
657 ✔
134
      auto it = ossia::find_if(rootNode, [&](const Device::Node& val) {
813 ✔
135
        return val.is<Device::DeviceSettings>()
312 ✔
136
               && val.get<Device::DeviceSettings>().name == audio_device->name();
156 ✔
137
      });
138
      if(it != rootNode.end())
657 ✔
139
        devs->updateProxy.removeDevice(audio_device->settings());
×
140

141
      devs->list().setAudioDevice(nullptr);
657 ✔
142
    }
657 ✔
143
  }
657 ✔
144
  if(audio_device)
657 ✔
145
    delete audio_device;
657 ✔
146
  if(m_ctxData)
657 ✔
147
  {
148
    m_ctxData->context.alias.reset();
657 ✔
149
  }
657 ✔
150
}
1,314 ✔
151

152
void DocumentPlugin::processEditCommands()
304,915 ✔
153
{
154
  ExecutionCommand cmd;
304,915 ✔
155
  GCCommand gc;
304,915 ✔
156
  bool ok = false;
304,915 ✔
157
  bool gc_ok = false;
304,915 ✔
158
  do
304,915 ✔
159
  {
160
    if((ok = m_ctxData->m_editionQueue.try_dequeue(cmd)))
315,266 ✔
161
      cmd();
8,967 ✔
162

163
    if((gc_ok = m_ctxData->m_gcQueue.try_dequeue(gc)))
315,266 ✔
164
      gc();
1,800 ✔
165
  } while(ok || gc_ok);
315,266 ✔
166
}
304,915 ✔
167

168
void DocumentPlugin::on_finished()
22 ✔
169
{
170
  if(m_tid != -1)
22 ✔
171
  {
172
    killTimer(m_tid);
22 ✔
173
    m_tid = -1;
22 ✔
174
  }
22 ✔
175

176
  processEditCommands();
22 ✔
177
  clear();
22 ✔
178

179
  initExecState();
22 ✔
180

181
  for(auto& v : m_ctxData->setupContext.runtime_connections)
22 ✔
182
  {
183
    v.second.clear();
×
184
  }
185
  m_ctxData->setupContext.runtime_connections.clear();
22 ✔
186
}
22 ✔
187

188
void DocumentPlugin::initExecState()
730 ✔
189
{
190
  m_ctxData->execState = std::make_shared<ossia::execution_state>();
730 ✔
191
  auto& devlist = score::DocumentPlugin::context()
1,460 ✔
192
                      .plugin<Explorer::DeviceDocumentPlugin>()
730 ✔
193
                      .list()
730 ✔
194
                      .devices();
730 ✔
195
  if(audio_device)
730 ✔
196
    m_ctxData->execState->register_device(audio_device->getDevice());
73 ✔
197
  if(local_device)
730 ✔
198
    m_ctxData->execState->register_device(local_device->getDevice());
73 ✔
199
  for(auto dev : devlist)
781 ✔
200
  {
201
    registerDevice(dev->getDevice());
51 ✔
202
  }
203
  m_ctxData->execState->apply_device_changes();
730 ✔
204
}
730 ✔
205

206
void DocumentPlugin::timerEvent(QTimerEvent* event)
1,293 ✔
207
{
208
  processEditCommands();
1,293 ✔
209
}
1,293 ✔
210

211
void DocumentPlugin::registerDevice(ossia::net::device_base* d)
1,521 ✔
212
{
213
  if(!d)
1,521 ✔
214
    return;
10 ✔
215
  if(m_ctxData->execState)
1,511 ✔
216
  {
217
    m_ctxData->execState->register_device(d);
1,386 ✔
218

219
    if(m_base && m_base->active())
1,386 ✔
220
      d->get_protocol().start_execution();
1 ✔
221
  }
1,386 ✔
222
}
1,521 ✔
223

224
void DocumentPlugin::unregisterDevice(ossia::net::device_base* d)
1,421 ✔
225
{
226
  if(!m_ctxData->execState)
1,421 ✔
227
    return;
762 ✔
228

229
  m_ctxData->execState->unregister_device(d);
659 ✔
230

231
  // Ports keep raw pointers into the device tree: they must be dropped from
232
  // the execution thread before the device is destroyed.
233
  m_ctxData->context.executionQueue.enqueue(
1,318 ✔
234
      [wg = std::weak_ptr{m_ctxData->execGraph}, d]() noexcept {
659 ✔
235
    auto g = wg.lock();
×
236
    if(!g)
×
237
      return;
×
238

239
    auto belongs_to = [d](const ossia::destination_t& dest) {
×
240
      if(auto p = dest.target<ossia::net::parameter_base*>())
×
241
        return *p && &(*p)->get_node().get_device() == d;
×
242
      if(auto n = dest.target<ossia::net::node_base*>())
×
243
        return *n && &(*n)->get_device() == d;
×
244
      return false;
×
245
    };
×
246

247
    for(auto node : g->get_nodes())
×
248
    {
249
      ossia::for_each_inlet(*node, [&](ossia::inlet& p) {
×
250
        if(belongs_to(p.address))
×
251
          p.address = {};
×
252
      });
×
253
      ossia::for_each_outlet(*node, [&](ossia::outlet& p) {
×
254
        if(belongs_to(p.address))
×
255
          p.address = {};
×
256
      });
×
257
    }
258
      });
×
259
}
1,421 ✔
260

261
void DocumentPlugin::makeGraph()
708 ✔
262
{
263
  using namespace ossia;
264
  auto& audiosettings = this->m_context.app.settings<Audio::Settings::Model>();
708 ✔
265

266
  static const Execution::Settings::SchedulingPolicies sched_t;
708 ✔
267
  static const Execution::Settings::OrderingPolicies order_t;
708 ✔
268
  static const Execution::Settings::MergingPolicies merge_t;
708 ✔
269

270
  // note: cas qui n'ont pas de sens: dynamic avec les cas ou on append les
271
  // valeurs. parallel avec dynamic il manque le cas "default score order" il
272
  // manque le log pour dynamic
273

274
  auto sched = settings.getScheduling();
708 ✔
275

276
  auto& execGraph = m_ctxData->execGraph;
708 ✔
277
  auto& execState = m_ctxData->execState;
708 ✔
278
  auto& bench = m_ctxData->bench;
708 ✔
279

280
  if(execGraph)
708 ✔
281
  {
282
    SCORE_SOFT_ASSERT(!execGraph); // "execGraph should always be unset here");
×
283
    execGraph->clear();
×
284
  }
×
285
  execGraph.reset();
708 ✔
286

287
  execState.reset();
708 ✔
288

289
  initExecState();
708 ✔
290

291
  execState->bufferSize = audiosettings.getBufferSize();
708 ✔
292
  execState->sampleRate = audiosettings.getRate();
708 ✔
293

294
  // Publish audio clock state for video frame pacing
295
  Audio::execution_samples.store(0, std::memory_order_relaxed);
708 ✔
296
  Audio::execution_sample_rate.store(audiosettings.getRate(), std::memory_order_relaxed);
708 ✔
297
  execState->modelToSamplesRatio
708 ✔
298
      = audiosettings.getRate() / ossia::flicks_per_second<double>;
708 ✔
299
  execState->samplesToModelRatio
708 ✔
300
      = ossia::flicks_per_second<double> / audiosettings.getRate();
708 ✔
301
  execState->samples_since_start = 0;
708 ✔
302
  execState->start_date = 0; // TODO set it in the first callback
708 ✔
303
  execState->cur_date = execState->start_date;
708 ✔
304

305
  auto& p = ossia::audio_buffer_pool::instance();
708 ✔
306
  for(int i = 0; i < 500; i++)
354,708 ✔
307
  {
308
    auto v = p.acquire();
354,000 ✔
309
    v.reserve(execState->bufferSize);
354,000 ✔
310
    p.release(std::move(v));
354,000 ✔
311
  }
354,000 ✔
312

313
  ossia::graph_setup_options opt;
708 ✔
314
  opt.parallel = settings.getParallel();
708 ✔
315
  opt.parallel_threads = settings.getThreads();
708 ✔
316
  if(settings.getLogging())
708 ✔
317
    opt.log = ossia::logger_ptr();
×
318
  if(settings.getBench())
708 ✔
319
  {
320
    bench = std::make_shared<bench_map>();
×
321
    opt.bench = bench;
×
322
    opt.bench->clear();
×
323
  }
×
324

325
  if(sched == sched_t.StaticFixed)
708 ✔
326
    opt.scheduling = ossia::graph_setup_options::StaticFixed;
×
327
  else if(sched == sched_t.StaticBFS)
708 ✔
328
    opt.scheduling = ossia::graph_setup_options::StaticBFS;
×
329
  else if(sched == sched_t.StaticTC)
708 ✔
330
    opt.scheduling = ossia::graph_setup_options::StaticTC;
708 ✔
331
  else if(sched == sched_t.Dynamic)
×
332
    opt.scheduling = ossia::graph_setup_options::Dynamic;
×
333

334
  opt.scheduling = ossia::graph_setup_options::StaticFixed;
708 ✔
335
  execGraph = ossia::make_graph(opt);
708 ✔
336
}
708 ✔
337

338
void DocumentPlugin::reload(bool forcePlay, Scenario::IntervalModel& cst)
51 ✔
339
{
340
  if(m_base)
51 ✔
341
  {
342
    if(m_base->active())
×
343
    {
344
      m_base->baseInterval().stop();
×
345
    }
×
346
  }
×
347
  clear();
51 ✔
348

349
  const score::DocumentContext& ctx = m_context;
51 ✔
350
  auto& settings = ctx.app.settings<Execution::Settings::Model>();
51 ✔
351

352
  SCORE_ASSERT(m_ctxData);
51 ✔
353
  m_ctxData->context.time = settings.makeTimeFunction(ctx);
51 ✔
354
  m_ctxData->context.reverseTime = settings.makeReverseTimeFunction(ctx);
51 ✔
355

356
  // Notify devices that they have to start running stuff, polling frames, etc.
357
  auto& devs = m_context.plugin<Explorer::DeviceDocumentPlugin>();
51 ✔
358
  devs.list().apply([](const Device::DeviceInterface& d) {
177 ✔
359
    if(auto dev = d.getDevice())
126 ✔
360
      dev->get_protocol().start_execution();
122 ✔
361
  });
126 ✔
362

363
  makeGraph();
51 ✔
364

365
  auto parent = dynamic_cast<Scenario::ScenarioInterface*>(cst.parent());
51 ✔
366
  SCORE_ASSERT(parent);
51 ✔
367

368
  recreateBase();
51 ✔
369
  m_base->init(forcePlay, BaseScenarioRefContainer{cst, *parent});
51 ✔
370
  m_ctxData->m_created = true;
51 ✔
371

372
  auto& model = context().doc.model<Scenario::ScenarioDocumentModel>();
51 ✔
373
  Transaction t{m_ctxData->context};
51 ✔
374
  for(auto& cable : model.cables)
60 ✔
375
  {
376
    m_ctxData->setupContext.connectCable(cable, t);
9 ✔
377
  }
378

379
  for(auto ctl : model.statesWithControls)
51 ✔
380
  {
381
    auto state_comp
×
382
        = score::findComponent<Execution::StateComponentBase>(ctl->components());
×
383
    if(state_comp)
×
384
    {
385
      state_comp->updateControls(); // FIXME put in transaction too
×
386
    }
×
387
  }
388
  t.run_all();
51 ✔
389

390
  m_tid = startTimer(32);
51 ✔
391
}
51 ✔
392

393
void DocumentPlugin::clear()
1,518 ✔
394
{
395
  if(m_ctxData)
1,518 ✔
396
  {
397
    m_ctxData->setupContext.inlets.clear();
1,518 ✔
398
    m_ctxData->setupContext.outlets.clear();
1,518 ✔
399
    m_ctxData->setupContext.m_cables.clear();
1,518 ✔
400
    m_ctxData->setupContext.proc_map.clear();
1,518 ✔
401
  }
1,518 ✔
402
  // TODO do this in some shared object instead.
403
  m_base.reset();
1,518 ✔
404

405
  if(m_ctxData)
1,518 ✔
406
  {
407
    for(int i = 0; i < 100; i++)
153,318 ✔
408
    {
409
      processEditCommands();
151,800 ✔
410
      std::this_thread::yield();
151,800 ✔
411
    }
151,800 ✔
412
  }
1,518 ✔
413
  if(m_ctxData)
1,518 ✔
414
  {
415
    m_ctxData->m_created = false;
1,518 ✔
416
    if(m_ctxData)
1,518 ✔
417
      for(int i = 0; i < 100; i++)
153,318 ✔
418
      {
419
        processEditCommands();
151,800 ✔
420
        std::this_thread::yield();
151,800 ✔
421
        std::atomic_thread_fence(std::memory_order_seq_cst);
151,800 ✔
422
      }
153,318 ✔
423
  }
1,518 ✔
424
  m_ctxData.reset();
1,518 ✔
425
  m_ctxData = std::make_shared<ContextData>(this->m_context);
1,518 ✔
426
  m_ctxData->context.alias = m_ctxData;
1,518 ✔
427

428
  auto& model = this->m_context.model<Scenario::ScenarioDocumentModel>();
1,518 ✔
429
  model.cables.mutable_added.connect<&SetupContext::on_cableCreated>(
3,036 ✔
430
      m_ctxData->setupContext);
1,518 ✔
431
  model.cables.removing.connect<&SetupContext::on_cableRemoved>(m_ctxData->setupContext);
1,518 ✔
432

433
  // Notify devices that they have to stop running stuff, polling frames, etc.
434
  auto& devs = m_context.plugin<Explorer::DeviceDocumentPlugin>();
1,518 ✔
435
  devs.list().apply([](const Device::DeviceInterface& d) {
4,716 ✔
436
    if(auto dev = d.getDevice())
3,198 ✔
437
      dev->get_protocol().stop_execution();
3,124 ✔
438
  });
3,198 ✔
439
}
1,518 ✔
440

441
void DocumentPlugin::on_documentClosing()
657 ✔
442
{
443
  if(m_base && m_base->active())
657 ✔
444
  {
445
    m_base->baseInterval().stop();
45 ✔
446
    m_context.app.guiApplicationPlugin<Engine::ApplicationPlugin>()
45 ✔
447
        .execution()
45 ✔
448
        .request_stop();
45 ✔
449
    clear();
45 ✔
450
  }
45 ✔
451
  m_ctxData->execState.reset();
657 ✔
452
}
657 ✔
453

454
const std::shared_ptr<BaseScenarioElement>& DocumentPlugin::baseScenario() const noexcept
85 ✔
455
{
456
  return m_base;
85 ✔
457
}
458

459
void DocumentPlugin::playStartState()
1 ✔
460
{
461
  auto scenar = score::IDocument::try_get<Scenario::ScenarioDocumentModel>(
1 ✔
462
      this->m_context.document);
1 ✔
463
  if(!scenar)
1 ✔
464
    return;
×
465
  auto& sm = scenar->baseScenario().startState();
1 ✔
466

467
  Engine::score_to_ossia::play_state_from_ui(sm, this->context());
1 ✔
468
}
1 ✔
469

470
void DocumentPlugin::playStopState()
22 ✔
471
{
472
  auto scenar = score::IDocument::try_get<Scenario::ScenarioDocumentModel>(
22 ✔
473
      this->m_context.document);
22 ✔
474
  if(!scenar)
22 ✔
475
    return;
×
476
  auto& sm = scenar->baseScenario().endState();
22 ✔
477
  Engine::score_to_ossia::play_state_from_ui(sm, this->context());
22 ✔
478
}
22 ✔
479

480
bool DocumentPlugin::isPlaying() const
729 ✔
481
{
482
  if(m_base)
729 ✔
483
    return m_base->active();
5 ✔
484
  return false;
724 ✔
485
}
729 ✔
486

487
const ExecutionController& DocumentPlugin::executionController() const noexcept
24 ✔
488
{
489
  return m_context.app.guiApplicationPlugin<Engine::ApplicationPlugin>().execution();
24 ✔
490
}
491

492
std::shared_ptr<ossia::audio_protocol> DocumentPlugin::audioProto()
22 ✔
493
{
494
  auto dev = audio_device->sharedDevice();
22 ✔
495
  auto proto
22 ✔
496
      = &static_cast<ossia::audio_protocol&>(audio_device->getDevice()->get_protocol());
22 ✔
497

498
  return std::shared_ptr<ossia::audio_protocol>(dev, proto);
22 ✔
499
}
22 ✔
500

501
void DocumentPlugin::runAllCommands() const
970 ✔
502
{
503
  std::atomic_thread_fence(std::memory_order_seq_cst);
970 ✔
504
  ExecutionCommand com;
970 ✔
505
  while(m_ctxData->m_execQueue.try_dequeue(com))
2,159 ✔
506
    com();
1,189 ✔
507
}
970 ✔
508

509
void DocumentPlugin::registerAction(ExecutionAction& act)
657 ✔
510
{
511
  m_actions.push_back(&act);
657 ✔
512
}
657 ✔
513

514
void DocumentPlugin::slot_bench(ossia::bench_map b, int64_t ns)
×
515
{
516
  for(const auto& p : b)
×
517
  {
518
    if(p.second)
×
519
    {
520
      auto proc = m_ctxData->setupContext.proc_map.find(p.first);
×
521
      if(proc != m_ctxData->setupContext.proc_map.end())
×
522
      {
523
        if(proc->second)
×
524
        {
525
          const_cast<Process::ProcessModel*>(proc->second)
×
526
              ->benchmark(100. * *p.second / (double)ns);
×
527
        }
×
528
      }
×
529
    }
×
530
  }
531
}
×
532

533
void DocumentPlugin::on_deviceAdded(Device::DeviceInterface* dev)
1,504 ✔
534
{
535
  if(auto d = dev->getDevice())
1,504 ✔
536
  {
537
    connect(
1,440 ✔
538
        dev, &Device::DeviceInterface::deviceChanged, this,
1,440 ✔
539
        [this](ossia::net::device_base* old_dev, ossia::net::device_base* new_dev) {
2,971 ✔
540
      if(old_dev)
1,531 ✔
541
        unregisterDevice(old_dev);
1,395 ✔
542
      if(new_dev)
1,531 ✔
543
        registerDevice(new_dev);
30 ✔
544
        });
1,531 ✔
545
    registerDevice(d);
1,440 ✔
546
  }
1,440 ✔
547
}
1,504 ✔
548
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc