Showing the changes to libraries/script-engine/src/ScriptEngine.cpp (1 of 2 files). Show all
Unified · Split
| @@ -1494,6 +1494,7 @@ bool ScriptEngine::getEntityScriptDetails(const EntityItemID& entityID, EntitySc · 1493 unmodified lines | |||
| 1494 | 1494 | ||
| 1495 | const auto RETRY_ATTEMPT_BATCH = 100; //glm::clamp(qgetenv("RETRY_ATTEMPT_BATCH").toInt(), 1, 50); | 1495 | const auto RETRY_ATTEMPT_BATCH = 100; //glm::clamp(qgetenv("RETRY_ATTEMPT_BATCH").toInt(), 1, 50); |
| 1496 | const auto RETRY_ATTEMPT_MSECS = 250; //glm::clamp(qgetenv("RETRY_ATTEMPT_MSECS").toInt(), 50, 1000); | 1496 | const auto RETRY_ATTEMPT_MSECS = 250; //glm::clamp(qgetenv("RETRY_ATTEMPT_MSECS").toInt(), 50, 1000); |
| 1497 | const static EntityItemID BAD_SCRIPT_UUID_PLACEHOLDER { "{20170224-dead-face-0000-cee000021114}" }; | ||
| 1497 | 1498 | ||
| 1498 | void ScriptEngine::deferLoadEntityScript(const EntityItemID& entityID, const QString& entityScript, bool forceRedownload) { | 1499 | void ScriptEngine::deferLoadEntityScript(const EntityItemID& entityID, const QString& entityScript, bool forceRedownload) { |
| 1499 | const auto needTimer = _pendingLoadEntities.isEmpty(); | 1500 | const auto needTimer = _pendingLoadEntities.isEmpty(); |
| @@ -1513,12 +1514,18 @@ void ScriptEngine::deferLoadEntityScript(const EntityItemID& entityID, const QSt · 13 unmodified lines | |||
| 1513 | if (details.status != EntityScriptStatus::PENDING) { | 1514 | if (details.status != EntityScriptStatus::PENDING) { |
| 1514 | qCDebug(scriptengine) << "deferLoadEntityScript.retry -- entity status no longer PENDING; " << details.status; | 1515 | qCDebug(scriptengine) << "deferLoadEntityScript.retry -- entity status no longer PENDING; " << details.status; |
| 1515 | } else { | 1516 | } else { |
| 1516 | auto &locker = _lockPerUniqueScriptURL[retry.entityScript]; | 1517 | if (!_occupiedScriptURLs.contains(retry.entityScript)) { |
| 1517 | if (locker.tryLock()) { | 1518 | // the URL is no longer busy with another Entity, so now it's our turn |
| 1518 | locker.unlock(); | ||
| 1519 | loadEntityScript(retry.entityID, retry.entityScript, false); | 1519 | loadEntityScript(retry.entityID, retry.entityScript, false); |
| 1520 | } else { | 1520 | } else { |
| 1521 | stillPending << retry; | 1521 | auto entityID = _occupiedScriptURLs[retry.entityScript]; |
| 1522 | if (entityID == BAD_SCRIPT_UUID_PLACEHOLDER) { | ||
| 1523 | // the URL was marked bad by an earlier load attempt, so abort the retry mission | ||
| 1524 | qCDebug(scriptengine) << "... pending load of " << retry.entityID << " cancelled"; | ||
| 1525 | } else { | ||
| 1526 | // the URL is still occupied by another Entity, so add to next retry pass | ||
| 1527 | stillPending << retry; | ||
| 1528 | } | ||
| 1522 | } | 1529 | } |
| 1523 | } | 1530 | } |
| 1524 | } | 1531 | } |
| @@ -1543,7 +1550,7 @@ void ScriptEngine::deferLoadEntityScript(const EntityItemID& entityID, const QSt · 18 unmodified lines | |||
| 1543 | 1550 | ||
| 1544 | // processDeferredEntities gets wrapped as a "regular" JavaScript callback so that it can then | 1551 | // processDeferredEntities gets wrapped as a "regular" JavaScript callback so that it can then |
| 1545 | // be scheduled using setInterval per usual -- allowing the script engine to own its lifecycle. | 1552 | // be scheduled using setInterval per usual -- allowing the script engine to own its lifecycle. |
| 1546 | // we stil need to call clearInterval manually when done, so its passed to calle() above as a property. | 1553 | // we stil need to call clearInterval manually when done, so its passed to callee() above as a property. |
| 1547 | 1554 | ||
| 1548 | QScriptValue function = newLambdaFunction(processDeferredEntities, "processDeferredEntities-loader"); | 1555 | QScriptValue function = newLambdaFunction(processDeferredEntities, "processDeferredEntities-loader"); |
| 1549 | QObject *interval = setInterval( function, RETRY_ATTEMPT_MSECS ); | 1556 | QObject *interval = setInterval( function, RETRY_ATTEMPT_MSECS ); |
| @@ -1577,14 +1584,28 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& · 27 unmodified lines | |||
| 1577 | updateEntityScriptStatus(entityID, EntityScriptStatus::PENDING, "...pending..."); | 1584 | updateEntityScriptStatus(entityID, EntityScriptStatus::PENDING, "...pending..."); |
| 1578 | } | 1585 | } |
| 1579 | 1586 | ||
| 1580 | // This lock/defer approach allows multiple Entities to boot from the same script URL while still taking | 1587 | // This "occupied" approach allows multiple Entities to boot from the same script URL while still taking |
| 1581 | // full advantage of cacheable require modules. This only affects Entities literally coming in back-to-back | 1588 | // full advantage of cacheable require modules. This only affects Entities literally coming in back-to-back |
| 1582 | // before the first one has time to finish loading. | 1589 | // before the first one has time to finish loading. |
| 1583 | auto &locker = _lockPerUniqueScriptURL[entityScript]; | 1590 | if (!_occupiedScriptURLs.contains(entityScript)) { |
| 1584 | if (!locker.tryLock()) { | 1591 | // the scriptURL is available -- flag as in-use and proceed |
| 1585 | qCDebug(scriptengine) << QString("loadEntityScript.deferring[%1] entity: %2 script: %3") | 1592 | _occupiedScriptURLs[entityScript] = entityID; |
| 1586 | .arg( _pendingLoadEntities.size()).arg(entityID.toString()).arg(entityScript); | 1593 | } else { |
| 1587 | deferLoadEntityScript(entityID, entityScript, forceRedownload); | 1594 | // another Entity is either still booting from this script URL or has failed and marked it "out of order" |
| 1595 | auto currentEntityID = _occupiedScriptURLs[entityScript]; | ||
| 1596 | if (currentEntityID == BAD_SCRIPT_UUID_PLACEHOLDER && !forceRedownload) { | ||
| 1597 | // since not force-reloading, assume that the exact same input would produce the exact same output again | ||
| 1598 | // note: this state gets reset with "reload all scripts," leaving/returning to a Domain, clear cache, etc. | ||
| 1599 | qCDebug(scriptengine) << QString("loadEntityScript.cancelled entity: %1 script: %2 (previous script failure)") | ||
| 1600 | .arg(entityID.toString()).arg(entityScript); | ||
| 1601 | updateEntityScriptStatus(entityID, EntityScriptStatus::ERROR_LOADING_SCRIPT, | ||
| 1602 | "Cached Script was marked invalid by previous Entity load failure."); | ||
| 1603 | } else { | ||
| 1604 | // another entity is busy loading from this script URL so wait for them to finish | ||
| 1605 | qCDebug(scriptengine) << QString("loadEntityScript.deferring[%0] entity: %1 script: %2") | ||
| 1606 | .arg( _pendingLoadEntities.size()).arg(entityID.toString()).arg(entityScript); | ||
| 1607 | deferLoadEntityScript(entityID, entityScript, forceRedownload); | ||
| 1608 | } | ||
| 1588 | return; | 1609 | return; |
| 1589 | } | 1610 | } |
| 1590 | 1611 | ||
| @@ -1598,19 +1619,18 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& · 7 unmodified lines | |||
| 1598 | newDetails.scriptText = entityScript; | 1619 | newDetails.scriptText = entityScript; |
| 1599 | newDetails.status = EntityScriptStatus::LOADING; | 1620 | newDetails.status = EntityScriptStatus::LOADING; |
| 1600 | newDetails.definingSandboxURL = currentSandboxURL; | 1621 | newDetails.definingSandboxURL = currentSandboxURL; |
| 1601 | _entityScripts[entityID] = newDetails; | 1622 | setEntityScriptDetails(entityID, newDetails); |
| 1602 | 1623 | ||
| 1603 | auto scriptCache = DependencyManager::get<ScriptCache>(); | 1624 | auto scriptCache = DependencyManager::get<ScriptCache>(); |
| 1604 | scriptCache->getScriptContents(entityScript, | 1625 | scriptCache->getScriptContents(entityScript, |
| 1605 | [=, &locker](const QString& url, const QString& contents, bool isURL, bool success, const QString& status) { | 1626 | [this, entityScript, entityID](const QString& url, const QString& contents, bool isURL, bool success, const QString& status) { |
| 1606 | if (isStopping()) { | 1627 | if (isStopping()) { |
| 1607 | #ifdef DEBUG_ENTITY_STATES | 1628 | #ifdef DEBUG_ENTITY_STATES |
| 1608 | qCDebug(scriptengine) << "loadEntityScript.contentAvailable -- stopping"; | 1629 | qCDebug(scriptengine) << "loadEntityScript.contentAvailable -- stopping"; |
| 1609 | #endif | 1630 | #endif |
| 1610 | locker.unlock(); | ||
| 1611 | return; | 1631 | return; |
| 1612 | } | 1632 | } |
| 1613 | executeOnScriptThread([=, &locker]{ | 1633 | executeOnScriptThread([=]{ |
| 1614 | #ifdef DEBUG_ENTITY_STATES | 1634 | #ifdef DEBUG_ENTITY_STATES |
| 1615 | qCDebug(scriptengine) << "loadEntityScript.contentAvailable" << status << QUrl(url).fileName() << entityID.toString(); | 1635 | qCDebug(scriptengine) << "loadEntityScript.contentAvailable" << status << QUrl(url).fileName() << entityID.toString(); |
| 1616 | #endif | 1636 | #endif |
| @@ -1621,7 +1641,10 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& · 4 unmodified lines | |||
| 1621 | qCDebug(scriptengine) << "loadEntityScript.contentAvailable -- aborting"; | 1641 | qCDebug(scriptengine) << "loadEntityScript.contentAvailable -- aborting"; |
| 1622 | #endif | 1642 | #endif |
| 1623 | } | 1643 | } |
| 1624 | locker.unlock(); | 1644 | // recheck whether us since may have been set to BAD_SCRIPT_UUID_PLACEHOLDER in entityScriptContentAvailable |
| 1645 | if (_occupiedScriptURLs[entityScript] == entityID) { | ||
| 1646 | _occupiedScriptURLs.remove(entityScript); | ||
| 1647 | } | ||
| 1625 | }); | 1648 | }); |
| 1626 | }, forceRedownload); | 1649 | }, forceRedownload); |
| 1627 | } | 1650 | } |
| @@ -1666,16 +1689,10 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co · 38 unmodified lines | |||
| 1666 | newDetails.status = status; | 1689 | newDetails.status = status; |
| 1667 | setEntityScriptDetails(entityID, newDetails); | 1690 | setEntityScriptDetails(entityID, newDetails); |
| 1668 | 1691 | ||
| 1669 | QMutableListIterator<DeferredLoadEntity> i(_pendingLoadEntities); | 1692 | qCDebug(scriptengine) << "entityScriptContentAvailable -- flagging " << oldDetails.scriptText << " as BAD_SCRIPT_UUID_PLACEHOLDER"; |
| 1693 | // flag this scriptURL as unusuable | ||
| 1670 | // note: we want the *original* entityScript string (not the potentially normalized one from ScriptCache results) | 1694 | // note: we want the *original* entityScript string (not the potentially normalized one from ScriptCache results) |
| 1671 | const auto entityScript = oldDetails.scriptText; | 1695 | _occupiedScriptURLs[oldDetails.scriptText] = BAD_SCRIPT_UUID_PLACEHOLDER; |
| 1672 | while (i.hasNext()) { | ||
| 1673 | auto retry = i.next(); | ||
| 1674 | if (retry.entityScript == entityScript) { | ||
| 1675 | qCDebug(scriptengine) << "... pending load of " << retry.entityID << " cancelled"; | ||
| 1676 | i.remove(); | ||
| 1677 | } | ||
| 1678 | } | ||
| 1679 | }; | 1696 | }; |
| 1680 | 1697 | ||
| 1681 | // NETWORK / FILESYSTEM ERRORS | 1698 | // NETWORK / FILESYSTEM ERRORS |
| @@ -1856,7 +1873,7 @@ void ScriptEngine::unloadAllEntityScripts() { · 174 unmodified lines | |||
| 1856 | } | 1873 | } |
| 1857 | _entityScripts.clear(); | 1874 | _entityScripts.clear(); |
| 1858 | emit entityScriptDetailsUpdated(); | 1875 | emit entityScriptDetailsUpdated(); |
| 1859 | _lockPerUniqueScriptURL.clear(); | 1876 | _occupiedScriptURLs.clear(); |
| 1860 | 1877 | ||
| 1861 | #ifdef DEBUG_ENGINE_STATE | 1878 | #ifdef DEBUG_ENGINE_STATE |
| 1862 | _debugDump( | 1879 | _debugDump( |