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 | _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 | ||
| 842 | void ScriptEngine::run() { | 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 | if (DependencyManager::get<ScriptEngines>()->isStopped()) { | 852 | if (DependencyManager::get<ScriptEngines>()->isStopped()) { |
| 844 | return; // bail early - avoid setting state in init(), as evaluate() will bail too | 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 | 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_FPS | 879 | // 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 target | 881 | // 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 future | 886 | 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 the | 887 | 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 averageUpdate | 888 | |
| 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 end | 900 | // 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 process | 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 | 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 | ||
| 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 | // NOTE: This is private because it must be called on the same thread that created the timers, which is why | 1029 | // 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. |
| 987 | void ScriptEngine::stopAllTimers() { | 1031 | void ScriptEngine::stopAllTimers() { |
| @@ -1077,7 +1121,12 @@ void ScriptEngine::timerFired() { · 89 unmodified lines | |||
| 1077 | 1121 | ||
| 1078 | // call the associated JS function, if it exists | 1122 | // 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 does | 1147 | // 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); |