Showing the changes to libraries/script-engine/src/ScriptEngine.cpp (1 of 14 files). Show all
Unified · Split
| @@ -68,10 +68,12 @@ · 67 unmodified lines | |||
| 68 | 68 | ||
| 69 | #include "MIDIEvent.h" | 69 | #include "MIDIEvent.h" |
| 70 | 70 | ||
| 71 | static const QString SCRIPT_EXCEPTION_FORMAT = "[UncaughtException] %1 in %2:%3"; | 71 | const QString BaseScriptEngine::SCRIPT_EXCEPTION_FORMAT { "[UncaughtException] %1 in %2:%3" }; |
| 72 | static const QScriptEngine::QObjectWrapOptions DEFAULT_QOBJECT_WRAP_OPTIONS = | 72 | static const QScriptEngine::QObjectWrapOptions DEFAULT_QOBJECT_WRAP_OPTIONS = |
| 73 | QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects; | 73 | QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects; |
| 74 | 74 | ||
| 75 | static const bool HIFI_AUTOREFRESH_FILE_SCRIPTS { true }; | ||
| 76 | |||
| 75 | Q_DECLARE_METATYPE(QScriptEngine::FunctionSignature) | 77 | Q_DECLARE_METATYPE(QScriptEngine::FunctionSignature) |
| 76 | int functionSignatureMetaID = qRegisterMetaType<QScriptEngine::FunctionSignature>(); | 78 | int functionSignatureMetaID = qRegisterMetaType<QScriptEngine::FunctionSignature>(); |
| 77 | 79 | ||
| @@ -137,38 +139,48 @@ QString encodeEntityIdIntoEntityUrl(const QString& url, const QString& entityID) · 59 unmodified lines | |||
| 137 | return url + " [EntityID:" + entityID + "]"; | 139 | return url + " [EntityID:" + entityID + "]"; |
| 138 | } | 140 | } |
| 139 | 141 | ||
| 140 | static bool hasCorrectSyntax(const QScriptProgram& program, ScriptEngine* reportingEngine) { | 142 | QString BaseScriptEngine::lintScript(const QString& sourceCode, const QString& fileName, const int lineNumber) { |
| 141 | const auto syntaxCheck = QScriptEngine::checkSyntax(program.sourceCode()); | 143 | const auto syntaxCheck = checkSyntax(sourceCode); |
| 142 | if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) { | 144 | if (syntaxCheck.state() != syntaxCheck.Valid) { |
| 143 | const auto error = syntaxCheck.errorMessage(); | 145 | const auto error = syntaxCheck.errorMessage(); |
| 144 | const auto line = QString::number(syntaxCheck.errorLineNumber()); | 146 | const auto line = QString::number(syntaxCheck.errorLineNumber()); |
| 145 | const auto column = QString::number(syntaxCheck.errorColumnNumber()); | 147 | const auto column = QString::number(syntaxCheck.errorColumnNumber()); |
| 146 | const auto message = QString("[SyntaxError] %1 in %2:%3(%4)").arg(error, program.fileName(), line, column); | 148 | const auto message = QString("[SyntaxError] %1 in %2:%3(%4)").arg(error, fileName, line, column); |
| 147 | reportingEngine->scriptErrorMessage(qPrintable(message)); | 149 | return message; |
| 148 | return false; | 150 | } |
| 149 | } | 151 | return QString(); |
| 150 | return true; | 152 | } |
| 151 | } | 153 | |
| 152 | 154 | QString BaseScriptEngine::formatUncaughtException(const QString& overrideFileName) { | |
| 153 | static bool hadUncaughtExceptions(QScriptEngine& engine, const QString& fileName, ScriptEngine* reportingEngine, QString* exceptionMessage = nullptr) { | 155 | QString message; |
| 154 | if (engine.hasUncaughtException()) { | 156 | if (hasUncaughtException()) { |
| 155 | const auto backtrace = engine.uncaughtExceptionBacktrace(); | 157 | const auto error = uncaughtException(); |
| 156 | const auto exception = engine.uncaughtException().toString(); | 158 | const auto backtrace = uncaughtExceptionBacktrace(); |
| 157 | const auto line = QString::number(engine.uncaughtExceptionLineNumber()); | 159 | const auto exception = error.toString(); |
| 158 | engine.clearExceptions(); | 160 | auto filename = overrideFileName; |
| 161 | if (filename.isEmpty()) { | ||
| 162 | QScriptContextInfo ctx { currentContext() }; | ||
| 163 | filename = ctx.fileName(); | ||
| 164 | } | ||
| 165 | const auto line = QString::number(uncaughtExceptionLineNumber()); | ||
| 159 | 166 | ||
| 160 | QString message = QString(SCRIPT_EXCEPTION_FORMAT).arg(exception, fileName, line); | 167 | message = QString(SCRIPT_EXCEPTION_FORMAT).arg(exception, overrideFileName, line); |
| 161 | if (!backtrace.empty()) { | 168 | if (!backtrace.empty()) { |
| 162 | static const auto lineSeparator = "\n "; | 169 | static const auto lineSeparator = "\n "; |
| 163 | message += QString("\n[Backtrace]%1%2").arg(lineSeparator, backtrace.join(lineSeparator)); | 170 | message += QString("\n[Backtrace]%1%2").arg(lineSeparator, backtrace.join(lineSeparator)); |
| 164 | } | 171 | } |
| 165 | reportingEngine->scriptErrorMessage(qPrintable(message)); | ||
| 166 | if (exceptionMessage) { | ||
| 167 | *exceptionMessage = message; | ||
| 168 | } | ||
| 169 | return true; | ||
| 170 | } | 172 | } |
| 171 | return false; | 173 | return message; |
| 174 | } | ||
| 175 | |||
| 176 | QString ScriptEngine::reportUncaughtException(const QString& overrideFileName) { | ||
| 177 | QString message; | ||
| 178 | if (!hasUncaughtException()) { | ||
| 179 | return message; | ||
| 180 | } | ||
| 181 | message = formatUncaughtException(overrideFileName.isEmpty() ? _fileNameString : overrideFileName); | ||
| 182 | scriptErrorMessage(qPrintable(message)); | ||
| 183 | return message; | ||
| 172 | } | 184 | } |
| 173 | 185 | ||
| 174 | ScriptEngine::ScriptEngine(Context context, const QString& scriptContents, const QString& fileNameString) : | 186 | ScriptEngine::ScriptEngine(Context context, const QString& scriptContents, const QString& fileNameString) : |
| @@ -181,7 +193,8 @@ ScriptEngine::ScriptEngine(Context context, const QString& scriptContents, const · 6 unmodified lines | |||
| 181 | DependencyManager::get<ScriptEngines>()->addScriptEngine(this); | 193 | DependencyManager::get<ScriptEngines>()->addScriptEngine(this); |
| 182 | 194 | ||
| 183 | connect(this, &QScriptEngine::signalHandlerException, this, [this](const QScriptValue& exception) { | 195 | connect(this, &QScriptEngine::signalHandlerException, this, [this](const QScriptValue& exception) { |
| 184 | hadUncaughtExceptions(*this, _fileNameString, this); | 196 | reportUncaughtException(); |
| 197 | clearExceptions(); | ||
| 185 | }); | 198 | }); |
| 186 | 199 | ||
| 187 | setProcessEventsInterval(MSECS_PER_SECOND); | 200 | setProcessEventsInterval(MSECS_PER_SECOND); |
| @@ -301,7 +314,10 @@ void ScriptEngine::runDebuggable() { · 113 unmodified lines | |||
| 301 | } | 314 | } |
| 302 | _lastUpdate = now; | 315 | _lastUpdate = now; |
| 303 | // Debug and clear exceptions | 316 | // Debug and clear exceptions |
| 304 | hadUncaughtExceptions(*this, _fileNameString, this); | 317 | if (hasUncaughtException()) { |
| 318 | reportUncaughtException(); | ||
| 319 | clearExceptions(); | ||
| 320 | } | ||
| 305 | }); | 321 | }); |
| 306 | 322 | ||
| 307 | timer->start(10); | 323 | timer->start(10); |
| @@ -399,8 +415,6 @@ QString ScriptEngine::getFilename() const { · 91 unmodified lines | |||
| 399 | return lastPart; | 415 | return lastPart; |
| 400 | } | 416 | } |
| 401 | 417 | ||
| 402 | |||
| 403 | // FIXME - switch this to the new model of ScriptCache callbacks | ||
| 404 | void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) { | 418 | void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) { |
| 405 | if (_isRunning) { | 419 | if (_isRunning) { |
| 406 | return; | 420 | return; |
| @@ -410,19 +424,27 @@ void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) { · 3 unmodified lines | |||
| 410 | _fileNameString = url.toString(); | 424 | _fileNameString = url.toString(); |
| 411 | _isReloading = reload; | 425 | _isReloading = reload; |
| 412 | 426 | ||
| 413 | bool isPending; | 427 | const auto maxRetries = 0; // for consistency with previous scriptCache->getScript() behavior |
| 414 | auto scriptCache = DependencyManager::get<ScriptCache>(); | 428 | auto scriptCache = DependencyManager::get<ScriptCache>(); |
| 415 | scriptCache->getScript(url, this, isPending, reload); | 429 | scriptCache->getScriptContents(url.toString(), [this](const QString& url, const QString& scriptContents, bool isURL, bool success, const QString&status) { |
| 416 | } | 430 | qCDebug(scriptengine) << "loadURL" << url << status << QThread::currentThread(); |
| 431 | if (!success) { | ||
| 432 | scriptErrorMessage("ERROR Loading file (" + status + "):" + url); | ||
| 433 | emit errorLoadingScript(_fileNameString); | ||
| 434 | return; | ||
| 435 | } | ||
| 417 | 436 | ||
| 418 | // FIXME - switch this to the new model of ScriptCache callbacks | 437 | _scriptContents = scriptContents; |
| 419 | void ScriptEngine::scriptContentsAvailable(const QUrl& url, const QString& scriptContents) { | 438 | |
| 420 | _scriptContents = scriptContents; | 439 | { |
| 421 | static const QString DEBUG_FLAG("#debug"); | 440 | static const QString DEBUG_FLAG("#debug"); |
| 422 | if (QRegularExpression(DEBUG_FLAG).match(scriptContents).hasMatch()) { | 441 | if (QRegularExpression(DEBUG_FLAG).match(scriptContents).hasMatch()) { |
| 423 | _debuggable = true; | 442 | qCWarning(scriptengine) << "NOTE: ScriptEngine for " << QUrl(url).fileName() << " will be launched in debug mode"; |
| 424 | } | 443 | _debuggable = true; |
| 425 | emit scriptLoaded(url.toString()); | 444 | } |
| 445 | } | ||
| 446 | emit scriptLoaded(url); | ||
| 447 | }, reload, maxRetries); | ||
| 426 | } | 448 | } |
| 427 | 449 | ||
| 428 | void ScriptEngine::scriptErrorMessage(const QString& message) { | 450 | void ScriptEngine::scriptErrorMessage(const QString& message) { |
| @@ -440,12 +462,6 @@ void ScriptEngine::scriptInfoMessage(const QString& message) { · 11 unmodified lines | |||
| 440 | emit infoMessage(message); | 462 | emit infoMessage(message); |
| 441 | } | 463 | } |
| 442 | 464 | ||
| 443 | // FIXME - switch this to the new model of ScriptCache callbacks | ||
| 444 | void ScriptEngine::errorInLoadingScript(const QUrl& url) { | ||
| 445 | scriptErrorMessage("ERROR Loading file:" + url.toString()); | ||
| 446 | emit errorLoadingScript(_fileNameString); | ||
| 447 | } | ||
| 448 | |||
| 449 | // Even though we never pass AnimVariantMap directly to and from javascript, the queued invokeMethod of | 465 | // Even though we never pass AnimVariantMap directly to and from javascript, the queued invokeMethod of |
| 450 | // callAnimationStateHandler requires that the type be registered. | 466 | // callAnimationStateHandler requires that the type be registered. |
| 451 | // These two are meaningful, if we ever do want to use them... | 467 | // These two are meaningful, if we ever do want to use them... |
| @@ -520,6 +536,15 @@ void ScriptEngine::init() { · 68 unmodified lines | |||
| 520 | 536 | ||
| 521 | auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>(); | 537 | auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>(); |
| 522 | entityScriptingInterface->init(); | 538 | entityScriptingInterface->init(); |
| 539 | connect(entityScriptingInterface.data(), &EntityScriptingInterface::deletingEntity, this, [this](const EntityItemID& entityID) { | ||
| 540 | if (_entityScripts.contains(entityID)) { | ||
| 541 | if (isEntityScriptRunning(entityID)) { | ||
| 542 | qCWarning(scriptengine) << "deletingEntity while entity script is still running!" << entityID; | ||
| 543 | } | ||
| 544 | _entityScripts.remove(entityID); | ||
| 545 | } | ||
| 546 | }); | ||
| 547 | |||
| 523 | 548 | ||
| 524 | // register various meta-types | 549 | // register various meta-types |
| 525 | registerMetaTypes(this); | 550 | registerMetaTypes(this); |
| @@ -850,17 +875,25 @@ QScriptValue ScriptEngine::evaluate(const QString& sourceCode, const QString& fi · 324 unmodified lines | |||
| 850 | } | 875 | } |
| 851 | 876 | ||
| 852 | // Check syntax | 877 | // Check syntax |
| 853 | const QScriptProgram program(sourceCode, fileName, lineNumber); | 878 | auto syntaxError = lintScript(sourceCode, fileName); |
| 854 | if (!hasCorrectSyntax(program, this)) { | 879 | QScriptProgram program { sourceCode, fileName, lineNumber }; |
| 880 | if (!syntaxError.isEmpty() || program.isNull()) { | ||
| 881 | scriptErrorMessage(qPrintable(syntaxError)); | ||
| 855 | return QScriptValue(); | 882 | return QScriptValue(); |
| 856 | } | 883 | } |
| 857 | 884 | ||
| 858 | ++_evaluatesPending; | 885 | ++_evaluatesPending; |
| 859 | const auto result = QScriptEngine::evaluate(program); | 886 | auto result = BaseScriptEngine::evaluate(program); |
| 860 | --_evaluatesPending; | 887 | --_evaluatesPending; |
| 861 | 888 | ||
| 862 | const auto hadUncaughtException = hadUncaughtExceptions(*this, program.fileName(), this); | 889 | if (hasUncaughtException()) { |
| 863 | emit evaluationFinished(result, hadUncaughtException); | 890 | result = uncaughtException(); |
| 891 | reportUncaughtException(program.fileName()); | ||
| 892 | emit evaluationFinished(result, true); | ||
| 893 | clearExceptions(); | ||
| 894 | } else { | ||
| 895 | emit evaluationFinished(result, false); | ||
| 896 | } | ||
| 864 | return result; | 897 | return result; |
| 865 | } | 898 | } |
| 866 | 899 | ||
| @@ -1009,7 +1042,10 @@ void ScriptEngine::run() { · 142 unmodified lines | |||
| 1009 | _lastUpdate = now; | 1042 | _lastUpdate = now; |
| 1010 | 1043 | ||
| 1011 | // Debug and clear exceptions | 1044 | // Debug and clear exceptions |
| 1012 | hadUncaughtExceptions(*this, _fileNameString, this); | 1045 | if (hasUncaughtException()) { |
| 1046 | reportUncaughtException(); | ||
| 1047 | clearExceptions(); | ||
| 1048 | } | ||
| 1013 | } | 1049 | } |
| 1014 | 1050 | ||
| 1015 | scriptInfoMessage("Script Engine stopping:" + getFilename()); | 1051 | scriptInfoMessage("Script Engine stopping:" + getFilename()); |
| @@ -1299,12 +1335,12 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac · 283 unmodified lines | |||
| 1299 | EntityItemID capturedEntityIdentifier = currentEntityIdentifier; | 1335 | EntityItemID capturedEntityIdentifier = currentEntityIdentifier; |
| 1300 | QUrl capturedSandboxURL = currentSandboxURL; | 1336 | QUrl capturedSandboxURL = currentSandboxURL; |
| 1301 | 1337 | ||
| 1302 | auto evaluateScripts = [=](const QMap<QUrl, QString>& data) { | 1338 | auto evaluateScripts = [=](const QMap<QUrl, QString>& data, const QMap<QUrl, QString>& status) { |
| 1303 | auto parentURL = _parentURL; | 1339 | auto parentURL = _parentURL; |
| 1304 | for (QUrl url : urls) { | 1340 | for (QUrl url : urls) { |
| 1305 | QString contents = data[url]; | 1341 | QString contents = data[url]; |
| 1306 | if (contents.isNull()) { | 1342 | if (contents.isNull()) { |
| 1307 | scriptErrorMessage("Error loading file: " + url.toString()); | 1343 | scriptErrorMessage("Error loading file (" + status[url] +"): " + url.toString()); |
| 1308 | } else { | 1344 | } else { |
| 1309 | std::lock_guard<std::recursive_mutex> lock(_lock); | 1345 | std::lock_guard<std::recursive_mutex> lock(_lock); |
| 1310 | if (!_includedURLs.contains(url)) { | 1346 | if (!_includedURLs.contains(url)) { |
| @@ -1368,7 +1404,7 @@ void ScriptEngine::load(const QString& loadFile) { · 57 unmodified lines | |||
| 1368 | } | 1404 | } |
| 1369 | if (!currentEntityIdentifier.isInvalidID()) { | 1405 | if (!currentEntityIdentifier.isInvalidID()) { |
| 1370 | scriptWarningMessage("Script.load() from entity script is ignored... loadFile:" | 1406 | scriptWarningMessage("Script.load() from entity script is ignored... loadFile:" |
| 1371 | + loadFile + "parent script:" + getFilename()); | 1407 | + loadFile + "parent script:" + getFilename() + "entity: " + currentEntityIdentifier.toString()); |
| 1372 | return; // bail early | 1408 | return; // bail early |
| 1373 | } | 1409 | } |
| 1374 | 1410 | ||
| @@ -1411,11 +1447,16 @@ void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QStrin · 36 unmodified lines | |||
| 1411 | int ScriptEngine::getNumRunningEntityScripts() const { | 1447 | int ScriptEngine::getNumRunningEntityScripts() const { |
| 1412 | int sum = 0; | 1448 | int sum = 0; |
| 1413 | for (auto& st : _entityScripts) { | 1449 | for (auto& st : _entityScripts) { |
| 1414 | if (st.status == RUNNING) { | 1450 | if (st.status == EntityScriptStatus::RUNNING) { |
| 1415 | ++sum; | 1451 | ++sum; |
| 1416 | } | 1452 | } |
| 1417 | } | 1453 | } |
| 1418 | return sum; | 1454 | return sum; |
| 1455 | |||
| 1456 | QString ScriptEngine::getEntityScriptStatus(const EntityItemID& entityID) { | ||
| 1457 | if (_entityScripts.contains(entityID)) | ||
| 1458 | return EntityScriptStatus_::valueToKey(_entityScripts[entityID].status).toLower(); | ||
| 1459 | return QString(); | ||
| 1419 | } | 1460 | } |
| 1420 | 1461 | ||
| 1421 | bool ScriptEngine::getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const { | 1462 | bool ScriptEngine::getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const { |
| @@ -1430,25 +1471,37 @@ bool ScriptEngine::getEntityScriptDetails(const EntityItemID& entityID, EntitySc · 8 unmodified lines | |||
| 1430 | // since all of these operations can be asynch we will always do the actual work in the response handler | 1471 | // since all of these operations can be asynch we will always do the actual work in the response handler |
| 1431 | // for the download | 1472 | // for the download |
| 1432 | void ScriptEngine::loadEntityScript(QWeakPointer<ScriptEngine> theEngine, const EntityItemID& entityID, const QString& entityScript, bool forceRedownload) { | 1473 | void ScriptEngine::loadEntityScript(QWeakPointer<ScriptEngine> theEngine, const EntityItemID& entityID, const QString& entityScript, bool forceRedownload) { |
| 1474 | { | ||
| 1475 | // set EntityScriptDetails.status to LOADING (over on theEngine's thread) | ||
| 1476 | QObject threadPunter; | ||
| 1477 | connect(&threadPunter, &QObject::destroyed, theEngine.data(), [=]() { | ||
| 1478 | EntityScriptDetails &details = theEngine.data()->_entityScripts[entityID]; | ||
| 1479 | if (details.status == EntityScriptStatus::PENDING || details.status == EntityScriptStatus::UNLOADED) { | ||
| 1480 | details.status = EntityScriptStatus::LOADING; | ||
| 1481 | emit entityScriptDetailsUpdated(); | ||
| 1482 | } | ||
| 1483 | }); | ||
| 1484 | } | ||
| 1485 | |||
| 1433 | // NOTE: If the script content is not currently in the cache, the LAMBDA here will be called on the Main Thread | 1486 | // NOTE: If the script content is not currently in the cache, the LAMBDA here will be called on the Main Thread |
| 1434 | // which means we're guaranteed that it's not the correct thread for the ScriptEngine. This means | 1487 | // which means we're guaranteed that it's not the correct thread for the ScriptEngine. This means |
| 1435 | // when we get into entityScriptContentAvailable() we will likely invokeMethod() to get it over | 1488 | // when we get into entityScriptContentAvailable() we will likely invokeMethod() to get it over |
| 1436 | // to the "Entities" ScriptEngine thread. | 1489 | // to the "Entities" ScriptEngine thread. |
| 1437 | DependencyManager::get<ScriptCache>()->getScriptContents(entityScript, [theEngine, entityID](const QString& scriptOrURL, const QString& contents, bool isURL, bool success) { | 1490 | DependencyManager::get<ScriptCache>()->getScriptContents(entityScript, [theEngine, entityID](const QString& scriptOrURL, const QString& contents, bool isURL, bool success, const QString &status) { |
| 1438 | QSharedPointer<ScriptEngine> strongEngine = theEngine.toStrongRef(); | 1491 | QSharedPointer<ScriptEngine> strongEngine = theEngine.toStrongRef(); |
| 1439 | if (strongEngine) { | 1492 | if (strongEngine) { |
| 1440 | #ifdef THREAD_DEBUGGING | 1493 | #ifdef THREAD_DEBUGGING |
| 1441 | qCDebug(scriptengine) << "ScriptEngine::entityScriptContentAvailable() IN LAMBDA contentAvailable on thread [" | 1494 | qCDebug(scriptengine) << "ScriptEngine::entityScriptContentAvailable() IN LAMBDA contentAvailable on thread [" |
| 1442 | << QThread::currentThread() << "] expected thread [" << strongEngine->thread() << "]"; | 1495 | << QThread::currentThread() << "] expected thread [" << strongEngine->thread() << "]"; |
| 1443 | #endif | 1496 | #endif |
| 1444 | strongEngine->entityScriptContentAvailable(entityID, scriptOrURL, contents, isURL, success); | 1497 | strongEngine->entityScriptContentAvailable(entityID, scriptOrURL, contents, isURL, success, status); |
| 1445 | } | 1498 | } |
| 1446 | }, forceRedownload); | 1499 | }, forceRedownload); |
| 1447 | } | 1500 | } |
| 1448 | 1501 | ||
| 1449 | // since all of these operations can be asynch we will always do the actual work in the response handler | 1502 | // since all of these operations can be asynch we will always do the actual work in the response handler |
| 1450 | // for the download | 1503 | // for the download |
| 1451 | void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success) { | 1504 | void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success , const QString& status) { |
| 1452 | if (QThread::currentThread() != thread()) { | 1505 | if (QThread::currentThread() != thread()) { |
| 1453 | #ifdef THREAD_DEBUGGING | 1506 | #ifdef THREAD_DEBUGGING |
| 1454 | qCDebug(scriptengine) << "*** WARNING *** ScriptEngine::entityScriptContentAvailable() called on wrong thread [" | 1507 | qCDebug(scriptengine) << "*** WARNING *** ScriptEngine::entityScriptContentAvailable() called on wrong thread [" |
| @@ -1462,7 +1515,8 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co · 7 unmodified lines | |||
| 1462 | Q_ARG(const QString&, scriptOrURL), | 1515 | Q_ARG(const QString&, scriptOrURL), |
| 1463 | Q_ARG(const QString&, contents), | 1516 | Q_ARG(const QString&, contents), |
| 1464 | Q_ARG(bool, isURL), | 1517 | Q_ARG(bool, isURL), |
| 1465 | Q_ARG(bool, success)); | 1518 | Q_ARG(bool, success), |
| 1519 | Q_ARG(const QString&, status)); | ||
| 1466 | return; | 1520 | return; |
| 1467 | } | 1521 | } |
| 1468 | 1522 | ||
| @@ -1478,22 +1532,21 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co · 9 unmodified lines | |||
| 1478 | newDetails.scriptText = scriptOrURL; | 1532 | newDetails.scriptText = scriptOrURL; |
| 1479 | 1533 | ||
| 1480 | if (!success) { | 1534 | if (!success) { |
| 1481 | newDetails.status = ERROR_LOADING_SCRIPT; | 1535 | newDetails.status = EntityScriptStatus::ERROR_LOADING_SCRIPT; |
| 1482 | newDetails.errorInfo = "Failed to load script"; | 1536 | newDetails.errorInfo = "Failed to load script (" + status + ")"; |
| 1483 | _entityScripts[entityID] = newDetails; | 1537 | _entityScripts[entityID] = newDetails; |
| 1484 | emit entityScriptDetailsUpdated(); | 1538 | emit entityScriptDetailsUpdated(); |
| 1485 | return; | 1539 | return; |
| 1486 | } | 1540 | } |
| 1487 | 1541 | ||
| 1488 | QScriptProgram program(contents, fileName); | 1542 | auto syntaxError = lintScript(contents, fileName); |
| 1489 | if (!hasCorrectSyntax(program, this)) { | 1543 | QScriptProgram program { contents, fileName }; |
| 1490 | if (!isFileUrl) { | 1544 | if (!syntaxError.isNull() || program.isNull()) { |
| 1491 | scriptCache->addScriptToBadScriptList(scriptOrURL); | 1545 | newDetails.status = EntityScriptStatus::ERROR_RUNNING_SCRIPT; |
| 1492 | } | 1546 | newDetails.errorInfo = QString("Bad syntax (%1)").arg(syntaxError); |
| 1493 | newDetails.status = ERROR_RUNNING_SCRIPT; | ||
| 1494 | newDetails.errorInfo = "Bad syntax"; | ||
| 1495 | _entityScripts[entityID] = newDetails; | 1547 | _entityScripts[entityID] = newDetails; |
| 1496 | emit entityScriptDetailsUpdated(); | 1548 | emit entityScriptDetailsUpdated(); |
| 1549 | qCDebug(scriptengine) << newDetails.errorInfo << scriptOrURL; | ||
| 1497 | return; // done processing script | 1550 | return; // done processing script |
| 1498 | } | 1551 | } |
| 1499 | 1552 | ||
| @@ -1502,16 +1555,18 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co · 2 unmodified lines | |||
| 1502 | } | 1555 | } |
| 1503 | 1556 | ||
| 1504 | const int SANDBOX_TIMEOUT = 0.25 * MSECS_PER_SECOND; | 1557 | const int SANDBOX_TIMEOUT = 0.25 * MSECS_PER_SECOND; |
| 1505 | QScriptEngine sandbox; | 1558 | BaseScriptEngine sandbox; |
| 1506 | sandbox.setProcessEventsInterval(SANDBOX_TIMEOUT); | 1559 | sandbox.setProcessEventsInterval(SANDBOX_TIMEOUT); |
| 1507 | QScriptValue testConstructor; | 1560 | QScriptValue testConstructor; |
| 1508 | { | 1561 | { |
| 1509 | QTimer timeout; | 1562 | QTimer timeout; |
| 1510 | timeout.setSingleShot(true); | 1563 | timeout.setSingleShot(true); |
| 1511 | timeout.start(SANDBOX_TIMEOUT); | 1564 | timeout.start(SANDBOX_TIMEOUT); |
| 1512 | connect(&timeout, &QTimer::timeout, [&sandbox, SANDBOX_TIMEOUT]{ | 1565 | connect(&timeout, &QTimer::timeout, [&sandbox, SANDBOX_TIMEOUT, scriptOrURL]{ |
| 1513 | auto context = sandbox.currentContext(); | 1566 | auto context = sandbox.currentContext(); |
| 1514 | if (context) { | 1567 | if (context) { |
| 1568 | qCDebug(scriptengine) << "ScriptEngine::entityScriptContentAvailable timeout(" << scriptOrURL << ")"; | ||
| 1569 | |||
| 1515 | // Guard against infinite loops and non-performant code | 1570 | // Guard against infinite loops and non-performant code |
| 1516 | context->throwError(QString("Timed out (entity constructors are limited to %1ms)").arg(SANDBOX_TIMEOUT)); | 1571 | context->throwError(QString("Timed out (entity constructors are limited to %1ms)").arg(SANDBOX_TIMEOUT)); |
| 1517 | } | 1572 | } |
| @@ -1519,13 +1574,13 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co · 1 unmodified line | |||
| 1519 | testConstructor = sandbox.evaluate(program); | 1574 | testConstructor = sandbox.evaluate(program); |
| 1520 | } | 1575 | } |
| 1521 | 1576 | ||
| 1522 | QString exceptionMessage; | 1577 | QString exceptionMessage = sandbox.formatUncaughtException(program.fileName()); |
| 1523 | if (hadUncaughtExceptions(sandbox, program.fileName(), this, &exceptionMessage)) { | 1578 | if (!exceptionMessage.isNull()) { |
| 1524 | newDetails.status = ERROR_RUNNING_SCRIPT; | 1579 | newDetails.status = EntityScriptStatus::ERROR_RUNNING_SCRIPT; |
| 1525 | newDetails.errorInfo = exceptionMessage; | 1580 | newDetails.errorInfo = exceptionMessage; |
| 1526 | _entityScripts[entityID] = newDetails; | 1581 | _entityScripts[entityID] = newDetails; |
| 1527 | emit entityScriptDetailsUpdated(); | 1582 | emit entityScriptDetailsUpdated(); |
| 1528 | 1583 | qCDebug(scriptengine) << "----- ScriptEngine::entityScriptContentAvailable -- hadUncaughtExceptions (" << scriptOrURL << ")"; | |
| 1529 | return; | 1584 | return; |
| 1530 | } | 1585 | } |
| 1531 | 1586 | ||
| @@ -1544,15 +1599,12 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co · 12 unmodified lines | |||
| 1544 | + "," + testConstructorValue | 1599 | + "," + testConstructorValue |
| 1545 | + "," + scriptOrURL); | 1600 | + "," + scriptOrURL); |
| 1546 | 1601 | ||
| 1547 | if (!isFileUrl) { | 1602 | newDetails.status = EntityScriptStatus::ERROR_RUNNING_SCRIPT; |
| 1548 | scriptCache->addScriptToBadScriptList(scriptOrURL); | ||
| 1549 | } | ||
| 1550 | |||
| 1551 | newDetails.status = ERROR_RUNNING_SCRIPT; | ||
| 1552 | newDetails.errorInfo = "Could not find constructor"; | 1603 | newDetails.errorInfo = "Could not find constructor"; |
| 1553 | _entityScripts[entityID] = newDetails; | 1604 | _entityScripts[entityID] = newDetails; |
| 1554 | emit entityScriptDetailsUpdated(); | 1605 | emit entityScriptDetailsUpdated(); |
| 1555 | 1606 | ||
| 1607 | qCDebug(scriptengine) << "----- ScriptEngine::entityScriptContentAvailable -- failed to run (" << scriptOrURL << ")"; | ||
| 1556 | return; // done processing script | 1608 | return; // done processing script |
| 1557 | } | 1609 | } |
| 1558 | 1610 | ||
| @@ -1569,6 +1621,7 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co · 10 unmodified lines | |||
| 1569 | }; | 1621 | }; |
| 1570 | doWithEnvironment(entityID, sandboxURL, initialization); | 1622 | doWithEnvironment(entityID, sandboxURL, initialization); |
| 1571 | 1623 | ||
| 1624 | newDetails.status = EntityScriptStatus::RUNNING; | ||
| 1572 | newDetails.scriptObject = entityScriptObject; | 1625 | newDetails.scriptObject = entityScriptObject; |
| 1573 | newDetails.lastModified = lastModified; | 1626 | newDetails.lastModified = lastModified; |
| 1574 | newDetails.definingSandboxURL = sandboxURL; | 1627 | newDetails.definingSandboxURL = sandboxURL; |
| @@ -1600,10 +1653,12 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID) { · 25 unmodified lines | |||
| 1600 | #endif | 1653 | #endif |
| 1601 | 1654 | ||
| 1602 | if (_entityScripts.contains(entityID)) { | 1655 | if (_entityScripts.contains(entityID)) { |
| 1603 | if (_entityScripts[entityID].status == RUNNING) { | 1656 | if (isEntityScriptRunning(entityID)) { |
| 1604 | callEntityScriptMethod(entityID, "unload"); | 1657 | callEntityScriptMethod(entityID, "unload"); |
| 1605 | } | 1658 | } |
| 1606 | _entityScripts.remove(entityID); | 1659 | EntityScriptDetails details; |
| 1660 | details.status = EntityScriptStatus::UNLOADED; | ||
| 1661 | _entityScripts[entityID] = details; | ||
| 1607 | stopAllTimersForEntityScript(entityID); | 1662 | stopAllTimersForEntityScript(entityID); |
| 1608 | emit entityScriptDetailsUpdated(); | 1663 | emit entityScriptDetailsUpdated(); |
| 1609 | } | 1664 | } |
| @@ -1622,9 +1677,7 @@ void ScriptEngine::unloadAllEntityScripts() { · 12 unmodified lines | |||
| 1622 | qCDebug(scriptengine) << "ScriptEngine::unloadAllEntityScripts() called on correct thread [" << thread() << "]"; | 1677 | qCDebug(scriptengine) << "ScriptEngine::unloadAllEntityScripts() called on correct thread [" << thread() << "]"; |
| 1623 | #endif | 1678 | #endif |
| 1624 | foreach(const EntityItemID& entityID, _entityScripts.keys()) { | 1679 | foreach(const EntityItemID& entityID, _entityScripts.keys()) { |
| 1625 | if (_entityScripts[entityID].status == RUNNING) { | 1680 | unloadEntityScript(entityID); |
| 1626 | callEntityScriptMethod(entityID, "unload"); | ||
| 1627 | } | ||
| 1628 | } | 1681 | } |
| 1629 | _entityScripts.clear(); | 1682 | _entityScripts.clear(); |
| 1630 | emit entityScriptDetailsUpdated(); | 1683 | emit entityScriptDetailsUpdated(); |
| @@ -1641,7 +1694,7 @@ void ScriptEngine::unloadAllEntityScripts() { · 10 unmodified lines | |||
| 1641 | } | 1694 | } |
| 1642 | 1695 | ||
| 1643 | void ScriptEngine::refreshFileScript(const EntityItemID& entityID) { | 1696 | void ScriptEngine::refreshFileScript(const EntityItemID& entityID) { |
| 1644 | if (!_entityScripts.contains(entityID)) { | 1697 | if (!HIFI_AUTOREFRESH_FILE_SCRIPTS || !_entityScripts.contains(entityID)) { |
| 1645 | return; | 1698 | return; |
| 1646 | } | 1699 | } |
| 1647 | 1700 | ||
| @@ -1663,8 +1716,8 @@ void ScriptEngine::refreshFileScript(const EntityItemID& entityID) { · 15 unmodified lines | |||
| 1663 | file.open(QIODevice::ReadOnly); | 1716 | file.open(QIODevice::ReadOnly); |
| 1664 | QString scriptContents = QTextStream(&file).readAll(); | 1717 | QString scriptContents = QTextStream(&file).readAll(); |
| 1665 | this->unloadEntityScript(entityID); | 1718 | this->unloadEntityScript(entityID); |
| 1666 | this->entityScriptContentAvailable(entityID, details.scriptText, scriptContents, true, true); | 1719 | this->entityScriptContentAvailable(entityID, details.scriptText, scriptContents, true, true, "Success"); |
| 1667 | if (!_entityScripts.contains(entityID) || _entityScripts[entityID].status != RUNNING) { | 1720 | if (!isEntityScriptRunning(entityID)) { |
| 1668 | scriptWarningMessage("Reload script " + details.scriptText + " failed"); | 1721 | scriptWarningMessage("Reload script " + details.scriptText + " failed"); |
| 1669 | } else { | 1722 | } else { |
| 1670 | details = _entityScripts[entityID]; | 1723 | details = _entityScripts[entityID]; |
| @@ -1692,7 +1745,10 @@ void ScriptEngine::doWithEnvironment(const EntityItemID& entityID, const QUrl& s · 21 unmodified lines | |||
| 1692 | #else | 1745 | #else |
| 1693 | operation(); | 1746 | operation(); |
| 1694 | #endif | 1747 | #endif |
| 1695 | hadUncaughtExceptions(*this, _fileNameString, this); | 1748 | if (hasUncaughtException()) { |
| 1749 | reportUncaughtException(); | ||
| 1750 | clearExceptions(); | ||
| 1751 | } | ||
| 1696 | 1752 | ||
| 1697 | currentEntityIdentifier = oldIdentifier; | 1753 | currentEntityIdentifier = oldIdentifier; |
| 1698 | currentSandboxURL = oldSandboxURL; | 1754 | currentSandboxURL = oldSandboxURL; |
| @@ -1722,8 +1778,10 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS · 23 unmodified lines | |||
| 1722 | "entityID:" << entityID << "methodName:" << methodName; | 1778 | "entityID:" << entityID << "methodName:" << methodName; |
| 1723 | #endif | 1779 | #endif |
| 1724 | 1780 | ||
| 1725 | refreshFileScript(entityID); | 1781 | if (HIFI_AUTOREFRESH_FILE_SCRIPTS && methodName != "unload") { |
| 1726 | if (_entityScripts.contains(entityID) && _entityScripts[entityID].status == RUNNING) { | 1782 | refreshFileScript(entityID); |
| 1783 | } | ||
| 1784 | if (isEntityScriptRunning(entityID)) { | ||
| 1727 | EntityScriptDetails details = _entityScripts[entityID]; | 1785 | EntityScriptDetails details = _entityScripts[entityID]; |
| 1728 | QScriptValue entityScript = details.scriptObject; // previously loaded | 1786 | QScriptValue entityScript = details.scriptObject; // previously loaded |
| 1729 | if (entityScript.property(methodName).isFunction()) { | 1787 | if (entityScript.property(methodName).isFunction()) { |
| @@ -1754,8 +1812,10 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS · 24 unmodified lines | |||
| 1754 | "entityID:" << entityID << "methodName:" << methodName << "event: pointerEvent"; | 1812 | "entityID:" << entityID << "methodName:" << methodName << "event: pointerEvent"; |
| 1755 | #endif | 1813 | #endif |
| 1756 | 1814 | ||
| 1757 | refreshFileScript(entityID); | 1815 | if (HIFI_AUTOREFRESH_FILE_SCRIPTS) { |
| 1758 | if (_entityScripts.contains(entityID) && _entityScripts[entityID].status == RUNNING) { | 1816 | refreshFileScript(entityID); |
| 1817 | } | ||
| 1818 | if (isEntityScriptRunning(entityID)) { | ||
| 1759 | EntityScriptDetails details = _entityScripts[entityID]; | 1819 | EntityScriptDetails details = _entityScripts[entityID]; |
| 1760 | QScriptValue entityScript = details.scriptObject; // previously loaded | 1820 | QScriptValue entityScript = details.scriptObject; // previously loaded |
| 1761 | if (entityScript.property(methodName).isFunction()) { | 1821 | if (entityScript.property(methodName).isFunction()) { |
| @@ -1787,8 +1847,10 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS · 25 unmodified lines | |||
| 1787 | "entityID:" << entityID << "methodName:" << methodName << "otherID:" << otherID << "collision: collision"; | 1847 | "entityID:" << entityID << "methodName:" << methodName << "otherID:" << otherID << "collision: collision"; |
| 1788 | #endif | 1848 | #endif |
| 1789 | 1849 | ||
| 1790 | refreshFileScript(entityID); | 1850 | if (HIFI_AUTOREFRESH_FILE_SCRIPTS) { |
| 1791 | if (_entityScripts.contains(entityID) && _entityScripts[entityID].status == RUNNING) { | 1851 | refreshFileScript(entityID); |
| 1852 | } | ||
| 1853 | if (isEntityScriptRunning(entityID)) { | ||
| 1792 | EntityScriptDetails details = _entityScripts[entityID]; | 1854 | EntityScriptDetails details = _entityScripts[entityID]; |
| 1793 | QScriptValue entityScript = details.scriptObject; // previously loaded | 1855 | QScriptValue entityScript = details.scriptObject; // previously loaded |
| 1794 | if (entityScript.property(methodName).isFunction()) { | 1856 | if (entityScript.property(methodName).isFunction()) { |