diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index d18911122e..e44c7716bf 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3236,6 +3236,7 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node bool Application::isHMDMode() const { return getActiveDisplayPlugin()->isHmd(); } +float Application::getTargetFrameRate() { return getActiveDisplayPlugin()->getTargetFrameRate(); } QRect Application::getDesirableApplicationGeometry() { QRect applicationGeometry = getWindow()->geometry(); @@ -4049,7 +4050,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri scriptEngine->registerGlobalObject("Clipboard", clipboardScriptable); connect(scriptEngine, SIGNAL(finished(const QString&)), clipboardScriptable, SLOT(deleteLater())); - connect(scriptEngine, SIGNAL(finished(const QString&)), this, SLOT(scriptFinished(const QString&))); + connect(scriptEngine, &ScriptEngine::finished, this, &Application::scriptFinished, Qt::DirectConnection); connect(scriptEngine, SIGNAL(loadScript(const QString&, bool)), this, SLOT(loadScript(const QString&, bool))); connect(scriptEngine, SIGNAL(reloadScript(const QString&, bool)), this, SLOT(reloadScript(const QString&, bool))); @@ -4295,10 +4296,13 @@ ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUser QUrl scriptUrl(scriptFilename); const QString& scriptURLString = scriptUrl.toString(); - if (_scriptEnginesHash.contains(scriptURLString) && loadScriptFromEditor - && !_scriptEnginesHash[scriptURLString]->isFinished()) { + { + QReadLocker lock(&_scriptEnginesHashLock); + if (_scriptEnginesHash.contains(scriptURLString) && loadScriptFromEditor + && !_scriptEnginesHash[scriptURLString]->isFinished()) { - return _scriptEnginesHash[scriptURLString]; + return _scriptEnginesHash[scriptURLString]; + } } ScriptEngine* scriptEngine = new ScriptEngine(NO_SCRIPT, "", &_controllerScriptingInterface); @@ -4338,7 +4342,11 @@ void Application::reloadScript(const QString& scriptName, bool isUserLoaded) { void Application::handleScriptEngineLoaded(const QString& scriptFilename) { ScriptEngine* scriptEngine = qobject_cast(sender()); - _scriptEnginesHash.insertMulti(scriptFilename, scriptEngine); + { + QWriteLocker lock(&_scriptEnginesHashLock); + _scriptEnginesHash.insertMulti(scriptFilename, scriptEngine); + } + _runningScriptsWidget->setRunningScripts(getRunningScripts()); UserActivityLogger::getInstance().loadedScript(scriptFilename); @@ -4353,55 +4361,88 @@ void Application::handleScriptLoadError(const QString& scriptFilename) { QMessageBox::warning(getWindow(), "Error Loading Script", scriptFilename + " failed to load."); } -void Application::scriptFinished(const QString& scriptName) { - const QString& scriptURLString = QUrl(scriptName).toString(); - QHash::iterator it = _scriptEnginesHash.find(scriptURLString); - if (it != _scriptEnginesHash.end()) { - _scriptEnginesHash.erase(it); - _runningScriptsWidget->scriptStopped(scriptName); - _runningScriptsWidget->setRunningScripts(getRunningScripts()); +QStringList Application::getRunningScripts() { + QReadLocker lock(&_scriptEnginesHashLock); + return _scriptEnginesHash.keys(); +} + +ScriptEngine* Application::getScriptEngine(const QString& scriptHash) { + QReadLocker lock(&_scriptEnginesHashLock); + return _scriptEnginesHash.value(scriptHash, nullptr); +} + +void Application::scriptFinished(const QString& scriptName, ScriptEngine* engine) { + bool removed = false; + { + QWriteLocker lock(&_scriptEnginesHashLock); + const QString& scriptURLString = QUrl(scriptName).toString(); + for (auto it = _scriptEnginesHash.find(scriptURLString); it != _scriptEnginesHash.end(); ++it) { + if (it.value() == engine) { + _scriptEnginesHash.erase(it); + removed = true; + break; + } + } + } + if (removed) { + postLambdaEvent([this, scriptName]() { + _runningScriptsWidget->scriptStopped(scriptName); + _runningScriptsWidget->setRunningScripts(getRunningScripts()); + }); } } void Application::stopAllScripts(bool restart) { - if (restart) { - // Delete all running scripts from cache so that they are re-downloaded when they are restarted - auto scriptCache = DependencyManager::get(); - for (QHash::const_iterator it = _scriptEnginesHash.constBegin(); + { + QReadLocker lock(&_scriptEnginesHashLock); + + if (restart) { + // Delete all running scripts from cache so that they are re-downloaded when they are restarted + auto scriptCache = DependencyManager::get(); + for (QHash::const_iterator it = _scriptEnginesHash.constBegin(); it != _scriptEnginesHash.constEnd(); it++) { - if (!it.value()->isFinished()) { - scriptCache->deleteScript(it.key()); + if (!it.value()->isFinished()) { + scriptCache->deleteScript(it.key()); + } } } - } - // Stop and possibly restart all currently running scripts - for (QHash::const_iterator it = _scriptEnginesHash.constBegin(); - it != _scriptEnginesHash.constEnd(); it++) { - if (it.value()->isFinished()) { - continue; + // Stop and possibly restart all currently running scripts + for (QHash::const_iterator it = _scriptEnginesHash.constBegin(); + it != _scriptEnginesHash.constEnd(); it++) { + if (it.value()->isFinished()) { + continue; + } + if (restart && it.value()->isUserLoaded()) { + connect(it.value(), &ScriptEngine::finished, this, [this](QString scriptName, ScriptEngine* engine) { + reloadScript(scriptName); + }); + } + QMetaObject::invokeMethod(it.value(), "stop"); + //it.value()->stop(); + qCDebug(interfaceapp) << "stopping script..." << it.key(); } - if (restart && it.value()->isUserLoaded()) { - connect(it.value(), SIGNAL(finished(const QString&)), SLOT(reloadScript(const QString&))); - } - it.value()->stop(); - qCDebug(interfaceapp) << "stopping script..." << it.key(); } getMyAvatar()->clearScriptableSettings(); } bool Application::stopScript(const QString& scriptHash, bool restart) { bool stoppedScript = false; - if (_scriptEnginesHash.contains(scriptHash)) { - ScriptEngine* scriptEngine = _scriptEnginesHash[scriptHash]; - if (restart) { - auto scriptCache = DependencyManager::get(); - scriptCache->deleteScript(QUrl(scriptHash)); - connect(scriptEngine, SIGNAL(finished(const QString&)), SLOT(reloadScript(const QString&))); + { + QReadLocker lock(&_scriptEnginesHashLock); + if (_scriptEnginesHash.contains(scriptHash)) { + ScriptEngine* scriptEngine = _scriptEnginesHash[scriptHash]; + if (restart) { + auto scriptCache = DependencyManager::get(); + scriptCache->deleteScript(QUrl(scriptHash)); + connect(scriptEngine, &ScriptEngine::finished, this, [this](QString scriptName, ScriptEngine* engine) { + reloadScript(scriptName); + }); + } + scriptEngine->stop(); + stoppedScript = true; + qCDebug(interfaceapp) << "stopping script..." << scriptHash; } - scriptEngine->stop(); - stoppedScript = true; - qCDebug(interfaceapp) << "stopping script..." << scriptHash; } if (_scriptEnginesHash.empty()) { getMyAvatar()->clearScriptableSettings(); @@ -4420,6 +4461,7 @@ void Application::reloadOneScript(const QString& scriptName) { } void Application::loadDefaultScripts() { + QReadLocker lock(&_scriptEnginesHashLock); if (!_scriptEnginesHash.contains(DEFAULT_SCRIPTS_JS_URL)) { loadScript(DEFAULT_SCRIPTS_JS_URL); } diff --git a/interface/src/Application.h b/interface/src/Application.h index 7036bfdcfe..e0f86d97bd 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -160,9 +160,7 @@ public: uint32_t getFrameCount() { return _frameCount; } float getFps() const { return _fps; } - float const HMD_TARGET_FRAME_RATE = 75.0f; - float const DESKTOP_TARGET_FRAME_RATE = 60.0f; - float getTargetFrameRate() { return isHMDMode() ? HMD_TARGET_FRAME_RATE : DESKTOP_TARGET_FRAME_RATE; } + float getTargetFrameRate(); // frames/second float getLastInstanteousFps() const { return _lastInstantaneousFps; } float getLastUnsynchronizedFps() const { return _lastUnsynchronizedFps; } @@ -199,8 +197,8 @@ public: NodeToJurisdictionMap& getEntityServerJurisdictions() { return _entityServerJurisdictions; } - QStringList getRunningScripts() { return _scriptEnginesHash.keys(); } - ScriptEngine* getScriptEngine(const QString& scriptHash) { return _scriptEnginesHash.value(scriptHash, NULL); } + QStringList getRunningScripts(); + ScriptEngine* getScriptEngine(const QString& scriptHash); float getRenderResolutionScale() const; @@ -333,7 +331,7 @@ private slots: void loadSettings(); void saveSettings(); - void scriptFinished(const QString& scriptName); + void scriptFinished(const QString& scriptName, ScriptEngine* engine); void saveScripts(); void reloadScript(const QString& scriptName, bool isUserLoaded = true); @@ -500,6 +498,7 @@ private: TouchEvent _lastTouchEvent; + QReadWriteLock _scriptEnginesHashLock; RunningScriptsWidget* _runningScriptsWidget; QHash _scriptEnginesHash; bool _runningScriptsWidgetWasVisible; diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 59af6b18df..7c1a52f1b3 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -145,13 +145,19 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { PerformanceTimer perfTimer("otherAvatars"); - _renderDistanceController.setMeasuredValueSetpoint(qApp->getTargetFrameRate()); // No problem updating in flight. - // The PID controller raises the controlled value when the measured value goes up. - // The measured value is frame rate. When the controlled value (1 / render cutoff distance) - // goes up, the render cutoff distance gets closer, the number of rendered avatars is less, and frame rate - // goes up. - const float deduced = qApp->getLastUnsynchronizedFps(); - const float distance = 1.0f / _renderDistanceController.update(deduced, deltaTime); + float distance; + if (!qApp->isThrottleRendering()) { + _renderDistanceController.setMeasuredValueSetpoint(qApp->getTargetFrameRate()); // No problem updating in flight. + // The PID controller raises the controlled value when the measured value goes up. + // The measured value is frame rate. When the controlled value (1 / render cutoff distance) + // goes up, the render cutoff distance gets closer, the number of rendered avatars is less, and frame rate + // goes up. + const float deduced = qApp->getLastUnsynchronizedFps(); + distance = 1.0f / _renderDistanceController.update(deduced, deltaTime); + } else { + // Here we choose to just use the maximum render cutoff distance if throttled. + distance = 1.0f / _renderDistanceController.getControlledValueLowLimit(); + } _renderDistanceAverage.updateAverage(distance); _renderDistance = _renderDistanceAverage.getAverage(); int renderableCount = 0; diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp index 8fcc50931d..71a885b31c 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp @@ -83,16 +83,16 @@ void Basic2DWindowOpenGLDisplayPlugin::internalPresent() { } WindowOpenGLDisplayPlugin::internalPresent(); } - +const uint32_t THROTTLED_FRAMERATE = 15; int Basic2DWindowOpenGLDisplayPlugin::getDesiredInterval() const { - static const int THROTTLED_PAINT_TIMER_DELAY_MS = MSECS_PER_SECOND / 15; static const int ULIMIITED_PAINT_TIMER_DELAY_MS = 1; int result = ULIMIITED_PAINT_TIMER_DELAY_MS; - if (_isThrottled) { - result = THROTTLED_PAINT_TIMER_DELAY_MS; - } if (0 != _framerateTarget) { result = MSECS_PER_SECOND / _framerateTarget; + } else if (_isThrottled) { + // This test wouldn't be necessary if we could depend on updateFramerate setting _framerateTarget. + // Alas, that gets complicated: isThrottled() is const and other stuff depends on it. + result = MSECS_PER_SECOND / THROTTLED_FRAMERATE; } return result; @@ -111,7 +111,6 @@ bool Basic2DWindowOpenGLDisplayPlugin::isThrottled() const { return shouldThrottle; } - void Basic2DWindowOpenGLDisplayPlugin::updateFramerate() { QAction* checkedFramerate{ nullptr }; foreach(auto action, _framerateActions) { @@ -133,9 +132,12 @@ void Basic2DWindowOpenGLDisplayPlugin::updateFramerate() { } else if (FRAMERATE_30 == actionText) { _framerateTarget = 30; } - } + } else if (_isThrottled) { + _framerateTarget = THROTTLED_FRAMERATE; + } - _timer.start(getDesiredInterval()); + int newInterval = getDesiredInterval(); + _timer.start(newInterval); } // FIXME target the screen the window is currently on diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h index 36a1a73b94..e3633b5fe8 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h @@ -9,6 +9,8 @@ #include "WindowOpenGLDisplayPlugin.h" +const float TARGET_FRAMERATE_Basic2DWindowOpenGL = 60.0f; + class QScreen; class QAction; @@ -18,6 +20,8 @@ class Basic2DWindowOpenGLDisplayPlugin : public WindowOpenGLDisplayPlugin { public: virtual const QString & getName() const override; + virtual float getTargetFrameRate() override { return _framerateTarget ? (float) _framerateTarget : TARGET_FRAMERATE_Basic2DWindowOpenGL; } + virtual void activate() override; virtual void submitSceneTexture(uint32_t frameIndex, uint32_t sceneTexture, const glm::uvec2& sceneSize) override; diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index 83afbc9402..7f3b38e4a2 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -58,6 +58,7 @@ public: /// By default, all HMDs are stereo virtual bool isStereo() const { return isHmd(); } virtual bool isThrottled() const { return false; } + virtual float getTargetFrameRate() { return 0.0f; } // Rendering support diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index a075b0a91d..ded3db11e9 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -183,7 +183,7 @@ void ScriptEngine::runInThread() { QSet ScriptEngine::_allKnownScriptEngines; QMutex ScriptEngine::_allScriptsMutex; -bool ScriptEngine::_stoppingAllScripts = false; +std::atomic ScriptEngine::_stoppingAllScripts { false }; void ScriptEngine::stopAllScripts(QObject* application) { _allScriptsMutex.lock(); @@ -752,7 +752,7 @@ void ScriptEngine::run() { } if (_wantSignals) { - emit finished(_fileNameString); + emit finished(_fileNameString, this); } _isRunning = false; @@ -775,6 +775,10 @@ void ScriptEngine::stopAllTimers() { void ScriptEngine::stop() { if (!_isFinished) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "stop"); + return; + } _isFinished = true; if (_wantSignals) { emit runningStateChanged(); diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 90b99d46fd..67a206d673 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -153,7 +153,7 @@ signals: void errorLoadingScript(const QString& scriptFilename); void update(float deltaTime); void scriptEnding(); - void finished(const QString& fileNameString); + void finished(const QString& fileNameString, ScriptEngine* engine); void cleanupMenuItem(const QString& menuItemString); void printedMessage(const QString& message); void errorMessage(const QString& message); @@ -166,8 +166,8 @@ signals: protected: QString _scriptContents; QString _parentURL; - bool _isFinished { false }; - bool _isRunning { false }; + std::atomic _isFinished { false }; + std::atomic _isRunning { false }; int _evaluatesPending { 0 }; bool _isInitialized { false }; QHash _timerFunctionMap; @@ -193,7 +193,7 @@ protected: Quat _quatLibrary; Vec3 _vec3Library; ScriptUUID _uuidLibrary; - bool _isUserLoaded { false }; + std::atomic _isUserLoaded { false }; bool _isReloading { false }; ArrayBufferClass* _arrayBufferClass; @@ -206,7 +206,7 @@ protected: static QSet _allKnownScriptEngines; static QMutex _allScriptsMutex; - static bool _stoppingAllScripts; + static std::atomic _stoppingAllScripts; }; #endif // hifi_ScriptEngine_h \ No newline at end of file diff --git a/plugins/oculus/src/OculusDisplayPlugin.h b/plugins/oculus/src/OculusDisplayPlugin.h index 5509715b9f..03c9ba7511 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.h +++ b/plugins/oculus/src/OculusDisplayPlugin.h @@ -12,12 +12,16 @@ struct SwapFramebufferWrapper; using SwapFboPtr = QSharedPointer; +const float TARGET_RATE_Oculus = 75.0f; + class OculusDisplayPlugin : public OculusBaseDisplayPlugin { public: virtual void activate() override; virtual const QString & getName() const override; virtual void setEyeRenderPose(uint32_t frameIndex, Eye eye, const glm::mat4& pose) override final; + virtual float getTargetFrameRate() override { return TARGET_RATE_Oculus; } + protected: virtual void internalPresent() override; virtual void customizeContext() override; diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h index ccf1ffdb57..d3e68585df 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h @@ -13,6 +13,8 @@ #include +const float TARGET_RATE_OculusLegacy = 75.0f; + class OculusLegacyDisplayPlugin : public WindowOpenGLDisplayPlugin { public: OculusLegacyDisplayPlugin(); @@ -25,6 +27,8 @@ public: virtual bool eventFilter(QObject* receiver, QEvent* event) override; virtual int getHmdScreen() const override; + virtual float getTargetFrameRate() override { return TARGET_RATE_OculusLegacy; } + // Stereo specific methods virtual bool isHmd() const override { return true; } virtual glm::mat4 getProjection(Eye eye, const glm::mat4& baseProjection) const override; diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.h b/plugins/openvr/src/OpenVrDisplayPlugin.h index 4b5639d52b..8186e59936 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.h +++ b/plugins/openvr/src/OpenVrDisplayPlugin.h @@ -13,12 +13,16 @@ #include +const float TARGET_RATE_OpenVr = 90.0f; // FIXME: get from sdk tracked device property? This number is vive-only. + class OpenVrDisplayPlugin : public WindowOpenGLDisplayPlugin { public: virtual bool isSupported() const override; virtual const QString & getName() const override; virtual bool isHmd() const override { return true; } + virtual float getTargetFrameRate() override { return TARGET_RATE_OpenVr; } + virtual void activate() override; virtual void deactivate() override;