QTimerInfo: use chrono for time intervals
For VeryCoarseTimer: - The code used to convert QTimerInfo::interval to seconds in registerTimer(), then convert to milliseconds when creating QTimerInfo in registeredTimers(); this is a bit confusing as "interval" was sometimes milliseconds and sometimes seconds depending on the type of the timer; instead "round" to the nearest second while always keeping "interval" in milliseconds (relying on chrono doing the conversion).. - Add roundToSecs() helper; it behaves like the original code (i.e. rounding each 500ms to 1 full second): const auto list = {300, 499, 500, 600, 1000, 1300, 1499, 1500, 1501, 1600, 2000, 2300, 2499, 2500, 2600}; using namespace std::chrono; for (int dur : list) { auto i = dur; i /= 500; i += 1; i >>= 1; // Original code milliseconds msec{dur}; seconds secs = duration_cast<seconds>(msec); milliseconds frac = msec - secs; if (frac >= 500ms) secs += 1s; assert(i == secs.count()); } ---- - Don't mix signed and unsigned when doing arithmetic The next "chrono-first" step would be changing QAbstractEventDispatcher::TimerInfo::interval from int to chrono::milliseconds, and adding a virtual QAbstractEventDispatcher::registerTimer() overload that takes chrono::milliseconds; neither can be done until Qt7 due to binary compatibility constraints, c.f.: https://community.kde.org/Policies/Binary_Compatibility_Issues_With_C++ Task-number: QTBUG-110059 Change-Id: I36f9bd8fb29565b1131afb3cdfc313452f625598 Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
parent
39a4cd126f
commit
6754795e54
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -9,7 +9,6 @@
|
||||
#include "private/qtimerinfo_unix_p.h"
|
||||
#include "private/qobject_p.h"
|
||||
#include "private/qabstracteventdispatcher_p.h"
|
||||
#include <QtCore/private/qtools_p.h>
|
||||
|
||||
#ifdef QTIMERINFO_DEBUG
|
||||
# include <QDebug>
|
||||
@ -18,6 +17,8 @@
|
||||
|
||||
#include <sys/times.h>
|
||||
|
||||
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<seconds>(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<milliseconds>(nanoseconds{t->timeout.tv_nsec}).count();
|
||||
auto fracMsec = duration_cast<milliseconds>(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<int>(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<seconds>(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<QAbstractEventDispatcher::TimerInfo> QTimerInfoList::registeredTimers(QObject *object) const
|
||||
{
|
||||
QList<QAbstractEventDispatcher::TimerInfo> 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:
|
||||
|
@ -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<QAbstractEventDispatcher::TimerInfo> registeredTimers(QObject *object) const;
|
||||
|
Loading…
Reference in New Issue
Block a user