diff --git a/interface/resources/images/interstitialPage/goTo_button.png b/interface/resources/images/interstitialPage/goTo_button.png new file mode 100644 index 0000000000..7c1b0d8500 Binary files /dev/null and b/interface/resources/images/interstitialPage/goTo_button.png differ diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index 2b5ca9ae8c..729bfd9e45 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -45,10 +45,11 @@ const float LOD_ADJUST_RUNNING_AVG_TIMESCALE = 0.08f; // sec const float LOD_BATCH_TO_PRESENT_CUSHION_TIME = 3.0f; // msec void LODManager::setRenderTimes(float presentTime, float engineRunTime, float batchTime, float gpuTime) { - _presentTime = presentTime; - _engineRunTime = engineRunTime; - _batchTime = batchTime; - _gpuTime = gpuTime; + // Make sure the sampled time are positive values + _presentTime = std::max(0.0f, presentTime); + _engineRunTime = std::max(0.0f, engineRunTime); + _batchTime = std::max(0.0f, batchTime); + _gpuTime = std::max(0.0f, gpuTime); } void LODManager::autoAdjustLOD(float realTimeDelta) { @@ -64,16 +65,29 @@ void LODManager::autoAdjustLOD(float realTimeDelta) { auto presentTime = (_presentTime > _batchTime + LOD_BATCH_TO_PRESENT_CUSHION_TIME ? _batchTime + LOD_BATCH_TO_PRESENT_CUSHION_TIME : _presentTime); float maxRenderTime = glm::max(glm::max(presentTime, _engineRunTime), _gpuTime); - // compute time-weighted running average maxRenderTime - // Note: we MUST clamp the blend to 1.0 for stability + // maxRenderTime must be a realistic valid duration in order for the regulation to work correctly. + // We make sure it s a non zero positive value (1.0ms) under 1 sec + maxRenderTime = std::max(1.0f, std::min(maxRenderTime, (float)MSECS_PER_SECOND)); + + // realTimeDelta must be a realistic valid duration in order for the regulation to work correctly. + // We make sure it a positive value under 1 sec + // note that if real time delta is very small we will early exit to avoid division by zero + realTimeDelta = std::max(0.0f, std::min(realTimeDelta, 1.0f)); + + // compute time-weighted running average render time (now and smooth) + // We MUST clamp the blend between 0.0 and 1.0 for stability float nowBlend = (realTimeDelta < LOD_ADJUST_RUNNING_AVG_TIMESCALE) ? realTimeDelta / LOD_ADJUST_RUNNING_AVG_TIMESCALE : 1.0f; - _nowRenderTime = (1.0f - nowBlend) * _nowRenderTime + nowBlend * maxRenderTime; // msec - float smoothBlend = (realTimeDelta < LOD_ADJUST_RUNNING_AVG_TIMESCALE * _smoothScale) ? realTimeDelta / (LOD_ADJUST_RUNNING_AVG_TIMESCALE * _smoothScale) : 1.0f; - _smoothRenderTime = (1.0f - smoothBlend) * _smoothRenderTime + smoothBlend * maxRenderTime; // msec - if (!_automaticLODAdjust || _nowRenderTime == 0.0f || _smoothRenderTime == 0.0f) { - // early exit + //Evaluate the running averages for the render time + // We must sanity check for the output average evaluated to be in a valid range to avoid issues + _nowRenderTime = (1.0f - nowBlend) * _nowRenderTime + nowBlend * maxRenderTime; // msec + _nowRenderTime = std::max(0.0f, std::min(_nowRenderTime, (float)MSECS_PER_SECOND)); + _smoothRenderTime = (1.0f - smoothBlend) * _smoothRenderTime + smoothBlend * maxRenderTime; // msec + _smoothRenderTime = std::max(0.0f, std::min(_smoothRenderTime, (float)MSECS_PER_SECOND)); + + // Early exit if not regulating or if the simulation or render times don't matter + if (!_automaticLODAdjust || realTimeDelta <= 0.0f || _nowRenderTime <= 0.0f || _smoothRenderTime <= 0.0f) { return; } @@ -130,7 +144,8 @@ void LODManager::autoAdjustLOD(float realTimeDelta) { glm::clamp(integral, -1.0f, 1.0f); // Compute derivative - auto derivative = (error - previous_error) / dt; + // dt is never zero because realTimeDelta would have early exit above, but if it ever was let's zero the derivative term + auto derivative = (dt <= 0.0f ? 0.0f : (error - previous_error) / dt); // remember history _pidHistory.x = error; diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 7f2a992f68..cfd155e14b 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -1974,8 +1974,9 @@ void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QStrin } int ScriptEngine::getNumRunningEntityScripts() const { + QReadLocker locker { &_entityScriptsLock }; int sum = 0; - for (auto& st : _entityScripts) { + for (const auto& st : _entityScripts) { if (st.status == EntityScriptStatus::RUNNING) { ++sum; } @@ -1984,14 +1985,20 @@ int ScriptEngine::getNumRunningEntityScripts() const { } void ScriptEngine::setEntityScriptDetails(const EntityItemID& entityID, const EntityScriptDetails& details) { - _entityScripts[entityID] = details; + { + QWriteLocker locker { &_entityScriptsLock }; + _entityScripts[entityID] = details; + } emit entityScriptDetailsUpdated(); } void ScriptEngine::updateEntityScriptStatus(const EntityItemID& entityID, const EntityScriptStatus &status, const QString& errorInfo) { - EntityScriptDetails &details = _entityScripts[entityID]; - details.status = status; - details.errorInfo = errorInfo; + { + QWriteLocker locker { &_entityScriptsLock }; + EntityScriptDetails& details = _entityScripts[entityID]; + details.status = status; + details.errorInfo = errorInfo; + } emit entityScriptDetailsUpdated(); } @@ -2042,6 +2049,7 @@ QFuture ScriptEngine::getLocalEntityScriptDetails(const EntityItemID& } bool ScriptEngine::getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const { + QReadLocker locker { &_entityScriptsLock }; auto it = _entityScripts.constFind(entityID); if (it == _entityScripts.constEnd()) { return false; @@ -2050,6 +2058,11 @@ bool ScriptEngine::getEntityScriptDetails(const EntityItemID& entityID, EntitySc return true; } +bool ScriptEngine::hasEntityScriptDetails(const EntityItemID& entityID) const { + QReadLocker locker { &_entityScriptsLock }; + return _entityScripts.contains(entityID); +} + const static EntityItemID BAD_SCRIPT_UUID_PLACEHOLDER { "{20170224-dead-face-0000-cee000021114}" }; void ScriptEngine::processDeferredEntityLoads(const QString& entityScript, const EntityItemID& leaderID) { @@ -2064,14 +2077,15 @@ void ScriptEngine::processDeferredEntityLoads(const QString& entityScript, const } foreach(DeferredLoadEntity retry, retryLoads) { // check whether entity was since been deleted - if (!_entityScripts.contains(retry.entityID)) { + + EntityScriptDetails details; + if (!getEntityScriptDetails(retry.entityID, details)) { qCDebug(scriptengine) << "processDeferredEntityLoads -- entity details gone (entity deleted?)" << retry.entityID; continue; } // check whether entity has since been unloaded or otherwise errored-out - auto details = _entityScripts[retry.entityID]; if (details.status != EntityScriptStatus::PENDING) { qCDebug(scriptengine) << "processDeferredEntityLoads -- entity status no longer PENDING; " << retry.entityID << details.status; @@ -2079,7 +2093,11 @@ void ScriptEngine::processDeferredEntityLoads(const QString& entityScript, const } // propagate leader's failure reasons to the pending entity - const auto leaderDetails = _entityScripts[leaderID]; + EntityScriptDetails leaderDetails; + { + QWriteLocker locker { &_entityScriptsLock }; + leaderDetails = _entityScripts[leaderID]; + } if (leaderDetails.status != EntityScriptStatus::RUNNING) { qCDebug(scriptengine) << QString("... pending load of %1 cancelled (leader: %2 status: %3)") .arg(retry.entityID.toString()).arg(leaderID.toString()).arg(leaderDetails.status); @@ -2125,7 +2143,7 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& return; } - if (!_entityScripts.contains(entityID)) { + if (!hasEntityScriptDetails(entityID)) { // make sure EntityScriptDetails has an entry for this UUID right away // (which allows bailing from the loading/provisioning process early if the Entity gets deleted mid-flight) updateEntityScriptStatus(entityID, EntityScriptStatus::PENDING, "...pending..."); @@ -2166,9 +2184,12 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& _occupiedScriptURLs[entityScript] = entityID; #ifdef DEBUG_ENTITY_STATES - auto previousStatus = _entityScripts.contains(entityID) ? _entityScripts[entityID].status : EntityScriptStatus::PENDING; - qCDebug(scriptengine) << "loadEntityScript.LOADING: " << entityScript << entityID.toString() - << "(previous: " << previousStatus << ")"; + { + EntityScriptDetails details; + bool hasEntityScript = getEntityScriptDetails(entityID, details); + qCDebug(scriptengine) << "loadEntityScript.LOADING: " << entityScript << entityID.toString() + << "(previous: " << (hasEntityScript ? details.status : EntityScriptStatus::PENDING) << ")"; + } #endif EntityScriptDetails newDetails; @@ -2197,7 +2218,7 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& #ifdef DEBUG_ENTITY_STATES qCDebug(scriptengine) << "loadEntityScript.contentAvailable" << status << QUrl(url).fileName() << entityID.toString(); #endif - if (!isStopping() && _entityScripts.contains(entityID)) { + if (!isStopping() && hasEntityScriptDetails(entityID)) { _contentAvailableQueue[entityID] = { entityID, url, contents, isURL, success, status }; } else { #ifdef DEBUG_ENTITY_STATES @@ -2267,8 +2288,11 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co bool isFileUrl = isURL && scriptOrURL.startsWith("file://"); auto fileName = isURL ? scriptOrURL : "about:EmbeddedEntityScript"; - const EntityScriptDetails &oldDetails = _entityScripts[entityID]; - const QString entityScript = oldDetails.scriptText; + QString entityScript; + { + QWriteLocker locker { &_entityScriptsLock }; + entityScript = _entityScripts[entityID].scriptText; + } EntityScriptDetails newDetails; newDetails.scriptText = scriptOrURL; @@ -2446,8 +2470,8 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID, bool shouldR "entityID:" << entityID; #endif - if (_entityScripts.contains(entityID)) { - const EntityScriptDetails &oldDetails = _entityScripts[entityID]; + EntityScriptDetails oldDetails; + if (getEntityScriptDetails(entityID, oldDetails)) { auto scriptText = oldDetails.scriptText; if (isEntityScriptRunning(entityID)) { @@ -2460,7 +2484,10 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID, bool shouldR #endif if (shouldRemoveFromMap) { // this was a deleted entity, we've been asked to remove it from the map - _entityScripts.remove(entityID); + { + QWriteLocker locker { &_entityScriptsLock }; + _entityScripts.remove(entityID); + } emit entityScriptDetailsUpdated(); } else if (oldDetails.status != EntityScriptStatus::UNLOADED) { EntityScriptDetails newDetails; @@ -2491,10 +2518,19 @@ void ScriptEngine::unloadAllEntityScripts() { #ifdef THREAD_DEBUGGING qCDebug(scriptengine) << "ScriptEngine::unloadAllEntityScripts() called on correct thread [" << thread() << "]"; #endif - foreach(const EntityItemID& entityID, _entityScripts.keys()) { + + QList keys; + { + QReadLocker locker{ &_entityScriptsLock }; + keys = _entityScripts.keys(); + } + foreach(const EntityItemID& entityID, keys) { unloadEntityScript(entityID); } - _entityScripts.clear(); + { + QWriteLocker locker{ &_entityScriptsLock }; + _entityScripts.clear(); + } emit entityScriptDetailsUpdated(); _occupiedScriptURLs.clear(); @@ -2508,7 +2544,7 @@ void ScriptEngine::unloadAllEntityScripts() { } void ScriptEngine::refreshFileScript(const EntityItemID& entityID) { - if (!HIFI_AUTOREFRESH_FILE_SCRIPTS || !_entityScripts.contains(entityID)) { + if (!HIFI_AUTOREFRESH_FILE_SCRIPTS || !hasEntityScriptDetails(entityID)) { return; } @@ -2518,7 +2554,11 @@ void ScriptEngine::refreshFileScript(const EntityItemID& entityID) { } recurseGuard = true; - EntityScriptDetails details = _entityScripts[entityID]; + EntityScriptDetails details; + { + QWriteLocker locker { &_entityScriptsLock }; + details = _entityScripts[entityID]; + } // Check to see if a file based script needs to be reloaded (easier debugging) if (details.lastModified > 0) { QString filePath = QUrl(details.scriptText).toLocalFile(); @@ -2584,7 +2624,11 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS refreshFileScript(entityID); } if (isEntityScriptRunning(entityID)) { - EntityScriptDetails details = _entityScripts[entityID]; + EntityScriptDetails details; + { + QWriteLocker locker { &_entityScriptsLock }; + details = _entityScripts[entityID]; + } QScriptValue entityScript = details.scriptObject; // previously loaded // If this is a remote call, we need to check to see if the function is remotely callable @@ -2646,7 +2690,11 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS refreshFileScript(entityID); } if (isEntityScriptRunning(entityID)) { - EntityScriptDetails details = _entityScripts[entityID]; + EntityScriptDetails details; + { + QWriteLocker locker { &_entityScriptsLock }; + details = _entityScripts[entityID]; + } QScriptValue entityScript = details.scriptObject; // previously loaded if (entityScript.property(methodName).isFunction()) { QScriptValueList args; @@ -2680,7 +2728,11 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS refreshFileScript(entityID); } if (isEntityScriptRunning(entityID)) { - EntityScriptDetails details = _entityScripts[entityID]; + EntityScriptDetails details; + { + QWriteLocker locker { &_entityScriptsLock }; + details = _entityScripts[entityID]; + } QScriptValue entityScript = details.scriptObject; // previously loaded if (entityScript.property(methodName).isFunction()) { QScriptValueList args; diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 94b50bfd2c..08e2c492e8 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -461,7 +461,9 @@ public: * @returns {boolean} */ Q_INVOKABLE bool isEntityScriptRunning(const EntityItemID& entityID) { - return _entityScripts.contains(entityID) && _entityScripts[entityID].status == EntityScriptStatus::RUNNING; + QReadLocker locker { &_entityScriptsLock }; + auto it = _entityScripts.constFind(entityID); + return it != _entityScripts.constEnd() && it->status == EntityScriptStatus::RUNNING; } QVariant cloneEntityScriptDetails(const EntityItemID& entityID); QFuture getLocalEntityScriptDetails(const EntityItemID& entityID) override; @@ -559,6 +561,7 @@ public: void clearDebugLogWindow(); int getNumRunningEntityScripts() const; bool getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const; + bool hasEntityScriptDetails(const EntityItemID& entityID) const; public slots: @@ -771,6 +774,7 @@ protected: bool _isInitialized { false }; QHash _timerFunctionMap; QSet _includedURLs; + mutable QReadWriteLock _entityScriptsLock { QReadWriteLock::Recursive }; QHash _entityScripts; QHash _occupiedScriptURLs; QList _deferredEntityLoads; diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 615a3c8ddb..9cfdf6df22 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -282,14 +282,15 @@ function loaded() { function refreshEntityList() { PROFILE("refresh-entity-list", function() { PROFILE("filter", function() { - let searchTerm = elFilter.value; + let searchTerm = elFilter.value.toLowerCase(); if (searchTerm === '') { visibleEntities = entities.slice(0); } else { visibleEntities = entities.filter(function(e) { - return e.name.indexOf(searchTerm) > -1 - || e.type.indexOf(searchTerm) > -1 - || e.fullUrl.indexOf(searchTerm) > -1; + return e.name.toLowerCase().indexOf(searchTerm) > -1 + || e.type.toLowerCase().indexOf(searchTerm) > -1 + || e.fullUrl.toLowerCase().indexOf(searchTerm) > -1 + || e.id.toLowerCase().indexOf(searchTerm) > -1; }); } }); diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 9b91d06d41..7eece890c9 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -113,7 +113,7 @@ var span = document.createElement('span'); span.style = "margin:10px 5px;color:#1b6420;font-size:15px;"; - span.innerHTML = "Setup your Wallet to get money and shop in Marketplace."; + span.innerHTML = "Activate your Wallet to get money and shop in Marketplace."; var xButton = document.createElement('a'); xButton.id = "xButton"; diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js index dcce721cd9..57726f397b 100644 --- a/scripts/system/interstitialPage.js +++ b/scripts/system/interstitialPage.js @@ -137,10 +137,10 @@ var loadingToTheSpotID = Overlays.addOverlay("image3d", { name: "Loading-Destination-Card-Text", - localPosition: { x: 0.0 , y: -1.8, z: 0.0 }, - url: "http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/goTo_button.png", + localPosition: { x: 0.0 , y: -1.5, z: -0.3 }, + url: Script.resourcesPath() + "images/interstitialPage/goTo_button.png", alpha: 1, - dimensions: { x: 1.2, y: 0.6}, + dimensions: { x: 1.5, y: 1.0 }, visible: isVisible, emissive: true, ignoreRayIntersection: false, @@ -415,13 +415,13 @@ Overlays.mouseReleaseOnOverlay.connect(clickedOnOverlay); Overlays.hoverEnterOverlay.connect(function(overlayID, event) { if (overlayID === loadingToTheSpotID) { - Overlays.editOverlay(loadingToTheSpotID, { color: greyColor}); + Overlays.editOverlay(loadingToTheSpotID, { color: greyColor }); } }); Overlays.hoverLeaveOverlay.connect(function(overlayID, event) { if (overlayID === loadingToTheSpotID) { - Overlays.editOverlay(loadingToTheSpotID, { color: whiteColor}); + Overlays.editOverlay(loadingToTheSpotID, { color: whiteColor }); } }); diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js index 0778e2a44b..36fe264274 100644 --- a/scripts/system/notifications.js +++ b/scripts/system/notifications.js @@ -564,7 +564,7 @@ } function walletNotSetup() { - createNotification("Your wallet isn't set up. Open the WALLET app.", NotificationType.WALLET); + createNotification("Your wallet isn't activated yet. Open the WALLET app.", NotificationType.WALLET); } function connectionAdded(connectionName) {