Showing the changes to libraries/script-engine/src/ScriptEngine.cpp (1 of 3 files). Show all
Unified · Split
| @@ -800,7 +800,7 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& · 799 unmodified lines | ||
| 800 | 800 | _registeredHandlers[entityID] = RegisteredEventHandlers(); |
| 801 | 801 | } |
| 802 | 802 | CallbackList& handlersForEvent = _registeredHandlers[entityID][eventName]; |
| 803 | − CallbackData handlerData = {handler, currentEntityIdentifier, currentSandboxURL}; | |
| 803 | + CallbackData handlerData = { handler, currentEntityIdentifier, currentSandboxURL }; | |
| 804 | 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 | |
| 842 | 842 | 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 | + | |
| 843 | 852 | if (DependencyManager::get<ScriptEngines>()->isStopped()) { |
| 844 | 853 | 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 | 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 | 865 | clock::time_point startTime = clock::now(); |
| 865 | 866 | int thisFrame = 0; |
| 866 | 867 | |
| @@ -878,23 +879,48 @@ void ScriptEngine::run() { · 11 unmodified lines | ||
| 878 | 879 | // Throttle to SCRIPT_FPS |
| 879 | 880 | // We'd like to try to keep the script at a solid SCRIPT_FPS update rate. And so we will |
| 880 | 881 | // 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. | |
| 889 | 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 | 898 | // We don't want to actually sleep for too long, because it causes our scripts to hang |
| 893 | 899 | // on shutdown and stop... so we want to loop and sleep until we've spent our time in |
| 894 | 900 | // purgatory, constantly checking to see if our script was asked to end |
| 901 | + bool processedEvents = false; | |
| 895 | 902 | while (!_isFinished && clock::now() < sleepUntil) { |
| 903 | + | |
| 896 | 904 | 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 | 924 | std::this_thread::sleep_until(thisSleepUntil); |
| 899 | 925 | } |
| 900 | 926 | |
| @@ -919,7 +945,10 @@ void ScriptEngine::run() { · 18 unmodified lines | ||
| 919 | 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 | 953 | if (_isFinished) { |
| 925 | 954 | break; |
| @@ -982,6 +1011,21 @@ void ScriptEngine::run() { · 56 unmodified lines | ||
| 982 | 1011 | emit doneRunning(); |
| 983 | 1012 | } |
| 984 | 1013 | |
| 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 | + | |
| 985 | 1029 | // NOTE: This is private because it must be called on the same thread that created the timers, which is why |
| 986 | 1030 | // we want to only call it in our own run "shutdown" processing. |
| 987 | 1031 | void ScriptEngine::stopAllTimers() { |
| @@ -1077,7 +1121,12 @@ void ScriptEngine::timerFired() { · 89 unmodified lines | ||
| 1077 | 1121 | |
| 1078 | 1122 | // call the associated JS function, if it exists |
| 1079 | 1123 | if (timerData.function.isValid()) { |
| 1124 | + auto preTimer = p_high_resolution_clock::now(); | |
| 1080 | 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 | 1147 | // make sure the timer stops when the script does |
| 1099 | 1148 | connect(this, &ScriptEngine::scriptEnding, newTimer, &QTimer::stop); |
| 1100 | 1149 | |
| 1101 | − CallbackData timerData = {function, currentEntityIdentifier, currentSandboxURL}; | |
| 1150 | + CallbackData timerData = {function, currentEntityIdentifier, currentSandboxURL }; | |
| 1102 | 1151 | _timerFunctionMap.insert(newTimer, timerData); |
| 1103 | 1152 | |
| 1104 | 1153 | newTimer->start(intervalMS); |