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

ossia / score / 35899133125

23 Sep 2026 05:57PM UTC coverage: 41.488% (+1.2%) from 40.337%
35899133125

push

github

jcelerier
3rdparty: follow the merged libossia

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PWVTQ1pm3X2Uz7goFgULtC

123236 of 297041 relevant lines covered (41.49%)

113626.05 hits per line

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

80.18
/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/document/Document.hpp>
29
#include <core/document/DocumentModel.hpp>
30

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

43
#include <QCoreApplication>
44

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

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

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

98
void DocumentPlugin::recreateBase()
50✔
99
{
100
  m_base = std::make_shared<BaseScenarioElement>(m_ctxData->context, this);
50✔
101
  connect(
50✔
102
      m_base.get(), &Execution::BaseScenarioElement::finished, this,
50✔
103
      [this] {
50✔
104
    auto& stop_action = context().doc.app.actions.action<Actions::Stop>();
×
105
    stop_action.action()->trigger();
×
106
      },
×
107
      Qt::QueuedConnection);
108
}
50✔
109

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

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

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

144
void DocumentPlugin::processEditCommands()
277,499✔
145
{
146
  ExecutionCommand cmd;
277,499✔
147
  GCCommand gc;
277,499✔
148
  bool ok = false;
277,499✔
149
  bool gc_ok = false;
277,499✔
150
  do
277,499✔
151
  {
152
    if((ok = m_ctxData->m_editionQueue.try_dequeue(cmd)))
286,642✔
153
      cmd();
7,755✔
154

155
    if((gc_ok = m_ctxData->m_gcQueue.try_dequeue(gc)))
286,642✔
156
      gc();
1,638✔
157
  } while(ok || gc_ok);
286,642✔
158
}
277,499✔
159

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

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

171
  initExecState();
11✔
172

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

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

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

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

211
    if(m_base && m_base->active())
1,233✔
212
      d->get_protocol().start_execution();
1✔
213
  }
1,233✔
214
}
1,368✔
215

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

221
  m_ctxData->execState->unregister_device(d);
588✔
222

223
  // Ports keep raw pointers into the device tree: they must be dropped from
224
  // the execution thread before the device is destroyed.
225
  m_ctxData->context.executionQueue.enqueue(
1,176✔
226
      [wg = std::weak_ptr{m_ctxData->execGraph}, d]() noexcept {
588✔
227
    auto g = wg.lock();
×
228
    if(!g)
×
229
      return;
×
230

231
    auto belongs_to = [d](const ossia::destination_t& dest) {
×
232
      if(auto p = dest.target<ossia::net::parameter_base*>())
×
233
        return *p && &(*p)->get_node().get_device() == d;
×
234
      if(auto n = dest.target<ossia::net::node_base*>())
×
235
        return *n && &(*n)->get_device() == d;
×
236
      return false;
×
237
    };
×
238

239
    for(auto node : g->get_nodes())
×
240
    {
241
      ossia::for_each_inlet(*node, [&](ossia::inlet& p) {
×
242
        if(belongs_to(p.address))
×
243
          p.address = {};
×
244
      });
×
245
      ossia::for_each_outlet(*node, [&](ossia::outlet& p) {
×
246
        if(belongs_to(p.address))
×
247
          p.address = {};
×
248
      });
×
249
    }
250
      });
×
251
}
1,279✔
252

253
void DocumentPlugin::makeGraph()
636✔
254
{
255
  using namespace ossia;
256
  auto& audiosettings = this->m_context.app.settings<Audio::Settings::Model>();
636✔
257

258
  static const Execution::Settings::SchedulingPolicies sched_t;
636✔
259
  static const Execution::Settings::OrderingPolicies order_t;
636✔
260
  static const Execution::Settings::MergingPolicies merge_t;
636✔
261

262
  // note: cas qui n'ont pas de sens: dynamic avec les cas ou on append les
263
  // valeurs. parallel avec dynamic il manque le cas "default score order" il
264
  // manque le log pour dynamic
265

266
  auto sched = settings.getScheduling();
636✔
267

268
  auto& execGraph = m_ctxData->execGraph;
636✔
269
  auto& execState = m_ctxData->execState;
636✔
270
  auto& bench = m_ctxData->bench;
636✔
271

272
  if(execGraph)
636✔
273
  {
274
    SCORE_SOFT_ASSERT(!execGraph); // "execGraph should always be unset here");
×
275
    execGraph->clear();
×
276
  }
×
277
  execGraph.reset();
636✔
278

279
  execState.reset();
636✔
280

281
  initExecState();
636✔
282

283
  execState->bufferSize = audiosettings.getBufferSize();
636✔
284
  execState->sampleRate = audiosettings.getRate();
636✔
285

286
  // Publish audio clock state for video frame pacing
287
  Audio::execution_samples.store(0, std::memory_order_relaxed);
636✔
288
  Audio::execution_sample_rate.store(audiosettings.getRate(), std::memory_order_relaxed);
636✔
289
  execState->modelToSamplesRatio
636✔
290
      = audiosettings.getRate() / ossia::flicks_per_second<double>;
636✔
291
  execState->samplesToModelRatio
636✔
292
      = ossia::flicks_per_second<double> / audiosettings.getRate();
636✔
293
  execState->samples_since_start = 0;
636✔
294
  execState->start_date = 0; // TODO set it in the first callback
636✔
295
  execState->cur_date = execState->start_date;
636✔
296

297
  auto& p = ossia::audio_buffer_pool::instance();
636✔
298
  for(int i = 0; i < 500; i++)
318,636✔
299
  {
300
    auto v = p.acquire();
318,000✔
301
    v.reserve(execState->bufferSize);
318,000✔
302
    p.release(std::move(v));
318,000✔
303
  }
318,000✔
304

305
  ossia::graph_setup_options opt;
636✔
306
  opt.parallel = settings.getParallel();
636✔
307
  opt.parallel_threads = settings.getThreads();
636✔
308
  if(settings.getLogging())
636✔
309
    opt.log = ossia::logger_ptr();
×
310
  if(settings.getBench())
636✔
311
  {
312
    bench = std::make_shared<bench_map>();
×
313
    opt.bench = bench;
×
314
    opt.bench->clear();
×
315
  }
×
316

317
  if(sched == sched_t.StaticFixed)
636✔
318
    opt.scheduling = ossia::graph_setup_options::StaticFixed;
×
319
  else if(sched == sched_t.StaticBFS)
636✔
320
    opt.scheduling = ossia::graph_setup_options::StaticBFS;
×
321
  else if(sched == sched_t.StaticTC)
636✔
322
    opt.scheduling = ossia::graph_setup_options::StaticTC;
636✔
323
  else if(sched == sched_t.Dynamic)
×
324
    opt.scheduling = ossia::graph_setup_options::Dynamic;
×
325

326
  opt.scheduling = ossia::graph_setup_options::StaticFixed;
636✔
327
  execGraph = ossia::make_graph(opt);
636✔
328
}
636✔
329

330
void DocumentPlugin::reload(bool forcePlay, Scenario::IntervalModel& cst)
50✔
331
{
332
  if(m_base)
50✔
333
  {
334
    if(m_base->active())
×
335
    {
336
      m_base->baseInterval().stop();
×
337
    }
×
338
  }
×
339
  clear();
50✔
340

341
  const score::DocumentContext& ctx = m_context;
50✔
342
  auto& settings = ctx.app.settings<Execution::Settings::Model>();
50✔
343

344
  SCORE_ASSERT(m_ctxData);
50✔
345
  m_ctxData->context.time = settings.makeTimeFunction(ctx);
50✔
346
  m_ctxData->context.reverseTime = settings.makeReverseTimeFunction(ctx);
50✔
347

348
  // Notify devices that they have to start running stuff, polling frames, etc.
349
  auto& devs = m_context.plugin<Explorer::DeviceDocumentPlugin>();
50✔
350
  devs.list().apply([](const Device::DeviceInterface& d) {
174✔
351
    if(auto dev = d.getDevice())
124✔
352
      dev->get_protocol().start_execution();
120✔
353
  });
124✔
354

355
  makeGraph();
50✔
356

357
  auto parent = dynamic_cast<Scenario::ScenarioInterface*>(cst.parent());
50✔
358
  SCORE_ASSERT(parent);
50✔
359

360
  recreateBase();
50✔
361
  m_base->init(forcePlay, BaseScenarioRefContainer{cst, *parent});
50✔
362
  m_ctxData->m_created = true;
50✔
363

364
  auto& model = context().doc.model<Scenario::ScenarioDocumentModel>();
50✔
365
  Transaction t{m_ctxData->context};
50✔
366
  for(auto& cable : model.cables)
59✔
367
  {
368
    m_ctxData->setupContext.connectCable(cable, t);
9✔
369
  }
370

371
  for(auto ctl : model.statesWithControls)
50✔
372
  {
373
    auto state_comp
×
374
        = score::findComponent<Execution::StateComponentBase>(ctl->components());
×
375
    if(state_comp)
×
376
    {
377
      state_comp->updateControls(); // FIXME put in transaction too
×
378
    }
×
379
  }
380
  t.run_all();
50✔
381

382
  m_tid = startTimer(32);
50✔
383
}
50✔
384

385
void DocumentPlugin::clear()
1,381✔
386
{
387
  if(m_ctxData)
1,381✔
388
  {
389
    m_ctxData->setupContext.inlets.clear();
1,381✔
390
    m_ctxData->setupContext.outlets.clear();
1,381✔
391
    m_ctxData->setupContext.m_cables.clear();
1,381✔
392
    m_ctxData->setupContext.proc_map.clear();
1,381✔
393
  }
1,381✔
394
  // TODO do this in some shared object instead.
395
  m_base.reset();
1,381✔
396

397
  if(m_ctxData)
1,381✔
398
  {
399
    for(int i = 0; i < 100; i++)
139,481✔
400
    {
401
      processEditCommands();
138,100✔
402
      std::this_thread::yield();
138,100✔
403
    }
138,100✔
404
  }
1,381✔
405
  if(m_ctxData)
1,381✔
406
  {
407
    m_ctxData->m_created = false;
1,381✔
408
    if(m_ctxData)
1,381✔
409
      for(int i = 0; i < 100; i++)
139,481✔
410
      {
411
        processEditCommands();
138,100✔
412
        std::this_thread::yield();
138,100✔
413
        std::atomic_thread_fence(std::memory_order_seq_cst);
138,100✔
414
      }
139,481✔
415
  }
1,381✔
416
  m_ctxData.reset();
1,381✔
417
  m_ctxData = std::make_shared<ContextData>(this->m_context);
1,381✔
418
  m_ctxData->context.alias = m_ctxData;
1,381✔
419

420
  auto& model = this->m_context.model<Scenario::ScenarioDocumentModel>();
1,381✔
421
  model.cables.mutable_added.connect<&SetupContext::on_cableCreated>(
2,762✔
422
      m_ctxData->setupContext);
1,381✔
423
  model.cables.removing.connect<&SetupContext::on_cableRemoved>(m_ctxData->setupContext);
1,381✔
424

425
  // Notify devices that they have to stop running stuff, polling frames, etc.
426
  auto& devs = m_context.plugin<Explorer::DeviceDocumentPlugin>();
1,381✔
427
  devs.list().apply([](const Device::DeviceInterface& d) {
4,272✔
428
    if(auto dev = d.getDevice())
2,891✔
429
      dev->get_protocol().stop_execution();
2,817✔
430
  });
2,891✔
431
}
1,381✔
432

433
void DocumentPlugin::on_documentClosing()
586✔
434
{
435
  if(m_base && m_base->active())
586✔
436
  {
437
    m_base->baseInterval().stop();
33✔
438
    m_context.app.guiApplicationPlugin<Engine::ApplicationPlugin>()
33✔
439
        .execution()
33✔
440
        .request_stop();
33✔
441
    clear();
33✔
442
  }
33✔
443
  m_ctxData->execState.reset();
586✔
444
}
586✔
445

446
const std::shared_ptr<BaseScenarioElement>& DocumentPlugin::baseScenario() const noexcept
83✔
447
{
448
  return m_base;
83✔
449
}
450

451
void DocumentPlugin::playStartState()
1✔
452
{
453
  auto scenar = score::IDocument::try_get<Scenario::ScenarioDocumentModel>(
1✔
454
      this->m_context.document);
1✔
455
  if(!scenar)
1✔
456
    return;
×
457
  auto& sm = scenar->baseScenario().startState();
1✔
458

459
  Engine::score_to_ossia::play_state_from_ui(sm, this->context());
1✔
460
}
1✔
461

462
void DocumentPlugin::playStopState()
11✔
463
{
464
  auto scenar = score::IDocument::try_get<Scenario::ScenarioDocumentModel>(
11✔
465
      this->m_context.document);
11✔
466
  if(!scenar)
11✔
467
    return;
×
468
  auto& sm = scenar->baseScenario().endState();
11✔
469
  Engine::score_to_ossia::play_state_from_ui(sm, this->context());
11✔
470
}
11✔
471

472
bool DocumentPlugin::isPlaying() const
729✔
473
{
474
  if(m_base)
729✔
475
    return m_base->active();
5✔
476
  return false;
724✔
477
}
729✔
478

479
const ExecutionController& DocumentPlugin::executionController() const noexcept
24✔
480
{
481
  return m_context.app.guiApplicationPlugin<Engine::ApplicationPlugin>().execution();
24✔
482
}
483

484
std::shared_ptr<ossia::audio_protocol> DocumentPlugin::audioProto()
22✔
485
{
486
  auto dev = audio_device->sharedDevice();
22✔
487
  auto proto
22✔
488
      = &static_cast<ossia::audio_protocol&>(audio_device->getDevice()->get_protocol());
22✔
489

490
  return std::shared_ptr<ossia::audio_protocol>(dev, proto);
22✔
491
}
22✔
492

493
void DocumentPlugin::runAllCommands() const
967✔
494
{
495
  std::atomic_thread_fence(std::memory_order_seq_cst);
967✔
496
  ExecutionCommand com;
967✔
497
  while(m_ctxData->m_execQueue.try_dequeue(com))
2,137✔
498
    com();
1,170✔
499
}
967✔
500

501
void DocumentPlugin::registerAction(ExecutionAction& act)
586✔
502
{
503
  m_actions.push_back(&act);
586✔
504
}
586✔
505

506
void DocumentPlugin::slot_bench(ossia::bench_map b, int64_t ns)
×
507
{
508
  for(const auto& p : b)
×
509
  {
510
    if(p.second)
×
511
    {
512
      auto proc = m_ctxData->setupContext.proc_map.find(p.first);
×
513
      if(proc != m_ctxData->setupContext.proc_map.end())
×
514
      {
515
        if(proc->second)
×
516
        {
517
          const_cast<Process::ProcessModel*>(proc->second)
×
518
              ->benchmark(100. * *p.second / (double)ns);
×
519
        }
×
520
      }
×
521
    }
×
522
  }
523
}
×
524

525
void DocumentPlugin::on_deviceAdded(Device::DeviceInterface* dev)
1,362✔
526
{
527
  if(auto d = dev->getDevice())
1,362✔
528
  {
529
    connect(
1,298✔
530
        dev, &Device::DeviceInterface::deviceChanged, this,
1,298✔
531
        [this](ossia::net::device_base* old_dev, ossia::net::device_base* new_dev) {
2,687✔
532
      if(old_dev)
1,389✔
533
        unregisterDevice(old_dev);
1,253✔
534
      if(new_dev)
1,389✔
535
        registerDevice(new_dev);
30✔
536
        });
1,389✔
537
    registerDevice(d);
1,298✔
538
  }
1,298✔
539
}
1,362✔
540
}
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