QTimerInfo: store nanoseconds instead of milliseconds

No change in behavior, other than the ability for the precise timers to
be even more precise. There's a reduction in the stored interval range
from 292 million years to 292 years, but there's no change in behavior
because the timeout is stored as a steady_clock::time_point, which is
nanoseconds with libstdc++ and libc++. Now, if there's an overflow,
it'll happen on the call to registerTimer() instead of inside the
calculation of the wake-up.

Change-Id: I83dda2d36c904517b3c0fffd17b3d1f2d9505c1c
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
bb10
Thiago Macieira 2024-02-18 19:24:02 -08:00
parent 1ca89b65d8
commit 9dc2935462
5 changed files with 39 additions and 30 deletions

View File

@ -14,6 +14,7 @@
#include <glib.h>
using namespace std::chrono;
using namespace std::chrono_literals;
QT_BEGIN_NAMESPACE
@ -102,8 +103,8 @@ static gboolean timerSourcePrepareHelper(GTimerSource *src, gint *timeout)
return true;
}
auto msecs = src->timerList.timerWait().value_or(-1ms);
*timeout = qt_saturate<gint>(msecs.count());
auto remaining = src->timerList.timerWait().value_or(-1ms);
*timeout = qt_saturate<gint>(ceil<milliseconds>(remaining).count());
return (*timeout == 0);
}

View File

@ -29,6 +29,7 @@ static constexpr bool UsingEventfd = false;
# include <pipeDrv.h>
#endif
using namespace std::chrono;
using namespace std::chrono_literals;
QT_BEGIN_NAMESPACE
@ -431,8 +432,8 @@ bool QEventDispatcherUNIX::processEvents(QEventLoop::ProcessEventsFlags flags)
QDeadlineTimer deadline;
if (canWait) {
if (include_timers) {
std::optional<std::chrono::milliseconds> msecs = d->timerList.timerWait();
deadline = msecs ? QDeadlineTimer{*msecs}
std::optional<nanoseconds> remaining = d->timerList.timerWait();
deadline = remaining ? QDeadlineTimer{*remaining}
: QDeadlineTimer(QDeadlineTimer::Forever);
} else {
deadline = QDeadlineTimer(QDeadlineTimer::Forever);

View File

@ -14,6 +14,7 @@
#include <emscripten/threading.h>
#include <emscripten/val.h>
using namespace std::chrono;
using namespace std::chrono_literals;
QT_BEGIN_NAMESPACE
@ -619,8 +620,8 @@ void QEventDispatcherWasm::updateNativeTimer()
// access to m_timerInfo), and then call native API to set the new
// wakeup time on the main thread.
const std::optional<std::chrono::milliseconds> wait = m_timerInfo->timerWait();
const auto toWaitDuration = wait.value_or(0ms);
const std::optional<std::chrono::nanoseconds> wait = m_timerInfo->timerWait();
const auto toWaitDuration = duration_cast<milliseconds>(wait.value_or(0ms));
const auto newTargetTimePoint = m_timerInfo->currentTime + toWaitDuration;
auto epochNsecs = newTargetTimePoint.time_since_epoch();
auto newTargetTime = std::chrono::duration_cast<std::chrono::milliseconds>(epochNsecs);

View File

@ -74,7 +74,7 @@ static_assert(roundToMillisecond(999'000'001ns) == 1000ms);
static_assert(roundToMillisecond(999'999'999ns) == 1000ms);
static_assert(roundToMillisecond(1s) == 1s);
static constexpr seconds roundToSecs(milliseconds msecs)
static constexpr seconds roundToSecs(nanoseconds interval)
{
// 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.
@ -86,8 +86,8 @@ static constexpr seconds roundToSecs(milliseconds msecs)
// 1500 2 2
// 2500 2 3
auto secs = duration_cast<seconds>(msecs);
const milliseconds frac = msecs - secs;
auto secs = duration_cast<seconds>(interval);
const nanoseconds frac = interval - secs;
if (frac >= 500ms)
++secs;
return secs;
@ -122,14 +122,15 @@ static void calculateCoarseTimerTimeout(QTimerInfo *t, steady_clock::time_point
};
// Calculate how much we can round and still keep within 5% error
const milliseconds absMaxRounding = t->interval / 20;
milliseconds interval = roundToMillisecond(t->interval);
const milliseconds absMaxRounding = interval / 20;
auto fracMsec = duration_cast<milliseconds>(t->timeout - timeoutInSecs);
if (t->interval < 100ms && t->interval != 25ms && t->interval != 50ms && t->interval != 75ms) {
if (interval < 100ms && interval != 25ms && interval != 50ms && interval != 75ms) {
auto fracCount = fracMsec.count();
// special mode for timers of less than 100 ms
if (t->interval < 50ms) {
if (interval < 50ms) {
// round to even
// round towards multiples of 50 ms
bool roundUp = (fracCount % 50) >= 25;
@ -171,17 +172,17 @@ static void calculateCoarseTimerTimeout(QTimerInfo *t, steady_clock::time_point
// 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 ((t->interval % 500) == 0ms) {
if (t->interval >= 5s) {
if ((interval % 500) == 0ms) {
if (interval >= 5s) {
fracMsec = fracMsec >= 500ms ? max : min;
recalculate(fracMsec);
return;
} else {
wantedBoundaryMultiple = 500ms;
}
} else if ((t->interval % 50) == 0ms) {
} else if ((interval % 50) == 0ms) {
// 4) same for multiples of 250, 200, 100, 50
milliseconds mult50 = t->interval / 50;
milliseconds mult50 = interval / 50;
if ((mult50 % 4) == 0ms) {
// multiple of 200
wantedBoundaryMultiple = 200ms;
@ -234,7 +235,7 @@ static void calculateNextTimeout(QTimerInfo *t, steady_clock::time_point now)
Returns the time to wait for the first timer that has not been activated yet,
otherwise returns std::nullopt.
*/
std::optional<std::chrono::milliseconds> QTimerInfoList::timerWait()
std::optional<QTimerInfoList::Duration> QTimerInfoList::timerWait()
{
steady_clock::time_point now = updateCurrentTime();
@ -257,10 +258,10 @@ std::optional<std::chrono::milliseconds> QTimerInfoList::timerWait()
*/
qint64 QTimerInfoList::timerRemainingTime(int timerId)
{
return remainingDuration(timerId).count();
return roundToMillisecond(remainingDuration(timerId)).count();
}
milliseconds QTimerInfoList::remainingDuration(int timerId)
QTimerInfoList::Duration QTimerInfoList::remainingDuration(int timerId)
{
const steady_clock::time_point now = updateCurrentTime();
@ -274,7 +275,7 @@ milliseconds QTimerInfoList::remainingDuration(int timerId)
const QTimerInfo *t = *it;
if (now < t->timeout) // time to wait
return roundToMillisecond(t->timeout - now);
return t->timeout - now;
return 0ms;
}
@ -283,7 +284,7 @@ void QTimerInfoList::registerTimer(int timerId, qint64 interval, Qt::TimerType t
registerTimer(timerId, milliseconds{interval}, timerType, object);
}
void QTimerInfoList::registerTimer(int timerId, milliseconds interval,
void QTimerInfoList::registerTimer(int timerId, Duration interval,
Qt::TimerType timerType, QObject *object)
{
// correct the timer type first
@ -299,7 +300,7 @@ void QTimerInfoList::registerTimer(int timerId, milliseconds interval,
}
QTimerInfo *t = new QTimerInfo(timerId, interval, timerType, object);
steady_clock::time_point expected = updateCurrentTime() + interval;
QTimerInfo::TimePoint expected = updateCurrentTime() + interval;
switch (timerType) {
case Qt::PreciseTimer:
@ -310,6 +311,7 @@ void QTimerInfoList::registerTimer(int timerId, milliseconds interval,
case Qt::CoarseTimer:
t->timeout = expected;
t->interval = roundToMillisecond(interval);
calculateCoarseTimerTimeout(t, currentTime);
break;

View File

@ -25,14 +25,17 @@
QT_BEGIN_NAMESPACE
// internal timer info
struct QTimerInfo {
QTimerInfo(int timerId, std::chrono::milliseconds msecs, Qt::TimerType type, QObject *obj)
: interval(msecs), id(timerId), timerType(type), obj(obj)
struct QTimerInfo
{
using Duration = QAbstractEventDispatcher::Duration;
using TimePoint = std::chrono::time_point<std::chrono::steady_clock, Duration>;
QTimerInfo(int timerId, Duration interval, Qt::TimerType type, QObject *obj)
: interval(interval), id(timerId), timerType(type), obj(obj)
{
}
std::chrono::steady_clock::time_point timeout; // - when to actually fire
std::chrono::milliseconds interval = std::chrono::milliseconds{-1}; // - timer interval
TimePoint timeout = {}; // - when to actually fire
Duration interval = Duration{-1}; // - timer interval
int id = -1; // - timer identifier
Qt::TimerType timerType; // - timer type
QObject *obj = nullptr; // - object to receive event
@ -42,18 +45,19 @@ struct QTimerInfo {
class Q_CORE_EXPORT QTimerInfoList
{
public:
using Duration = QAbstractEventDispatcher::Duration;
QTimerInfoList();
std::chrono::steady_clock::time_point currentTime;
std::optional<std::chrono::milliseconds> timerWait();
std::optional<Duration> timerWait();
void timerInsert(QTimerInfo *);
qint64 timerRemainingTime(int timerId);
std::chrono::milliseconds remainingDuration(int timerId);
Duration 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,
void registerTimer(int timerId, Duration interval, Qt::TimerType timerType,
QObject *object);
bool unregisterTimer(int timerId);
bool unregisterTimers(QObject *object);