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

ossia / score / 36095816006

25 Sep 2026 04:46AM UTC coverage: 42.219% (-0.01%) from 42.23%
36095816006

Pull #2304

github

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

39 of 229 new or added lines in 13 files covered. (17.03%)

160 existing lines in 12 files now uncovered.

125818 of 298010 relevant lines covered (42.22%)

117820.05 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,097✔
146
{
147
  ExecutionCommand cmd;
297,097✔
148
  GCCommand gc;
297,097✔
149
  bool ok = false;
297,097✔
150
  bool gc_ok = false;
297,097✔
151
  do
297,097✔
152
  {
153
    if((ok = m_ctxData->m_editionQueue.try_dequeue(cmd)))
306,240✔
154
      cmd();
7,761✔
155

156
    if((gc_ok = m_ctxData->m_gcQueue.try_dequeue(gc)))
306,240✔
157
      gc();
1,642✔
158
  } while(ok || gc_ok);
306,240✔
159
}
297,097✔
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,286✔
200
{
201
  processEditCommands();
1,286✔
202
}
1,286✔
203

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

212
    if(m_base && m_base->active())
1,372✔
213
      d->get_protocol().start_execution();
1✔
214
  }
1,372✔
215
}
1,507✔
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
971✔
479
{
480
  std::atomic_thread_fence(std::memory_order_seq_cst);
971✔
481
  ExecutionCommand com;
971✔
482
  while(m_ctxData->m_execQueue.try_dequeue(com))
2,160✔
483
    com();
1,189✔
484
}
971✔
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,964✔
517
      if(old_dev)
1,528✔
518
        unregisterDevice(old_dev);
1,391✔
519
      if(new_dev)
1,528✔
520
        registerDevice(new_dev);
31✔
521
        });
1,528✔
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