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
43 43
44#include "MIDIEvent.h"44#include "MIDIEvent.h"
45 45
46
46EntityScriptingInterface ScriptEngine::_entityScriptingInterface;47EntityScriptingInterface ScriptEngine::_entityScriptingInterface;
47 48
48static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine){49static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine){
@@ -96,8 +97,109 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam · 47 unmodified lines
96 _isUserLoaded(false),97 _isUserLoaded(false),
97 _arrayBufferClass(new ArrayBufferClass(this))98 _arrayBufferClass(new ArrayBufferClass(this))
98{99{
100 _allScriptsMutex.lock();
101 _allKnownScriptEngines.insert(this);
102 _allScriptsMutex.unlock();
103}
104
105ScriptEngine::~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
116QSet<ScriptEngine*> ScriptEngine::_allKnownScriptEngines;
117QMutex ScriptEngine::_allScriptsMutex;
118bool ScriptEngine::_stoppingAllScripts = false;
119bool ScriptEngine::_doneRunningThisScript = false;
120
121void 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
173void 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 }
99}191}
100 192
193QString 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
101void ScriptEngine::setIsAvatar(bool isAvatar) {203void ScriptEngine::setIsAvatar(bool isAvatar) {
102 _isAvatar = isAvatar;204 _isAvatar = isAvatar;
103 205
@@ -300,12 +402,18 @@ void ScriptEngine::registerGetterSetter(const QString& name, QScriptEngine::Func · 196 unmodified lines
300}402}
301 403
302void ScriptEngine::evaluate() {404void ScriptEngine::evaluate() {
405 if (_stoppingAllScripts) {
406 return; // bail early
407 }
408
303 if (!_isInitialized) {409 if (!_isInitialized) {
304 init();410 init();
305 }411 }
306 412
307 QScriptValue result = evaluate(_scriptContents);413 QScriptValue result = evaluate(_scriptContents);
308 414
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...
309 if (hasUncaughtException()) {417 if (hasUncaughtException()) {
310 int line = uncaughtExceptionLineNumber();418 int line = uncaughtExceptionLineNumber();
311 qDebug() << "Uncaught exception at (" << _fileNameString << ") line" << line << ":" << result.toString();419 qDebug() << "Uncaught exception at (" << _fileNameString << ") line" << line << ":" << result.toString();
@@ -315,11 +423,17 @@ void ScriptEngine::evaluate() { · 3 unmodified lines
315}423}
316 424
317QScriptValue ScriptEngine::evaluate(const QString& program, const QString& fileName, int lineNumber) {425QScriptValue ScriptEngine::evaluate(const QString& program, const QString& fileName, int lineNumber) {
426 if (_stoppingAllScripts) {
427 return QScriptValue(); // bail early
428 }
429
430 _evaluatesPending++;
318 QScriptValue result = QScriptEngine::evaluate(program, fileName, lineNumber);431 QScriptValue result = QScriptEngine::evaluate(program, fileName, lineNumber);
319 if (hasUncaughtException()) {432 if (hasUncaughtException()) {
320 int line = uncaughtExceptionLineNumber();433 int line = uncaughtExceptionLineNumber();
321 qDebug() << "Uncaught exception at (" << _fileNameString << " : " << fileName << ") line" << line << ": " << result.toString();434 qDebug() << "Uncaught exception at (" << _fileNameString << " : " << fileName << ") line" << line << ": " << result.toString();
322 }435 }
436 _evaluatesPending--;
323 emit evaluationFinished(result, hasUncaughtException());437 emit evaluationFinished(result, hasUncaughtException());
324 clearExceptions();438 clearExceptions();
325 return result;439 return result;
@@ -338,6 +452,9 @@ void ScriptEngine::sendAvatarBillboardPacket() { · 12 unmodified lines
338}452}
339 453
340void ScriptEngine::run() {454void 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
341 if (!_isInitialized) {458 if (!_isInitialized) {
342 init();459 init();
343 }460 }
@@ -346,12 +463,6 @@ void ScriptEngine::run() { · 2 unmodified lines
346 emit runningStateChanged();463 emit runningStateChanged();
347 464
348 QScriptValue result = evaluate(_scriptContents);465 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 }
355 466
356 QElapsedTimer startTime;467 QElapsedTimer startTime;
357 startTime.start();468 startTime.start();
@@ -378,7 +489,7 @@ void ScriptEngine::run() { · 20 unmodified lines
378 break;489 break;
379 }490 }
380 491
381 if (_entityScriptingInterface.getEntityPacketSender()->serversExist()) {492 if (!_isFinished && _entityScriptingInterface.getEntityPacketSender()->serversExist()) {
382 // release the queue of edit entity messages.493 // release the queue of edit entity messages.
383 _entityScriptingInterface.getEntityPacketSender()->releaseQueuedMessages();494 _entityScriptingInterface.getEntityPacketSender()->releaseQueuedMessages();
384 495
@@ -388,7 +499,7 @@ void ScriptEngine::run() { · 3 unmodified lines
388 }499 }
389 }500 }
390 501
391 if (_isAvatar && _avatarData) {502 if (!_isFinished && _isAvatar && _avatarData) {
392 503
393 const int SCRIPT_AUDIO_BUFFER_SAMPLES = floor(((SCRIPT_DATA_CALLBACK_USECS * AudioConstants::SAMPLE_RATE)504 const int SCRIPT_AUDIO_BUFFER_SAMPLES = floor(((SCRIPT_DATA_CALLBACK_USECS * AudioConstants::SAMPLE_RATE)
394 / (1000 * 1000)) + 0.5);505 / (1000 * 1000)) + 0.5);
@@ -498,9 +609,14 @@ void ScriptEngine::run() { · 103 unmodified lines
498 clearExceptions();609 clearExceptions();
499 }610 }
500 611
501 emit update(deltaTime);612 if (!_isFinished) {
613 emit update(deltaTime);
614 }
502 lastUpdate = now;615 lastUpdate = now;
616
503 }617 }
618
619 stopAllTimers(); // make sure all our timers are stopped if the script is ending
504 emit scriptEnding();620 emit scriptEnding();
505 621
506 // kill the avatar identity timer622 // kill the avatar identity timer
@@ -525,6 +641,21 @@ void ScriptEngine::run() { · 18 unmodified lines
525 641
526 _isRunning = false;642 _isRunning = false;
527 emit runningStateChanged();643 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.
652void ScriptEngine::stopAllTimers() {
653 QMutableHashIterator<QTimer*, QScriptValue> i(_timerFunctionMap);
654 while (i.hasNext()) {
655 i.next();
656 QTimer* timer = i.key();
657 stopTimer(timer);
658 }
528}659}
529 660
530void ScriptEngine::stop() {661void ScriptEngine::stop() {
@@ -565,10 +696,20 @@ QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int · 34 unmodified lines
565}696}
566 697
567QObject* ScriptEngine::setInterval(const QScriptValue& function, int intervalMS) {698QObject* 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
568 return setupTimerWithInterval(function, intervalMS, false);704 return setupTimerWithInterval(function, intervalMS, false);
569}705}
570 706
571QObject* ScriptEngine::setTimeout(const QScriptValue& function, int timeoutMS) {707QObject* 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
572 return setupTimerWithInterval(function, timeoutMS, true);713 return setupTimerWithInterval(function, timeoutMS, true);
573}714}
574 715
@@ -609,13 +750,16 @@ void ScriptEngine::print(const QString& message) { · 34 unmodified lines
609 emit printedMessage(message);750 emit printedMessage(message);
610}751}
611 752
612/**753// If a callback is specified, the included files will be loaded asynchronously and the callback will be called
613 * If a callback is specified, the included files will be loaded asynchronously and the callback will be called754// when all of the files have finished loading.
614 * 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
615 * If no callback is specified, the included files will be loaded synchronously and will block execution until756// all of the files have finished loading.
616 * all of the files have finished loading.
617 */
618void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callback) {757void 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 }
619 QList<QUrl> urls;763 QList<QUrl> urls;
620 for (QString file : includeFiles) {764 for (QString file : includeFiles) {
621 urls.append(resolvePath(file));765 urls.append(resolvePath(file));
@@ -655,12 +799,27 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac · 33 unmodified lines
655}799}
656 800
657void ScriptEngine::include(const QString& includeFile, QScriptValue callback) {801void 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
658 QStringList urls;808 QStringList urls;
659 urls.append(includeFile);809 urls.append(includeFile);
660 include(urls, callback);810 include(urls, callback);
661}811}
662 812
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
663void ScriptEngine::load(const QString& loadFile) {816void 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
664 QUrl url = resolvePath(loadFile);823 QUrl url = resolvePath(loadFile);
665 emit loadScript(url.toString(), false);824 emit loadScript(url.toString(), false);
666}825}