History

Merge branch 'master' of https://github.com/highfidelity/hifi into temp1

Sam Gateau committed 11 years ago

Commit a7572292 parents 4959e29 cfa6352135 files · +1631 −801Browse files

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

Unified · Split

libraries/script-engine/src/ScriptEngine.cpp+174−15
@@ -43,6 +43,7 @@ · 42 unmodified lines
4343
4444 #include "MIDIEvent.h"
4545
46+
4647 EntityScriptingInterface ScriptEngine::_entityScriptingInterface;
4748
4849 static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine){
@@ -96,8 +97,109 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam · 47 unmodified lines
9697 _isUserLoaded(false),
9798 _arrayBufferClass(new ArrayBufferClass(this))
9899 {
100+ _allScriptsMutex.lock();
101+ _allKnownScriptEngines.insert(this);
102+ _allScriptsMutex.unlock();
103+}
104+
105+ScriptEngine::~ScriptEngine() {
106+ // If we're not already in the middle of stopping all scripts, then we should remove ourselves
107+ // from the list of running scripts. We don't do this if we're in the process of stopping all scripts
108+ // because that method removes scripts from its list as it iterates them
109+ if (!_stoppingAllScripts) {
110+ _allScriptsMutex.lock();
111+ _allKnownScriptEngines.remove(this);
112+ _allScriptsMutex.unlock();
113+ }
114+}
115+
116+QSet<ScriptEngine*> ScriptEngine::_allKnownScriptEngines;
117+QMutex ScriptEngine::_allScriptsMutex;
118+bool ScriptEngine::_stoppingAllScripts = false;
119+bool ScriptEngine::_doneRunningThisScript = false;
120+
121+void ScriptEngine::stopAllScripts(QObject* application) {
122+ _allScriptsMutex.lock();
123+ _stoppingAllScripts = true;
124+
125+ QMutableSetIterator<ScriptEngine*> i(_allKnownScriptEngines);
126+ while (i.hasNext()) {
127+ ScriptEngine* scriptEngine = i.next();
128+
129+ QString scriptName = scriptEngine->getFilename();
130+
131+ // NOTE: typically all script engines are running. But there's at least one known exception to this, the
132+ // "entities sandbox" which is only used to evaluate entities scripts to test their validity before using
133+ // them. We don't need to stop scripts that aren't running.
134+ if (scriptEngine->isRunning()) {
135+
136+ // If the script is running, but still evaluating then we need to wait for its evaluation step to
137+ // complete. After that we can handle the stop process appropriately
138+ if (scriptEngine->evaluatePending()) {
139+ while (scriptEngine->evaluatePending()) {
140+
141+ // This event loop allows any started, but not yet finished evaluate() calls to complete
142+ // we need to let these complete so that we can be guaranteed that the script engine isn't
143+ // in a partially setup state, which can confuse our shutdown unwinding.
144+ QEventLoop loop;
145+ QObject::connect(scriptEngine, &ScriptEngine::evaluationFinished, &loop, &QEventLoop::quit);
146+ loop.exec();
147+ }
148+ }
149+
150+ // We disconnect any script engine signals from the application because we don't want to do any
151+ // extra stopScript/loadScript processing that the Application normally does when scripts start
152+ // and stop. We can safely short circuit this because we know we're in the "quitting" process
153+ scriptEngine->disconnect(application);
154+
155+ // Calling stop on the script engine will set it's internal _isFinished state to true, and result
156+ // in the ScriptEngine gracefully ending it's run() method.
157+ scriptEngine->stop();
158+
159+ // We need to wait for the engine to be done running before we proceed, because we don't
160+ // want any of the scripts final "scriptEnding()" or pending "update()" methods from accessing
161+ // any application state after we leave this stopAllScripts() method
162+ scriptEngine->waitTillDoneRunning();
163+
164+ // If the script is stopped, we can remove it from our set
165+ i.remove();
166+ }
167+ }
168+ _stoppingAllScripts = false;
169+ _allScriptsMutex.unlock();
170+}
171+
172+
173+void ScriptEngine::waitTillDoneRunning() {
174+ QString scriptName = getFilename();
175+
176+ // If the script never started running or finished running before we got here, we don't need to wait for it
177+ if (_isRunning) {
178+
179+ _doneRunningThisScript = false; // NOTE: this is static, we serialize our waiting for scripts to finish
180+
181+ // NOTE: waitTillDoneRunning() will be called on the main Application thread, inside of stopAllScripts()
182+ // we want the application thread to continue to process events, because the scripts will likely need to
183+ // marshall messages across to the main thread. For example if they access Settings or Meny in any of their
184+ // shutdown code.
185+ while (!_doneRunningThisScript) {
186+
187+ // process events for the main application thread, allowing invokeMethod calls to pass between threads
188+ QCoreApplication::processEvents();
189+ }
190+ }
99191 }
100192
193+QString ScriptEngine::getFilename() const {
194+ QStringList fileNameParts = _fileNameString.split("/");
195+ QString lastPart;
196+ if (!fileNameParts.isEmpty()) {
197+ lastPart = fileNameParts.last();
198+ }
199+ return lastPart;
200+}
201+
202+
101203 void ScriptEngine::setIsAvatar(bool isAvatar) {
102204 _isAvatar = isAvatar;
103205
@@ -300,12 +402,18 @@ void ScriptEngine::registerGetterSetter(const QString& name, QScriptEngine::Func · 196 unmodified lines
300402 }
301403
302404 void ScriptEngine::evaluate() {
405+ if (_stoppingAllScripts) {
406+ return; // bail early
407+ }
408+
303409 if (!_isInitialized) {
304410 init();
305411 }
306412
307413 QScriptValue result = evaluate(_scriptContents);
308414
415+ // TODO: why do we check this twice? It seems like the call to clearExcpetions() in the lower level evaluate call
416+ // will cause this code to never actually run...
309417 if (hasUncaughtException()) {
310418 int line = uncaughtExceptionLineNumber();
311419 qDebug() << "Uncaught exception at (" << _fileNameString << ") line" << line << ":" << result.toString();
@@ -315,11 +423,17 @@ void ScriptEngine::evaluate() { · 3 unmodified lines
315423 }
316424
317425 QScriptValue ScriptEngine::evaluate(const QString& program, const QString& fileName, int lineNumber) {
426+ if (_stoppingAllScripts) {
427+ return QScriptValue(); // bail early
428+ }
429+
430+ _evaluatesPending++;
318431 QScriptValue result = QScriptEngine::evaluate(program, fileName, lineNumber);
319432 if (hasUncaughtException()) {
320433 int line = uncaughtExceptionLineNumber();
321434 qDebug() << "Uncaught exception at (" << _fileNameString << " : " << fileName << ") line" << line << ": " << result.toString();
322435 }
436+ _evaluatesPending--;
323437 emit evaluationFinished(result, hasUncaughtException());
324438 clearExceptions();
325439 return result;
@@ -338,6 +452,9 @@ void ScriptEngine::sendAvatarBillboardPacket() { · 12 unmodified lines
338452 }
339453
340454 void ScriptEngine::run() {
455+ // TODO: can we add a short circuit for _stoppingAllScripts here? What does it mean to not start running if
456+ // we're in the process of stopping?
457+
341458 if (!_isInitialized) {
342459 init();
343460 }
@@ -346,12 +463,6 @@ void ScriptEngine::run() { · 2 unmodified lines
346463 emit runningStateChanged();
347464
348465 QScriptValue result = evaluate(_scriptContents);
349− if (hasUncaughtException()) {
350− int line = uncaughtExceptionLineNumber();
351− qDebug() << "Uncaught exception at (" << _fileNameString << ") line" << line << ":" << result.toString();
352− emit errorMessage("Uncaught exception at (" + _fileNameString + ") line" + QString::number(line) + ":" + result.toString());
353− clearExceptions();
354− }
355466
356467 QElapsedTimer startTime;
357468 startTime.start();
@@ -378,7 +489,7 @@ void ScriptEngine::run() { · 20 unmodified lines
378489 break;
379490 }
380491
381− if (_entityScriptingInterface.getEntityPacketSender()->serversExist()) {
492+ if (!_isFinished && _entityScriptingInterface.getEntityPacketSender()->serversExist()) {
382493 // release the queue of edit entity messages.
383494 _entityScriptingInterface.getEntityPacketSender()->releaseQueuedMessages();
384495
@@ -388,7 +499,7 @@ void ScriptEngine::run() { · 3 unmodified lines
388499 }
389500 }
390501
391− if (_isAvatar && _avatarData) {
502+ if (!_isFinished && _isAvatar && _avatarData) {
392503
393504 const int SCRIPT_AUDIO_BUFFER_SAMPLES = floor(((SCRIPT_DATA_CALLBACK_USECS * AudioConstants::SAMPLE_RATE)
394505 / (1000 * 1000)) + 0.5);
@@ -498,9 +609,14 @@ void ScriptEngine::run() { · 103 unmodified lines
498609 clearExceptions();
499610 }
500611
501− emit update(deltaTime);
612+ if (!_isFinished) {
613+ emit update(deltaTime);
614+ }
502615 lastUpdate = now;
616+
503617 }
618+
619+ stopAllTimers(); // make sure all our timers are stopped if the script is ending
504620 emit scriptEnding();
505621
506622 // kill the avatar identity timer
@@ -525,6 +641,21 @@ void ScriptEngine::run() { · 18 unmodified lines
525641
526642 _isRunning = false;
527643 emit runningStateChanged();
644+
645+ emit doneRunning();
646+
647+ _doneRunningThisScript = true;
648+}
649+
650+// NOTE: This is private because it must be called on the same thread that created the timers, which is why
651+// we want to only call it in our own run "shutdown" processing.
652+void ScriptEngine::stopAllTimers() {
653+ QMutableHashIterator<QTimer*, QScriptValue> i(_timerFunctionMap);
654+ while (i.hasNext()) {
655+ i.next();
656+ QTimer* timer = i.key();
657+ stopTimer(timer);
658+ }
528659 }
529660
530661 void ScriptEngine::stop() {
@@ -565,10 +696,20 @@ QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int · 34 unmodified lines
565696 }
566697
567698 QObject* ScriptEngine::setInterval(const QScriptValue& function, int intervalMS) {
699+ if (_stoppingAllScripts) {
700+ qDebug() << "Script.setInterval() while shutting down is ignored... parent script:" << getFilename();
701+ return NULL; // bail early
702+ }
703+
568704 return setupTimerWithInterval(function, intervalMS, false);
569705 }
570706
571707 QObject* ScriptEngine::setTimeout(const QScriptValue& function, int timeoutMS) {
708+ if (_stoppingAllScripts) {
709+ qDebug() << "Script.setTimeout() while shutting down is ignored... parent script:" << getFilename();
710+ return NULL; // bail early
711+ }
712+
572713 return setupTimerWithInterval(function, timeoutMS, true);
573714 }
574715
@@ -609,13 +750,16 @@ void ScriptEngine::print(const QString& message) { · 34 unmodified lines
609750 emit printedMessage(message);
610751 }
611752
612−/**
613− * If a callback is specified, the included files will be loaded asynchronously and the callback will be called
614− * when all of the files have finished loading.
615− * If no callback is specified, the included files will be loaded synchronously and will block execution until
616− * all of the files have finished loading.
617− */
753+// If a callback is specified, the included files will be loaded asynchronously and the callback will be called
754+// when all of the files have finished loading.
755+// If no callback is specified, the included files will be loaded synchronously and will block execution until
756+// all of the files have finished loading.
618757 void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callback) {
758+ if (_stoppingAllScripts) {
759+ qDebug() << "Script.include() while shutting down is ignored..."
760+ << "includeFiles:" << includeFiles << "parent script:" << getFilename();
761+ return; // bail early
762+ }
619763 QList<QUrl> urls;
620764 for (QString file : includeFiles) {
621765 urls.append(resolvePath(file));
@@ -655,12 +799,27 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac · 33 unmodified lines
655799 }
656800
657801 void ScriptEngine::include(const QString& includeFile, QScriptValue callback) {
802+ if (_stoppingAllScripts) {
803+ qDebug() << "Script.include() while shutting down is ignored... "
804+ << "includeFile:" << includeFile << "parent script:" << getFilename();
805+ return; // bail early
806+ }
807+
658808 QStringList urls;
659809 urls.append(includeFile);
660810 include(urls, callback);
661811 }
662812
813+// NOTE: The load() command is similar to the include() command except that it loads the script
814+// as a stand-alone script. To accomplish this, the ScriptEngine class just emits a signal which
815+// the Application or other context will connect to in order to know to actually load the script
663816 void ScriptEngine::load(const QString& loadFile) {
817+ if (_stoppingAllScripts) {
818+ qDebug() << "Script.load() while shutting down is ignored... "
819+ << "loadFile:" << loadFile << "parent script:" << getFilename();
820+ return; // bail early
821+ }
822+
664823 QUrl url = resolvePath(loadFile);
665824 emit loadScript(url.toString(), false);
666825 }