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

ossia / score / 36173900300

25 Sep 2026 06:30PM UTC coverage: 42.234% (+0.004%) from 42.23%
36173900300

Pull #2304

github

web-flow
Merge f49c5df7b into c0c64ec39
Pull Request #2304: QML API additions, and three teardown fixes

135 of 408 new or added lines in 22 files covered. (33.09%)

181 existing lines in 15 files now uncovered.

125892 of 298079 relevant lines covered (42.23%)

117687.12 hits per line

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

84.47
/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/DeviceAddresses.hpp>
22
#include <Execution/Settings/ExecutorModel.hpp>
23

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

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,767 ✔
49
namespace Execution
50
{
51
DocumentPlugin::ContextData::ContextData(const score::DocumentContext& ctx)
2,134 ✔
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)
655 ✔
67
    : score::DocumentPlugin{ctx, "OSSIADocumentPlugin", parent}
655 ✔
68
    , settings{ctx.app.settings<Execution::Settings::Model>()}
655 ✔
69
    , m_ctxData{std::make_shared<ContextData>(ctx)}
70
{
655 ✔
71
  m_ctxData->context.alias = m_ctxData;
655 ✔
72
  makeGraph();
655 ✔
73
  auto& devs = ctx.plugin<Explorer::DeviceDocumentPlugin>();
655 ✔
74
  local_device = devs.list().localDevice();
655 ✔
75
  if(auto dev = devs.list().audioDevice())
655 ✔
76
  {
77
    audio_device = static_cast<Dataflow::AudioDevice*>(dev);
×
78
  }
×
79
  else
80
  {
81
    audio_device = new Dataflow::AudioDevice(
1,310 ✔
82
        {Dataflow::AudioProtocolFactory::static_concreteKey(), "audio", {}});
655 ✔
83
    ctx.plugin<Explorer::DeviceDocumentPlugin>().list().setAudioDevice(audio_device);
655 ✔
84
  }
85

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

94
  connect(
655 ✔
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 ✔
105
    auto& stop_action = context().doc.app.actions.action<Actions::Stop>();
×
106
    stop_action.action()->trigger();
×
107
      },
×
108
      Qt::QueuedConnection);
109
}
51 ✔
110

111
DocumentPlugin::~DocumentPlugin()
1,310 ✔
112
{
655 ✔
113
  if(m_base)
655 ✔
114
  {
115
    if(m_base->active())
11 ✔
116
    {
117
      m_base->baseInterval().stop();
×
118
      clear();
×
119
    }
×
120
  }
11 ✔
121

122
  if(audio_device)
655 ✔
123
  {
124
    if(auto devs = context().doc.findPlugin<Explorer::DeviceDocumentPlugin>())
655 ✔
125
    {
126
      const auto& rootNode = devs->explorer().rootNode();
655 ✔
127
      auto it = ossia::find_if(rootNode, [&](const Device::Node& val) {
811 ✔
128
        return val.is<Device::DeviceSettings>()
312 ✔
129
               && val.get<Device::DeviceSettings>().name == audio_device->name();
156 ✔
130
      });
131
      if(it != rootNode.end())
655 ✔
132
        devs->updateProxy.removeDevice(audio_device->settings());
×
133

134
      devs->list().setAudioDevice(nullptr);
655 ✔
135
    }
655 ✔
136
  }
655 ✔
137
  if(audio_device)
655 ✔
138
    delete audio_device;
655 ✔
139
  if(m_ctxData)
655 ✔
140
  {
141
    m_ctxData->context.alias.reset();
655 ✔
142
  }
655 ✔
143
}
1,310 ✔
144

145
void DocumentPlugin::processEditCommands()
297,098 ✔
146
{
147
  ExecutionCommand cmd;
297,098 ✔
148
  GCCommand gc;
297,098 ✔
149
  bool ok = false;
297,098 ✔
150
  bool gc_ok = false;
297,098 ✔
151
  do
297,098 ✔
152
  {
153
    if((ok = m_ctxData->m_editionQueue.try_dequeue(cmd)))
306,248 ✔
154
      cmd();
7,765 ✔
155

156
    if((gc_ok = m_ctxData->m_gcQueue.try_dequeue(gc)))
306,248 ✔
157
      gc();
1,642 ✔
158
  } while(ok || gc_ok);
306,248 ✔
159
}
297,098 ✔
160

161
void DocumentPlugin::on_finished()
11 ✔
162
{
163
  if(m_tid != -1)
11 ✔
164
  {
165
    killTimer(m_tid);
11 ✔
166
    m_tid = -1;
11 ✔
167
  }
11 ✔
168

169
  processEditCommands();
11 ✔
170
  clear();
11 ✔
171

172
  initExecState();
11 ✔
173

174
  for(auto& v : m_ctxData->setupContext.runtime_connections)
11 ✔
175
  {
176
    v.second.clear();
×
177
  }
178
  m_ctxData->setupContext.runtime_connections.clear();
11 ✔
179
}
11 ✔
180

181
void DocumentPlugin::initExecState()
717 ✔
182
{
183
  m_ctxData->execState = std::make_shared<ossia::execution_state>();
717 ✔
184
  auto& devlist = score::DocumentPlugin::context()
1,434 ✔
185
                      .plugin<Explorer::DeviceDocumentPlugin>()
717 ✔
186
                      .list()
717 ✔
187
                      .devices();
717 ✔
188
  if(audio_device)
717 ✔
189
    m_ctxData->execState->register_device(audio_device->getDevice());
62 ✔
190
  if(local_device)
717 ✔
191
    m_ctxData->execState->register_device(local_device->getDevice());
62 ✔
192
  for(auto dev : devlist)
757 ✔
193
  {
194
    registerDevice(dev->getDevice());
40 ✔
195
  }
196
  m_ctxData->execState->apply_device_changes();
717 ✔
197
}
717 ✔
198

199
void DocumentPlugin::timerEvent(QTimerEvent* event)
1,287 ✔
200
{
201
  processEditCommands();
1,287 ✔
202
}
1,287 ✔
203

204
void DocumentPlugin::registerDevice(ossia::net::device_base* d)
1,506 ✔
205
{
206
  if(!d)
1,506 ✔
207
    return;
10 ✔
208
  if(m_ctxData->execState)
1,496 ✔
209
  {
210
    m_ctxData->execState->register_device(d);
1,371 ✔
211

212
    if(m_base && m_base->active())
1,371 ✔
213
      d->get_protocol().start_execution();
1 ✔
214
  }
1,371 ✔
215
}
1,506 ✔
216

217
void DocumentPlugin::unregisterDevice(ossia::net::device_base* d)
1,417 ✔
218
{
219
  if(!m_ctxData->execState)
1,417 ✔
220
    return;
760 ✔
221

222
  m_ctxData->execState->unregister_device(d);
657 ✔
223

224
  // Ports keep raw pointers into the device tree and must be dropped from the
225
  // execution thread. The cleanup is queued, so by the time it runs this
226
  // device - and any other removed since - may be destroyed: snapshot the
227
  // addresses here and match on pointer identity below. One set for both kinds
228
  // also keeps the capture within the 128-byte ExecutionCommand budget.
229
  auto owned = deviceAddresses(*d);
657 ✔
230

231
  m_ctxData->context.executionQueue.enqueue(
1,314 ✔
232
      [wg = std::weak_ptr{m_ctxData->execGraph}, owned = std::move(owned)]() noexcept {
657 ✔
NEW
233
    if(auto g = wg.lock())
×
NEW
234
      clearAddresses(g->get_nodes(), owned);
×
NEW
235
  });
×
236
}
1,417 ✔
237

238
void DocumentPlugin::makeGraph()
706 ✔
239
{
240
  using namespace ossia;
241
  auto& audiosettings = this->m_context.app.settings<Audio::Settings::Model>();
706 ✔
242

243
  static const Execution::Settings::SchedulingPolicies sched_t;
706 ✔
244
  static const Execution::Settings::OrderingPolicies order_t;
706 ✔
245
  static const Execution::Settings::MergingPolicies merge_t;
706 ✔
246

247
  // note: cas qui n'ont pas de sens: dynamic avec les cas ou on append les
248
  // valeurs. parallel avec dynamic il manque le cas "default score order" il
249
  // manque le log pour dynamic
250

251
  auto sched = settings.getScheduling();
706 ✔
252

253
  auto& execGraph = m_ctxData->execGraph;
706 ✔
254
  auto& execState = m_ctxData->execState;
706 ✔
255
  auto& bench = m_ctxData->bench;
706 ✔
256

257
  if(execGraph)
706 ✔
258
  {
259
    SCORE_SOFT_ASSERT(!execGraph); // "execGraph should always be unset here");
×
260
    execGraph->clear();
×
261
  }
×
262
  execGraph.reset();
706 ✔
263

264
  execState.reset();
706 ✔
265

266
  initExecState();
706 ✔
267

268
  execState->bufferSize = audiosettings.getBufferSize();
706 ✔
269
  execState->sampleRate = audiosettings.getRate();
706 ✔
270

271
  // Publish audio clock state for video frame pacing
272
  Audio::execution_samples.store(0, std::memory_order_relaxed);
706 ✔
273
  Audio::execution_sample_rate.store(audiosettings.getRate(), std::memory_order_relaxed);
706 ✔
274
  execState->modelToSamplesRatio
706 ✔
275
      = audiosettings.getRate() / ossia::flicks_per_second<double>;
706 ✔
276
  execState->samplesToModelRatio
706 ✔
277
      = ossia::flicks_per_second<double> / audiosettings.getRate();
706 ✔
278
  execState->samples_since_start = 0;
706 ✔
279
  execState->start_date = 0; // TODO set it in the first callback
706 ✔
280
  execState->cur_date = execState->start_date;
706 ✔
281

282
  auto& p = ossia::audio_buffer_pool::instance();
706 ✔
283
  for(int i = 0; i < 500; i++)
353,706 ✔
284
  {
285
    auto v = p.acquire();
353,000 ✔
286
    v.reserve(execState->bufferSize);
353,000 ✔
287
    p.release(std::move(v));
353,000 ✔
288
  }
353,000 ✔
289

290
  ossia::graph_setup_options opt;
706 ✔
291
  opt.parallel = settings.getParallel();
706 ✔
292
  opt.parallel_threads = settings.getThreads();
706 ✔
293
  if(settings.getLogging())
706 ✔
294
    opt.log = ossia::logger_ptr();
×
295
  if(settings.getBench())
706 ✔
296
  {
297
    bench = std::make_shared<bench_map>();
×
298
    opt.bench = bench;
×
299
    opt.bench->clear();
×
300
  }
×
301

302
  if(sched == sched_t.StaticFixed)
706 ✔
303
    opt.scheduling = ossia::graph_setup_options::StaticFixed;
×
304
  else if(sched == sched_t.StaticBFS)
706 ✔
305
    opt.scheduling = ossia::graph_setup_options::StaticBFS;
×
306
  else if(sched == sched_t.StaticTC)
706 ✔
307
    opt.scheduling = ossia::graph_setup_options::StaticTC;
706 ✔
308
  else if(sched == sched_t.Dynamic)
×
309
    opt.scheduling = ossia::graph_setup_options::Dynamic;
×
310

311
  opt.scheduling = ossia::graph_setup_options::StaticFixed;
706 ✔
312
  execGraph = ossia::make_graph(opt);
706 ✔
313
}
706 ✔
314

315
void DocumentPlugin::reload(bool forcePlay, Scenario::IntervalModel& cst)
51 ✔
316
{
317
  if(m_base)
51 ✔
318
  {
319
    if(m_base->active())
×
320
    {
321
      m_base->baseInterval().stop();
×
322
    }
×
323
  }
×
324
  clear();
51 ✔
325

326
  const score::DocumentContext& ctx = m_context;
51 ✔
327
  auto& settings = ctx.app.settings<Execution::Settings::Model>();
51 ✔
328

329
  SCORE_ASSERT(m_ctxData);
51 ✔
330
  m_ctxData->context.time = settings.makeTimeFunction(ctx);
51 ✔
331
  m_ctxData->context.reverseTime = settings.makeReverseTimeFunction(ctx);
51 ✔
332

333
  // Notify devices that they have to start running stuff, polling frames, etc.
334
  auto& devs = m_context.plugin<Explorer::DeviceDocumentPlugin>();
51 ✔
335
  devs.list().apply([](const Device::DeviceInterface& d) {
177 ✔
336
    if(auto dev = d.getDevice())
126 ✔
337
      dev->get_protocol().start_execution();
122 ✔
338
  });
126 ✔
339

340
  makeGraph();
51 ✔
341

342
  auto parent = dynamic_cast<Scenario::ScenarioInterface*>(cst.parent());
51 ✔
343
  SCORE_ASSERT(parent);
51 ✔
344

345
  recreateBase();
51 ✔
346
  m_base->init(forcePlay, BaseScenarioRefContainer{cst, *parent});
51 ✔
347
  m_ctxData->m_created = true;
51 ✔
348

349
  auto& model = context().doc.model<Scenario::ScenarioDocumentModel>();
51 ✔
350
  Transaction t{m_ctxData->context};
51 ✔
351
  for(auto& cable : model.cables)
60 ✔
352
  {
353
    m_ctxData->setupContext.connectCable(cable, t);
9 ✔
354
  }
355

356
  for(auto ctl : model.statesWithControls)
51 ✔
357
  {
358
    auto state_comp
×
359
        = score::findComponent<Execution::StateComponentBase>(ctl->components());
×
360
    if(state_comp)
×
361
    {
362
      state_comp->updateControls(); // FIXME put in transaction too
×
363
    }
×
364
  }
365
  t.run_all();
51 ✔
366

367
  m_tid = startTimer(32);
51 ✔
368
}
51 ✔
369

370
void DocumentPlugin::clear()
1,479 ✔
371
{
372
  if(m_ctxData)
1,479 ✔
373
  {
374
    m_ctxData->setupContext.inlets.clear();
1,479 ✔
375
    m_ctxData->setupContext.outlets.clear();
1,479 ✔
376
    m_ctxData->setupContext.m_cables.clear();
1,479 ✔
377
    m_ctxData->setupContext.proc_map.clear();
1,479 ✔
378
  }
1,479 ✔
379
  // TODO do this in some shared object instead.
380
  m_base.reset();
1,479 ✔
381

382
  if(m_ctxData)
1,479 ✔
383
  {
384
    for(int i = 0; i < 100; i++)
149,379 ✔
385
    {
386
      processEditCommands();
147,900 ✔
387
      std::this_thread::yield();
147,900 ✔
388
    }
147,900 ✔
389
  }
1,479 ✔
390
  if(m_ctxData)
1,479 ✔
391
  {
392
    m_ctxData->m_created = false;
1,479 ✔
393
    if(m_ctxData)
1,479 ✔
394
      for(int i = 0; i < 100; i++)
149,379 ✔
395
      {
396
        processEditCommands();
147,900 ✔
397
        std::this_thread::yield();
147,900 ✔
398
        std::atomic_thread_fence(std::memory_order_seq_cst);
147,900 ✔
399
      }
149,379 ✔
400
  }
1,479 ✔
401
  m_ctxData.reset();
1,479 ✔
402
  m_ctxData = std::make_shared<ContextData>(this->m_context);
1,479 ✔
403
  m_ctxData->context.alias = m_ctxData;
1,479 ✔
404

405
  auto& model = this->m_context.model<Scenario::ScenarioDocumentModel>();
1,479 ✔
406
  model.cables.mutable_added.connect<&SetupContext::on_cableCreated>(
2,958 ✔
407
      m_ctxData->setupContext);
1,479 ✔
408
  model.cables.removing.connect<&SetupContext::on_cableRemoved>(m_ctxData->setupContext);
1,479 ✔
409

410
  // Notify devices that they have to stop running stuff, polling frames, etc.
411
  auto& devs = m_context.plugin<Explorer::DeviceDocumentPlugin>();
1,479 ✔
412
  devs.list().apply([](const Device::DeviceInterface& d) {
4,566 ✔
413
    if(auto dev = d.getDevice())
3,087 ✔
414
      dev->get_protocol().stop_execution();
3,013 ✔
415
  });
3,087 ✔
416
}
1,479 ✔
417

418
void DocumentPlugin::on_documentClosing()
655 ✔
419
{
420
  if(m_base && m_base->active())
655 ✔
421
  {
422
    m_base->baseInterval().stop();
34 ✔
423
    m_context.app.guiApplicationPlugin<Engine::ApplicationPlugin>()
34 ✔
424
        .execution()
34 ✔
425
        .request_stop();
34 ✔
426
    clear();
34 ✔
427
  }
34 ✔
428
  m_ctxData->execState.reset();
655 ✔
429
}
655 ✔
430

431
const std::shared_ptr<BaseScenarioElement>& DocumentPlugin::baseScenario() const noexcept
85 ✔
432
{
433
  return m_base;
85 ✔
434
}
435

436
void DocumentPlugin::playStartState()
1 ✔
437
{
438
  auto scenar = score::IDocument::try_get<Scenario::ScenarioDocumentModel>(
1 ✔
439
      this->m_context.document);
1 ✔
440
  if(!scenar)
1 ✔
441
    return;
×
442
  auto& sm = scenar->baseScenario().startState();
1 ✔
443

444
  Engine::score_to_ossia::play_state_from_ui(sm, this->context());
1 ✔
445
}
1 ✔
446

447
void DocumentPlugin::playStopState()
11 ✔
448
{
449
  auto scenar = score::IDocument::try_get<Scenario::ScenarioDocumentModel>(
11 ✔
450
      this->m_context.document);
11 ✔
451
  if(!scenar)
11 ✔
452
    return;
×
453
  auto& sm = scenar->baseScenario().endState();
11 ✔
454
  Engine::score_to_ossia::play_state_from_ui(sm, this->context());
11 ✔
455
}
11 ✔
456

457
bool DocumentPlugin::isPlaying() const
729 ✔
458
{
459
  if(m_base)
729 ✔
460
    return m_base->active();
5 ✔
461
  return false;
724 ✔
462
}
729 ✔
463

464
const ExecutionController& DocumentPlugin::executionController() const noexcept
24 ✔
465
{
466
  return m_context.app.guiApplicationPlugin<Engine::ApplicationPlugin>().execution();
24 ✔
467
}
468

469
std::shared_ptr<ossia::audio_protocol> DocumentPlugin::audioProto()
22 ✔
470
{
471
  auto dev = audio_device->sharedDevice();
22 ✔
472
  auto proto
22 ✔
473
      = &static_cast<ossia::audio_protocol&>(audio_device->getDevice()->get_protocol());
22 ✔
474

475
  return std::shared_ptr<ossia::audio_protocol>(dev, proto);
22 ✔
476
}
22 ✔
477

478
void DocumentPlugin::runAllCommands() const
968 ✔
479
{
480
  std::atomic_thread_fence(std::memory_order_seq_cst);
968 ✔
481
  ExecutionCommand com;
968 ✔
482
  while(m_ctxData->m_execQueue.try_dequeue(com))
2,157 ✔
483
    com();
1,189 ✔
484
}
968 ✔
485

486
void DocumentPlugin::registerAction(ExecutionAction& act)
655 ✔
487
{
488
  m_actions.push_back(&act);
655 ✔
489
}
655 ✔
490

491
void DocumentPlugin::slot_bench(ossia::bench_map b, int64_t ns)
×
492
{
493
  for(const auto& p : b)
×
494
  {
495
    if(p.second)
×
496
    {
497
      auto proc = m_ctxData->setupContext.proc_map.find(p.first);
×
498
      if(proc != m_ctxData->setupContext.proc_map.end())
×
499
      {
500
        if(proc->second)
×
501
        {
502
          const_cast<Process::ProcessModel*>(proc->second)
×
503
              ->benchmark(100. * *p.second / (double)ns);
×
504
        }
×
505
      }
×
506
    }
×
507
  }
508
}
×
509

510
void DocumentPlugin::on_deviceAdded(Device::DeviceInterface* dev)
1,500 ✔
511
{
512
  if(auto d = dev->getDevice())
1,500 ✔
513
  {
514
    connect(
1,436 ✔
515
        dev, &Device::DeviceInterface::deviceChanged, this,
1,436 ✔
516
        [this](ossia::net::device_base* old_dev, ossia::net::device_base* new_dev) {
2,963 ✔
517
      if(old_dev)
1,527 ✔
518
        unregisterDevice(old_dev);
1,391 ✔
519
      if(new_dev)
1,527 ✔
520
        registerDevice(new_dev);
30 ✔
521
        });
1,527 ✔
522
    registerDevice(d);
1,436 ✔
523
  }
1,436 ✔
524
}
1,500 ✔
525
}
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