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