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

ossia / score / 32671464175

23 Aug 2026 10:43PM UTC coverage: 23.21% (+1.6%) from 21.606%
32671464175

push

github

jcelerier
scenario: export NodalIntervalView

test_integration_nodal_viewport and test_integration_cable_drag_view_scroll
call into the class from outside the plug-in, so a shared-plugin build could
not link either of them: the library held 136 NodalIntervalView symbols and
exported none. A static-plugin build never sees it, which is why CI does not.

51875 of 223502 relevant lines covered (23.21%)

63063.32 hits per line

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

26.19
/src/plugins/score-plugin-js/JS/JSProcessModel.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

4
#include "JSProcessModel.hpp"
5

6
#include <State/Expression.hpp>
7

8
#include <Process/Dataflow/Port.hpp>
9
#include <Process/ExternalFiles.hpp>
10
#include <Process/PresetHelpers.hpp>
11

12
#include <JS/ApplicationPlugin.hpp>
13
#include <JS/Commands/EditScript.hpp>
14
#include <JS/Executor/ExecutionHelpers.hpp>
15
#include <JS/JSProcessMetadata.hpp>
16
#include <JS/Qml/QmlObjects.hpp>
17
#include <Library/LibrarySettings.hpp>
18

19
#include <score/application/GUIApplicationContext.hpp>
20
#include <score/command/Dispatchers/MultiOngoingCommandDispatcher.hpp>
21
#include <score/command/Dispatchers/SingleOngoingCommandDispatcher.hpp>
22
#include <score/document/DocumentInterface.hpp>
23
#include <score/model/Identifier.hpp>
24
#include <score/serialization/VisitorCommon.hpp>
25
#include <score/tools/DeleteAll.hpp>
26
#include <score/tools/File.hpp>
27

28
#include <core/document/Document.hpp>
29

30
#include <QDebug>
31
#include <QDir>
32
#include <QFileInfo>
33
#include <QFileSystemWatcher>
34
#include <QQmlComponent>
35
#include <QQuickItem>
36
#include <QQuickWindow>
37
#include <QStandardPaths>
38

39
#include <wobjectimpl.h>
40

41
#include <vector>
42
W_OBJECT_IMPL(JS::ProcessModel)
43✔
43
namespace JS
44
{
45
static constexpr const char* default_js_program =
46
    R"_(import Score
47
import QtQuick
48
// This is a minimal example script that showcases the available API.
49
// View the complete documentation at
50
// https://ossia.io/score-docs/processes/javascript.html
51
Script {
52
  ValueInlet { id: in1; objectName: "Value In" }
53
  ValueOutlet { id: out1; objectName: "Value Out" }
54
  FloatSlider { id: sl; min: 10; max: 100; objectName: "Control" }
55

56
  // Called on every tick
57
  tick: function(token, state)
58
  {
59
    if (typeof in1.value !== 'undefined')
60
    {
61
      console.log(in1.value);
62
      out1.value = in1.value * mx + sl.value * my;
63
    }
64
  }
65
})_";
66
static constexpr const char* default_js_ui =
67
    R"_()_";
68

69
ProcessModel::ProcessModel(
2✔
70
    const TimeVal& duration, const QString& data, const Id<Process::ProcessModel>& id,
71
    QObject* parent)
72
    : Process::ProcessModel{
2✔
73
        duration, id, Metadata<ObjectKey_k, ProcessModel>::get(), parent}
2✔
74
{
2✔
75
  if(data.isEmpty())
2✔
76
  {
77
    (void)setProgram({default_js_program, default_js_ui});
2✔
78
  }
2✔
79
  else
80
  {
81
    if(!data.endsWith(".qml")) {
×
82
      (void)setProgram({data, {}});
×
83
    }
×
84
    else {
85
      auto path = data;
×
86
      QFile f{path};
×
87
      m_root = path;
×
88
      QString exec_data;
×
89
      if(f.open(QIODevice::ReadOnly))
×
90
        exec_data = f.readAll();
×
91

92
      path.resize(data.size() - 3);
×
93
      path.append("ui.qml");
×
94
      QString ui_data;
×
95
      if(QFile ui_f{path}; ui_f.open(QIODevice::ReadOnly)) {
×
96
        ui_data = ui_f.readAll();
×
97
      }
×
98
      (void)setProgram({exec_data, ui_data});
×
99
    }
×
100
  }
101

102
  metadata().setInstanceName(*this);
2✔
103
}
×
104

105
Process::ProcessFlags ProcessModel::flags() const noexcept
8✔
106
{
107
  auto flags = Metadata<Process::ProcessFlags_k, JS::ProcessModel>::get();
8✔
108
  if(m_ui_component)
8✔
109
    flags |= Process::ExternalUIAvailable; // FIXME set in every relevant process
×
110
  return flags;
8✔
111
}
112

113
ProcessModel::~ProcessModel()
8✔
114
{
4✔
115
  if(this->externalUI)
4✔
116
  {
117
    this->externalUI->close();
×
118
    this->externalUI = nullptr;
×
119
  }
×
120
}
8✔
121

122
void ProcessModel::mapExternalFiles(Process::ExternalFileMap& map)
×
123
{
124
  Process::ProcessModel::mapExternalFiles(map);
×
125

126
  auto& ctx = score::IDocument::documentContext(*this);
×
127

128
  // The QML import root. Relocating it means collecting a whole include tree,
129
  // which score does not attempt: report it so the user knows the other
130
  // machine needs it.
131
  if(!m_root.isEmpty())
×
132
    map.readOnly(m_root, score::FileKind::Script);
×
133

134
  // A script is stored either inline or as a path to a .qml file; only the
135
  // latter is an external dependency.
136
  QmlSource next = m_program;
×
137
  bool changed = false;
×
138
  const auto relocate = [&](QString& script) {
×
139
    if(!Process::looksLikeExistingFile(script, ctx))
×
140
      return;
×
141

142
    const QString relocated = map.map(
×
143
        {.path = script,
×
144
         .kind = score::FileKind::Script,
145
         .usage = Process::FileUsage::Input,
146
         .directory = false,
147
         .rewritable = true,
148
         .owner = map.owner});
×
149
    if(relocated.isEmpty())
×
150
      return;
×
151

152
    script = relocated;
×
153
    changed = true;
×
154
  };
×
155
  relocate(next.execution);
×
156
  relocate(next.ui);
×
157

158
  if(changed)
×
159
    map.addCommand(new JS::EditScript{*this, next, ctx});
×
160
}
×
161

162
QString ProcessModel::rootPath() const noexcept
4✔
163
{
164
  if(!m_root.isEmpty())
4✔
165
  {
166
    return m_root;
×
167
  }
168
  else
169
  {
170
    static const auto& lib = score::AppContext().settings<Library::Settings::Model>();
4✔
171

172
    return lib.getDefaultLibraryPath() + QDir::separator() + "Scripts"
8✔
173
           + QDir::separator() + "include" + QDir::separator() + "Script/Script.qml";
4✔
174
  }
175
}
4✔
176

177
bool ProcessModel::validate(const std::vector<QString>& script) const noexcept
×
178
{
179
  if(script.empty())
×
180
    return false;
×
181
  if(script[0].isEmpty())
×
182
    return false;
×
183

184
  const auto trimmed = script[0].trimmed();
×
185
  const QByteArray data = trimmed.toUtf8();
×
186

187
  auto path = score::locateFilePath(trimmed, score::IDocument::documentContext(*this));
×
188

189
  if(QFileInfo::exists(path))
×
190
  {
191
    return (bool)m_cache.getExecution(*this, path.toUtf8(), true);
×
192
  }
193
  else
194
  {
195
    if(!data.startsWith("import"))
×
196
      return false;
×
197
    return (bool)m_cache.getExecution(*this, data, false);
×
198
  }
199
}
×
200

201

202
QString ProcessModel::effect() const noexcept
2✔
203
{
204
  return m_qmlData;
2✔
205
}
206

207
QQuickItem* ProcessModel::createItemForUI(const score::DocumentContext& ctx) const noexcept
×
208
{
209
  if(!m_ui_component)
×
210
    return nullptr;
×
211
  auto& dummyEngine =  ctx.app
×
212
                          .guiApplicationPlugin<JS::ApplicationPlugin>()
×
213
                          .m_scriptProcessUIEngine;
×
214

215
  auto obj = m_ui_component->beginCreate(dummyEngine.rootContext());
×
216

217
  if(!obj)
×
218
    return nullptr;
×
219
  auto script = qobject_cast<ScriptUI*>(obj);
×
220
  auto self = const_cast<JS::ProcessModel*>(this);
×
221
  if(script) {
×
222
    script->setProcess(self);
×
223
  }
×
224
  m_ui_component->completeCreate();
×
225

226
  if(!script) {
×
227
    delete obj;
×
228
    return nullptr;
×
229
  }
230

231
  if(const auto& on_exec = script->executionEvent(); on_exec.isCallable())
×
232
  {
233
    connect(this, &JS::ProcessModel::executionToUi,
×
234
            script, [&dummyEngine, on_exec] (const QVariant& v) {
×
235
      on_exec.call({dummyEngine.toScriptValue(v)});
×
236
    });
×
237
  }
×
238

239
  connect(script, &ScriptUI::executionSend, this, [self](const QJSValue& v) {
×
240
    self->uiToExecution(v.toVariant());
×
241
  });
×
242

243
  struct StateUpdater
244
  {
245
    const score::DocumentContext& ctx;
246
    JS::ProcessModel& self;
247
    std::unique_ptr<MultiOngoingCommandDispatcher> disp;
248
    int count = 0;
249

250
    void beginUpdateState(const QString& name)
×
251
    {
252
      if(count > 0) {
×
253
        count++;
×
254
      }
×
255
      else {
256
        disp = std::make_unique<MultiOngoingCommandDispatcher>(ctx.commandStack);
×
257
        count = 1;
×
258
      }
259
    }
×
260

261
    void endUpdateState()
×
262
    {
263
      if(!disp)
×
264
        return;
×
265
      count--;
×
266
      if(count > 0)
×
267
        return;
×
268

269
      disp->commit<JS::UpdateStateMacro>();
×
270
      disp.reset();
×
271
      count = 0;
×
272
    }
×
273

274
    void updateState(const QString& k, const QJSValue& v)
×
275
    {
276
      beginUpdateState("Update");
×
277

278
      disp->submit<JS::UpdateStateElement>(self, k, ossia::qt::value_from_js(v));
×
279

280
      endUpdateState();
×
281
    }
×
282

283
    void cancelUpdateState()
×
284
    {
285
      if(!disp)
×
286
        return;
×
287

288
      disp->rollback();
×
289
      disp.reset();
×
290
      count = 0;
×
291
    }
×
292

293
    void clearState()
×
294
    {
295
      beginUpdateState("Clear");
×
296

297
      disp->submit<JS::ReplaceState>(self, JS::JSState{});
×
298

299
      endUpdateState();
×
300
    }
×
301

302
    void replaceState(const QJSValue& v)
×
303
    {
304
      beginUpdateState("Replace");
×
305

306
      JS::JSState cur;
×
307
      auto var = v.toVariant().toMap();
×
308
      for(auto it = var.constBegin(); it != var.constEnd(); ++it) {
×
309
        if(it.value().isValid()) {
×
310
          cur.insert_or_assign(it.key(), ossia::qt::qt_to_ossia{}(it.value()));
×
311
        }
×
312
      }
×
313
      disp->submit<JS::ReplaceState>(self, std::move(cur));
×
314

315
      endUpdateState();
×
316
    }
×
317
  };
318
  auto updater = std::make_shared<StateUpdater>(StateUpdater{ctx, *self});
×
319

320
  connect(script, &ScriptUI::beginUpdateState,
×
321
          this, [updater] (const QString& name) {
×
322
    updater->beginUpdateState(name);
×
323
  });
×
324
  connect(script, &ScriptUI::updateState,
×
325
          this, [updater] (const QString& name, const QJSValue& v) {
×
326
    updater->updateState(name, v);
×
327
  });
×
328
  connect(script, &ScriptUI::endUpdateState,
×
329
          this, [updater] () {
×
330
    updater->endUpdateState();
×
331
  });
×
332
  connect(script, &ScriptUI::cancelUpdateState,
×
333
          this, [updater] () {
×
334
    updater->cancelUpdateState();
×
335
  });
×
336
  connect(script, &ScriptUI::clearState,
×
337
          this, [updater] () {
×
338
    updater->clearState();
×
339
  });
×
340
  connect(script, &ScriptUI::replaceState,
×
341
          this, [updater] (const QJSValue& v) {
×
342
    updater->replaceState(v);
×
343
  });
×
344

345
  if(const auto& on_stateUpdated = script->stateUpdated(); on_stateUpdated.isCallable())
×
346
  {
347
    connect(
×
348
        this, &JS::ProcessModel::stateElementChanged, script,
×
349
        [on_stateUpdated, &dummyEngine](const QString& k, const ossia::value& v) {
×
350
      if(v.valid())
×
351
      {
352
        if(auto res = v.apply(ossia::qt::ossia_to_qvariant{}); res.isValid())
×
353
          on_stateUpdated.call({k, dummyEngine.toScriptValue(res)});
×
354
        else
355
          on_stateUpdated.call({k, QJSValue{}});
×
356
      }
×
357
      else
358
        on_stateUpdated.call({k, QJSValue{}});
×
359
    }, Qt::QueuedConnection);
×
360
  }
×
361

362
  if(const auto& on_load = script->loadState(); on_load.isCallable())
×
363
  {
364
    QVariantMap vm;
×
365
    for(auto& [k, v]: this->m_state) {
×
366
      if(auto res = v.apply(ossia::qt::ossia_to_qvariant{}); res.isValid())
×
367
        vm[k] = std::move(res);
×
368
    }
369
    on_load.call({dummyEngine.toScriptValue(vm)});
×
370
  }
×
371

372
  return script;
×
373
}
×
374

375
QWidget* ProcessModel::createWindowForUI(const score::DocumentContext& ctx,
×
376
                                         QWidget* parent) const noexcept
377
{
378
  m_ui_object = createItemForUI(ctx);
×
379
  if(!m_ui_object)
×
380
    return nullptr;
×
381

382
  auto win = new QQuickWindow{};
×
383
  // QWidget gets these from QWidgetPrivate::adjustFlags; a bare QQuickWindow
384
  // does not, and on platforms where Qt draws the chrome itself (wasm) that
385
  // leaves the window with no title bar, close or minimise button.
386
  win->setFlags(
×
387
      win->flags() | Qt::Window | Qt::WindowTitleHint | Qt::WindowSystemMenuHint
×
388
      | Qt::WindowCloseButtonHint | Qt::WindowMinimizeButtonHint
×
389
      | Qt::WindowMaximizeButtonHint);
×
390
#if defined(__EMSCRIPTEN__)
391
  // Qt for wasm reports ShowIsFullScreen unconditionally, so QWindow::show()
392
  // turns into showFullScreen() for every top level: the requested size is
393
  // discarded and the full-screen state suppresses the frame. Only Qt::Dialog
394
  // and Qt::Popup opt out (QWasmIntegration::defaultWindowState).
395
  win->setFlags(win->flags() | Qt::Dialog);
396
#endif
397
  win->setWidth(640);
×
398
  win->setHeight(640);
×
399

400
  m_ui_object->setParentItem(win->contentItem());
×
401

402
  const auto cleanup_ui = [this] {
×
403
    delete m_ui_object;
×
404
    m_ui_object = nullptr;
×
405

406
    const_cast<QWidget*&>(externalUI) = nullptr;
×
407
    externalUIVisible(false);
×
408
  };
×
409

410
  auto widg = QWidget::createWindowContainer(win, parent);
×
411
  if(!widg) {
×
412
    cleanup_ui();
×
413
    return nullptr;
×
414
  }
415
  widg->setAttribute(Qt::WA_DeleteOnClose);
×
416

417
  // The container widget does not follow the QQuickWindow's size: size it
418
  // explicitly, letting the ScriptUI root ask for a size through its implicit
419
  // width / height (e.g. `implicitWidth: 1280`), defaulting to 640x640.
420
  {
421
    int w = 640, h = 640;
×
422
    if(m_ui_object->implicitWidth() >= 100.)
×
423
      w = static_cast<int>(m_ui_object->implicitWidth());
×
424
    if(m_ui_object->implicitHeight() >= 100.)
×
425
      h = static_cast<int>(m_ui_object->implicitHeight());
×
426
    widg->resize(w, h);
×
427
  }
428

429
#if QT_VERSION >= QT_VERSION_CHECK(6,8,2)
430
  // Bug in older Qt 6 versions:
431
  // QtCore/qmetatype.h:842:23: error: invalid application of 'sizeof' to an incomplete type 'QQuickCloseEvent'
432
  // static_assert(sizeof(T), "Type argument of Q_PROPERTY or Q_DECLARE_METATYPE(T*) must be fully defined");
433
  connect(win, &QQuickWindow::closing, this, cleanup_ui);
434
#endif
435
  connect(win, &QQuickWindow::destroyed, this, cleanup_ui);
×
436
  connect(this, &JS::ProcessModel::uiScriptOk, win, [this, win, &ctx]() mutable {
×
437
    delete m_ui_object;
×
438
    m_ui_object = nullptr;
×
439
    if(!m_ui_component)
×
440
    {
441
      win->close();
×
442
      win->deleteLater();
×
443
      const_cast<QWidget*&>(externalUI) = nullptr;
×
444
      externalUIVisible(false);
×
445
      return;
×
446
    }
447

448
    m_ui_object = createItemForUI(ctx);
×
449
    if(!m_ui_object)
×
450
    {
451
      win->close();
×
452
      win->deleteLater();
×
453
      const_cast<QWidget*&>(externalUI) = nullptr;
×
454
      externalUIVisible(false);
×
455
      return;
×
456
    }
457
    m_ui_object->setParentItem(win->contentItem());
×
458
    m_ui_object->setParent(win->contentItem());
×
459
  });
×
460
  return widg;
×
461
}
×
462

463
void ProcessModel::setExecutionScript(const QString& f)
4✔
464
{
465
  if(f == m_program.execution)
4✔
466
    return;
×
467
  m_program.execution = std::move(f);
4✔
468

469
  executionScriptChanged(m_program.execution);
4✔
470
}
4✔
471

472
void ProcessModel::setUiScript(const QString& f)
4✔
473
{
474
  if(f == m_program.ui)
4✔
475
    return;
4✔
476
  m_program.ui = std::move(f);
×
477

478
  uiScriptChanged(m_program.ui);
×
479
}
4✔
480

481
void ProcessModel::setState(const JSState &s)
2✔
482
{
483
  if(s == m_state)
2✔
484
    return;
2✔
485

486
  {
487
    const auto prev = std::move(m_state);
×
488
    for(auto& [prev_k, prev_v] : prev) {
×
489
      stateElementChanged(prev_k, prev_v);
×
490
    }
491
  }
×
492

493
  m_state = std::move(s);
×
494
  for(auto& [k, v] : m_state) {
×
495
    stateElementChanged(k, v);
×
496
  }
497

498
  stateChanged();
×
499
}
2✔
500

501
void ProcessModel::updateState(const QString &k, const ossia::value& res)
×
502
{
503
  if(auto it = m_state.find(k); it != m_state.end())
×
504
  {
505
    if(res.valid())
×
506
    {
507
      if(res != it->second)
×
508
      {
509
        // Updating a new element
510
        m_state[k] = res;
×
511
        stateElementChanged(k, res);
×
512
        stateChanged();
×
513
      }
×
514
    }
×
515
    else
516
    {
517
      // Removing an element
518
      m_state.erase(k);
×
519
      stateElementChanged(k, res);
×
520
      stateChanged();
×
521
    }
522
  }
×
523
  else
524
  {
525
    if(res.valid())
×
526
    {
527
      // Adding a new element
528
      m_state[k] = res;
×
529
      stateElementChanged(k, res);
×
530
      stateChanged();
×
531
    }
×
532
    else
533
    {
534
      // Already not there, nothing to do
535
    }
536
  }
537
}
×
538

539
[[nodiscard]] Process::ScriptChangeResult ProcessModel::setProgram(const JS::QmlSource& script)
4✔
540
{
541
  setExecutionScript(script.execution);
4✔
542
  setUiScript(script.ui);
4✔
543

544
  Process::ScriptChangeResult res;
4✔
545
  const auto trimmed = script.execution.trimmed();
4✔
546
  const QByteArray data = trimmed.toUtf8();
4✔
547

548
  auto path = score::locateFilePath(trimmed, score::IDocument::documentContext(*this));
4✔
549

550
  if(QFileInfo::exists(path))
4✔
551
  {
552
    if(res = setQmlData(path.toUtf8(), true); !res.valid)
×
553
      return res;
×
554
  }
×
555
  else
556
  {
557
    if(res = setQmlData(data, false); !res.valid)
4✔
558
      return res;
×
559
  }
560

561
  m_program = script;
4✔
562
  return res;
4✔
563
}
4✔
564

565
Process::ScriptChangeResult ProcessModel::setQmlData(const QByteArray& data, bool isFile)
4✔
566
{
567
  Process::ScriptChangeResult res;
4✔
568
  if(!isFile && !data.contains("import "))
4✔
569
    return res;
×
570

571
  // When loading inline scripts, pre-create all cache files before loading any QML.
572
  // Qt's QQmlTypeLoader caches directory listings on first access; if the UI cache file
573
  // doesn't exist yet when the execution script triggers that scan, it gets flagged
574
  // as "File name case mismatch" when loaded later.
575
  if(!isFile && !this->m_program.ui.isEmpty())
4✔
576
    ensureJSCacheFile(this->m_program.ui.toUtf8(), true);
×
577

578
  auto script = m_cache.getExecution(*this, data, isFile);
4✔
579
  if(!script)
4✔
580
    return res;
×
581

582
  m_isFile = isFile;
4✔
583
  m_qmlData = data;
4✔
584

585
  res.inlets = score::clearAndDeleteLater(m_inlets);
4✔
586
  res.outlets = score::clearAndDeleteLater(m_outlets);
4✔
587
  const bool had_ui = m_ui_component;
4✔
588
  m_ui_component = nullptr;
4✔
589
  delete m_ui_object;
4✔
590
  m_ui_object = nullptr;
4✔
591

592
  SCORE_ASSERT(m_inlets.size() == 0);
4✔
593
  SCORE_ASSERT(m_outlets.size() == 0);
4✔
594

595
  // Check inlets / outlets
596
  {
597
    auto cld_inlet = script->findChildren<Inlet*>();
4✔
598
    int i = 0;
4✔
599
    for(auto n : cld_inlet)
12✔
600
    {
601
      auto port = n->make(Id<Process::Port>(i++), this);
8✔
602
      if(const auto& name = n->objectName(); !name.isEmpty())
16✔
603
        port->setName(name);
8✔
604
      if(auto addr = State::parseAddressAccessor(n->address()))
8✔
605
        port->setAddress(std::move(*addr));
×
606
      m_inlets.push_back(port);
8✔
607
    }
608
  }
4✔
609

610
  {
611
    auto cld_outlet = script->findChildren<Outlet*>();
4✔
612
    int i = 0;
4✔
613
    for(auto n : cld_outlet)
8✔
614
    {
615
      auto port = n->make(Id<Process::Port>(i++), this);
4✔
616
      if(const auto& name = n->objectName(); !name.isEmpty())
8✔
617
        port->setName(name);
4✔
618
      if(auto addr = State::parseAddressAccessor(n->address()))
4✔
619
        port->setAddress(std::move(*addr));
×
620
      m_outlets.push_back(port);
4✔
621
    }
622
  }
4✔
623

624
  // Create ui if any
625
  if(!this->m_program.ui.isEmpty()) {
4✔
626
    m_ui_component = m_cache.getUi(*this, this->m_program.ui.toUtf8(), isFile);
×
627
  }
×
628

629
  if(m_isFile)
4✔
630
  {
631
    const auto name = QFileInfo{data}.baseName();
×
632
    metadata().setName(name);
×
633
    metadata().setLabel(name);
×
634
  }
×
635
  else if(metadata().getName().isEmpty())
4✔
636
  {
637
    metadata().setName(QStringLiteral("Script"));
2✔
638
  }
2✔
639

640
  executionScriptOk();
4✔
641
  res.valid = true;
4✔
642

643
  if(bool(m_ui_component) != had_ui)
4✔
644
    flagsChanged();
×
645

646
  if(m_ui_component)
4✔
647
  {
648
    uiScriptOk();
×
649
  }
×
650
  else if(externalUI)
4✔
651
  {
652
    externalUI->close();
×
653
    externalUI->deleteLater();
×
654
    externalUI = nullptr;
×
655
    externalUIVisible(false);
×
656
  }
×
657

658
  // inlets / outletsChanged : in ScriptEditCommand
659
  return res;
4✔
660
}
4✔
661

662
Script* ProcessModel::currentExecutionObject() const noexcept
×
663
{
664
  if(auto cache = m_cache.tryGet(m_qmlData, m_isFile))
×
665
    return cache->object.get();
×
666
  return nullptr;
×
667
}
×
668

669
bool ProcessModel::isGpu() const noexcept
×
670
{
671
#if defined(SCORE_HAS_GPU_JS)
672
  if(auto script = currentExecutionObject())
673
  {
674
    return
675
        script->findChild<JS::TextureInlet*>() != nullptr
676
           || script->findChild<JS::TextureOutlet*>() != nullptr
677
           // || script->findChild<JS::BufferInlet*>() != nullptr
678
           // || script->findChild<JS::BufferOutlet*>() != nullptr
679
        ;
680
  }
681
#endif
682
  return false;
×
683
}
684

685
ComponentCache::ComponentCache() { }
4✔
686
ComponentCache::~ComponentCache() { }
4✔
687

688
const ComponentCache::Cache* ComponentCache::tryGet(const QByteArray& str, bool isFile) const noexcept
4✔
689
{
690
  QByteArray content;
4✔
691
  QFile f;
4✔
692
  if(isFile)
4✔
693
  {
694
    f.setFileName(str);
×
695
    if(f.open(QIODevice::ReadOnly))
×
696
      content = score::mapAsByteArray(f);
×
697
    else
698
      return nullptr;
×
699
  }
×
700
  else
701
  {
702
    content = str;
4✔
703
  }
704

705
  if(auto it = ossia::find_if(m_map, [&](const auto& k) { return k.key == content; });
4✔
706
     it != m_map.end())
4✔
707
  {
708
    return &*it;
×
709
  }
710
  return nullptr;
4✔
711
}
4✔
712

713
Script* ComponentCache::getExecution(
4✔
714
    const ProcessModel& process, const QByteArray& str, bool isFile) noexcept
715
{
716
  if(auto cache = tryGet(str, isFile))
4✔
717
    return cache->object.get();
×
718

719
  auto& dummyEngine = score::GUIAppContext()
8✔
720
                          .guiApplicationPlugin<JS::ApplicationPlugin>()
4✔
721
                          .m_scriptProcessUIEngine;
4✔
722
  std::unique_ptr<QQmlComponent> comp;
4✔
723
  if(!isFile)
4✔
724
  {
725
    comp = std::make_unique<QQmlComponent>(&dummyEngine);
4✔
726
    loadJSObjectFromString(process.rootPath(), str, *comp, false);
4✔
727
  }
4✔
728
  else
729
  {
730
    comp = std::make_unique<QQmlComponent>(&dummyEngine, QUrl::fromLocalFile(str));
×
731
  }
732

733
  const auto& errs = comp->errors();
4✔
734
  if(!errs.empty())
4✔
735
  {
736
    const auto& err = errs.first();
×
737
    qDebug() << err.line() << err.toString();
×
738
    auto str = err.toString();
×
739
    str.remove("<Unknown File>:");
×
740
    process.errorMessage(/* err.line(), */str);
×
741
    return nullptr;
×
742
  }
×
743

744
  auto obj = comp->create();
4✔
745
  auto script = qobject_cast<JS::Script*>(obj);
4✔
746
  if(script)
4✔
747
  {
748
    if(m_map.size() > 5)
4✔
749
      m_map.erase(m_map.begin());
×
750

751
    m_map.emplace_back(
8✔
752
        Cache{str, std::move(comp), std::unique_ptr<JS::Script>(script)});
4✔
753
    return script;
4✔
754
  }
755
  else
756
  {
757
    process.errorMessage(/* 0, */"The component must be of type Script");
×
758
    if(obj)
×
759
    {
760
      delete obj;
×
761
    }
×
762
    return nullptr;
×
763
  }
764
}
4✔
765

766
QQmlComponent* ComponentCache::getUi(
×
767
    const ProcessModel& process, const QByteArray& str, bool isFile) noexcept
768
{
769
  if(auto cache = tryGet(str, isFile))
×
770
    return cache->component.get();
×
771

772
  auto& dummyEngine = score::GUIAppContext()
×
773
                          .guiApplicationPlugin<JS::ApplicationPlugin>()
×
774
                          .m_scriptProcessUIEngine;
×
775

776
  std::unique_ptr<QQmlComponent> comp;
×
777
  if(!isFile)
×
778
  {
779
    comp = std::make_unique<QQmlComponent>(&dummyEngine);
×
780
    loadJSObjectFromString(process.rootPath(), str, *comp, true);
×
781
  }
×
782
  else
783
  {
784
    comp = std::make_unique<QQmlComponent>(&dummyEngine, QUrl::fromLocalFile(str));
×
785
  }
786

787
  const auto& errs = comp->errors();
×
788
  if(!errs.empty())
×
789
  {
790
    const auto& err = errs.first();
×
791
    qDebug() << err.line() << err.toString();
×
792
    auto str = err.toString();
×
793
    str.remove("<Unknown File>:");
×
794
    process.errorMessage(/* err.line(), */str);
×
795
    return nullptr;
×
796
  }
×
797

798
  auto obj = comp->beginCreate(dummyEngine.rootContext());
×
799
  if(!obj) {
×
800
    process.errorMessage(/* 0, */"Cannot create UI object");
×
801
    return nullptr;
×
802
  }
803
  auto script = qobject_cast<ScriptUI*>(obj);
×
804
  if(script) {
×
805
    script->setProcess((Process::ProcessModel*)&process);
×
806
  }
×
807
  comp->completeCreate();
×
808
  if(script)
×
809
  {
810
    if(m_map.size() > 5)
×
811
      m_map.erase(m_map.begin());
×
812

813
    m_map.emplace_back(
×
814
        Cache{str, std::move(comp), {}});
×
815
    delete script;
×
816
    return m_map.back().component.get();
×
817
  }
818
  else
819
  {
820
    process.errorMessage(/* 0, */"The component must be of type Script");
×
821
    if(obj)
×
822
      delete obj;
×
823
    return nullptr;
×
824
  }
825
}
×
826

827
void ProcessModel::loadPreset(const Process::Preset& preset)
×
828
{
829
  Process::loadScriptProcessPreset<ProcessModel::p_program>(*this, preset);
×
830
}
×
831

832
Process::Preset ProcessModel::savePreset() const noexcept
×
833
{
834
  // FIXME this should save p_program
835
  return Process::saveScriptProcessPreset(*this, this->m_qmlData);
×
836
}
837

838
}
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