Showing the changes to libraries/script-engine/src/ScriptEngine.cpp (1 of 135 files). Show all
Unified · Split
| @@ -43,6 +43,7 @@ · 42 unmodified lines | |||
| 43 | 43 | ||
| 44 | #include "MIDIEvent.h" | 44 | #include "MIDIEvent.h" |
| 45 | 45 | ||
| 46 | |||
| 46 | EntityScriptingInterface ScriptEngine::_entityScriptingInterface; | 47 | EntityScriptingInterface ScriptEngine::_entityScriptingInterface; |
| 47 | 48 | ||
| 48 | static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine){ | 49 | static 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 | |||
| 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 | } | ||
| 99 | } | 191 | } |
| 100 | 192 | ||
| 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 | |||
| 101 | void ScriptEngine::setIsAvatar(bool isAvatar) { | 203 | void 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 | ||
| 302 | void ScriptEngine::evaluate() { | 404 | void 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 | ||
| 317 | QScriptValue ScriptEngine::evaluate(const QString& program, const QString& fileName, int lineNumber) { | 425 | QScriptValue 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 | ||
| 340 | void ScriptEngine::run() { | 454 | 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 | |||
| 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 timer | 622 | // 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. | ||
| 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 | } | ||
| 528 | } | 659 | } |
| 529 | 660 | ||
| 530 | void ScriptEngine::stop() { | 661 | void ScriptEngine::stop() { |
| @@ -565,10 +696,20 @@ QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int · 34 unmodified lines | |||
| 565 | } | 696 | } |
| 566 | 697 | ||
| 567 | QObject* ScriptEngine::setInterval(const QScriptValue& function, int intervalMS) { | 698 | 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 | |||
| 568 | return setupTimerWithInterval(function, intervalMS, false); | 704 | return setupTimerWithInterval(function, intervalMS, false); |
| 569 | } | 705 | } |
| 570 | 706 | ||
| 571 | QObject* ScriptEngine::setTimeout(const QScriptValue& function, int timeoutMS) { | 707 | 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 | |||
| 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 called | 754 | // 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 until | 756 | // all of the files have finished loading. |
| 616 | * all of the files have finished loading. | ||
| 617 | */ | ||
| 618 | void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callback) { | 757 | 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 | } | ||
| 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 | ||
| 657 | void ScriptEngine::include(const QString& includeFile, QScriptValue callback) { | 801 | 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 | |||
| 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 | ||
| 663 | void ScriptEngine::load(const QString& loadFile) { | 816 | 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 | |||
| 664 | QUrl url = resolvePath(loadFile); | 823 | QUrl url = resolvePath(loadFile); |
| 665 | emit loadScript(url.toString(), false); | 824 | emit loadScript(url.toString(), false); |
| 666 | } | 825 | } |