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

ossia / score / 36098644919

25 Sep 2026 05:27AM UTC coverage: 42.234% (+0.004%) from 42.23%
36098644919

Pull #2305

github

web-flow
Merge 5f5eefee5 into c0c64ec39
Pull Request #2305: Texture outlets, presets while playing, headless end of playback

46 of 99 new or added lines in 14 files covered. (46.46%)

165 existing lines in 15 files now uncovered.

125807 of 297879 relevant lines covered (42.23%)

116193.83 hits per line

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

79.71
/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)
8,075✔
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✔
NEW
105
    auto& app = context().doc.app;
×
106
    // The transport actions only exist with a GUI: looking up Actions::Stop
107
    // headless (--no-gui) threw out_of_range and terminated the application.
NEW
108
    if(app.applicationSettings.gui)
×
NEW
109
      app.actions.action<Actions::Stop>().action()->trigger();
×
110
    else
NEW
111
      app.guiApplicationPlugin<Engine::ApplicationPlugin>().execution().request_stop();
×
UNCOV
112
      },
×
113
      Qt::QueuedConnection);
114
}
51✔
115

116
DocumentPlugin::~DocumentPlugin()
1,310✔
117
{
655✔
118
  if(m_base)
655✔
119
  {
120
    if(m_base->active())
11✔
121
    {
122
      m_base->baseInterval().stop();
×
123
      clear();
×
124
    }
×
125
  }
11✔
126

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

139
      devs->list().setAudioDevice(nullptr);
655✔
140
    }
655✔
141
  }
655✔
142
  if(audio_device)
655✔
143
    delete audio_device;
655✔
144
  if(m_ctxData)
655✔
145
  {
146
    m_ctxData->context.alias.reset();
655✔
147
  }
655✔
148
}
1,310✔
149

150
void DocumentPlugin::processEditCommands()
297,100✔
151
{
152
  ExecutionCommand cmd;
297,100✔
153
  GCCommand gc;
297,100✔
154
  bool ok = false;
297,100✔
155
  bool gc_ok = false;
297,100✔
156
  do
297,100✔
157
  {
158
    if((ok = m_ctxData->m_editionQueue.try_dequeue(cmd)))
306,266✔
159
      cmd();
7,779✔
160

161
    if((gc_ok = m_ctxData->m_gcQueue.try_dequeue(gc)))
306,266✔
162
      gc();
1,641✔
163
  } while(ok || gc_ok);
306,266✔
164
}
297,100✔
165

166
void DocumentPlugin::on_finished()
11✔
167
{
168
  if(m_tid != -1)
11✔
169
  {
170
    killTimer(m_tid);
11✔
171
    m_tid = -1;
11✔
172
  }
11✔
173

174
  processEditCommands();
11✔
175
  clear();
11✔
176

177
  initExecState();
11✔
178

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

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

204
void DocumentPlugin::timerEvent(QTimerEvent* event)
1,289✔
205
{
206
  processEditCommands();
1,289✔
207
}
1,289✔
208

209
void DocumentPlugin::registerDevice(ossia::net::device_base* d)
1,507✔
210
{
211
  if(!d)
1,507✔
212
    return;
10✔
213
  if(m_ctxData->execState)
1,497✔
214
  {
215
    m_ctxData->execState->register_device(d);
1,372✔
216

217
    if(m_base && m_base->active())
1,372✔
218
      d->get_protocol().start_execution();
1✔
219
  }
1,372✔
220
}
1,507✔
221

222
void DocumentPlugin::unregisterDevice(ossia::net::device_base* d)
1,417✔
223
{
224
  if(!m_ctxData->execState)
1,417✔
225
    return;
760✔
226

227
  m_ctxData->execState->unregister_device(d);
657✔
228

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

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

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

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

264
  static const Execution::Settings::SchedulingPolicies sched_t;
706✔
265
  static const Execution::Settings::OrderingPolicies order_t;
706✔
266
  static const Execution::Settings::MergingPolicies merge_t;
706✔
267

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

272
  auto sched = settings.getScheduling();
706✔
273

274
  auto& execGraph = m_ctxData->execGraph;
706✔
275
  auto& execState = m_ctxData->execState;
706✔
276
  auto& bench = m_ctxData->bench;
706✔
277

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

285
  execState.reset();
706✔
286

287
  initExecState();
706✔
288

289
  execState->bufferSize = audiosettings.getBufferSize();
706✔
290
  execState->sampleRate = audiosettings.getRate();
706✔
291

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

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

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

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

332
  opt.scheduling = ossia::graph_setup_options::StaticFixed;
706✔
333
  execGraph = ossia::make_graph(opt);
706✔
334
}
706✔
335

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

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

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

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

361
  makeGraph();
51✔
362

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

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

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

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

388
  m_tid = startTimer(32);
51✔
389
}
51✔
390

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

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

426
  auto& model = this->m_context.model<Scenario::ScenarioDocumentModel>();
1,479✔
427
  model.cables.mutable_added.connect<&SetupContext::on_cableCreated>(
2,958✔
428
      m_ctxData->setupContext);
1,479✔
429
  model.cables.removing.connect<&SetupContext::on_cableRemoved>(m_ctxData->setupContext);
1,479✔
430

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

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

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

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

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

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

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

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

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

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

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

507
void DocumentPlugin::registerAction(ExecutionAction& act)
655✔
508
{
509
  m_actions.push_back(&act);
655✔
510
}
655✔
511

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

531
void DocumentPlugin::on_deviceAdded(Device::DeviceInterface* dev)
1,500✔
532
{
533
  if(auto d = dev->getDevice())
1,500✔
534
  {
535
    connect(
1,436✔
536
        dev, &Device::DeviceInterface::deviceChanged, this,
1,436✔
537
        [this](ossia::net::device_base* old_dev, ossia::net::device_base* new_dev) {
2,964✔
538
      if(old_dev)
1,528✔
539
        unregisterDevice(old_dev);
1,391✔
540
      if(new_dev)
1,528✔
541
        registerDevice(new_dev);
31✔
542
        });
1,528✔
543
    registerDevice(d);
1,436✔
544
  }
1,436✔
545
}
1,500✔
546
}
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