mirror of
https://github.com/lubosz/overte.git
synced 2025-04-08 02:24:09 +02:00
better handling of timers
This commit is contained in:
parent
a72f60152b
commit
0c22fcb5c4
3 changed files with 73 additions and 21 deletions
|
@ -91,7 +91,7 @@ void DiskCacheEditor::makeDialog() {
|
|||
|
||||
static const int REFRESH_INTERVAL = 100; // msec
|
||||
_refreshTimer = new QTimer(_dialog);
|
||||
_refreshTimer->setInterval(REFRESH_INTERVAL); // 100ms, Qt::CoarseTimer acceptable
|
||||
_refreshTimer->setInterval(REFRESH_INTERVAL); // Qt::CoarseTimer acceptable, no need for real time accuracy
|
||||
_refreshTimer->setSingleShot(false);
|
||||
QObject::connect(_refreshTimer.data(), &QTimer::timeout, this, &DiskCacheEditor::refresh);
|
||||
_refreshTimer->start();
|
||||
|
|
|
@ -800,7 +800,7 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString&
|
|||
_registeredHandlers[entityID] = RegisteredEventHandlers();
|
||||
}
|
||||
CallbackList& handlersForEvent = _registeredHandlers[entityID][eventName];
|
||||
CallbackData handlerData = {handler, currentEntityIdentifier, currentSandboxURL};
|
||||
CallbackData handlerData = { handler, currentEntityIdentifier, currentSandboxURL };
|
||||
handlersForEvent << handlerData; // Note that the same handler can be added many times. See removeEntityEventHandler().
|
||||
}
|
||||
|
||||
|
@ -840,6 +840,15 @@ QScriptValue ScriptEngine::evaluate(const QString& sourceCode, const QString& fi
|
|||
}
|
||||
|
||||
void ScriptEngine::run() {
|
||||
|
||||
#ifdef _WIN32
|
||||
// VS13 does not sleep_until unless it uses the system_clock, see:
|
||||
// https://www.reddit.com/r/cpp_questions/comments/3o71ic/sleep_until_not_working_with_a_time_pointsteady/
|
||||
using clock = std::chrono::system_clock;
|
||||
#else
|
||||
using clock = std::chrono::high_resolution_clock;
|
||||
#endif
|
||||
|
||||
if (DependencyManager::get<ScriptEngines>()->isStopped()) {
|
||||
return; // bail early - avoid setting state in init(), as evaluate() will bail too
|
||||
}
|
||||
|
@ -853,14 +862,6 @@ void ScriptEngine::run() {
|
|||
|
||||
QScriptValue result = evaluate(_scriptContents, _fileNameString);
|
||||
|
||||
#ifdef _WIN32
|
||||
// VS13 does not sleep_until unless it uses the system_clock, see:
|
||||
// https://www.reddit.com/r/cpp_questions/comments/3o71ic/sleep_until_not_working_with_a_time_pointsteady/
|
||||
using clock = std::chrono::system_clock;
|
||||
#else
|
||||
using clock = std::chrono::high_resolution_clock;
|
||||
#endif
|
||||
|
||||
clock::time_point startTime = clock::now();
|
||||
int thisFrame = 0;
|
||||
|
||||
|
@ -878,23 +879,48 @@ void ScriptEngine::run() {
|
|||
// Throttle to SCRIPT_FPS
|
||||
// We'd like to try to keep the script at a solid SCRIPT_FPS update rate. And so we will
|
||||
// calculate a sleepUntil to be the time from our start time until the original target
|
||||
// sleepUntil for this frame.
|
||||
const std::chrono::microseconds FRAME_DURATION(USECS_PER_SECOND / SCRIPT_FPS + 1);
|
||||
clock::time_point targetSleepUntil(startTime + thisFrame++ * FRAME_DURATION);
|
||||
// sleepUntil for this frame. This approach will allow us to "catch up" in the event
|
||||
// that some of our script udpates/frames take a little bit longer than the target average
|
||||
// to execute.
|
||||
// NOTE: if we go to variable SCRIPT_FPS, then we will need to reconsider this approach
|
||||
const std::chrono::microseconds TARGET_SCRIPT_FRAME_DURATION(USECS_PER_SECOND / SCRIPT_FPS + 1);
|
||||
clock::time_point targetSleepUntil(startTime + (thisFrame++ * TARGET_SCRIPT_FRAME_DURATION));
|
||||
|
||||
// However, if our sleepUntil is not at least our average update time into the future
|
||||
// it means our script is taking too long in it's updates, and we want to punish the
|
||||
// script a little bit. So we will force the sleepUntil to be at least our averageUpdate
|
||||
// time into the future.
|
||||
// However, if our sleepUntil is not at least our average update and timer execution time
|
||||
// into the future it means our script is taking too long in its updates, and we want to
|
||||
// punish the script a little bit. So we will force the sleepUntil to be at least our
|
||||
// averageUpdate + averageTimerPerFrame time into the future.
|
||||
auto averageUpdate = totalUpdates / thisFrame;
|
||||
auto sleepUntil = std::max(targetSleepUntil, beforeSleep + averageUpdate);
|
||||
auto averageTimerPerFrame = _totalTimerExecution / thisFrame;
|
||||
auto averageTimerAndUpdate = averageUpdate + averageTimerPerFrame;
|
||||
auto sleepUntil = std::max(targetSleepUntil, beforeSleep + averageTimerAndUpdate);
|
||||
|
||||
// We don't want to actually sleep for too long, because it causes our scripts to hang
|
||||
// on shutdown and stop... so we want to loop and sleep until we've spent our time in
|
||||
// purgatory, constantly checking to see if our script was asked to end
|
||||
bool processedEvents = false;
|
||||
while (!_isFinished && clock::now() < sleepUntil) {
|
||||
|
||||
QCoreApplication::processEvents(); // before we sleep again, give events a chance to process
|
||||
auto thisSleepUntil = std::min(sleepUntil, clock::now() + FRAME_DURATION);
|
||||
processedEvents = true;
|
||||
|
||||
// If after processing events, we're past due, exit asap
|
||||
if (clock::now() >= sleepUntil) {
|
||||
break;
|
||||
}
|
||||
|
||||
// determine how long before the next timer should fire, we'd ideally like to sleep just
|
||||
// that long, so the next processEvents() will allow the timers to fire on time.
|
||||
const std::chrono::microseconds minTimerTimeRemaining(USECS_PER_MSEC * getTimersRemainingTime());
|
||||
|
||||
// However, if we haven't yet slept at least as long as our average timer per frame, then we will
|
||||
// punish the timers to at least wait as long as the average run time of the timers.
|
||||
auto untilTimer = std::max(minTimerTimeRemaining, averageTimerPerFrame);
|
||||
|
||||
// choose the closest time point, our
|
||||
auto remainingSleepUntil = std::chrono::duration_cast<std::chrono::microseconds>(sleepUntil - clock::now());
|
||||
auto closestUntil = std::min(remainingSleepUntil, untilTimer);
|
||||
auto thisSleepUntil = std::min(sleepUntil, clock::now() + closestUntil);
|
||||
std::this_thread::sleep_until(thisSleepUntil);
|
||||
}
|
||||
|
||||
|
@ -919,7 +945,10 @@ void ScriptEngine::run() {
|
|||
break;
|
||||
}
|
||||
|
||||
QCoreApplication::processEvents();
|
||||
// Only call this if we didn't processEvents as part of waiting for next frame
|
||||
if (!processedEvents) {
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
|
||||
if (_isFinished) {
|
||||
break;
|
||||
|
@ -982,6 +1011,21 @@ void ScriptEngine::run() {
|
|||
emit doneRunning();
|
||||
}
|
||||
|
||||
quint64 ScriptEngine::getTimersRemainingTime() {
|
||||
quint64 minimumTime = USECS_PER_SECOND; // anything larger than this can be ignored
|
||||
QMutableHashIterator<QTimer*, CallbackData> i(_timerFunctionMap);
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
QTimer* timer = i.key();
|
||||
int remainingTime = timer->remainingTime();
|
||||
if (remainingTime >= 0) {
|
||||
minimumTime = std::min((quint64)remainingTime, minimumTime);
|
||||
}
|
||||
}
|
||||
return minimumTime;
|
||||
}
|
||||
|
||||
|
||||
// NOTE: This is private because it must be called on the same thread that created the timers, which is why
|
||||
// we want to only call it in our own run "shutdown" processing.
|
||||
void ScriptEngine::stopAllTimers() {
|
||||
|
@ -1077,7 +1121,12 @@ void ScriptEngine::timerFired() {
|
|||
|
||||
// call the associated JS function, if it exists
|
||||
if (timerData.function.isValid()) {
|
||||
auto preTimer = p_high_resolution_clock::now();
|
||||
callWithEnvironment(timerData.definingEntityIdentifier, timerData.definingSandboxURL, timerData.function, timerData.function, QScriptValueList());
|
||||
auto postTimer = p_high_resolution_clock::now();
|
||||
auto elapsed = (postTimer - preTimer);
|
||||
_totalTimerExecution += std::chrono::duration_cast<std::chrono::microseconds>(elapsed);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1098,7 +1147,7 @@ QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int
|
|||
// make sure the timer stops when the script does
|
||||
connect(this, &ScriptEngine::scriptEnding, newTimer, &QTimer::stop);
|
||||
|
||||
CallbackData timerData = {function, currentEntityIdentifier, currentSandboxURL};
|
||||
CallbackData timerData = {function, currentEntityIdentifier, currentSandboxURL };
|
||||
_timerFunctionMap.insert(newTimer, timerData);
|
||||
|
||||
newTimer->start(intervalMS);
|
||||
|
|
|
@ -218,6 +218,7 @@ protected:
|
|||
void init();
|
||||
|
||||
bool evaluatePending() const { return _evaluatesPending > 0; }
|
||||
quint64 getTimersRemainingTime();
|
||||
void timerFired();
|
||||
void stopAllTimers();
|
||||
void stopAllTimersForEntityScript(const EntityItemID& entityID);
|
||||
|
@ -252,6 +253,8 @@ protected:
|
|||
std::function<bool()> _emitScriptUpdates{ [](){ return true; } };
|
||||
|
||||
std::recursive_mutex _lock;
|
||||
|
||||
std::chrono::microseconds _totalTimerExecution { 0 };
|
||||
};
|
||||
|
||||
#endif // hifi_ScriptEngine_h
|
||||
|
|
Loading…
Reference in a new issue