History

Moving running scripts functionality to scripting library

Brad Davis committed 10 years ago

Commit d28f80a1 parent ddd9b0820 files · +664 −569Browse files

Showing the changes to libraries/script-engine/src/ScriptEngines.cpp (1 of 20 files). Show all

Unified · Split

libraries/script-engine/src/ScriptEngines.cpp+434−0
@@ -0,0 +1,434 @@
1+//
2+// Created by Bradley Austin Davis on 2016/01/08
3+// Copyright 2013-2016 High Fidelity, Inc.
4+//
5+// Distributed under the Apache License, Version 2.0.
6+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
7+//
8+
9+#include "ScriptEngines.h"
10+
11+#include <QtCore/QStandardPaths>
12+#include <QtCore/QCoreApplication>
13+
14+#include <SettingHandle.h>
15+#include <UserActivityLogger.h>
16+
17+#include "ScriptEngine.h"
18+#include "ScriptEngineLogging.h"
19+
20+#define __STR2__(x) #x
21+#define __STR1__(x) __STR2__(x)
22+#define __LOC__ __FILE__ "("__STR1__(__LINE__)") : Warning Msg: "
23+
24+#ifndef __APPLE__
25+static const QString DESKTOP_LOCATION = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
26+#else
27+// Temporary fix to Qt bug: http://stackoverflow.com/questions/16194475
28+static const QString DESKTOP_LOCATION = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation).append("/script.js");
29+#endif
30+
31+ScriptsModel& getScriptsModel() {
32+ static ScriptsModel scriptsModel;
33+ return scriptsModel;
34+}
35+
36+ScriptEngines::ScriptEngines()
37+ : _scriptsLocationHandle("scriptsLocation", DESKTOP_LOCATION),
38+ _previousScriptLocation("LastScriptLocation", DESKTOP_LOCATION){
39+ _scriptsModelFilter.setSourceModel(&_scriptsModel);
40+ _scriptsModelFilter.sort(0, Qt::AscendingOrder);
41+ _scriptsModelFilter.setDynamicSortFilter(true);
42+}
43+
44+
45+QObject* scriptsModel();
46+
47+void ScriptEngines::registerScriptInitializer(ScriptInitializer initializer) {
48+ _scriptInitializers.push_back(initializer);
49+}
50+
51+void ScriptEngines::addScriptEngine(ScriptEngine* engine) {
52+ _allScriptsMutex.lock();
53+ _allKnownScriptEngines.insert(engine);
54+ _allScriptsMutex.unlock();
55+}
56+
57+void ScriptEngines::removeScriptEngine(ScriptEngine* engine) {
58+ // If we're not already in the middle of stopping all scripts, then we should remove ourselves
59+ // from the list of running scripts. We don't do this if we're in the process of stopping all scripts
60+ // because that method removes scripts from its list as it iterates them
61+ if (!_stoppingAllScripts) {
62+ _allScriptsMutex.lock();
63+ _allKnownScriptEngines.remove(engine);
64+ _allScriptsMutex.unlock();
65+ }
66+}
67+
68+void ScriptEngines::shutdownScripting() {
69+ _allScriptsMutex.lock();
70+ _stoppingAllScripts = true;
71+ ScriptEngine::_stoppingAllScripts = true;
72+ qCDebug(scriptengine) << "Stopping all scripts.... currently known scripts:" << _allKnownScriptEngines.size();
73+
74+ QMutableSetIterator<ScriptEngine*> i(_allKnownScriptEngines);
75+ while (i.hasNext()) {
76+ ScriptEngine* scriptEngine = i.next();
77+ QString scriptName = scriptEngine->getFilename();
78+
79+ // NOTE: typically all script engines are running. But there's at least one known exception to this, the
80+ // "entities sandbox" which is only used to evaluate entities scripts to test their validity before using
81+ // them. We don't need to stop scripts that aren't running.
82+ if (scriptEngine->isRunning()) {
83+
84+ // If the script is running, but still evaluating then we need to wait for its evaluation step to
85+ // complete. After that we can handle the stop process appropriately
86+ if (scriptEngine->evaluatePending()) {
87+ while (scriptEngine->evaluatePending()) {
88+
89+ // This event loop allows any started, but not yet finished evaluate() calls to complete
90+ // we need to let these complete so that we can be guaranteed that the script engine isn't
91+ // in a partially setup state, which can confuse our shutdown unwinding.
92+ QEventLoop loop;
93+ QObject::connect(scriptEngine, &ScriptEngine::evaluationFinished, &loop, &QEventLoop::quit);
94+ loop.exec();
95+ }
96+ }
97+
98+ // We disconnect any script engine signals from the application because we don't want to do any
99+ // extra stopScript/loadScript processing that the Application normally does when scripts start
100+ // and stop. We can safely short circuit this because we know we're in the "quitting" process
101+ scriptEngine->disconnect(this);
102+
103+ // Calling stop on the script engine will set it's internal _isFinished state to true, and result
104+ // in the ScriptEngine gracefully ending it's run() method.
105+ scriptEngine->stop();
106+
107+ // We need to wait for the engine to be done running before we proceed, because we don't
108+ // want any of the scripts final "scriptEnding()" or pending "update()" methods from accessing
109+ // any application state after we leave this stopAllScripts() method
110+ qCDebug(scriptengine) << "waiting on script:" << scriptName;
111+ scriptEngine->waitTillDoneRunning();
112+ qCDebug(scriptengine) << "done waiting on script:" << scriptName;
113+
114+ // If the script is stopped, we can remove it from our set
115+ i.remove();
116+ }
117+ }
118+ _stoppingAllScripts = false;
119+ _allScriptsMutex.unlock();
120+ qCDebug(scriptengine) << "DONE Stopping all scripts....";
121+}
122+
123+QVariantList getPublicChildNodes(TreeNodeFolder* parent) {
124+ QVariantList result;
125+ QList<TreeNodeBase*> treeNodes = getScriptsModel().getFolderNodes(parent);
126+ for (int i = 0; i < treeNodes.size(); i++) {
127+ TreeNodeBase* node = treeNodes.at(i);
128+ if (node->getType() == TREE_NODE_TYPE_FOLDER) {
129+ TreeNodeFolder* folder = static_cast<TreeNodeFolder*>(node);
130+ QVariantMap resultNode;
131+ resultNode.insert("name", node->getName());
132+ resultNode.insert("type", "folder");
133+ resultNode.insert("children", getPublicChildNodes(folder));
134+ result.append(resultNode);
135+ continue;
136+ }
137+ TreeNodeScript* script = static_cast<TreeNodeScript*>(node);
138+ if (script->getOrigin() == ScriptOrigin::SCRIPT_ORIGIN_LOCAL) {
139+ continue;
140+ }
141+ QVariantMap resultNode;
142+ resultNode.insert("name", node->getName());
143+ resultNode.insert("type", "script");
144+ resultNode.insert("url", script->getFullPath());
145+ result.append(resultNode);
146+ }
147+ return result;
148+}
149+
150+QVariantList ScriptEngines::getPublic() {
151+ return getPublicChildNodes(NULL);
152+}
153+
154+QVariantList ScriptEngines::getLocal() {
155+ QVariantList result;
156+ QList<TreeNodeBase*> treeNodes = getScriptsModel().getFolderNodes(NULL);
157+ for (int i = 0; i < treeNodes.size(); i++) {
158+ TreeNodeBase* node = treeNodes.at(i);
159+ if (node->getType() != TREE_NODE_TYPE_SCRIPT) {
160+ continue;
161+ }
162+ TreeNodeScript* script = static_cast<TreeNodeScript*>(node);
163+ if (script->getOrigin() != ScriptOrigin::SCRIPT_ORIGIN_LOCAL) {
164+ continue;
165+ }
166+ QVariantMap resultNode;
167+ resultNode.insert("name", node->getName());
168+ resultNode.insert("path", script->getFullPath());
169+ result.append(resultNode);
170+ }
171+ return result;
172+}
173+
174+QVariantList ScriptEngines::getRunning() {
175+ const int WINDOWS_DRIVE_LETTER_SIZE = 1;
176+ QVariantList result;
177+ auto runningScripts = getRunningScripts();
178+ foreach(const QString& runningScript, runningScripts) {
179+ QUrl runningScriptURL = QUrl(runningScript);
180+ if (runningScriptURL.scheme().size() <= WINDOWS_DRIVE_LETTER_SIZE) {
181+ runningScriptURL = QUrl::fromLocalFile(runningScriptURL.toDisplayString(QUrl::FormattingOptions(QUrl::FullyEncoded)));
182+ }
183+ QVariantMap resultNode;
184+ resultNode.insert("name", runningScriptURL.fileName());
185+ resultNode.insert("url", runningScriptURL.toDisplayString(QUrl::FormattingOptions(QUrl::FullyEncoded)));
186+ // The path contains the exact path/URL of the script, which also is used in the stopScript function.
187+ resultNode.insert("path", runningScript);
188+ resultNode.insert("local", runningScriptURL.isLocalFile());
189+ result.append(resultNode);
190+ }
191+ return result;
192+}
193+
194+
195+static const QString SETTINGS_KEY = "Settings";
196+static const QString DEFAULT_SCRIPTS_JS_URL = "http://s3.amazonaws.com/hifi-public/scripts/defaultScripts.js";
197+
198+void ScriptEngines::loadDefaultScripts() {
199+ loadScript(DEFAULT_SCRIPTS_JS_URL);
200+}
201+
202+void ScriptEngines::loadOneScript(const QString& scriptFilename) {
203+ loadScript(scriptFilename);
204+}
205+
206+void ScriptEngines::loadScripts() {
207+ // check first run...
208+ if (_firstRun.get()) {
209+ qCDebug(scriptengine) << "This is a first run...";
210+ // clear the scripts, and set out script to our default scripts
211+ clearScripts();
212+ loadDefaultScripts();
213+ _firstRun.set(false);
214+ return;
215+ }
216+
217+ // loads all saved scripts
218+ Settings settings;
219+ int size = settings.beginReadArray(SETTINGS_KEY);
220+ for (int i = 0; i < size; ++i) {
221+ settings.setArrayIndex(i);
222+ QString string = settings.value("script").toString();
223+ if (!string.isEmpty()) {
224+ loadScript(string);
225+ }
226+ }
227+ settings.endArray();
228+}
229+
230+void ScriptEngines::clearScripts() {
231+ // clears all scripts from the settingsSettings settings;
232+ Settings settings;
233+ settings.beginWriteArray(SETTINGS_KEY);
234+ settings.remove("");
235+}
236+
237+void ScriptEngines::saveScripts() {
238+ // Saves all currently running user-loaded scripts
239+ Settings settings;
240+ settings.beginWriteArray(SETTINGS_KEY);
241+ settings.remove("");
242+
243+ QStringList runningScripts = getRunningScripts();
244+ int i = 0;
245+ for (auto it = runningScripts.begin(); it != runningScripts.end(); ++it) {
246+ if (getScriptEngine(*it)->isUserLoaded()) {
247+ settings.setArrayIndex(i);
248+ settings.setValue("script", *it);
249+ ++i;
250+ }
251+ }
252+ settings.endArray();
253+}
254+
255+QStringList ScriptEngines::getRunningScripts() {
256+ QReadLocker lock(&_scriptEnginesHashLock);
257+ return _scriptEnginesHash.keys();
258+}
259+
260+void ScriptEngines::stopAllScripts(bool restart) {
261+ QReadLocker lock(&_scriptEnginesHashLock);
262+ if (restart) {
263+ // Delete all running scripts from cache so that they are re-downloaded when they are restarted
264+ auto scriptCache = DependencyManager::get<ScriptCache>();
265+ for (QHash<QString, ScriptEngine*>::const_iterator it = _scriptEnginesHash.constBegin();
266+ it != _scriptEnginesHash.constEnd(); it++) {
267+ if (!it.value()->isFinished()) {
268+ scriptCache->deleteScript(it.key());
269+ }
270+ }
271+ }
272+
273+ // Stop and possibly restart all currently running scripts
274+ for (QHash<QString, ScriptEngine*>::const_iterator it = _scriptEnginesHash.constBegin();
275+ it != _scriptEnginesHash.constEnd(); it++) {
276+ if (it.value()->isFinished()) {
277+ continue;
278+ }
279+ if (restart && it.value()->isUserLoaded()) {
280+ connect(it.value(), &ScriptEngine::finished, this, [this](QString scriptName, ScriptEngine* engine) {
281+ reloadScript(scriptName);
282+ });
283+ }
284+ QMetaObject::invokeMethod(it.value(), "stop");
285+ //it.value()->stop();
286+ qCDebug(scriptengine) << "stopping script..." << it.key();
287+ }
288+}
289+
290+bool ScriptEngines::stopScript(const QString& scriptHash, bool restart) {
291+ bool stoppedScript = false;
292+ {
293+ QReadLocker lock(&_scriptEnginesHashLock);
294+ if (_scriptEnginesHash.contains(scriptHash)) {
295+ ScriptEngine* scriptEngine = _scriptEnginesHash[scriptHash];
296+ if (restart) {
297+ auto scriptCache = DependencyManager::get<ScriptCache>();
298+ scriptCache->deleteScript(QUrl(scriptHash));
299+ connect(scriptEngine, &ScriptEngine::finished, this, [this](QString scriptName, ScriptEngine* engine) {
300+ reloadScript(scriptName);
301+ });
302+ }
303+ scriptEngine->stop();
304+ stoppedScript = true;
305+ qCDebug(scriptengine) << "stopping script..." << scriptHash;
306+ }
307+ }
308+ return stoppedScript;
309+}
310+
311+QString ScriptEngines::getScriptsLocation() const {
312+ return _scriptsLocationHandle.get();
313+}
314+
315+void ScriptEngines::setScriptsLocation(const QString& scriptsLocation) {
316+ _scriptsLocationHandle.set(scriptsLocation);
317+ _scriptsModel.updateScriptsLocation(scriptsLocation);
318+}
319+
320+void ScriptEngines::reloadAllScripts() {
321+ DependencyManager::get<ScriptCache>()->clearCache();
322+ emit scriptsReloading();
323+ stopAllScripts(true);
324+}
325+
326+ScriptEngine* ScriptEngines::loadScript(const QString& scriptFilename, bool isUserLoaded, bool loadScriptFromEditor, bool activateMainWindow, bool reload) {
327+ if (thread() != QThread::currentThread()) {
328+ ScriptEngine* result { nullptr };
329+ QMetaObject::invokeMethod(this, "loadScript", Qt::BlockingQueuedConnection, Q_RETURN_ARG(ScriptEngine*, result),
330+ Q_ARG(QString, scriptFilename),
331+ Q_ARG(bool, isUserLoaded),
332+ Q_ARG(bool, loadScriptFromEditor),
333+ Q_ARG(bool, activateMainWindow),
334+ Q_ARG(bool, reload));
335+ return result;
336+ }
337+ QUrl scriptUrl(scriptFilename);
338+ auto scriptEngine = getScriptEngine(scriptUrl.toString());
339+ if (scriptEngine) {
340+ return scriptEngine;
341+ }
342+
343+ scriptEngine = new ScriptEngine(NO_SCRIPT, "", true);
344+ scriptEngine->setUserLoaded(isUserLoaded);
345+
346+ if (scriptFilename.isNull()) {
347+ launchScriptEngine(scriptEngine);
348+ } else {
349+ // connect to the appropriate signals of this script engine
350+ connect(scriptEngine, &ScriptEngine::scriptLoaded, this, &ScriptEngines::onScriptEngineLoaded);
351+ connect(scriptEngine, &ScriptEngine::errorLoadingScript, this, &ScriptEngines::onScriptEngineError);
352+
353+ // get the script engine object to load the script at the designated script URL
354+ scriptEngine->loadURL(scriptUrl, reload);
355+ }
356+
357+ return scriptEngine;
358+}
359+
360+ScriptEngine* ScriptEngines::getScriptEngine(const QString& scriptHash) {
361+ ScriptEngine* result = nullptr;
362+ {
363+ QReadLocker lock(&_scriptEnginesHashLock);
364+ auto it = _scriptEnginesHash.find(scriptHash);
365+ if (it != _scriptEnginesHash.end()) {
366+ result = it.value();
367+ }
368+ }
369+ return result;
370+}
371+
372+// FIXME - change to new version of ScriptCache loading notification
373+void ScriptEngines::onScriptEngineLoaded(const QString& scriptFilename) {
374+ UserActivityLogger::getInstance().loadedScript(scriptFilename);
375+ ScriptEngine* scriptEngine = qobject_cast<ScriptEngine*>(sender());
376+
377+ launchScriptEngine(scriptEngine);
378+
379+ {
380+ QWriteLocker lock(&_scriptEnginesHashLock);
381+ _scriptEnginesHash.insertMulti(scriptFilename, scriptEngine);
382+ }
383+ emit scriptCountChanged();
384+}
385+
386+void ScriptEngines::launchScriptEngine(ScriptEngine* scriptEngine) {
387+ connect(scriptEngine, &ScriptEngine::finished, this, &ScriptEngines::onScriptFinished, Qt::DirectConnection);
388+ connect(scriptEngine, &ScriptEngine::loadScript, [&](const QString& scriptName, bool userLoaded) {
389+ loadScript(scriptName, userLoaded);
390+ });
391+ connect(scriptEngine, &ScriptEngine::reloadScript, [&](const QString& scriptName, bool userLoaded) {
392+ loadScript(scriptName, userLoaded, false, false, true);
393+ });
394+
395+ // register our application services and set it off on its own thread
396+ for (auto initializer : _scriptInitializers) {
397+ initializer(scriptEngine);
398+ }
399+ scriptEngine->runInThread();
400+}
401+
402+
403+void ScriptEngines::onScriptFinished(const QString& scriptName, ScriptEngine* engine) {
404+ bool removed = false;
405+ {
406+ QWriteLocker lock(&_scriptEnginesHashLock);
407+ const QString& scriptURLString = QUrl(scriptName).toString();
408+ for (auto it = _scriptEnginesHash.find(scriptURLString); it != _scriptEnginesHash.end(); ++it) {
409+ if (it.value() == engine) {
410+ _scriptEnginesHash.erase(it);
411+ removed = true;
412+ break;
413+ }
414+ }
415+ }
416+
417+ if (removed) {
418+ emit scriptCountChanged();
419+ }
420+}
421+
422+// FIXME - change to new version of ScriptCache loading notification
423+void ScriptEngines::onScriptEngineError(const QString& scriptFilename) {
424+ qCDebug(scriptengine) << "Application::loadScript(), script failed to load...";
425+ emit scriptLoadError(scriptFilename, "");
426+}
427+
428+QString ScriptEngines::getPreviousScriptLocation() const {
429+ return _previousScriptLocation.get();
430+}
431+
432+void ScriptEngines::setPreviousScriptLocation(const QString& previousScriptLocation) {
433+ _previousScriptLocation.set(previousScriptLocation);
434+}