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