History

better handling of timers

Brad Hefta-Gaub committed 9 years ago

Commit 0c22fcb1 parent a72f6013 files · +74 −22Browse files

Showing the changes to libraries/script-engine/src/ScriptEngine.cpp (1 of 3 files). Show all

Unified · Split

libraries/script-engine/src/ScriptEngine.cpp+70−21
@@ -800,7 +800,7 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& · 799 unmodified lines
800800 _registeredHandlers[entityID] = RegisteredEventHandlers();
801801 }
802802 CallbackList& handlersForEvent = _registeredHandlers[entityID][eventName];
803− CallbackData handlerData = {handler, currentEntityIdentifier, currentSandboxURL};
803+ CallbackData handlerData = { handler, currentEntityIdentifier, currentSandboxURL };
804804 handlersForEvent << handlerData; // Note that the same handler can be added many times. See removeEntityEventHandler().
805805 }
806806
@@ -840,6 +840,15 @@ QScriptValue ScriptEngine::evaluate(const QString& sourceCode, const QString& fi · 33 unmodified lines
840840 }
841841
842842 void ScriptEngine::run() {
843+
844+#ifdef _WIN32
845+ // VS13 does not sleep_until unless it uses the system_clock, see:
846+ // https://www.reddit.com/r/cpp_questions/comments/3o71ic/sleep_until_not_working_with_a_time_pointsteady/
847+ using clock = std::chrono::system_clock;
848+#else
849+ using clock = std::chrono::high_resolution_clock;
850+#endif
851+
843852 if (DependencyManager::get<ScriptEngines>()->isStopped()) {
844853 return; // bail early - avoid setting state in init(), as evaluate() will bail too
845854 }
@@ -853,14 +862,6 @@ void ScriptEngine::run() { · 7 unmodified lines
853862
854863 QScriptValue result = evaluate(_scriptContents, _fileNameString);
855864
856−#ifdef _WIN32
857− // VS13 does not sleep_until unless it uses the system_clock, see:
858− // https://www.reddit.com/r/cpp_questions/comments/3o71ic/sleep_until_not_working_with_a_time_pointsteady/
859− using clock = std::chrono::system_clock;
860−#else
861− using clock = std::chrono::high_resolution_clock;
862−#endif
863−
864865 clock::time_point startTime = clock::now();
865866 int thisFrame = 0;
866867
@@ -878,23 +879,48 @@ void ScriptEngine::run() { · 11 unmodified lines
878879 // Throttle to SCRIPT_FPS
879880 // We'd like to try to keep the script at a solid SCRIPT_FPS update rate. And so we will
880881 // calculate a sleepUntil to be the time from our start time until the original target
881− // sleepUntil for this frame.
882− const std::chrono::microseconds FRAME_DURATION(USECS_PER_SECOND / SCRIPT_FPS + 1);
883− clock::time_point targetSleepUntil(startTime + thisFrame++ * FRAME_DURATION);
884−
885− // However, if our sleepUntil is not at least our average update time into the future
886− // it means our script is taking too long in it's updates, and we want to punish the
887− // script a little bit. So we will force the sleepUntil to be at least our averageUpdate
888− // time into the future.
882+ // sleepUntil for this frame. This approach will allow us to "catch up" in the event
883+ // that some of our script udpates/frames take a little bit longer than the target average
884+ // to execute.
885+ // NOTE: if we go to variable SCRIPT_FPS, then we will need to reconsider this approach
886+ const std::chrono::microseconds TARGET_SCRIPT_FRAME_DURATION(USECS_PER_SECOND / SCRIPT_FPS + 1);
887+ clock::time_point targetSleepUntil(startTime + (thisFrame++ * TARGET_SCRIPT_FRAME_DURATION));
888+
889+ // However, if our sleepUntil is not at least our average update and timer execution time
890+ // into the future it means our script is taking too long in its updates, and we want to
891+ // punish the script a little bit. So we will force the sleepUntil to be at least our
892+ // averageUpdate + averageTimerPerFrame time into the future.
889893 auto averageUpdate = totalUpdates / thisFrame;
890− auto sleepUntil = std::max(targetSleepUntil, beforeSleep + averageUpdate);
894+ auto averageTimerPerFrame = _totalTimerExecution / thisFrame;
895+ auto averageTimerAndUpdate = averageUpdate + averageTimerPerFrame;
896+ auto sleepUntil = std::max(targetSleepUntil, beforeSleep + averageTimerAndUpdate);
891897
892898 // We don't want to actually sleep for too long, because it causes our scripts to hang
893899 // on shutdown and stop... so we want to loop and sleep until we've spent our time in
894900 // purgatory, constantly checking to see if our script was asked to end
901+ bool processedEvents = false;
895902 while (!_isFinished && clock::now() < sleepUntil) {
903+
896904 QCoreApplication::processEvents(); // before we sleep again, give events a chance to process
897− auto thisSleepUntil = std::min(sleepUntil, clock::now() + FRAME_DURATION);
905+ processedEvents = true;
906+
907+ // If after processing events, we're past due, exit asap
908+ if (clock::now() >= sleepUntil) {
909+ break;
910+ }
911+
912+ // determine how long before the next timer should fire, we'd ideally like to sleep just
913+ // that long, so the next processEvents() will allow the timers to fire on time.
914+ const std::chrono::microseconds minTimerTimeRemaining(USECS_PER_MSEC * getTimersRemainingTime());
915+
916+ // However, if we haven't yet slept at least as long as our average timer per frame, then we will
917+ // punish the timers to at least wait as long as the average run time of the timers.
918+ auto untilTimer = std::max(minTimerTimeRemaining, averageTimerPerFrame);
919+
920+ // choose the closest time point, our
921+ auto remainingSleepUntil = std::chrono::duration_cast<std::chrono::microseconds>(sleepUntil - clock::now());
922+ auto closestUntil = std::min(remainingSleepUntil, untilTimer);
923+ auto thisSleepUntil = std::min(sleepUntil, clock::now() + closestUntil);
898924 std::this_thread::sleep_until(thisSleepUntil);
899925 }
900926
@@ -919,7 +945,10 @@ void ScriptEngine::run() { · 18 unmodified lines
919945 break;
920946 }
921947
922− QCoreApplication::processEvents();
948+ // Only call this if we didn't processEvents as part of waiting for next frame
949+ if (!processedEvents) {
950+ QCoreApplication::processEvents();
951+ }
923952
924953 if (_isFinished) {
925954 break;
@@ -982,6 +1011,21 @@ void ScriptEngine::run() { · 56 unmodified lines
9821011 emit doneRunning();
9831012 }
9841013
1014+quint64 ScriptEngine::getTimersRemainingTime() {
1015+ quint64 minimumTime = USECS_PER_SECOND; // anything larger than this can be ignored
1016+ QMutableHashIterator<QTimer*, CallbackData> i(_timerFunctionMap);
1017+ while (i.hasNext()) {
1018+ i.next();
1019+ QTimer* timer = i.key();
1020+ int remainingTime = timer->remainingTime();
1021+ if (remainingTime >= 0) {
1022+ minimumTime = std::min((quint64)remainingTime, minimumTime);
1023+ }
1024+ }
1025+ return minimumTime;
1026+}
1027+
1028+
9851029 // NOTE: This is private because it must be called on the same thread that created the timers, which is why
9861030 // we want to only call it in our own run "shutdown" processing.
9871031 void ScriptEngine::stopAllTimers() {
@@ -1077,7 +1121,12 @@ void ScriptEngine::timerFired() { · 89 unmodified lines
10771121
10781122 // call the associated JS function, if it exists
10791123 if (timerData.function.isValid()) {
1124+ auto preTimer = p_high_resolution_clock::now();
10801125 callWithEnvironment(timerData.definingEntityIdentifier, timerData.definingSandboxURL, timerData.function, timerData.function, QScriptValueList());
1126+ auto postTimer = p_high_resolution_clock::now();
1127+ auto elapsed = (postTimer - preTimer);
1128+ _totalTimerExecution += std::chrono::duration_cast<std::chrono::microseconds>(elapsed);
1129+
10811130 }
10821131 }
10831132
@@ -1098,7 +1147,7 @@ QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int · 14 unmodified lines
10981147 // make sure the timer stops when the script does
10991148 connect(this, &ScriptEngine::scriptEnding, newTimer, &QTimer::stop);
11001149
1101− CallbackData timerData = {function, currentEntityIdentifier, currentSandboxURL};
1150+ CallbackData timerData = {function, currentEntityIdentifier, currentSandboxURL };
11021151 _timerFunctionMap.insert(newTimer, timerData);
11031152
11041153 newTimer->start(intervalMS);