diff --git a/src/corelib/kernel/qeventdispatcher_glib.cpp b/src/corelib/kernel/qeventdispatcher_glib.cpp index c644dd35d8..eeb175c374 100644 --- a/src/corelib/kernel/qeventdispatcher_glib.cpp +++ b/src/corelib/kernel/qeventdispatcher_glib.cpp @@ -489,7 +489,8 @@ void QEventDispatcherGlib::registerTimer(int timerId, qint64 interval, Qt::Timer #endif Q_D(QEventDispatcherGlib); - d->timerSource->timerList.registerTimer(timerId, interval, timerType, object); + d->timerSource->timerList.registerTimer(timerId, std::chrono::milliseconds{ interval }, + timerType, object); } bool QEventDispatcherGlib::unregisterTimer(int timerId) diff --git a/src/corelib/kernel/qeventdispatcher_unix.cpp b/src/corelib/kernel/qeventdispatcher_unix.cpp index d7b2fd6dd7..c75b59acfe 100644 --- a/src/corelib/kernel/qeventdispatcher_unix.cpp +++ b/src/corelib/kernel/qeventdispatcher_unix.cpp @@ -299,7 +299,7 @@ void QEventDispatcherUNIX::registerTimer(int timerId, qint64 interval, Qt::Timer #endif Q_D(QEventDispatcherUNIX); - d->timerList.registerTimer(timerId, interval, timerType, obj); + d->timerList.registerTimer(timerId, std::chrono::milliseconds{ interval }, timerType, obj); } /*! diff --git a/src/corelib/kernel/qtimerinfo_unix.cpp b/src/corelib/kernel/qtimerinfo_unix.cpp index 0a9e7e0a6a..48b1c2cb71 100644 --- a/src/corelib/kernel/qtimerinfo_unix.cpp +++ b/src/corelib/kernel/qtimerinfo_unix.cpp @@ -9,7 +9,6 @@ #include "private/qtimerinfo_unix_p.h" #include "private/qobject_p.h" #include "private/qabstracteventdispatcher_p.h" -#include #ifdef QTIMERINFO_DEBUG # include @@ -18,6 +17,8 @@ #include +using namespace std::chrono; + QT_BEGIN_NAMESPACE Q_CORE_EXPORT bool qt_disable_lowpriority_timers=false; @@ -153,6 +154,25 @@ static_assert(roundToMillisecond({0, 1'000'000}) == timespec{0, 1'000'000}); static_assert(roundToMillisecond({0, 999'999'999}) == timespec{1, 0}); static_assert(roundToMillisecond({1, 0}) == timespec{1, 0}); +static constexpr seconds roundToSecs(milliseconds msecs) +{ + // The very coarse timer is based on full second precision, so we want to + // round the interval to the closest second, rounding 500ms up to 1s. + // + // std::chrono::round() wouldn't work with all multiples of 500 because for the + // middle point it would round to even: + // value round() wanted + // 500 0 1 + // 1500 2 2 + // 2500 2 3 + + auto secs = duration_cast(msecs); + const milliseconds frac = msecs - secs; + if (frac >= 500ms) + ++secs; + return secs; +} + #ifdef QTIMERINFO_DEBUG QDebug operator<<(QDebug s, timeval tv) { @@ -187,97 +207,96 @@ static void calculateCoarseTimerTimeout(QTimerInfo *t, timespec currentTime) // // The objective is to make most timers wake up at the same time, thereby reducing CPU wakeups. - uint interval = uint(t->interval); - Q_ASSERT(interval >= 20); + Q_ASSERT(t->interval >= 20ms); + // Calculate how much we can round and still keep within 5% error - uint absMaxRounding = interval / 20; + const milliseconds absMaxRounding = t->interval / 20; - using namespace std::chrono; - uint msec = duration_cast(nanoseconds{t->timeout.tv_nsec}).count(); + auto fracMsec = duration_cast(nanoseconds{t->timeout.tv_nsec}); - if (interval < 100 && interval != 25 && interval != 50 && interval != 75) { + if (t->interval < 100ms && t->interval != 25ms && t->interval != 50ms && t->interval != 75ms) { + auto fracCount = fracMsec.count(); // special mode for timers of less than 100 ms - if (interval < 50) { + if (t->interval < 50ms) { // round to even // round towards multiples of 50 ms - bool roundUp = (msec % 50) >= 25; - msec >>= 1; - msec |= uint(roundUp); - msec <<= 1; + bool roundUp = (fracCount % 50) >= 25; + fracCount >>= 1; + fracCount |= roundUp; + fracCount <<= 1; } else { // round to multiple of 4 // round towards multiples of 100 ms - bool roundUp = (msec % 100) >= 50; - msec >>= 2; - msec |= uint(roundUp); - msec <<= 2; + bool roundUp = (fracCount % 100) >= 50; + fracCount >>= 2; + fracCount |= roundUp; + fracCount <<= 2; } + fracMsec = milliseconds{fracCount}; } else { - uint min = qMax(0, msec - absMaxRounding); - uint max = qMin(1000u, msec + absMaxRounding); + milliseconds min = std::max(0ms, fracMsec - absMaxRounding); + milliseconds max = std::min(1000ms, fracMsec + absMaxRounding); // find the boundary that we want, according to the rules above // extra rules: // 1) whatever the interval, we'll take any round-to-the-second timeout - if (min == 0) { - msec = 0; + if (min == 0ms) { + fracMsec = 0ms; goto recalculate; - } else if (max == 1000) { - msec = 1000; + } else if (max == 1000ms) { + fracMsec = 1000ms; goto recalculate; } - uint wantedBoundaryMultiple; + milliseconds wantedBoundaryMultiple{25}; // 2) if the interval is a multiple of 500 ms and > 5000 ms, we'll always round // towards a round-to-the-second // 3) if the interval is a multiple of 500 ms, we'll round towards the nearest // multiple of 500 ms - if ((interval % 500) == 0) { - if (interval >= 5000) { - msec = msec >= 500 ? max : min; + if ((t->interval % 500) == 0ms) { + if (t->interval >= 5s) { + fracMsec = fracMsec >= 500ms ? max : min; goto recalculate; } else { - wantedBoundaryMultiple = 500; + wantedBoundaryMultiple = 500ms; } - } else if ((interval % 50) == 0) { + } else if ((t->interval % 50) == 0ms) { // 4) same for multiples of 250, 200, 100, 50 - uint mult50 = interval / 50; - if ((mult50 % 4) == 0) { + milliseconds mult50 = t->interval / 50; + if ((mult50 % 4) == 0ms) { // multiple of 200 - wantedBoundaryMultiple = 200; - } else if ((mult50 % 2) == 0) { + wantedBoundaryMultiple = 200ms; + } else if ((mult50 % 2) == 0ms) { // multiple of 100 - wantedBoundaryMultiple = 100; - } else if ((mult50 % 5) == 0) { + wantedBoundaryMultiple = 100ms; + } else if ((mult50 % 5) == 0ms) { // multiple of 250 - wantedBoundaryMultiple = 250; + wantedBoundaryMultiple = 250ms; } else { // multiple of 50 - wantedBoundaryMultiple = 50; + wantedBoundaryMultiple = 50ms; } - } else { - wantedBoundaryMultiple = 25; } - uint base = msec / wantedBoundaryMultiple * wantedBoundaryMultiple; - uint middlepoint = base + wantedBoundaryMultiple / 2; - if (msec < middlepoint) - msec = qMax(base, min); + milliseconds base = (fracMsec / wantedBoundaryMultiple) * wantedBoundaryMultiple; + milliseconds middlepoint = base + wantedBoundaryMultiple / 2; + if (fracMsec < middlepoint) + fracMsec = qMax(base, min); else - msec = qMin(base + wantedBoundaryMultiple, max); + fracMsec = qMin(base + wantedBoundaryMultiple, max); } recalculate: - if (msec == 1000u) { + if (fracMsec == 1000ms) { ++t->timeout.tv_sec; t->timeout.tv_nsec = 0; } else { - t->timeout.tv_nsec = nanoseconds{milliseconds{msec}}.count(); + t->timeout.tv_nsec = nanoseconds{fracMsec}.count(); } if (t->timeout < currentTime) - t->timeout += interval; + t->timeout += t->interval; } static void calculateNextTimeout(QTimerInfo *t, timespec currentTime) @@ -302,10 +321,11 @@ static void calculateNextTimeout(QTimerInfo *t, timespec currentTime) return; case Qt::VeryCoarseTimer: - // we don't need to take care of the microsecond component of t->interval - t->timeout.tv_sec += t->interval; + // t->interval already rounded to full seconds in registerTimer() + const auto secs = duration_cast(t->interval).count(); + t->timeout.tv_sec += secs; if (t->timeout.tv_sec <= currentTime.tv_sec) - t->timeout.tv_sec = currentTime.tv_sec + t->interval; + t->timeout.tv_sec = currentTime.tv_sec + secs; #ifdef QTIMERINFO_DEBUG t->expected.tv_sec += t->interval; if (t->expected.tv_sec <= currentTime.tv_sec) @@ -361,6 +381,11 @@ bool QTimerInfoList::timerWait(timespec &tm) If the timer is overdue, the returned value will be 0. */ qint64 QTimerInfoList::timerRemainingTime(int timerId) +{ + return remainingDuration(timerId).count(); +} + +milliseconds QTimerInfoList::remainingDuration(int timerId) { timespec currentTime = updateCurrentTime(); repairTimersIfNeeded(); @@ -371,10 +396,9 @@ qint64 QTimerInfoList::timerRemainingTime(int timerId) if (currentTime < t->timeout) { // time to wait tm = roundToMillisecond(t->timeout - currentTime); - const std::chrono::milliseconds dur = QtMiscUtils::timespecToChronoMs(&tm); - return dur.count(); + return QtMiscUtils::timespecToChronoMs(&tm); } else { - return 0; + return milliseconds{0}; } } } @@ -383,10 +407,16 @@ qint64 QTimerInfoList::timerRemainingTime(int timerId) qWarning("QTimerInfoList::timerRemainingTime: timer id %i not found", timerId); #endif - return -1; + return milliseconds{-1}; } void QTimerInfoList::registerTimer(int timerId, qint64 interval, Qt::TimerType timerType, QObject *object) +{ + registerTimer(timerId, milliseconds{interval}, timerType, object); +} + +void QTimerInfoList::registerTimer(int timerId, milliseconds interval, + Qt::TimerType timerType, QObject *object) { QTimerInfo *t = new QTimerInfo; t->id = timerId; @@ -409,30 +439,26 @@ void QTimerInfoList::registerTimer(int timerId, qint64 interval, Qt::TimerType t // so our boundaries are 20 ms and 20 s // below 20 ms, 5% inaccuracy is below 1 ms, so we convert to high precision // above 20 s, 5% inaccuracy is above 1 s, so we convert to VeryCoarseTimer - if (interval >= 20000) { + if (interval >= 20s) { t->timerType = Qt::VeryCoarseTimer; } else { t->timeout = expected; - if (interval <= 20) { + if (interval <= 20ms) { t->timerType = Qt::PreciseTimer; // no adjustment is necessary - } else if (interval <= 20000) { + } else if (interval <= 20s) { calculateCoarseTimerTimeout(t, currentTime); } break; } Q_FALLTHROUGH(); case Qt::VeryCoarseTimer: - // the very coarse timer is based on full second precision, - // so we keep the interval in seconds (round to closest second) - t->interval /= 500; - t->interval += 1; - t->interval >>= 1; - t->timeout.tv_sec = currentTime.tv_sec + t->interval; + const seconds secs = roundToSecs(t->interval); + t->interval = secs; + t->timeout.tv_sec = currentTime.tv_sec + secs.count(); t->timeout.tv_nsec = 0; // if we're past the half-second mark, increase the timeout again - using namespace std::chrono; if (currentTime.tv_nsec > nanoseconds{500ms}.count()) ++t->timeout.tv_sec; } @@ -493,15 +519,9 @@ bool QTimerInfoList::unregisterTimers(QObject *object) QList QTimerInfoList::registeredTimers(QObject *object) const { QList list; - for (int i = 0; i < size(); ++i) { - const QTimerInfo * const t = at(i); - if (t->obj == object) { - list << QAbstractEventDispatcher::TimerInfo(t->id, - (t->timerType == Qt::VeryCoarseTimer - ? t->interval * 1000 - : t->interval), - t->timerType); - } + for (const QTimerInfo *const t : std::as_const(*this)) { + if (t->obj == object) + list.emplaceBack(t->id, t->interval.count(), t->timerType); } return list; } @@ -576,7 +596,7 @@ int QTimerInfoList::activateTimers() // reinsert timer timerInsert(currentTimerInfo); - if (currentTimerInfo->interval > 0) + if (currentTimerInfo->interval > 0ms) n_act++; // Send event, but don't allow it to recurse: diff --git a/src/corelib/kernel/qtimerinfo_unix_p.h b/src/corelib/kernel/qtimerinfo_unix_p.h index 902450dee9..630c1d532a 100644 --- a/src/corelib/kernel/qtimerinfo_unix_p.h +++ b/src/corelib/kernel/qtimerinfo_unix_p.h @@ -31,7 +31,7 @@ QT_BEGIN_NAMESPACE struct QTimerInfo { int id; // - timer identifier Qt::TimerType timerType; // - timer type - qint64 interval; // - timer interval in milliseconds + std::chrono::milliseconds interval; // - timer interval timespec timeout; // - when to actually fire QObject *obj; // - object to receive event QTimerInfo **activateRef; // - ref from activateTimers @@ -71,8 +71,11 @@ public: void timerInsert(QTimerInfo *); qint64 timerRemainingTime(int timerId); + std::chrono::milliseconds remainingDuration(int timerId); void registerTimer(int timerId, qint64 interval, Qt::TimerType timerType, QObject *object); + void registerTimer(int timerId, std::chrono::milliseconds interval, Qt::TimerType timerType, + QObject *object); bool unregisterTimer(int timerId); bool unregisterTimers(QObject *object); QList registeredTimers(QObject *object) const;