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

ossia / score / 31975605394

16 Aug 2026 10:10PM UTC coverage: 20.35%. First build
31975605394

Pull #2211

github

web-flow
Merge 1defa584c into bb5e533af
Pull Request #2211: project: collect, relink, trim and archive a project's files

1182 of 1996 new or added lines in 43 files covered. (59.22%)

43464 of 213584 relevant lines covered (20.35%)

5552.46 hits per line

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

26.51
/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

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

NEW
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.
NEW
131
  if(!m_root.isEmpty())
×
NEW
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.
NEW
136
  QmlSource next = m_program;
×
NEW
137
  bool changed = false;
×
NEW
138
  const auto relocate = [&](QString& script) {
×
NEW
139
    if(!Process::looksLikeExistingFile(script, ctx))
×
NEW
140
      return;
×
141

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

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

NEW
158
  if(changed)
×
NEW
159
    map.addCommand(new JS::EditScript{*this, next, ctx});
×
NEW
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
#if QT_VERSION >= QT_VERSION_CHECK(6,8,2)
418
  // Bug in older Qt 6 versions:
419
  // QtCore/qmetatype.h:842:23: error: invalid application of 'sizeof' to an incomplete type 'QQuickCloseEvent'
420
  // static_assert(sizeof(T), "Type argument of Q_PROPERTY or Q_DECLARE_METATYPE(T*) must be fully defined");
421
  connect(win, &QQuickWindow::closing, this, cleanup_ui);
422
#endif
423
  connect(win, &QQuickWindow::destroyed, this, cleanup_ui);
×
424
  connect(this, &JS::ProcessModel::uiScriptOk, win, [this, win, &ctx]() mutable {
×
425
    delete m_ui_object;
×
426
    m_ui_object = nullptr;
×
427
    if(!m_ui_component)
×
428
    {
429
      win->close();
×
430
      win->deleteLater();
×
431
      const_cast<QWidget*&>(externalUI) = nullptr;
×
432
      externalUIVisible(false);
×
433
      return;
×
434
    }
435

436
    m_ui_object = createItemForUI(ctx);
×
437
    if(!m_ui_object)
×
438
    {
439
      win->close();
×
440
      win->deleteLater();
×
441
      const_cast<QWidget*&>(externalUI) = nullptr;
×
442
      externalUIVisible(false);
×
443
      return;
×
444
    }
445
    m_ui_object->setParentItem(win->contentItem());
×
446
    m_ui_object->setParent(win->contentItem());
×
447
  });
×
448
  return widg;
×
449
}
×
450

451
void ProcessModel::setExecutionScript(const QString& f)
4✔
452
{
453
  if(f == m_program.execution)
4✔
454
    return;
×
455
  m_program.execution = std::move(f);
4✔
456

457
  executionScriptChanged(m_program.execution);
4✔
458
}
4✔
459

460
void ProcessModel::setUiScript(const QString& f)
4✔
461
{
462
  if(f == m_program.ui)
4✔
463
    return;
4✔
464
  m_program.ui = std::move(f);
×
465

466
  uiScriptChanged(m_program.ui);
×
467
}
4✔
468

469
void ProcessModel::setState(const JSState &s)
2✔
470
{
471
  if(s == m_state)
2✔
472
    return;
2✔
473

474
  {
475
    const auto prev = std::move(m_state);
×
476
    for(auto& [prev_k, prev_v] : prev) {
×
477
      stateElementChanged(prev_k, prev_v);
×
478
    }
479
  }
×
480

481
  m_state = std::move(s);
×
482
  for(auto& [k, v] : m_state) {
×
483
    stateElementChanged(k, v);
×
484
  }
485

486
  stateChanged();
×
487
}
2✔
488

489
void ProcessModel::updateState(const QString &k, const ossia::value& res)
×
490
{
491
  if(auto it = m_state.find(k); it != m_state.end())
×
492
  {
493
    if(res.valid())
×
494
    {
495
      if(res != it->second)
×
496
      {
497
        // Updating a new element
498
        m_state[k] = res;
×
499
        stateElementChanged(k, res);
×
500
        stateChanged();
×
501
      }
×
502
    }
×
503
    else
504
    {
505
      // Removing an element
506
      m_state.erase(k);
×
507
      stateElementChanged(k, res);
×
508
      stateChanged();
×
509
    }
510
  }
×
511
  else
512
  {
513
    if(res.valid())
×
514
    {
515
      // Adding a new element
516
      m_state[k] = res;
×
517
      stateElementChanged(k, res);
×
518
      stateChanged();
×
519
    }
×
520
    else
521
    {
522
      // Already not there, nothing to do
523
    }
524
  }
525
}
×
526

527
[[nodiscard]] Process::ScriptChangeResult ProcessModel::setProgram(const JS::QmlSource& script)
4✔
528
{
529
  setExecutionScript(script.execution);
4✔
530
  setUiScript(script.ui);
4✔
531

532
  Process::ScriptChangeResult res;
4✔
533
  const auto trimmed = script.execution.trimmed();
4✔
534
  const QByteArray data = trimmed.toUtf8();
4✔
535

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

538
  if(QFileInfo::exists(path))
4✔
539
  {
540
    if(res = setQmlData(path.toUtf8(), true); !res.valid)
×
541
      return res;
×
542
  }
×
543
  else
544
  {
545
    if(res = setQmlData(data, false); !res.valid)
4✔
546
      return res;
×
547
  }
548

549
  m_program = script;
4✔
550
  return res;
4✔
551
}
4✔
552

553
Process::ScriptChangeResult ProcessModel::setQmlData(const QByteArray& data, bool isFile)
4✔
554
{
555
  Process::ScriptChangeResult res;
4✔
556
  if(!isFile && !data.contains("import "))
4✔
557
    return res;
×
558

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

566
  auto script = m_cache.getExecution(*this, data, isFile);
4✔
567
  if(!script)
4✔
568
    return res;
×
569

570
  m_isFile = isFile;
4✔
571
  m_qmlData = data;
4✔
572

573
  res.inlets = score::clearAndDeleteLater(m_inlets);
4✔
574
  res.outlets = score::clearAndDeleteLater(m_outlets);
4✔
575
  const bool had_ui = m_ui_component;
4✔
576
  m_ui_component = nullptr;
4✔
577
  delete m_ui_object;
4✔
578
  m_ui_object = nullptr;
4✔
579

580
  SCORE_ASSERT(m_inlets.size() == 0);
4✔
581
  SCORE_ASSERT(m_outlets.size() == 0);
4✔
582

583
  // Check inlets / outlets
584
  {
585
    auto cld_inlet = script->findChildren<Inlet*>();
4✔
586
    int i = 0;
4✔
587
    for(auto n : cld_inlet)
12✔
588
    {
589
      auto port = n->make(Id<Process::Port>(i++), this);
8✔
590
      if(const auto& name = n->objectName(); !name.isEmpty())
16✔
591
        port->setName(name);
8✔
592
      if(auto addr = State::parseAddressAccessor(n->address()))
8✔
593
        port->setAddress(std::move(*addr));
×
594
      m_inlets.push_back(port);
8✔
595
    }
596
  }
4✔
597

598
  {
599
    auto cld_outlet = script->findChildren<Outlet*>();
4✔
600
    int i = 0;
4✔
601
    for(auto n : cld_outlet)
8✔
602
    {
603
      auto port = n->make(Id<Process::Port>(i++), this);
4✔
604
      if(const auto& name = n->objectName(); !name.isEmpty())
8✔
605
        port->setName(name);
4✔
606
      if(auto addr = State::parseAddressAccessor(n->address()))
4✔
607
        port->setAddress(std::move(*addr));
×
608
      m_outlets.push_back(port);
4✔
609
    }
610
  }
4✔
611

612
  // Create ui if any
613
  if(!this->m_program.ui.isEmpty()) {
4✔
614
    m_ui_component = m_cache.getUi(*this, this->m_program.ui.toUtf8(), isFile);
×
615
  }
×
616

617
  if(m_isFile)
4✔
618
  {
619
    const auto name = QFileInfo{data}.baseName();
×
620
    metadata().setName(name);
×
621
    metadata().setLabel(name);
×
622
  }
×
623
  else if(metadata().getName().isEmpty())
4✔
624
  {
625
    metadata().setName(QStringLiteral("Script"));
2✔
626
  }
2✔
627

628
  executionScriptOk();
4✔
629
  res.valid = true;
4✔
630

631
  if(bool(m_ui_component) != had_ui)
4✔
632
    flagsChanged();
×
633

634
  if(m_ui_component)
4✔
635
  {
636
    uiScriptOk();
×
637
  }
×
638
  else if(externalUI)
4✔
639
  {
640
    externalUI->close();
×
641
    externalUI->deleteLater();
×
642
    externalUI = nullptr;
×
643
    externalUIVisible(false);
×
644
  }
×
645

646
  // inlets / outletsChanged : in ScriptEditCommand
647
  return res;
4✔
648
}
4✔
649

650
Script* ProcessModel::currentExecutionObject() const noexcept
×
651
{
652
  if(auto cache = m_cache.tryGet(m_qmlData, m_isFile))
×
653
    return cache->object.get();
×
654
  return nullptr;
×
655
}
×
656

657
bool ProcessModel::isGpu() const noexcept
×
658
{
659
#if defined(SCORE_HAS_GPU_JS)
660
  if(auto script = currentExecutionObject())
661
  {
662
    return
663
        script->findChild<JS::TextureInlet*>() != nullptr
664
           || script->findChild<JS::TextureOutlet*>() != nullptr
665
           // || script->findChild<JS::BufferInlet*>() != nullptr
666
           // || script->findChild<JS::BufferOutlet*>() != nullptr
667
        ;
668
  }
669
#endif
670
  return false;
×
671
}
672

673
ComponentCache::ComponentCache() { }
4✔
674
ComponentCache::~ComponentCache() { }
4✔
675

676
const ComponentCache::Cache* ComponentCache::tryGet(const QByteArray& str, bool isFile) const noexcept
4✔
677
{
678
  QByteArray content;
4✔
679
  QFile f;
4✔
680
  if(isFile)
4✔
681
  {
682
    f.setFileName(str);
×
683
    if(f.open(QIODevice::ReadOnly))
×
684
      content = score::mapAsByteArray(f);
×
685
    else
686
      return nullptr;
×
687
  }
×
688
  else
689
  {
690
    content = str;
4✔
691
  }
692

693
  if(auto it = ossia::find_if(m_map, [&](const auto& k) { return k.key == content; });
4✔
694
     it != m_map.end())
4✔
695
  {
696
    return &*it;
×
697
  }
698
  return nullptr;
4✔
699
}
4✔
700

701
Script* ComponentCache::getExecution(
4✔
702
    const ProcessModel& process, const QByteArray& str, bool isFile) noexcept
703
{
704
  if(auto cache = tryGet(str, isFile))
4✔
705
    return cache->object.get();
×
706

707
  auto& dummyEngine = score::GUIAppContext()
8✔
708
                          .guiApplicationPlugin<JS::ApplicationPlugin>()
4✔
709
                          .m_scriptProcessUIEngine;
4✔
710
  std::unique_ptr<QQmlComponent> comp;
4✔
711
  if(!isFile)
4✔
712
  {
713
    comp = std::make_unique<QQmlComponent>(&dummyEngine);
4✔
714
    loadJSObjectFromString(process.rootPath(), str, *comp, false);
4✔
715
  }
4✔
716
  else
717
  {
718
    comp = std::make_unique<QQmlComponent>(&dummyEngine, QUrl::fromLocalFile(str));
×
719
  }
720

721
  const auto& errs = comp->errors();
4✔
722
  if(!errs.empty())
4✔
723
  {
724
    const auto& err = errs.first();
×
725
    qDebug() << err.line() << err.toString();
×
726
    auto str = err.toString();
×
727
    str.remove("<Unknown File>:");
×
728
    process.errorMessage(/* err.line(), */str);
×
729
    return nullptr;
×
730
  }
×
731

732
  auto obj = comp->create();
4✔
733
  auto script = qobject_cast<JS::Script*>(obj);
4✔
734
  if(script)
4✔
735
  {
736
    if(m_map.size() > 5)
4✔
737
      m_map.erase(m_map.begin());
×
738

739
    m_map.emplace_back(
8✔
740
        Cache{str, std::move(comp), std::unique_ptr<JS::Script>(script)});
4✔
741
    return script;
4✔
742
  }
743
  else
744
  {
745
    process.errorMessage(/* 0, */"The component must be of type Script");
×
746
    if(obj)
×
747
    {
748
      delete obj;
×
749
    }
×
750
    return nullptr;
×
751
  }
752
}
4✔
753

754
QQmlComponent* ComponentCache::getUi(
×
755
    const ProcessModel& process, const QByteArray& str, bool isFile) noexcept
756
{
757
  if(auto cache = tryGet(str, isFile))
×
758
    return cache->component.get();
×
759

760
  auto& dummyEngine = score::GUIAppContext()
×
761
                          .guiApplicationPlugin<JS::ApplicationPlugin>()
×
762
                          .m_scriptProcessUIEngine;
×
763

764
  std::unique_ptr<QQmlComponent> comp;
×
765
  if(!isFile)
×
766
  {
767
    comp = std::make_unique<QQmlComponent>(&dummyEngine);
×
768
    loadJSObjectFromString(process.rootPath(), str, *comp, true);
×
769
  }
×
770
  else
771
  {
772
    comp = std::make_unique<QQmlComponent>(&dummyEngine, QUrl::fromLocalFile(str));
×
773
  }
774

775
  const auto& errs = comp->errors();
×
776
  if(!errs.empty())
×
777
  {
778
    const auto& err = errs.first();
×
779
    qDebug() << err.line() << err.toString();
×
780
    auto str = err.toString();
×
781
    str.remove("<Unknown File>:");
×
782
    process.errorMessage(/* err.line(), */str);
×
783
    return nullptr;
×
784
  }
×
785

786
  auto obj = comp->beginCreate(dummyEngine.rootContext());
×
787
  if(!obj) {
×
788
    process.errorMessage(/* 0, */"Cannot create UI object");
×
789
    return nullptr;
×
790
  }
791
  auto script = qobject_cast<ScriptUI*>(obj);
×
792
  if(script) {
×
793
    script->setProcess((Process::ProcessModel*)&process);
×
794
  }
×
795
  comp->completeCreate();
×
796
  if(script)
×
797
  {
798
    if(m_map.size() > 5)
×
799
      m_map.erase(m_map.begin());
×
800

801
    m_map.emplace_back(
×
802
        Cache{str, std::move(comp), {}});
×
803
    delete script;
×
804
    return m_map.back().component.get();
×
805
  }
806
  else
807
  {
808
    process.errorMessage(/* 0, */"The component must be of type Script");
×
809
    if(obj)
×
810
      delete obj;
×
811
    return nullptr;
×
812
  }
813
}
×
814

815
void ProcessModel::loadPreset(const Process::Preset& preset)
×
816
{
817
  Process::loadScriptProcessPreset<ProcessModel::p_program>(*this, preset);
×
818
}
×
819

820
Process::Preset ProcessModel::savePreset() const noexcept
×
821
{
822
  // FIXME this should save p_program
823
  return Process::saveScriptProcessPreset(*this, this->m_qmlData);
×
824
}
825

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