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
800 _registeredHandlers[entityID] = RegisteredEventHandlers();800 _registeredHandlers[entityID] = RegisteredEventHandlers();
801 }801 }
802 CallbackList& handlersForEvent = _registeredHandlers[entityID][eventName];802 CallbackList& handlersForEvent = _registeredHandlers[entityID][eventName];
803 CallbackData handlerData = {handler, currentEntityIdentifier, currentSandboxURL};803 CallbackData handlerData = { handler, currentEntityIdentifier, currentSandboxURL };
804 handlersForEvent << handlerData; // Note that the same handler can be added many times. See removeEntityEventHandler().804 handlersForEvent << handlerData; // Note that the same handler can be added many times. See removeEntityEventHandler().
805}805}
806 806
@@ -840,6 +840,15 @@ QScriptValue ScriptEngine::evaluate(const QString& sourceCode, const QString& fi · 33 unmodified lines
840}840}
841 841
842void ScriptEngine::run() {842void 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
843 if (DependencyManager::get<ScriptEngines>()->isStopped()) {852 if (DependencyManager::get<ScriptEngines>()->isStopped()) {
844 return; // bail early - avoid setting state in init(), as evaluate() will bail too853 return; // bail early - avoid setting state in init(), as evaluate() will bail too
845 }854 }
@@ -853,14 +862,6 @@ void ScriptEngine::run() { · 7 unmodified lines
853 862
854 QScriptValue result = evaluate(_scriptContents, _fileNameString);863 QScriptValue result = evaluate(_scriptContents, _fileNameString);
855 864
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
864 clock::time_point startTime = clock::now();865 clock::time_point startTime = clock::now();
865 int thisFrame = 0;866 int thisFrame = 0;
866 867
@@ -878,23 +879,48 @@ void ScriptEngine::run() { · 11 unmodified lines
878 // Throttle to SCRIPT_FPS879 // Throttle to SCRIPT_FPS
879 // We'd like to try to keep the script at a solid SCRIPT_FPS update rate. And so we will 880 // We'd like to try to keep the script at a solid SCRIPT_FPS update rate. And so we will
880 // calculate a sleepUntil to be the time from our start time until the original target881 // calculate a sleepUntil to be the time from our start time until the original target
881 // sleepUntil for this frame.882 // sleepUntil for this frame. This approach will allow us to "catch up" in the event
882 const std::chrono::microseconds FRAME_DURATION(USECS_PER_SECOND / SCRIPT_FPS + 1);883 // that some of our script udpates/frames take a little bit longer than the target average
883 clock::time_point targetSleepUntil(startTime + thisFrame++ * FRAME_DURATION);884 // to execute.
884 885 // NOTE: if we go to variable SCRIPT_FPS, then we will need to reconsider this approach
885 // However, if our sleepUntil is not at least our average update time into the future886 const std::chrono::microseconds TARGET_SCRIPT_FRAME_DURATION(USECS_PER_SECOND / SCRIPT_FPS + 1);
886 // it means our script is taking too long in it's updates, and we want to punish the887 clock::time_point targetSleepUntil(startTime + (thisFrame++ * TARGET_SCRIPT_FRAME_DURATION));
887 // script a little bit. So we will force the sleepUntil to be at least our averageUpdate888
888 // time into the future.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.
889 auto averageUpdate = totalUpdates / thisFrame;893 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);
891 897
892 // We don't want to actually sleep for too long, because it causes our scripts to hang 898 // We don't want to actually sleep for too long, because it causes our scripts to hang
893 // on shutdown and stop... so we want to loop and sleep until we've spent our time in 899 // on shutdown and stop... so we want to loop and sleep until we've spent our time in
894 // purgatory, constantly checking to see if our script was asked to end900 // purgatory, constantly checking to see if our script was asked to end
901 bool processedEvents = false;
895 while (!_isFinished && clock::now() < sleepUntil) {902 while (!_isFinished && clock::now() < sleepUntil) {
903
896 QCoreApplication::processEvents(); // before we sleep again, give events a chance to process904 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);
898 std::this_thread::sleep_until(thisSleepUntil);924 std::this_thread::sleep_until(thisSleepUntil);
899 }925 }
900 926
@@ -919,7 +945,10 @@ void ScriptEngine::run() { · 18 unmodified lines
919 break;945 break;
920 }946 }
921 947
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 }
923 952
924 if (_isFinished) {953 if (_isFinished) {
925 break;954 break;
@@ -982,6 +1011,21 @@ void ScriptEngine::run() { · 56 unmodified lines
982 emit doneRunning();1011 emit doneRunning();
983}1012}
984 1013
1014quint64 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
985// NOTE: This is private because it must be called on the same thread that created the timers, which is why1029// NOTE: This is private because it must be called on the same thread that created the timers, which is why
986// we want to only call it in our own run "shutdown" processing.1030// we want to only call it in our own run "shutdown" processing.
987void ScriptEngine::stopAllTimers() {1031void ScriptEngine::stopAllTimers() {
@@ -1077,7 +1121,12 @@ void ScriptEngine::timerFired() { · 89 unmodified lines
1077 1121
1078 // call the associated JS function, if it exists1122 // call the associated JS function, if it exists
1079 if (timerData.function.isValid()) {1123 if (timerData.function.isValid()) {
1124 auto preTimer = p_high_resolution_clock::now();
1080 callWithEnvironment(timerData.definingEntityIdentifier, timerData.definingSandboxURL, timerData.function, timerData.function, QScriptValueList());1125 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
1081 }1130 }
1082}1131}
1083 1132
@@ -1098,7 +1147,7 @@ QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int · 14 unmodified lines
1098 // make sure the timer stops when the script does1147 // make sure the timer stops when the script does
1099 connect(this, &ScriptEngine::scriptEnding, newTimer, &QTimer::stop);1148 connect(this, &ScriptEngine::scriptEnding, newTimer, &QTimer::stop);
1100 1149
1101 CallbackData timerData = {function, currentEntityIdentifier, currentSandboxURL};1150 CallbackData timerData = {function, currentEntityIdentifier, currentSandboxURL };
1102 _timerFunctionMap.insert(newTimer, timerData);1151 _timerFunctionMap.insert(newTimer, timerData);
1103 1152
1104 newTimer->start(intervalMS);1153 newTimer->start(intervalMS);