From 83dc9ea6bba4391b2642d9dba405f1afe8e0697f Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Mon, 25 Jul 2016 21:46:30 -0700 Subject: [PATCH] punish slow scripts and don't send updates while physics is still loading --- interface/src/Application.cpp | 5 ++++ interface/src/Application.h | 1 + libraries/script-engine/src/ScriptEngine.cpp | 31 +++++++++++++++++--- libraries/script-engine/src/ScriptEngine.h | 5 ++++ 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index dc37325ac5..faa1229cad 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4678,6 +4678,11 @@ void Application::packetSent(quint64 length) { } void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scriptEngine) { + + scriptEngine->setPhysicsEnabledFunction([this]() { + return isPhysicsEnabled(); + }); + // setup the packet senders and jurisdiction listeners of the script engine's scripting interfaces so // we can use the same ones from the application. auto entityScriptingInterface = DependencyManager::get(); diff --git a/interface/src/Application.h b/interface/src/Application.h index a254072561..0af65f665f 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -215,6 +215,7 @@ public: qint64 getCurrentSessionRuntime() const { return _sessionRunTimer.elapsed(); } bool isAboutToQuit() const { return _aboutToQuit; } + bool isPhysicsEnabled() const { return _physicsEnabled; } // the isHMDMode is true whenever we use the interface from an HMD and not a standard flat display // rendering of several elements depend on that diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 9642aaf588..2ce3fcd7e0 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -828,24 +828,42 @@ void ScriptEngine::run() { _lastUpdate = usecTimestampNow(); + qint64 totalSleepFor = 0; + std::chrono::microseconds totalUpdates; + auto lastLoopStart = clock::now(); + // TODO: Integrate this with signals/slots instead of reimplementing throttling for ScriptEngine while (!_isFinished) { + auto thisLoopStart = clock::now(); + // Throttle to SCRIPT_FPS const std::chrono::microseconds FRAME_DURATION(USECS_PER_SECOND / SCRIPT_FPS + 1); + const std::chrono::microseconds MINIMUM_SLEEP { FRAME_DURATION / 2 }; + + auto beforeSleep = clock::now(); clock::time_point sleepTime(startTime + thisFrame++ * FRAME_DURATION); + auto wouldSleep = (sleepTime - clock::now()); + auto avgUpdates = totalUpdates / thisFrame; + + if (wouldSleep < avgUpdates) { + sleepTime = beforeSleep + avgUpdates; + } + std::this_thread::sleep_until(sleepTime); #ifdef SCRIPT_DELAY_DEBUG { - auto now = clock::now(); - uint64_t seconds = std::chrono::duration_cast(now - startTime).count(); + auto sleptTill = clock::now(); + uint64_t seconds = std::chrono::duration_cast(sleptTill - startTime).count(); if (seconds > 0) { // avoid division by zero and time travel uint64_t fps = thisFrame / seconds; // Overreporting artificially reduces the reported rate if (thisFrame % SCRIPT_FPS == 0) { qCDebug(scriptengine) << "Frame:" << thisFrame << - "Slept (us):" << std::chrono::duration_cast(now - sleepTime).count() << + "Slept (us):" << std::chrono::duration_cast(sleptTill - beforeSleep).count() << + "Avg Updates (us):" << avgUpdates.count() << + "Last loop time (us):" << std::chrono::duration_cast(thisLoopStart - lastLoopStart).count() << "FPS:" << fps; } } @@ -874,16 +892,21 @@ void ScriptEngine::run() { qint64 now = usecTimestampNow(); // we check for 'now' in the past in case people set their clock back - if (_lastUpdate < now) { + if (_isPhysicsEnabledFunc() && _lastUpdate < now) { float deltaTime = (float) (now - _lastUpdate) / (float) USECS_PER_SECOND; if (!_isFinished) { + auto preUpdate = clock::now(); emit update(deltaTime); + auto postUpdate = clock::now(); + auto elapsed = (postUpdate - preUpdate); + totalUpdates += std::chrono::duration_cast(elapsed); } } _lastUpdate = now; // Debug and clear exceptions hadUncaughtExceptions(*this, _fileNameString); + lastLoopStart = thisLoopStart; } qCDebug(scriptengine) << "Script Engine stopping:" << getFilename(); diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 1077dce686..a62f6e1a88 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -168,6 +168,8 @@ public: // NOTE - this is used by the TypedArray implemetation. we need to review this for thread safety ArrayBufferClass* getArrayBufferClass() { return _arrayBufferClass; } + void setPhysicsEnabledFunction(std::function func) { _isPhysicsEnabledFunc = func; } + public slots: void callAnimationStateHandler(QScriptValue callback, AnimVariantMap parameters, QStringList names, bool useNames, AnimVariantResultHandler resultHandler); void updateMemoryCost(const qint64&); @@ -236,6 +238,9 @@ protected: QUrl currentSandboxURL {}; // The toplevel url string for the entity script that loaded the code being executed, else empty. void doWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, std::function operation); void callWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, QScriptValue function, QScriptValue thisObject, QScriptValueList args); + + std::function _isPhysicsEnabledFunc{ [](){ return true; } }; + }; #endif // hifi_ScriptEngine_h