Loading...
Searching...
No Matches
ExecutionHelpers.hpp
1#pragma once
2#include <JS/Qml/QmlObjects.hpp>
3#include <Library/LibrarySettings.hpp>
4#include <score/application/ApplicationContext.hpp>
5#include <score/application/GUIApplicationContext.hpp>
6
7#include <ossia/detail/logger.hpp>
8#include <ossia-qt/invoke.hpp>
9#include <ossia-qt/qml_engine_functions.hpp>
10#include <JS/ConsolePanel.hpp>
11
12#include <QDir>
13#include <QQmlComponent>
14#include <QQmlEngine>
15#include <QStandardPaths>
16#include <QUrl>
17
18#if __has_include(<boost/hash2/xxh3.hpp>)
19#include <boost/hash2/xxh3.hpp>
20#include <boost/algorithm/hex.hpp>
21#endif
22
23namespace JS
24{
25
26static inline QString hashFileData(const QByteArray& str)
27{
28 QString hexName;
29#if __has_include(<boost/hash2/xxh3.hpp>)
30 boost::hash2::xxh3_128 hasher;
31 hasher.update(str.constData(), str.size());
32 const auto result = hasher.result();
33 std::string hexString;
34 boost::algorithm::hex(result.begin(), result.end(), std::back_inserter(hexString));
35
36 hexName.reserve(32);
37 hexName.push_back("-");
38 hexName.append(hexString.data());
39#endif
40 return hexName;
41}
42
43inline void loadJSObjectFromString(const QByteArray& str, QQmlComponent& comp, bool is_ui)
44{
45 static const auto& lib = score::AppContext().settings<Library::Settings::Model>();
46#if __has_include(<boost/hash2/xxh3.hpp>)
47 static const auto cache_path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
48 QFile f{cache_path + "/Script" + hashFileData(str) + (is_ui ? ".ui.qml" : ".qml")};
49 if(f.open(QIODevice::ReadWrite))
50 {
51 // Only way to make sure we hit the QML cache
52 if(str != f.readAll())
53 {
54 f.resize(0);
55 f.reset();
56 f.write(str);
57 f.flush();
58 f.close();
59 }
60 comp.loadUrl(QUrl::fromLocalFile(f.fileName()));
61 }
62 else
63#endif
64 {
65 QString path = lib.getDefaultLibraryPath() + QDir::separator() + "Scripts"
66 + QDir::separator() + "include" + QDir::separator() + "Script" + hashFileData(str) + ".qml";
67 comp.setData(str, QUrl::fromLocalFile(path));
68 }
69}
70
71inline JS::Script* createJSObject(QQmlComponent& c, QQmlContext* context)
72{
73 const auto& errs = c.errors();
74 if(!errs.empty())
75 {
76 ossia::logger().error(
77 "Uncaught exception at line {} : {}", errs[0].line(),
78 errs[0].toString().toStdString());
79 return nullptr;
80 }
81 else
82 {
83 auto object = c.create(context);
84 auto obj = qobject_cast<JS::Script*>(object);
85 if(obj)
86 return obj;
87 delete object;
88 return nullptr;
89 }
90}
91
92inline JS::Script* createJSObject(const QString& val, QQmlEngine* engine, QQmlContext* context)
93{
94 if(val.trimmed().startsWith("import"))
95 {
96 QQmlComponent c{engine};
97 loadJSObjectFromString(val.toUtf8(), c, false);
98 return createJSObject(c, context);
99 }
100 else if(QFile::exists(val))
101 {
102 QQmlComponent c{engine, QUrl::fromLocalFile(val)};
103 return createJSObject(c, context);
104 }
105 return nullptr;
106}
107
108inline void setupExecFuncs(auto* self, QObject* context, ossia::qt::qml_engine_functions* m_execFuncs)
109{
110 QObject::connect(
111 m_execFuncs, &ossia::qt::qml_engine_functions::system, qApp,
112 [](const QString& code) {
113 std::thread{[code] { ::system(code.toStdString().c_str()); }}.detach();
114 }, Qt::QueuedConnection);
115
116 if(auto* js_panel = score::GUIAppContext().findPanel<JS::PanelDelegate>())
117 {
118 QObject::connect(
119 m_execFuncs, &ossia::qt::qml_engine_functions::exec, js_panel,
120 &JS::PanelDelegate::evaluate, Qt::QueuedConnection);
121
122 QObject::connect(
123 m_execFuncs, &ossia::qt::qml_engine_functions::compute, m_execFuncs,
124 [self, context, m_execFuncs, js_panel](const QString& code, const QString& cbname) {
125 // Exec thread
126
127 // Callback ran in UI thread
128 auto cb = [self
129 , context=QPointer{context}
130 , cur = QPointer{self->m_object}
131 , m_execFuncs
132 , cbname] (const QVariant& v) {
133 if(!self)
134 return;
135
136 // Go back to exec thread, we have to go through the normal engine exec ctx
137 ossia::qt::run_async(m_execFuncs, [self, context, cur, v, cbname] {
138 if(!context || !cur)
139 return;
140 if(self->m_object != cur)
141 return;
142
143 auto mo = self->m_object->metaObject();
144 for(int i = 0; i < mo->methodCount(); i++)
145 {
146 if(mo->method(i).name() == cbname)
147 {
148 mo->method(i).invoke(
149 self->m_object, Qt::DirectConnection, QGenericReturnArgument(),
150 QArgument<QVariant>{"v", v});
151 }
152 }
153 });
154 };
155
156 // Go to ui thread
157 ossia::qt::run_async(js_panel, [js_panel, code, cb]() {
158 js_panel->compute(code, cb); // This invokes cb
159 });
160 }, Qt::DirectConnection);
161 }
162}
163}
Definition ConsolePanel.hpp:40
Definition QmlObjects.hpp:845
Definition LibrarySettings.hpp:46
T & settings() const
Access a specific Settings model instance.
Definition ApplicationContext.hpp:40