diff options
author | Ahmad Samir <a.samirh78@gmail.com> | 2023-07-11 16:32:39 +0300 |
---|---|---|
committer | Ahmad Samir <a.samirh78@gmail.com> | 2024-03-03 19:56:55 +0200 |
commit | bd764cc1ca578759f16fbe292fbe14a243b7d263 (patch) | |
tree | 9e270f40316c60a3b536e2019fc70d63cadc0224 | |
parent | 4fa9034d0c592e5f07531d41463c8c462f5e8895 (diff) |
Add QChronoTimer, a timer with nanoseconds precision
The interval in QTimer is a QProperty of type int, which means it's
limited to the number of milliseconds that would fit in an int (~24
days), this could cause overflow if a user constructs a QTimer with an
interval > INT_MAX milliseconds. And it can't be easily changed to use
qint64/std::chrono::nanoseconds:
- changing the getters to return qint64 means user code would have
narrowing conversions
- the bindable QProperty interval can't be changed to qint64 during
Qt6's lifetime without the risk of breaking user code
- adding a new bindable QProperty that is qint64/nanoseconds is an
option, but it has the complication of what to do with the int
interval; set it when setInterval(milliseconds) is used by using
saturation arithmetic? and what about notifying observers of the
changed interval?
Thus the idea of creating a new stop-gap class, QChronoTimer, as a
cleaner solution. Both classes use QTimerPrivate.
During the lifetime of Qt6, QTimer's interval range is about 24 days,
whereas QChronoTimer's interval range is about 292 years
(duration_cast<years>nanoseconds::max()).
Currently the plan is to fold QChronotTimer back into QTimer in Qt7.
Mark all QPropertyS in the new class as FINAL since they aren't
intended to be overridden; this offers a performance boost for QML[1].
[1] https://lists.qt-project.org/pipermail/development/2024-February/044977.html
[ChangeLog][QtCore] Added QChronoTimer, which uses a
std::chrono::nanoseconds intervals, as a replacement for QTimer.
Fixes: QTBUG-113544
Change-Id: I71697f4a8b35452c6b5604b1322ee7f0b4453f04
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
-rw-r--r-- | src/corelib/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/corelib/doc/snippets/timers/timers.cpp | 44 | ||||
-rw-r--r-- | src/corelib/kernel/qchronotimer.cpp | 645 | ||||
-rw-r--r-- | src/corelib/kernel/qchronotimer.h | 178 | ||||
-rw-r--r-- | src/corelib/kernel/qobjectdefs.h | 1 | ||||
-rw-r--r-- | src/corelib/kernel/qtimer.cpp | 3 | ||||
-rw-r--r-- | src/corelib/kernel/qtimer_p.h | 38 | ||||
-rw-r--r-- | tests/auto/corelib/kernel/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tests/auto/corelib/kernel/qchronotimer/.gitignore | 2 | ||||
-rw-r--r-- | tests/auto/corelib/kernel/qchronotimer/CMakeLists.txt | 15 | ||||
-rw-r--r-- | tests/auto/corelib/kernel/qchronotimer/tst_qchronotimer.cpp | 1035 | ||||
-rw-r--r-- | tests/auto/gui/kernel/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tests/auto/gui/kernel/qguichronotimer/CMakeLists.txt | 20 |
13 files changed, 776 insertions, 1208 deletions
diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt index fa18010965..f1f38c95ff 100644 --- a/src/corelib/CMakeLists.txt +++ b/src/corelib/CMakeLists.txt @@ -153,6 +153,7 @@ qt_internal_add_module(Core kernel/qassociativeiterable.cpp kernel/qassociativeiterable.h kernel/qbasictimer.cpp kernel/qbasictimer.h kernel/qbindingstorage.h + kernel/qchronotimer.cpp kernel/qchronotimer.h kernel/qcoreapplication.cpp kernel/qcoreapplication.h kernel/qcoreapplication_p.h kernel/qcoreapplication_platform.h kernel/qcorecmdlineargs_p.h diff --git a/src/corelib/doc/snippets/timers/timers.cpp b/src/corelib/doc/snippets/timers/timers.cpp index c89db6890c..84618adb81 100644 --- a/src/corelib/doc/snippets/timers/timers.cpp +++ b/src/corelib/doc/snippets/timers/timers.cpp @@ -1,8 +1,12 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +#include <QChronoTimer> +#include <QObject> #include <QTimer> +using namespace std::chrono; + class Foo : public QObject { public: @@ -35,7 +39,45 @@ Foo::Foo() } } -int main() +// QChronoTimer +class MyWidget : QObject { + MyWidget() + { +//! [qchronotimer-singleshot] + MyWidget widget; + QChronoTimer::singleShot(100ms, &widget, &MyWidget::processOneThing); +//! [qchronotimer-singleshot] +//! [zero-timer] + // The default interval is 0ns + QChronoTimer *timer = new QChronoTimer(this); + connect(timer, &QChronoTimer::timeout, this, &MyWidget::processOneThing); + timer->start(); +//! [zero-timer] + + { +//! [timer-interval-in-ctor] + QChronoTimer *timer = new QChronoTimer(1s, this); + connect(timer, &QChronoTimer::timeout, this, &MyWidget::processOneThing); + timer->start(); +//! [timer-interval-in-ctor] + } + + { +//! [timer-setinterval] + QChronoTimer *timer = new QChronoTimer(this); + connect(timer, &QChronoTimer::timeout, this, &MyWidget::processOneThing); + timer->setInterval(1s); + timer->start(); +//! [timer-setinterval] + } + } + +public Q_SLOTS: + void processOneThing(); +}; + +int main() +{ } diff --git a/src/corelib/kernel/qchronotimer.cpp b/src/corelib/kernel/qchronotimer.cpp index cbeeb97934..d67d7ca48f 100644 --- a/src/corelib/kernel/qchronotimer.cpp +++ b/src/corelib/kernel/qchronotimer.cpp @@ -2,7 +2,7 @@ // Copyright (C) 2016 Intel Corporation. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#include "qtimer.h" +#include "qchronotimer.h" #include "qtimer_p.h" #include "qsingleshottimer_p.h" @@ -20,123 +20,125 @@ using namespace std::chrono_literals; QT_BEGIN_NAMESPACE /*! - \class QTimer + \class QChronoTimer \inmodule QtCore - \brief The QTimer class provides repetitive and single-shot timers. - + \since 6.8 \ingroup events + \brief The QChronoTimer class provides repetitive and single-shot timers. - The QTimer class provides a high-level programming interface for - timers. To use it, create a QTimer, connect its timeout() signal - to the appropriate slots, and call start(). From then on, it will - emit the timeout() signal at constant intervals. - - Example for a one second (1000 millisecond) timer (from the - \l{widgets/analogclock}{Analog Clock} example): + The QChronoTimer class provides a high-level programming interface for + timers. To use it, create a QChronoTimer, either passing the interval to the + constructor, or setting it after construction using setInterval(), connect + its timeout() signal to the appropriate slots, and call start(). From then + on, it will emit the timeout() signal at constant intervals. For example: - \snippet ../widgets/widgets/analogclock/analogclock.cpp 4 - \snippet ../widgets/widgets/analogclock/analogclock.cpp 5 - \snippet ../widgets/widgets/analogclock/analogclock.cpp 6 + \snippet timers/timers.cpp timer-interval-in-ctor + \snippet timers/timers.cpp timer-setinterval - From then on, the \c update() slot is called every second. + You can set a timer to time out only once by calling setSingleShot(true). - You can set a timer to time out only once by calling - setSingleShot(true). You can also use the static - QTimer::singleShot() function to call a slot after a specified - interval: + QChronoTimer also has singleShot() static methods: - \snippet timers/timers.cpp 3 + \snippet timers/timers.cpp qchronotimer-singleshot - In multithreaded applications, you can use QTimer in any thread + In multithreaded applications, you can use QChronoTimer in any thread that has an event loop. To start an event loop from a non-GUI thread, use QThread::exec(). Qt uses the timer's \l{QObject::thread()}{thread affinity} to determine which thread - will emit the \l{QTimer::}{timeout()} signal. Because of this, you + will emit the \l{QChronoTimer::}{timeout()} signal. Because of this, you must start and stop the timer in its thread; it is not possible to start a timer from another thread. - As a special case, a QTimer with a timeout of 0 will time out as soon as - possible, though the ordering between zero timers and other sources of - events is unspecified. Zero timers can be used to do some work while still - providing a snappy user interface: + As a special case, a QChronoTimer with a timeout of \c 0ns will time out + as soon as possible, though the ordering between zero timers and other + sources of events is unspecified. Zero timers can be used to do some + work while still providing a responsive user interface: - \snippet timers/timers.cpp 4 - \snippet timers/timers.cpp 5 - \snippet timers/timers.cpp 6 + \snippet timers/timers.cpp zero-timer - From then on, \c processOneThing() will be called repeatedly. It - should be written in such a way that it always returns quickly - (typically after processing one data item) so that Qt can deliver - events to the user interface and stop the timer as soon as it has done all - its work. This is the traditional way of implementing heavy work - in GUI applications, but as multithreading is nowadays becoming available on - more and more platforms, we expect that zero-millisecond - QTimer objects will gradually be replaced by \l{QThread}s. + From then on, \c processOneThing() will be called repeatedly. It should + be written in such a way that it always returns quickly (for example, + after processing one data item) so that Qt can deliver events to the user + interface and stop the timer as soon as it has done all its work. This + is the traditional way of implementing heavy work in GUI applications, + but as multithreading is becoming available on more platforms, a modern + alternative is doing the heavy work in a thread other than the GUI (main) + thread. Qt has the QThread class, which can be used to achieve that. \section1 Accuracy and Timer Resolution - The accuracy of timers depends on the underlying operating system - and hardware. Most platforms support a resolution of 1 millisecond, - though the accuracy of the timer will not equal this resolution - in many real-world situations. + The accuracy of timers depends on the underlying operating system and + hardware. Most platforms support requesting nano-second precision for + timers (for example, libc's \c nanosleep), though the accuracy of the + timer will not equal this resolution in many real-world situations. - The accuracy also depends on the \l{Qt::TimerType}{timer type}. For - Qt::PreciseTimer, QTimer will try to keep the accuracy at 1 millisecond. - Precise timers will also never time out earlier than expected. + You can set the \l{Qt::TimerType}{timer type} to tell QChronoTimer which + precision to request from the system. - For Qt::CoarseTimer and Qt::VeryCoarseTimer types, QTimer may wake up - earlier than expected, within the margins for those types: 5% of the - interval for Qt::CoarseTimer and 500 ms for Qt::VeryCoarseTimer. + For Qt::PreciseTimer, QChronoTimer will try to keep the precision at + \c 1ns. Precise timers will never time out earlier than expected. + + For Qt::CoarseTimer and Qt::VeryCoarseTimer types, QChronoTimer may wake + up earlier than expected, within the margins for those types: + \list + \li 5% of the interval for Qt::CoarseTimer + \li \c 500ms for Qt::VeryCoarseTimer + \endlist All timer types may time out later than expected if the system is busy or unable to provide the requested accuracy. In such a case of timeout overrun, Qt will emit timeout() only once, even if multiple timeouts have expired, and then will resume the original interval. - \section1 Alternatives to QTimer + \section1 Alternatives to QChronoTimer - An alternative to using QTimer is to call QObject::startTimer() - for your object and reimplement the QObject::timerEvent() event - handler in your class (which must inherit QObject). The - disadvantage is that timerEvent() does not support such - high-level features as single-shot timers or signals. + An alternative to using QChronoTimer is to call QObject::startTimer() + for your object and reimplement the QObject::timerEvent() event handler + in your class (which must be a sub-class of QObject). The disadvantage + is that timerEvent() does not support such high-level features as + single-shot timers or signals. - Another alternative is QBasicTimer. It is typically less - cumbersome than using QObject::startTimer() - directly. See \l{Timers} for an overview of all three approaches. + Another alternative is QBasicTimer. It is typically less cumbersome + than using QObject::startTimer() directly. See \l{Timers} for an + overview of all three approaches. - Some operating systems limit the number of timers that may be - used; Qt tries to work around these limitations. + Some operating systems limit the number of timers that may be used; + Qt does its best to work around these limitations. \sa QBasicTimer, QTimerEvent, QObject::timerEvent(), Timers, {Analog Clock} */ /*! - Constructs a timer with the given \a parent. + Constructs a timer with the given \a parent, using the default interval, + \c 0ns. */ - -QTimer::QTimer(QObject *parent) - : QObject(*new QTimerPrivate(this), parent) +QChronoTimer::QChronoTimer(QObject *parent) + : QChronoTimer(0ns, parent) { - Q_ASSERT(d_func()->isQTimer); } +/*! + Constructs a timer with the given \a parent, using an interval of \a nsec. +*/ +QChronoTimer::QChronoTimer(std::chrono::nanoseconds nsec, QObject *parent) + : QObject(*new QTimerPrivate(nsec, this), parent) +{ + Q_ASSERT(!d_func()->isQTimer); +} /*! Destroys the timer. */ - -QTimer::~QTimer() +QChronoTimer::~QChronoTimer() { if (d_func()->id != QTimerPrivate::INV_TIMER) // stop running timer stop(); } - /*! - \fn void QTimer::timeout() + \fn void QChronoTimer::timeout() This signal is emitted when the timer times out. @@ -144,56 +146,50 @@ QTimer::~QTimer() */ /*! - \property QTimer::active - \since 4.3 + \property QChronoTimer::active This boolean property is \c true if the timer is running; otherwise - false. + \c false. */ /*! - \fn bool QTimer::isActive() const - Returns \c true if the timer is running (pending); otherwise returns false. */ -bool QTimer::isActive() const +bool QChronoTimer::isActive() const { return d_func()->isActiveData.value(); } -QBindable<bool> QTimer::bindableActive() +QBindable<bool> QChronoTimer::bindableActive() { return QBindable<bool>(&d_func()->isActiveData); } /*! - \fn int QTimer::timerId() const - Returns the ID of the timer if the timer is running; otherwise returns -1. */ -int QTimer::timerId() const +int QChronoTimer::id() const { return d_func()->id; } - /*! \overload start() Starts or restarts the timer with the timeout specified in \l interval. If the timer is already running, it will be - \l{QTimer::stop()}{stopped} and restarted. + \l{QChronoTimer::stop()}{stopped} and restarted. If \l singleShot is true, the timer will be activated only once. */ -void QTimer::start() +void QChronoTimer::start() { - Q_D(QTimer); + auto *d = d_func(); if (d->id != QTimerPrivate::INV_TIMER) // stop running timer stop(); - const int id = QObject::startTimer(std::chrono::milliseconds{d->inter}, d->type); + const auto id = QObject::startTimer(d->intervalDuration, d->type); if (id > 0) { d->id = id; d->isActiveData.notify(); @@ -201,52 +197,13 @@ void QTimer::start() } /*! - Starts or restarts the timer with a timeout interval of \a msec - milliseconds. - - If the timer is already running, it will be - \l{QTimer::stop()}{stopped} and restarted. - - If \l singleShot is true, the timer will be activated only once. This is - equivalent to: - - \code - timer.setInterval(msec); - timer.start(); - \endcode - - \note Keeping the event loop busy with a zero-timer is bound to - cause trouble and highly erratic behavior of the UI. -*/ -void QTimer::start(int msec) -{ - start(msec * 1ms); -} - -void QTimer::start(std::chrono::milliseconds interval) -{ - Q_D(QTimer); - // This could be narrowing as the interval is stored in an `int` QProperty, - // and the type can't be changed in Qt6. - const int msec = interval.count(); - const bool intervalChanged = msec != d->inter; - d->inter.setValue(msec); - start(); - if (intervalChanged) - d->inter.notify(); -} - - - -/*! Stops the timer. \sa start() */ - -void QTimer::stop() +void QChronoTimer::stop() { - Q_D(QTimer); + auto *d = d_func(); if (d->id != QTimerPrivate::INV_TIMER) { QObject::killTimer(d->id); d->id = QTimerPrivate::INV_TIMER; @@ -254,325 +211,84 @@ void QTimer::stop() } } - /*! \reimp */ -void QTimer::timerEvent(QTimerEvent *e) +void QChronoTimer::timerEvent(QTimerEvent *e) { - Q_D(QTimer); + auto *d = d_func(); if (e->timerId() == d->id) { if (d->single) stop(); - emit timeout(QPrivateSignal()); + Q_EMIT timeout(QPrivateSignal()); } } /*! - \internal - - Implementation of the template version of singleShot - - \a msec is the timer interval - \a timerType is the timer type - \a receiver is the receiver object, can be null. In such a case, it will be the same - as the final sender class. - \a slotObj the slot object -*/ -void QTimer::singleShotImpl(std::chrono::milliseconds msec, Qt::TimerType timerType, - const QObject *receiver, - QtPrivate::QSlotObjectBase *slotObj) -{ - if (msec == 0ms) { - bool deleteReceiver = false; - // Optimize: set a receiver context when none is given, such that we can use - // QMetaObject::invokeMethod which is more efficient than going through a timer. - // We need a QObject living in the current thread. But the QThread itself lives - // in a different thread - with the exception of the main QThread which lives in - // itself. And QThread::currentThread() is among the few QObjects we know that will - // most certainly be there. Note that one can actually call singleShot before the - // QApplication is created! - if (!receiver && QThread::currentThread() == QCoreApplicationPrivate::mainThread()) { - // reuse main thread as context object - receiver = QThread::currentThread(); - } else if (!receiver) { - // Create a receiver context object on-demand. According to the benchmarks, - // this is still more efficient than going through a timer. - receiver = new QObject; - deleteReceiver = true; - } - - auto h = QtPrivate::invokeMethodHelper({}); - QMetaObject::invokeMethodImpl(const_cast<QObject *>(receiver), slotObj, - Qt::QueuedConnection, h.parameterCount(), h.parameters.data(), h.typeNames.data(), - h.metaTypes.data()); - - if (deleteReceiver) - const_cast<QObject *>(receiver)->deleteLater(); - return; - } - - new QSingleShotTimer(msec, timerType, receiver, slotObj); -} - -/*! - \fn void QTimer::singleShot(int msec, const QObject *receiver, const char *member) - \reentrant - \deprecated [6.8] Use the chrono overloads. - This static function calls a slot after a given time interval. - - It is very convenient to use this function because you do not need - to bother with a \l{QObject::timerEvent()}{timerEvent} or - create a local QTimer object. - - Example: - \snippet code/src_corelib_kernel_qtimer.cpp 0 - - This sample program automatically terminates after 10 minutes - (600,000 milliseconds). - - The \a receiver is the receiving object and the \a member is the - slot. The time interval is \a msec milliseconds. - - \sa start() -*/ - -/*! - \fn void QTimer::singleShot(int msec, Qt::TimerType timerType, const QObject *receiver, const char *member) - \overload - \reentrant - \deprecated [6.8] Use the chrono overloads. - This static function calls a slot after a given time interval. - - It is very convenient to use this function because you do not need - to bother with a \l{QObject::timerEvent()}{timerEvent} or - create a local QTimer object. - - The \a receiver is the receiving object and the \a member is the slot. The - time interval is \a msec milliseconds. The \a timerType affects the - accuracy of the timer. - - \sa start() -*/ - -void QTimer::singleShot(std::chrono::milliseconds msec, Qt::TimerType timerType, - const QObject *receiver, const char *member) -{ - if (Q_UNLIKELY(msec < 0ms)) { - qWarning("QTimer::singleShot: Timers cannot have negative timeouts"); - return; - } - if (receiver && member) { - if (msec == 0ms) { - // special code shortpath for 0-timers - const char* bracketPosition = strchr(member, '('); - if (!bracketPosition || !(member[0] >= '0' && member[0] <= '2')) { - qWarning("QTimer::singleShot: Invalid slot specification"); - return; - } - const auto methodName = QByteArrayView(member + 1, // extract method name - bracketPosition - 1 - member).trimmed(); - QMetaObject::invokeMethod(const_cast<QObject *>(receiver), methodName.toByteArray().constData(), - Qt::QueuedConnection); - return; - } - (void) new QSingleShotTimer(msec, timerType, receiver, member); - } -} - -/*! \fn template<typename Duration, typename Functor> void QTimer::singleShot(Duration msec, const QObject *context, Functor &&functor) - \fn template<typename Duration, typename Functor> void QTimer::singleShot(Duration msec, Qt::TimerType timerType, const QObject *context, Functor &&functor) - \fn template<typename Duration, typename Functor> void QTimer::singleShot(Duration msec, Functor &&functor) - \fn template<typename Duration, typename Functor> void QTimer::singleShot(Duration msec, Qt::TimerType timerType, Functor &&functor) - \since 5.4 - - \reentrant - This static function calls \a functor after \a msec milliseconds. - - It is very convenient to use this function because you do not need - to bother with a \l{QObject::timerEvent()}{timerEvent} or - create a local QTimer object. - - If \a context is specified, then the \a functor will be called only if the - \a context object has not been destroyed before the interval occurs. The functor - will then be run the thread of \a context. The context's thread must have a - running Qt event loop. - - If \a functor is a member - function of \a context, then the function will be called on the object. - - The \a msec parameter can be an \c int or a \c std::chrono::milliseconds value. - - \sa start() -*/ - -/*! - \fn void QTimer::singleShot(std::chrono::milliseconds msec, const QObject *receiver, const char *member) - \since 5.8 - \overload - \reentrant - - This static function calls a slot after a given time interval. - - It is very convenient to use this function because you do not need - to bother with a \l{QObject::timerEvent()}{timerEvent} or - create a local QTimer object. - - The \a receiver is the receiving object and the \a member is the slot. The - time interval is given in the duration object \a msec. - - \sa start() -*/ - -/*! - \fn void QTimer::singleShot(std::chrono::milliseconds msec, Qt::TimerType timerType, const QObject *receiver, const char *member) - \since 5.8 - \overload - \reentrant - - This static function calls a slot after a given time interval. - - It is very convenient to use this function because you do not need - to bother with a \l{QObject::timerEvent()}{timerEvent} or - create a local QTimer object. - - The \a receiver is the receiving object and the \a member is the slot. The - time interval is given in the duration object \a msec. The \a timerType affects the - accuracy of the timer. - - \sa start() -*/ - -/*! - \fn template <typename Functor> QMetaObject::Connection QTimer::callOnTimeout(Functor &&slot) - \since 5.12 - - Creates a connection from the timer's timeout() signal to \a slot. - Returns a handle to the connection. - - This method is provided for convenience. It's equivalent to calling: - \code - QObject::connect(timer, &QTimer::timeout, timer, slot, Qt::DirectConnection); - \endcode - - \note This overload is not available when \c {QT_NO_CONTEXTLESS_CONNECT} is - defined, instead use the callOnTimeout() overload that takes a context object. - - \sa QObject::connect(), timeout() -*/ - -/*! - \fn template <typename Functor> QMetaObject::Connection QTimer::callOnTimeout(const QObject *context, Functor &&slot, Qt::ConnectionType connectionType = Qt::AutoConnection) - \since 5.12 + \fn template <typename Functor> QMetaObject::Connection QChronoTimer::callOnTimeout(const QObject *context, Functor &&slot, Qt::ConnectionType connectionType = Qt::AutoConnection) \overload callOnTimeout() - Creates a connection from the timeout() signal to \a slot to be placed in a specific - event loop of \a context, and returns a handle to the connection. + Creates a connection from the timeout() signal to \a slot to be placed in a + specific event loop of \a context, with connection type \a connectionType, + and returns a handle to the connection. - This method is provided for convenience. It's equivalent to calling: + This method is provided as a convenience. It's equivalent to calling: \code - QObject::connect(timer, &QTimer::timeout, context, slot, connectionType); + QObject::connect(timer, &QChronoTimer::timeout, context, slot, connectionType); \endcode \sa QObject::connect(), timeout() */ /*! - \fn void QTimer::start(std::chrono::milliseconds msec) - \since 5.8 - \overload - - Starts or restarts the timer with a timeout of duration \a msec milliseconds. + \property QChronoTimer::singleShot + \brief Whether the timer is a single-shot timer - If the timer is already running, it will be - \l{QTimer::stop()}{stopped} and restarted. - - If \l singleShot is true, the timer will be activated only once. This is - equivalent to: - - \code - timer.setInterval(msec); - timer.start(); - \endcode -*/ - -/*! - \fn std::chrono::milliseconds QTimer::intervalAsDuration() const - \since 5.8 - - Returns the interval of this timer as a \c std::chrono::milliseconds object. - - \sa interval -*/ - -/*! - \fn std::chrono::milliseconds QTimer::remainingTimeAsDuration() const - \since 5.8 - - Returns the time remaining in this timer object as a \c - std::chrono::milliseconds object. If this timer is due or overdue, the - returned value is \c std::chrono::milliseconds::zero(). If the remaining - time could not be found or the timer is not active, this function returns a - negative duration. - - \sa remainingTime() -*/ - -/*! - \property QTimer::singleShot - \brief whether the timer is a single-shot timer - - A single-shot timer fires only once, non-single-shot timers fire - every \l interval milliseconds. + A single-shot timer fires only once, non-single-shot timers fire every + \l interval. The default value for this property is \c false. \sa interval, singleShot() */ -void QTimer::setSingleShot(bool singleShot) +void QChronoTimer::setSingleShot(bool singleShot) { d_func()->single = singleShot; } -bool QTimer::isSingleShot() const +bool QChronoTimer::isSingleShot() const { return d_func()->single; } -QBindable<bool> QTimer::bindableSingleShot() +QBindable<bool> QChronoTimer::bindableSingleShot() { return QBindable<bool>(&d_func()->single); } /*! - \property QTimer::interval - \brief the timeout interval in milliseconds + \property QChronoTimer::interval + \brief The timeout interval - The default value for this property is 0. A QTimer with a timeout - interval of 0 will time out as soon as all the events in the window - system's event queue have been processed. + The default value for this property is \c 0ns. - Setting the interval of an active timer changes its timerId(). + A QChronoTimer with a timeout of \c 0ns will time out as soon as all + the events in the window system's event queue have been processed. + + Setting the interval of an active timer changes the interval and acquires + a new id(). If the timer is not active, only the interval is changed. \sa singleShot */ -void QTimer::setInterval(int msec) -{ - setInterval(std::chrono::milliseconds{msec}); -} - -void QTimer::setInterval(std::chrono::milliseconds interval) +void QChronoTimer::setInterval(std::chrono::nanoseconds nsec) { - Q_D(QTimer); - // This could be narrowing as the interval is stored in an `int` QProperty, - // and the type can't be changed in Qt6. - const int msec = interval.count(); - d->inter.removeBindingUnlessInWrapper(); - const bool intervalChanged = msec != d->inter.valueBypassingBindings(); - d->inter.setValueBypassingBindings(msec); - if (d->id != QTimerPrivate::INV_TIMER) { // create new timer - QObject::killTimer(d->id); // restart timer - const int id = QObject::startTimer(std::chrono::milliseconds{msec}, d->type); + auto *d = d_func(); + d->intervalDuration.removeBindingUnlessInWrapper(); + const bool intervalChanged = nsec != d->intervalDuration.valueBypassingBindings(); + d->intervalDuration.setValueBypassingBindings(nsec); + if (d->id != QTimerPrivate::INV_TIMER) { // Create new timer + QObject::killTimer(d->id); // Restart timer + const auto id = QObject::startTimer(nsec, d->type); if (id > 0) { // Restarted successfully. No need to update the active state. d->id = id; @@ -584,63 +300,150 @@ void QTimer::setInterval(std::chrono::milliseconds interval) } } if (intervalChanged) - d->inter.notify(); + d->intervalDuration.notify(); } -int QTimer::interval() const +std::chrono::nanoseconds QChronoTimer::interval() const { - return d_func()->inter; + return d_func()->intervalDuration.value(); } -QBindable<int> QTimer::bindableInterval() +QBindable<std::chrono::nanoseconds> QChronoTimer::bindableInterval() { - return QBindable<int>(&d_func()->inter); + return {&d_func()->intervalDuration}; } /*! - \property QTimer::remainingTime - \since 5.0 - \brief the remaining time in milliseconds + \property QChronoTimer::remainingTime + \brief The remaining time + + Returns the remaining duration until the timeout. - Returns the timer's remaining value in milliseconds left until the timeout. - If the timer is inactive, the returned value will be -1. If the timer is - overdue, the returned value will be 0. + If the timer is inactive, the returned duration will be negative. + + If the timer is overdue, the returned duration will be \c 0ns. \sa interval */ -int QTimer::remainingTime() const +std::chrono::nanoseconds QChronoTimer::remainingTime() const { - Q_D(const QTimer); - if (d->id != QTimerPrivate::INV_TIMER) { - return QAbstractEventDispatcher::instance()->remainingTime(d->id); - } - - return -1; + if (isActive()) + return QAbstractEventDispatcher::instance()->remainingTime(d_func()->id) * 1ms; + return std::chrono::nanoseconds::min(); } /*! - \property QTimer::timerType - \brief controls the accuracy of the timer + \property QChronoTimer::timerType + \brief Controls the accuracy of the timer The default value for this property is \c Qt::CoarseTimer. \sa Qt::TimerType */ -void QTimer::setTimerType(Qt::TimerType atype) +void QChronoTimer::setTimerType(Qt::TimerType atype) { d_func()->type = atype; } -Qt::TimerType QTimer::timerType() const +Qt::TimerType QChronoTimer::timerType() const { return d_func()->type; } -QBindable<Qt::TimerType> QTimer::bindableTimerType() +QBindable<Qt::TimerType> QChronoTimer::bindableTimerType() +{ + return {&d_func()->type}; +} + +/*! + \overload + \reentrant + + This static function calls the slot \a member, on object \a receiver, after + time interval \a interval. \a timerType affects the precision of the timer + + \a member has to be a member function of \a receiver; you need to use the + \c SLOT() macro to get this parameter. + + This function is provided as a convenience to save the need to use a + \l{QObject::timerEvent()}{timerEvent} or create a local QTimer object. + + \sa start(), Qt::TimerType +*/ +void QChronoTimer::singleShot(std::chrono::nanoseconds interval, Qt::TimerType timerType, + const QObject *receiver, const char *member) { - return QBindable<Qt::TimerType>(&d_func()->type); + if (Q_UNLIKELY(interval < 0ns)) { + qWarning("QChronoTimer::singleShot: Timers cannot have negative timeouts"); + return; + } + if (receiver && member) { + if (interval == 0ns) { + // special code shortpath for 0-timers + const char* bracketPosition = strchr(member, '('); + if (!bracketPosition || !(member[0] >= '0' && member[0] <= '2')) { + qWarning("QChronoTimer::singleShot: Invalid slot specification"); + return; + } + const auto methodName = QByteArrayView(member + 1, // extract method name + bracketPosition - 1 - member).trimmed(); + QMetaObject::invokeMethod(const_cast<QObject *>(receiver), + methodName.toByteArray().constData(), + Qt::QueuedConnection); + return; + } + (void) new QSingleShotTimer(interval, timerType, receiver, member); + } +} + +/*! + \internal + + \list + \li \a interval the time interval + \li \a timerType the type of the timer; this affects the precision of + the timer + \li \a receiver the receiver or context object; if this is \c nullptr, + this method will figure out a context object to use, see code + comments below + \li \a slotObj a callable, for example a lambda + \endlist +*/ +void QChronoTimer::singleShotImpl(std::chrono::nanoseconds interval, Qt::TimerType timerType, + const QObject *receiver, QtPrivate::QSlotObjectBase *slotObj) +{ + if (interval == 0ns) { + bool deleteReceiver = false; + // Optimize: set a receiver context when none is given, such that we can use + // QMetaObject::invokeMethod which is more efficient than going through a timer. + // We need a QObject living in the current thread. But the QThread itself lives + // in a different thread - with the exception of the main QThread which lives in + // itself. And QThread::currentThread() is among the few QObjects we know that will + // most certainly be there. Note that one can actually call singleShot before the + // QApplication is created! + if (!receiver && QThread::currentThread() == QCoreApplicationPrivate::mainThread()) { + // reuse main thread as context object + receiver = QThread::currentThread(); + } else if (!receiver) { + // Create a receiver context object on-demand. According to the benchmarks, + // this is still more efficient than going through a timer. + receiver = new QObject; + deleteReceiver = true; + } + + auto h = QtPrivate::invokeMethodHelper({}); + QMetaObject::invokeMethodImpl(const_cast<QObject *>(receiver), slotObj, + Qt::QueuedConnection, h.parameterCount(), h.parameters.data(), h.typeNames.data(), + h.metaTypes.data()); + + if (deleteReceiver) + const_cast<QObject *>(receiver)->deleteLater(); + return; + } + + new QSingleShotTimer(interval, timerType, receiver, slotObj); } QT_END_NAMESPACE -#include "moc_qtimer.cpp" +#include "moc_qchronotimer.cpp" diff --git a/src/corelib/kernel/qchronotimer.h b/src/corelib/kernel/qchronotimer.h index 9b59895e60..58735c46f2 100644 --- a/src/corelib/kernel/qchronotimer.h +++ b/src/corelib/kernel/qchronotimer.h @@ -1,42 +1,50 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#ifndef QTIMER_H -#define QTIMER_H - -#include <QtCore/qglobal.h> +#ifndef QCHRONOTIMER_H +#define QCHRONOTIMER_H #ifndef QT_NO_QOBJECT -#include <QtCore/qbasictimer.h> // conceptual inheritance +#include <QtCore/qcoreevent.h> +#include <QtCore/qnamespace.h> #include <QtCore/qobject.h> +#include <QtCore/qproperty.h> #include <chrono> QT_BEGIN_NAMESPACE class QTimerPrivate; -class Q_CORE_EXPORT QTimer : public QObject +class Q_CORE_EXPORT QChronoTimer : public QObject { Q_OBJECT - Q_PROPERTY(bool singleShot READ isSingleShot WRITE setSingleShot BINDABLE bindableSingleShot) - Q_PROPERTY(int interval READ interval WRITE setInterval BINDABLE bindableInterval) - Q_PROPERTY(int remainingTime READ remainingTime) - Q_PROPERTY(Qt::TimerType timerType READ timerType WRITE setTimerType BINDABLE bindableTimerType) - Q_PROPERTY(bool active READ isActive STORED false BINDABLE bindableActive) + Q_PROPERTY(bool singleShot READ isSingleShot WRITE setSingleShot + BINDABLE bindableSingleShot FINAL) + Q_PROPERTY(std::chrono::nanoseconds interval READ interval WRITE setInterval + BINDABLE bindableInterval FINAL) + Q_PROPERTY(std::chrono::nanoseconds remainingTime READ remainingTime FINAL) + Q_PROPERTY(Qt::TimerType timerType READ timerType WRITE setTimerType + BINDABLE bindableTimerType FINAL) + Q_PROPERTY(bool active READ isActive STORED false BINDABLE bindableActive FINAL) + + template <typename Functor> + using FunctorContext = typename QtPrivate::ContextTypeForFunctor<Functor>::ContextType; + public: - explicit QTimer(QObject *parent = nullptr); - ~QTimer(); + explicit QChronoTimer(std::chrono::nanoseconds nsec, QObject *parent = nullptr); + explicit QChronoTimer(QObject *parent = nullptr); + ~QChronoTimer() override; bool isActive() const; QBindable<bool> bindableActive(); - int timerId() const; + int id() const; - void setInterval(int msec); - int interval() const; - QBindable<int> bindableInterval(); + void setInterval(std::chrono::nanoseconds nsec); + std::chrono::nanoseconds interval() const; + QBindable<std::chrono::nanoseconds> bindableInterval(); - int remainingTime() const; + std::chrono::nanoseconds remainingTime() const; void setTimerType(Qt::TimerType atype); Qt::TimerType timerType() const; @@ -46,142 +54,96 @@ public: bool isSingleShot() const; QBindable<bool> bindableSingleShot(); - QT_CORE_INLINE_SINCE(6, 8) - static void singleShot(int msec, const QObject *receiver, const char *member); - - QT_CORE_INLINE_SINCE(6, 8) - static void singleShot(int msec, Qt::TimerType timerType, const QObject *receiver, const char *member); - // singleShot with context #ifdef Q_QDOC - template <typename Duration, typename Functor> - static inline void singleShot(Duration interval, const QObject *receiver, Functor &&slot); - template <typename Duration, typename Functor> - static inline void singleShot(Duration interval, Qt::TimerType timerType, + template <typename Functor> + static inline void singleShot(std::chrono::nanoseconds interval, + const QObject *receiver, Functor &&slot); + template <typename Functor> + static inline void singleShot(std::chrono::nanoseconds interval interval, + Qt::TimerType timerType, const QObject *receiver, Functor &&slot); #else - template <typename Duration, typename Functor> - static inline void singleShot(Duration interval, - const typename QtPrivate::ContextTypeForFunctor<Functor>::ContextType *receiver, - Functor &&slot) + template <typename Functor> + static void singleShot(std::chrono::nanoseconds interval, + const FunctorContext<Functor> *receiver, Functor &&slot) { - singleShot(interval, defaultTypeFor(interval), receiver, std::forward<Functor>(slot)); + singleShot(interval, defaultTimerTypeFor(interval), receiver, std::forward<Functor>(slot)); } - template <typename Duration, typename Functor> - static inline void singleShot(Duration interval, Qt::TimerType timerType, - const typename QtPrivate::ContextTypeForFunctor<Functor>::ContextType *receiver, - Functor &&slot) + template <typename Functor> + static void singleShot(std::chrono::nanoseconds interval, Qt::TimerType timerType, + const FunctorContext<Functor> *receiver, Functor &&slot) { using Prototype = void(*)(); - singleShotImpl(interval, timerType, receiver, - QtPrivate::makeCallableObject<Prototype>(std::forward<Functor>(slot))); + auto *slotObj = QtPrivate::makeCallableObject<Prototype>(std::forward<Functor>(slot)); + singleShotImpl(interval, timerType, receiver, slotObj); } #endif - // singleShot without context - template <typename Duration, typename Functor> - static inline void singleShot(Duration interval, Functor &&slot) - { - singleShot(interval, defaultTypeFor(interval), nullptr, std::forward<Functor>(slot)); - } - template <typename Duration, typename Functor> - static inline void singleShot(Duration interval, Qt::TimerType timerType, Functor &&slot) + template <typename Functor> + static void singleShot(std::chrono::nanoseconds interval, Qt::TimerType timerType, + Functor &&slot) + { singleShot(interval, timerType, nullptr, std::forward<Functor>(slot)); } + + template <typename Functor> + static void singleShot(std::chrono::nanoseconds interval, Functor &&slot) { - singleShot(interval, timerType, nullptr, std::forward<Functor>(slot)); + singleShot(interval, defaultTimerTypeFor(interval), nullptr, std::forward<Functor>(slot)); } + static void singleShot(std::chrono::nanoseconds interval, Qt::TimerType timerType, + const QObject *receiver, const char *member); + static void singleShot(std::chrono::nanoseconds interval, const QObject *receiver, + const char *member) + { singleShot(interval, defaultTimerTypeFor(interval), receiver, member); } + #ifdef Q_QDOC template <typename Functor> - QMetaObject::Connection callOnTimeout(Functor &&slot); - template <typename Functor> - QMetaObject::Connection callOnTimeout(const QObject *context, Functor &&slot, Qt::ConnectionType connectionType = Qt::AutoConnection); + QMetaObject::Connection callOnTimeout(const QObject *context, Functor &&slot, + Qt::ConnectionType connectionType = Qt::AutoConnection); #else template <typename ... Args> QMetaObject::Connection callOnTimeout(Args && ...args) { - return QObject::connect(this, &QTimer::timeout, std::forward<Args>(args)... ); + return QObject::connect(this, &QChronoTimer::timeout, std::forward<Args>(args)... ); } - #endif public Q_SLOTS: - void start(int msec); - void start(); void stop(); Q_SIGNALS: void timeout(QPrivateSignal); -public: - void setInterval(std::chrono::milliseconds value); - - std::chrono::milliseconds intervalAsDuration() const - { - return std::chrono::milliseconds(interval()); - } - - std::chrono::milliseconds remainingTimeAsDuration() const - { - return std::chrono::milliseconds(remainingTime()); - } - - static void singleShot(std::chrono::milliseconds value, const QObject *receiver, const char *member) - { - singleShot(value, defaultTypeFor(value), receiver, member); - } - static void singleShot(std::chrono::milliseconds interval, Qt::TimerType timerType, - const QObject *receiver, const char *member); - - void start(std::chrono::milliseconds value); - protected: void timerEvent(QTimerEvent *) override; private: - Q_DISABLE_COPY(QTimer) - Q_DECLARE_PRIVATE(QTimer) + Q_DISABLE_COPY(QChronoTimer) - inline int startTimer(int){ return -1;} - inline void killTimer(int){} + // QChronoTimer uses QTimerPrivate + inline QTimerPrivate *d_func() noexcept + { Q_CAST_IGNORE_ALIGN(return reinterpret_cast<QTimerPrivate *>(qGetPtrHelper(d_ptr));) } + inline const QTimerPrivate *d_func() const noexcept + { Q_CAST_IGNORE_ALIGN(return reinterpret_cast<const QTimerPrivate *>(qGetPtrHelper(d_ptr));) } - static constexpr Qt::TimerType defaultTypeFor(int msecs) noexcept - { return defaultTypeFor(std::chrono::milliseconds{msecs}); } + // These two functions are inherited from QObject + int startTimer(std::chrono::nanoseconds) = delete; + void killTimer(int) = delete; - static constexpr Qt::TimerType defaultTypeFor(std::chrono::milliseconds interval) noexcept + static constexpr Qt::TimerType defaultTimerTypeFor(std::chrono::nanoseconds interval) noexcept { - // coarse timers are worst in their first firing - // so we prefer a high precision timer for something that happens only once - // unless the timeout is too big, in which case we go for coarse anyway using namespace std::chrono_literals; return interval >= 2s ? Qt::CoarseTimer : Qt::PreciseTimer; } - QT_CORE_INLINE_SINCE(6, 8) - static void singleShotImpl(int msec, Qt::TimerType timerType, - const QObject *receiver, QtPrivate::QSlotObjectBase *slotObj); - - static void singleShotImpl(std::chrono::milliseconds interval, Qt::TimerType timerType, + static void singleShotImpl(std::chrono::nanoseconds interval, Qt::TimerType timerType, const QObject *receiver, QtPrivate::QSlotObjectBase *slotObj); }; -#if QT_CORE_INLINE_IMPL_SINCE(6, 8) -void QTimer::singleShot(int msec, const QObject *receiver, const char *member) -{ singleShot(std::chrono::milliseconds{msec}, receiver, member); } - -void QTimer::singleShot(int msec, Qt::TimerType timerType, const QObject *receiver, - const char *member) -{ singleShot(std::chrono::milliseconds{msec}, timerType, receiver, member); } - -void QTimer::singleShotImpl(int msec, Qt::TimerType timerType, - const QObject *receiver, QtPrivate::QSlotObjectBase *slotObj) -{ - singleShotImpl(std::chrono::milliseconds{msec}, timerType, receiver, slotObj); -} -#endif - QT_END_NAMESPACE #endif // QT_NO_QOBJECT -#endif // QTIMER_H +#endif // QCHRONOTIMER_H diff --git a/src/corelib/kernel/qobjectdefs.h b/src/corelib/kernel/qobjectdefs.h index ff06d3762c..190901d5d1 100644 --- a/src/corelib/kernel/qobjectdefs.h +++ b/src/corelib/kernel/qobjectdefs.h @@ -639,6 +639,7 @@ private: const void **parameters, const char **typeNames, const QtPrivate::QMetaTypeInterface **metaTypes); friend class QTimer; + friend class QChronoTimer; }; class Q_CORE_EXPORT QMetaObject::Connection { diff --git a/src/corelib/kernel/qtimer.cpp b/src/corelib/kernel/qtimer.cpp index 0e09bc9565..cbeeb97934 100644 --- a/src/corelib/kernel/qtimer.cpp +++ b/src/corelib/kernel/qtimer.cpp @@ -118,8 +118,9 @@ QT_BEGIN_NAMESPACE */ QTimer::QTimer(QObject *parent) - : QObject(*new QTimerPrivate, parent) + : QObject(*new QTimerPrivate(this), parent) { + Q_ASSERT(d_func()->isQTimer); } diff --git a/src/corelib/kernel/qtimer_p.h b/src/corelib/kernel/qtimer_p.h index 81e8a5fccb..b1e0830dcf 100644 --- a/src/corelib/kernel/qtimer_p.h +++ b/src/corelib/kernel/qtimer_p.h @@ -15,24 +15,58 @@ #include "qobject_p.h" #include "qproperty_p.h" #include "qtimer.h" +#include "qchronotimer.h" QT_BEGIN_NAMESPACE class QTimerPrivate : public QObjectPrivate { - Q_DECLARE_PUBLIC(QTimer) public: + QTimerPrivate(QTimer *qq) + : q(qq), + isQTimer(true) + {} + + QTimerPrivate(std::chrono::nanoseconds nsec, QChronoTimer *qq) + : intervalDuration(nsec), + q(qq) + { + intervalDuration.notify(); + } + static constexpr int INV_TIMER = -1; // invalid timer id - void setInterval(int msec) { q_func()->setInterval(msec); } + void setIntervalDuration(std::chrono::nanoseconds nsec) + { + if (isQTimer) { + const auto msec = std::chrono::duration_cast<std::chrono::milliseconds>(nsec); + static_cast<QTimer *>(q)->setInterval(msec); + } else { + static_cast<QChronoTimer *>(q)->setInterval(nsec); + } + } + + void setInterval(int msec) + { + Q_ASSERT(isQTimer); + static_cast<QTimer *>(q)->setInterval(msec); + } + bool isActiveActualCalculation() const { return id > 0; } int id = INV_TIMER; Q_OBJECT_COMPAT_PROPERTY_WITH_ARGS(QTimerPrivate, int, inter, &QTimerPrivate::setInterval, 0) + Q_OBJECT_COMPAT_PROPERTY_WITH_ARGS(QTimerPrivate, std::chrono::nanoseconds, intervalDuration, + &QTimerPrivate::setIntervalDuration, + std::chrono::nanoseconds{0}) Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(QTimerPrivate, bool, single, false) Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(QTimerPrivate, Qt::TimerType, type, Qt::CoarseTimer) Q_OBJECT_COMPUTED_PROPERTY(QTimerPrivate, bool, isActiveData, &QTimerPrivate::isActiveActualCalculation) + + QObject *q; + // true if q is a QTimer*, false otherwise + const bool isQTimer = false; }; QT_END_NAMESPACE diff --git a/tests/auto/corelib/kernel/CMakeLists.txt b/tests/auto/corelib/kernel/CMakeLists.txt index 130fc080b6..c2feb38641 100644 --- a/tests/auto/corelib/kernel/CMakeLists.txt +++ b/tests/auto/corelib/kernel/CMakeLists.txt @@ -2,6 +2,7 @@ # SPDX-License-Identifier: BSD-3-Clause add_subdirectory(qapplicationstatic) +add_subdirectory(qchronotimer) add_subdirectory(qcoreapplication) add_subdirectory(qdeadlinetimer) add_subdirectory(qelapsedtimer) diff --git a/tests/auto/corelib/kernel/qchronotimer/.gitignore b/tests/auto/corelib/kernel/qchronotimer/.gitignore index 14fd00629e..254f7a0281 100644 --- a/tests/auto/corelib/kernel/qchronotimer/.gitignore +++ b/tests/auto/corelib/kernel/qchronotimer/.gitignore @@ -1 +1 @@ -tst_qtimer +tst_qchronotimer diff --git a/tests/auto/corelib/kernel/qchronotimer/CMakeLists.txt b/tests/auto/corelib/kernel/qchronotimer/CMakeLists.txt index 6bb3b15850..43164858c5 100644 --- a/tests/auto/corelib/kernel/qchronotimer/CMakeLists.txt +++ b/tests/auto/corelib/kernel/qchronotimer/CMakeLists.txt @@ -3,7 +3,7 @@ if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) cmake_minimum_required(VERSION 3.16) - project(tst_qtimer LANGUAGES CXX) + project(tst_qchronotimer LANGUAGES CXX) find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) endif() @@ -11,24 +11,23 @@ if (NOT QT_FEATURE_thread) return() endif() -function(addTimerTest test) +function(addChronoTimerTest test) qt_internal_add_test(${test} SOURCES - tst_qtimer.cpp + tst_qchronotimer.cpp LIBRARIES Qt::CorePrivate Qt::TestPrivate ) endfunction() -addTimerTest(tst_qtimer) +addChronoTimerTest(tst_qchronotimer) if(QT_FEATURE_glib AND UNIX) - addTimerTest(tst_qtimer_no_glib) - qt_internal_extend_target(tst_qtimer_no_glib + addChronoTimerTest(tst_qchronotimer_no_glib) + qt_internal_extend_target(tst_qchronotimer_no_glib DEFINES DISABLE_GLIB - tst_QTimer=tst_QTimer_no_glib # Class name in the unittest + tst_QChronoTimer=tst_QChronoTimer_no_glib # Class name in the unittest ) endif() - diff --git a/tests/auto/corelib/kernel/qchronotimer/tst_qchronotimer.cpp b/tests/auto/corelib/kernel/qchronotimer/tst_qchronotimer.cpp index 467fc9abd7..2b137b06e3 100644 --- a/tests/auto/corelib/kernel/qchronotimer/tst_qchronotimer.cpp +++ b/tests/auto/corelib/kernel/qchronotimer/tst_qchronotimer.cpp @@ -1,20 +1,10 @@ // Copyright (C) 2020 The Qt Company Ltd. // Copyright (C) 2016 Intel Corporation. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -/* WARNING: this source-code is reused by another test. - - As Qt built with GUI support may use a different backend for its event loops - and other timer-related matters, it is important to test it in that form, as - well as in its GUI-less form. So this source file is reused by a build config - in the GUI module. Similarly, testing with and without glib is supported, - where relevant (see DISABLE_GLIB below). -*/ #ifdef QT_GUI_LIB -// When compiled as tests/auto/gui/kernel/qguitimer/'s source-code: # include <QtGui/QGuiApplication> #else -// When compiled as tests/auto/corelib/kernel/qtimer/'s source-code: # include <QtCore/QCoreApplication> #endif @@ -23,8 +13,10 @@ #include <QSignalSpy> #include <QtTest/private/qpropertytesthelper_p.h> -#include <qtimer.h> +#include <qbasictimer.h> +#include <qchronotimer.h> #include <qthread.h> +#include <qtimer.h> #include <qelapsedtimer.h> #include <qproperty.h> @@ -32,6 +24,8 @@ #include <unistd.h> #endif +using namespace std::chrono_literals; + #ifdef DISABLE_GLIB static bool glibDisabled = []() { qputenv("QT_NO_GLIB", "1"); @@ -39,25 +33,16 @@ static bool glibDisabled = []() { }(); #endif -using namespace std::chrono_literals; - -class tst_QTimer : public QObject +class tst_QChronoTimer : public QObject { Q_OBJECT -public: - static void initMain(); private slots: - void cleanupTestCase(); void zeroTimer(); - void singleShotTimeout(); + void singleShotTimeout(); // Non-static singleShot() void timeout(); - void singleShotNormalizes_data(); - void singleShotNormalizes(); void sequentialTimers_data(); void sequentialTimers(); - void singleShotSequentialTimers_data(); - void singleShotSequentialTimers(); void remainingTime(); void remainingTimeInitial_data(); void remainingTimeInitial(); @@ -70,24 +55,19 @@ private slots: void timerInfiniteRecursion(); void recurringTimer_data(); void recurringTimer(); - void deleteLaterOnQTimer(); // long name, don't want to shadow QObject::deleteLater() + void deleteLaterOnQChronoTimer(); // long name, don't want to shadow QObject::deleteLater() void moveToThread(); void restartedTimerFiresTooSoon(); void timerFiresOnlyOncePerProcessEvents_data(); void timerFiresOnlyOncePerProcessEvents(); void timerIdPersistsAfterThreadExit(); void cancelLongTimer(); - void singleShotStaticFunctionZeroTimeout(); void recurseOnTimeoutAndStopTimer(); - void singleShotToFunctors(); - void singleShot_chrono(); - void singleShot_static(); - void crossThreadSingleShotToFunctor_data(); - void crossThreadSingleShotToFunctor(); void timerOrder(); void timerOrder_data(); void timerOrderBackgroundThread(); void timerOrderBackgroundThread_data() { timerOrder_data(); } + void timerPrecision(); void dontBlockEvents(); void postedEventsShouldNotStarveTimers(); @@ -100,15 +80,15 @@ private slots: void negativeInterval(); }; -void tst_QTimer::zeroTimer() +void tst_QChronoTimer::zeroTimer() { - QTimer timer; + QChronoTimer timer; QVERIFY(!timer.isSingleShot()); - timer.setInterval(0); + timer.setInterval(0ns); timer.setSingleShot(true); QVERIFY(timer.isSingleShot()); - QSignalSpy timeoutSpy(&timer, &QTimer::timeout); + QSignalSpy timeoutSpy(&timer, &QChronoTimer::timeout); timer.start(); // Pass timeout to work round glib issue, see QTBUG-84291. @@ -117,135 +97,80 @@ void tst_QTimer::zeroTimer() QCOMPARE(timeoutSpy.size(), 1); } -void tst_QTimer::singleShotTimeout() +void tst_QChronoTimer::singleShotTimeout() { - QTimer timer; + QChronoTimer timer; QVERIFY(!timer.isSingleShot()); timer.setSingleShot(true); QVERIFY(timer.isSingleShot()); - QSignalSpy timeoutSpy(&timer, &QTimer::timeout); - timer.start(100); + QSignalSpy timeoutSpy(&timer, &QChronoTimer::timeout); + timer.setInterval(100ms); + timer.start(); - QVERIFY(timeoutSpy.wait(500)); + QVERIFY(timeoutSpy.wait(500ms)); QCOMPARE(timeoutSpy.size(), 1); - QTest::qWait(500); + QTest::qWait(500ms); QCOMPARE(timeoutSpy.size(), 1); } -#define TIMEOUT_TIMEOUT 200 +static constexpr auto Timeout_Interval = 200ms; -void tst_QTimer::timeout() +void tst_QChronoTimer::timeout() { - QTimer timer; - QSignalSpy timeoutSpy(&timer, &QTimer::timeout); - timer.start(100); + QChronoTimer timer{100ms}; + QSignalSpy timeoutSpy(&timer, &QChronoTimer::timeout); + timer.start(); QCOMPARE(timeoutSpy.size(), 0); - QTRY_VERIFY_WITH_TIMEOUT(timeoutSpy.size() > 0, TIMEOUT_TIMEOUT); - int oldCount = timeoutSpy.size(); - - QTRY_VERIFY_WITH_TIMEOUT(timeoutSpy.size() > oldCount, TIMEOUT_TIMEOUT); -} - -void tst_QTimer::singleShotNormalizes_data() -{ - QTest::addColumn<QByteArray>("slotName"); + QTRY_VERIFY_WITH_TIMEOUT(timeoutSpy.size() > 0, Timeout_Interval); + const qsizetype oldCount = timeoutSpy.size(); - QTest::newRow("normalized") << QByteArray(SLOT(exitLoop())); - - QTest::newRow("space-before") << QByteArray(SLOT( exitLoop())); - QTest::newRow("space-after") << QByteArray(SLOT(exitLoop ())); - QTest::newRow("space-around") << QByteArray(SLOT( exitLoop ())); - QTest::newRow("spaces-before") << QByteArray(SLOT( exitLoop())); - QTest::newRow("spaces-after") << QByteArray(SLOT(exitLoop ())); - QTest::newRow("spaces-around") << QByteArray(SLOT( exitLoop ())); - - QTest::newRow("space-in-parens") << QByteArray(SLOT(exitLoop( ))); - QTest::newRow("spaces-in-parens") << QByteArray(SLOT(exitLoop( ))); - QTest::newRow("space-after-parens") << QByteArray(SLOT(exitLoop() )); - QTest::newRow("spaces-after-parens") << QByteArray(SLOT(exitLoop() )); + QTRY_VERIFY_WITH_TIMEOUT(timeoutSpy.size() > oldCount, Timeout_Interval); } -void tst_QTimer::singleShotNormalizes() -{ - static constexpr auto TestTimeout = 250ms; - QFETCH(QByteArray, slotName); - QEventLoop loop; - - // control test: regular connection - { - QTimer timer; - QVERIFY(QObject::connect(&timer, SIGNAL(timeout()), &QTestEventLoop::instance(), slotName)); - timer.setSingleShot(true); - timer.start(1); - QTestEventLoop::instance().enterLoop(TestTimeout); - QVERIFY(!QTestEventLoop::instance().timeout()); - } - - // non-zero time - QTimer::singleShot(1, &QTestEventLoop::instance(), slotName); - QTestEventLoop::instance().enterLoop(TestTimeout); - QVERIFY(!QTestEventLoop::instance().timeout()); - - QTimer::singleShot(1ms, &QTestEventLoop::instance(), slotName); - QTestEventLoop::instance().enterLoop(TestTimeout); - QVERIFY(!QTestEventLoop::instance().timeout()); - - // zero time - QTimer::singleShot(0, &QTestEventLoop::instance(), slotName); - QTestEventLoop::instance().enterLoop(TestTimeout); - QVERIFY(!QTestEventLoop::instance().timeout()); - - QTimer::singleShot(0ms, &QTestEventLoop::instance(), slotName); - QTestEventLoop::instance().enterLoop(TestTimeout); - QVERIFY(!QTestEventLoop::instance().timeout()); -} - -void tst_QTimer::sequentialTimers_data() +void tst_QChronoTimer::sequentialTimers_data() { #ifdef Q_OS_WIN QSKIP("The API used by QEventDispatcherWin32 doesn't respect the order"); #endif - QTest::addColumn<QList<int>>("timeouts"); - auto addRow = [](const QList<int> &l) { + QTest::addColumn<QList<std::chrono::milliseconds>>("timeouts"); + auto addRow = [](const QList<std::chrono::milliseconds> &l) { + Q_ASSERT_X(std::is_sorted(l.begin(), l.end()), + "tst_QChronoTimer", "input list must be sorted"); QByteArray name; - int last = -1; - for (int i = 0; i < l.size(); ++i) { - Q_ASSERT_X(l[i] >= last, "tst_QTimer", "input list must be sorted"); - name += QByteArray::number(l[i]) + ','; - } + for (auto msec : l) + name += QByteArray::number(msec.count()) + ','; name.chop(1); QTest::addRow("%s", name.constData()) << l; }; // PreciseTimers - addRow({0, 0, 0, 0, 0, 0}); - addRow({0, 1, 2}); - addRow({1, 1, 1, 2, 2, 2, 2}); - addRow({1, 2, 3}); - addRow({19, 19, 19}); - // CoarseTimer for setInterval - addRow({20, 20, 20, 20, 20}); - addRow({25, 25, 25, 25, 25, 25, 50}); + addRow({0ms, 0ms, 0ms, 0ms, 0ms, 0ms}); + addRow({0ms, 1ms, 2ms}); + addRow({1ms, 1ms, 1ms, 2ms, 2ms, 2ms, 2ms}); + addRow({1ms, 2ms, 3ms}); + addRow({19ms, 19ms, 19ms}); + // CoarseTimer for setinterval + addRow({20ms, 20ms, 20ms, 20ms, 20ms}); + addRow({25ms, 25ms, 25ms, 25ms, 25ms, 25ms, 50ms}); } -void tst_QTimer::sequentialTimers() +void tst_QChronoTimer::sequentialTimers() { - QFETCH(const QList<int>, timeouts); + QFETCH(const QList<std::chrono::milliseconds>, timeouts); QByteArray result, expected; - std::vector<std::unique_ptr<QTimer>> timers; + std::vector<std::unique_ptr<QChronoTimer>> timers; expected.resize(timeouts.size()); result.reserve(timeouts.size()); timers.reserve(timeouts.size()); for (int i = 0; i < timeouts.size(); ++i) { - auto timer = std::make_unique<QTimer>(); + auto timer = std::make_unique<QChronoTimer>(timeouts[i]); timer->setSingleShot(true); - timer->setInterval(timeouts[i]); char c = 'A' + i; expected[i] = c; - QObject::connect(timer.get(), &QTimer::timeout, this, [&result, c = c]() { + QObject::connect(timer.get(), &QChronoTimer::timeout, this, [&result, c = c]() { result.append(c); }); timers.push_back(std::move(timer)); @@ -255,62 +180,37 @@ void tst_QTimer::sequentialTimers() for (auto &timer : timers) timer->start(); - QTestEventLoop::instance().enterLoopMSecs(timeouts.last() * 2 + 10); - - QCOMPARE(result, expected); -} - -void tst_QTimer::singleShotSequentialTimers_data() -{ - sequentialTimers_data(); -} - -void tst_QTimer::singleShotSequentialTimers() -{ - QFETCH(const QList<int>, timeouts); - QByteArray result, expected; - expected.resize(timeouts.size()); - result.reserve(timeouts.size()); - for (int i = 0; i < timeouts.size(); ++i) { - char c = 'A' + i; - expected[i] = c; - QTimer::singleShot(timeouts[i], this, [&result, c = c]() { - result.append(c); - }); - } - - QTestEventLoop::instance().enterLoopMSecs(timeouts.last() * 2 + 10); + QTestEventLoop::instance().enterLoop(timeouts.last() * 2 + 10ms); QCOMPARE(result, expected); } -void tst_QTimer::remainingTime() +void tst_QChronoTimer::remainingTime() { - QTimer tested; + QChronoTimer tested; tested.setTimerType(Qt::PreciseTimer); - QTimer tester; + QChronoTimer tester; tester.setTimerType(Qt::PreciseTimer); tester.setSingleShot(true); - const int testedInterval = 200; - const int testerInterval = 50; - const int expectedRemainingTime = testedInterval - testerInterval; + constexpr auto tested_interval = 200ms; + constexpr auto tester_interval = 50ms; + constexpr auto expectedRemainingTime = tested_interval - tester_interval; int testIteration = 0; const int desiredTestCount = 2; - auto connection = QObject::connect(&tested, &QTimer::timeout, [&tester]() { - // We let tested (which isn't a single-shot) run repeatedly, to verify - // it *does* repeat, and check that the single-shot tester, starting - // at the same time, does finish first each time, by about the right duration. - tester.start(); // Start tester again. - }); + // We let tested (which isn't a single-shot) run repeatedly, to verify + // it *does* repeat, and check that the single-shot tester, starting + // at the same time, does finish first each time, by about the right duration. + auto connection = QObject::connect(&tested, &QChronoTimer::timeout, + &tester, &QChronoTimer::start); - QObject::connect(&tester, &QTimer::timeout, [&]() { - const int remainingTime = tested.remainingTime(); + QObject::connect(&tester, &QChronoTimer::timeout, this, [&]() { + const std::chrono::nanoseconds remainingTime = tested.remainingTime(); // We expect that remainingTime is at most 150 and not overdue. - const bool remainingTimeInRange = remainingTime > 0 + const bool remainingTimeInRange = remainingTime > 0ns && remainingTime <= expectedRemainingTime; if (remainingTimeInRange) ++testIteration; @@ -322,145 +222,155 @@ void tst_QTimer::remainingTime() if (testIteration == desiredTestCount) QObject::disconnect(connection); // Last iteration, don't start tester again. QVERIFY2(remainingTimeInRange, qPrintable("Remaining time " - + QByteArray::number(remainingTime) + "ms outside expected range (0ms, " - + QByteArray::number(expectedRemainingTime) + "ms]")); + + QByteArray::number(remainingTime.count()) + "ms outside expected range (0ns, " + + QByteArray::number(expectedRemainingTime.count()) + "ms]")); }); - tested.start(testedInterval); - tester.start(testerInterval); // Start tester for the 1st time. + tested.setInterval(tested_interval); + tested.start(); + tester.setInterval(tester_interval); + tester.start(); // Start tester for the 1st time. // Test it desiredTestCount times, give it reasonable amount of time // (twice as much as needed). - QTRY_COMPARE_WITH_TIMEOUT(testIteration, desiredTestCount, - testedInterval * desiredTestCount * 2); + const auto tryTimeout = tested_interval * desiredTestCount * 2; + QTRY_COMPARE_WITH_TIMEOUT(testIteration, desiredTestCount, tryTimeout); } -void tst_QTimer::remainingTimeInitial_data() +void tst_QChronoTimer::remainingTimeInitial_data() { - QTest::addColumn<int>("startTimeMs"); + using namespace std::chrono; + + QTest::addColumn<nanoseconds>("startTimeNs"); QTest::addColumn<Qt::TimerType>("timerType"); - QTest::addRow("precise time 0ms") << 0 << Qt::PreciseTimer; - QTest::addRow("precise time 1ms") << 1 << Qt::PreciseTimer; - QTest::addRow("precise time 10ms") << 10 << Qt::PreciseTimer; + QTest::addRow("precisetiemr-0ns") << 0ns << Qt::PreciseTimer; + QTest::addRow("precisetimer-1ms") << nanoseconds{1ms} << Qt::PreciseTimer; + QTest::addRow("precisetimer-10ms") <<nanoseconds{10ms} << Qt::PreciseTimer; - QTest::addRow("coarse time 0ms") << 0 << Qt::CoarseTimer; - QTest::addRow("coarse time 1ms") << 1 << Qt::CoarseTimer; - QTest::addRow("coarse time 10ms") << 10 << Qt::CoarseTimer; + QTest::addRow("coarsetimer-0ns") << 0ns << Qt::CoarseTimer; + QTest::addRow("coarsetimer-1ms") << nanoseconds{1ms} << Qt::CoarseTimer; + QTest::addRow("coarsetimer-10ms") << nanoseconds{10ms} << Qt::CoarseTimer; } -void tst_QTimer::remainingTimeInitial() +void tst_QChronoTimer::remainingTimeInitial() { - QFETCH(int, startTimeMs); + QFETCH(std::chrono::nanoseconds, startTimeNs); QFETCH(Qt::TimerType, timerType); - QTimer timer; + QChronoTimer timer; QCOMPARE(timer.timerType(), Qt::CoarseTimer); timer.setTimerType(timerType); QCOMPARE(timer.timerType(), timerType); - timer.start(startTimeMs); + timer.setInterval(startTimeNs); + timer.start(); - const int rt = timer.remainingTime(); - QVERIFY2(rt >= 0 && rt <= startTimeMs, qPrintable(QString::number(rt))); + const std::chrono::nanoseconds rt = timer.remainingTime(); + QCOMPARE_GE(rt, 0ns); + QCOMPARE_LE(rt, startTimeNs); } -void tst_QTimer::remainingTimeDuringActivation_data() +void tst_QChronoTimer::remainingTimeDuringActivation_data() { QTest::addColumn<bool>("singleShot"); QTest::newRow("repeating") << false; QTest::newRow("single-shot") << true; } -void tst_QTimer::remainingTimeDuringActivation() +void tst_QChronoTimer::remainingTimeDuringActivation() { QFETCH(bool, singleShot); - QTimer timer; + QChronoTimer timer; timer.setSingleShot(singleShot); - int remainingTime = 0; // not the expected value in either case - connect(&timer, &QTimer::timeout, - [&]() { - remainingTime = timer.remainingTime(); - }); - QSignalSpy timeoutSpy(&timer, &QTimer::timeout); - const int timeout = 20; // 20 ms is short enough and should not round down to 0 in any timer mode - timer.start(timeout); + auto remainingTime = 0ns; // not the expected value in either case + connect(&timer, &QChronoTimer::timeout, this, [&]() { remainingTime = timer.remainingTime(); }); + QSignalSpy timeoutSpy(&timer, &QChronoTimer::timeout); + // 20 ms is short enough and should not round down to 0 in any timer mode + constexpr auto timeout = 20ms; + timer.setInterval(timeout); + timer.start(); QVERIFY(timeoutSpy.wait()); if (singleShot) - QCOMPARE(remainingTime, -1); // timer not running - else - QVERIFY2(remainingTime <= timeout && remainingTime > 0, - qPrintable(QString::number(remainingTime))); + QCOMPARE_LT(remainingTime, 0ns); // timer not running + else { + QCOMPARE_LE(remainingTime, timeout); + QCOMPARE_GT(remainingTime, 0ns); + } if (!singleShot) { // do it again - see QTBUG-46940 - remainingTime = -1; + remainingTime = std::chrono::milliseconds::min(); QVERIFY(timeoutSpy.wait()); - QVERIFY2(remainingTime <= timeout && remainingTime > 0, - qPrintable(QString::number(remainingTime))); + QCOMPARE_LE(remainingTime, timeout); + QCOMPARE_GT(remainingTime, 0ns); } } namespace { - template <typename T> - std::chrono::milliseconds to_ms(T t) - { return std::chrono::duration_cast<std::chrono::milliseconds>(t); } - + auto to_ms(T t) + { + using namespace std::chrono; + return duration_cast<milliseconds>(t); + } } // unnamed namespace -void tst_QTimer::basic_chrono() +void tst_QChronoTimer::basic_chrono() { // duplicates zeroTimer, singleShotTimeout, interval and remainingTime using namespace std::chrono; - QTimer timer; - QSignalSpy timeoutSpy(&timer, &QTimer::timeout); - timer.setInterval(to_ms(nanoseconds(0))); + QChronoTimer timer; + QSignalSpy timeoutSpy(&timer, &QChronoTimer::timeout); timer.start(); - QCOMPARE(timer.intervalAsDuration().count(), milliseconds::rep(0)); - QCOMPARE(timer.remainingTimeAsDuration().count(), milliseconds::rep(0)); + QCOMPARE(timer.interval(), 0ns); + QCOMPARE(timer.remainingTime(), 0ns); QCoreApplication::processEvents(); QCOMPARE(timeoutSpy.size(), 1); timeoutSpy.clear(); - timer.start(milliseconds(100)); + timer.setInterval(100ms); + timer.start(); QCOMPARE(timeoutSpy.size(), 0); - QVERIFY(timeoutSpy.wait(TIMEOUT_TIMEOUT)); + QVERIFY(timeoutSpy.wait(Timeout_Interval)); QVERIFY(timeoutSpy.size() > 0); - int oldCount = timeoutSpy.size(); + const qsizetype oldCount = timeoutSpy.size(); - QVERIFY(timeoutSpy.wait(TIMEOUT_TIMEOUT)); + QVERIFY(timeoutSpy.wait(Timeout_Interval)); QVERIFY(timeoutSpy.size() > oldCount); timeoutSpy.clear(); - timer.start(to_ms(microseconds(200000))); - QCOMPARE(timer.intervalAsDuration().count(), milliseconds::rep(200)); - QTest::qWait(50); + timer.setInterval(200ms); + timer.start(); + QCOMPARE(timer.interval(), 200ms); + QTest::qWait(50ms); QCOMPARE(timeoutSpy.size(), 0); - milliseconds rt = timer.remainingTimeAsDuration(); - QVERIFY2(rt.count() >= 50 && rt.count() <= 200, qPrintable(QString::number(rt.count()))); + nanoseconds rt = timer.remainingTime(); + QCOMPARE_GE(rt, 50ms); + QCOMPARE_LE(rt, 200ms); timeoutSpy.clear(); timer.setSingleShot(true); - timer.start(milliseconds(100)); - QVERIFY(timeoutSpy.wait(TIMEOUT_TIMEOUT)); + timer.setInterval(100ms); + timer.start(); + QVERIFY(timeoutSpy.wait(Timeout_Interval)); QCOMPARE(timeoutSpy.size(), 1); - QTest::qWait(500); + QTest::qWait(500ms); QCOMPARE(timeoutSpy.size(), 1); } -void tst_QTimer::livelock_data() +void tst_QChronoTimer::livelock_data() { - QTest::addColumn<int>("interval"); - QTest::newRow("zero timer") << 0; - QTest::newRow("non-zero timer") << 1; - QTest::newRow("longer than sleep") << 20; + QTest::addColumn<std::chrono::nanoseconds>("interval"); + QTest::newRow("zero-timer") << 0ns; + QTest::newRow("non-zero-timer") << std::chrono::nanoseconds{1ms}; + QTest::newRow("longer-than-sleep") << std::chrono::nanoseconds{20ms}; } /*! @@ -471,20 +381,19 @@ void tst_QTimer::livelock_data() */ class LiveLockTester : public QObject { + static constexpr QEvent::Type PostEventType = static_cast<QEvent::Type>(4002); public: - LiveLockTester(int i) - : interval(i), - timeoutsForFirst(0), timeoutsForExtra(0), timeoutsForSecond(0), - postEventAtRightTime(false) + LiveLockTester(std::chrono::nanoseconds i) + : interval(i) { firstTimerId = startTimer(interval); - extraTimerId = startTimer(interval + 80); + extraTimerId = startTimer(interval + 80ms); secondTimerId = -1; // started later } bool event(QEvent *e) override { - if (e->type() == 4002) { + if (e->type() == PostEventType) { // got the posted event if (timeoutsForFirst == 1 && timeoutsForSecond == 0) postEventAtRightTime = true; @@ -499,7 +408,7 @@ public: if (++timeoutsForFirst == 1) { killTimer(extraTimerId); extraTimerId = -1; - QCoreApplication::postEvent(this, new QEvent(static_cast<QEvent::Type>(4002))); + QCoreApplication::postEvent(this, new QEvent(PostEventType)); secondTimerId = startTimer(interval); } } else if (te->timerId() == secondTimerId) { @@ -513,17 +422,17 @@ public: killTimer(te->timerId()); } - const int interval; - int firstTimerId; - int secondTimerId; - int extraTimerId; - int timeoutsForFirst; - int timeoutsForExtra; - int timeoutsForSecond; - bool postEventAtRightTime; + const std::chrono::nanoseconds interval; + int firstTimerId = -1; + int secondTimerId = -1; + int extraTimerId = -1; + int timeoutsForFirst = 0; + int timeoutsForExtra = 0; + int timeoutsForSecond = 0; + bool postEventAtRightTime = false; }; -void tst_QTimer::livelock() +void tst_QChronoTimer::livelock() { /* New timers created in timer event handlers should not be sent @@ -532,9 +441,9 @@ void tst_QTimer::livelock() events (since new posted events are not sent until the next iteration of the eventloop either). */ - QFETCH(int, interval); + QFETCH(std::chrono::nanoseconds, interval); LiveLockTester tester(interval); - QTest::qWait(180); // we have to use wait here, since we're testing timers with a non-zero timeout + QTest::qWait(180ms); // we have to use wait here, since we're testing timers with a non-zero timeout QTRY_COMPARE(tester.timeoutsForFirst, 1); QCOMPARE(tester.timeoutsForExtra, 0); QTRY_COMPARE(tester.timeoutsForSecond, 1); @@ -544,12 +453,12 @@ void tst_QTimer::livelock() class TimerInfiniteRecursionObject : public QObject { public: - bool inTimerEvent; - bool timerEventRecursed; - int interval; + bool inTimerEvent = false; + bool timerEventRecursed = false; + std::chrono::nanoseconds interval; - TimerInfiniteRecursionObject(int interval) - : inTimerEvent(false), timerEventRecursed(false), interval(interval) + TimerInfiniteRecursionObject(std::chrono::nanoseconds interval) + : interval(interval) { } void timerEvent(QTimerEvent *timerEvent) override @@ -563,7 +472,8 @@ public: inTimerEvent = true; QEventLoop eventLoop; - QTimer::singleShot(qMax(100, interval * 2), &eventLoop, SLOT(quit())); + QChronoTimer::singleShot(std::max<std::chrono::nanoseconds>(100ms, interval * 2), + &eventLoop, &QEventLoop::quit); eventLoop.exec(); inTimerEvent = false; @@ -572,26 +482,27 @@ public: } }; -void tst_QTimer::timerInfiniteRecursion_data() +void tst_QChronoTimer::timerInfiniteRecursion_data() { - QTest::addColumn<int>("interval"); - QTest::newRow("zero timer") << 0; - QTest::newRow("non-zero timer") << 1; - QTest::newRow("10ms timer") << 10; - QTest::newRow("11ms timer") << 11; - QTest::newRow("100ms timer") << 100; - QTest::newRow("1s timer") << 1000; + QTest::addColumn<std::chrono::nanoseconds>("interval"); + QTest::newRow("zero timer") << 0ns; + QTest::newRow("non-zero timer") << std::chrono::nanoseconds{1ms}; + QTest::newRow("10ms timer") << std::chrono::nanoseconds{10ms}; + QTest::newRow("11ms timer") << std::chrono::nanoseconds{11ms}; + QTest::newRow("100ms timer") << std::chrono::nanoseconds{100ms}; + QTest::newRow("1s timer") << std::chrono::nanoseconds{1000ms}; } -void tst_QTimer::timerInfiniteRecursion() +void tst_QChronoTimer::timerInfiniteRecursion() { - QFETCH(int, interval); + QFETCH(std::chrono::nanoseconds, interval); TimerInfiniteRecursionObject object(interval); (void) object.startTimer(interval); QEventLoop eventLoop; - QTimer::singleShot(qMax(100, interval * 2), &eventLoop, SLOT(quit())); + QChronoTimer::singleShot(std::max<std::chrono::nanoseconds>(100ms, interval * 2), &eventLoop, + &QEventLoop::quit); eventLoop.exec(); QVERIFY(!object.timerEventRecursed); @@ -613,10 +524,10 @@ public: { if (++times == target) { killTimer(timerEvent->timerId()); - emit done(); + Q_EMIT done(); } if (recurse) { QEventLoop eventLoop; - QTimer::singleShot(100, &eventLoop, SLOT(quit())); + QChronoTimer::singleShot(100ms, &eventLoop, &QEventLoop::quit); eventLoop.exec(); } } @@ -625,21 +536,21 @@ signals: void done(); }; -void tst_QTimer::recurringTimer_data() +void tst_QChronoTimer::recurringTimer_data() { - QTest::addColumn<int>("interval"); + QTest::addColumn<std::chrono::nanoseconds>("interval"); QTest::addColumn<bool>("recurse"); // make sure that eventloop recursion doesn't affect timer recurrence - QTest::newRow("zero timer, don't recurse") << 0 << false; - QTest::newRow("zero timer, recurse") << 0 << true; - QTest::newRow("non-zero timer, don't recurse") << 1 << false; - QTest::newRow("non-zero timer, recurse") << 1 << true; + QTest::newRow("zero timer, don't recurse") << 0ns << false; + QTest::newRow("zero timer, recurse") << 0ns << true; + QTest::newRow("non-zero timer, don't recurse") << std::chrono::nanoseconds{1ms} << false; + QTest::newRow("non-zero timer, recurse") << std::chrono::nanoseconds{1ms} << true; } -void tst_QTimer::recurringTimer() +void tst_QChronoTimer::recurringTimer() { const int target = 5; - QFETCH(int, interval); + QFETCH(std::chrono::nanoseconds, interval); QFETCH(bool, recurse); RecurringTimerObject object(target); @@ -652,54 +563,56 @@ void tst_QTimer::recurringTimer() QCOMPARE(object.times, target); } -void tst_QTimer::deleteLaterOnQTimer() +void tst_QChronoTimer::deleteLaterOnQChronoTimer() { - QTimer *timer = new QTimer; - connect(timer, SIGNAL(timeout()), timer, SLOT(deleteLater())); + QChronoTimer *timer = new QChronoTimer; + connect(timer, &QChronoTimer::timeout, timer, &QObject::deleteLater); QSignalSpy destroyedSpy(timer, &QObject::destroyed); - timer->setInterval(1); + timer->setInterval(1ms); timer->setSingleShot(true); timer->start(); - QPointer<QTimer> pointer = timer; + QPointer<QChronoTimer> pointer = timer; QVERIFY(destroyedSpy.wait()); QVERIFY(pointer.isNull()); } -#define MOVETOTHREAD_TIMEOUT 200 -#define MOVETOTHREAD_WAIT 300 +static constexpr auto MoveToThread_Timeout = 200ms; +static constexpr auto MoveToThread_Wait = 300ms; -void tst_QTimer::moveToThread() +void tst_QChronoTimer::moveToThread() { #if defined(Q_OS_WIN32) QSKIP("Does not work reliably on Windows :("); #elif defined(Q_OS_MACOS) QSKIP("Does not work reliably on macOS 10.12+ (QTBUG-59679)"); #endif - QTimer ti1; - QTimer ti2; - ti1.setSingleShot(true); - ti1.start(MOVETOTHREAD_TIMEOUT); - ti2.start(MOVETOTHREAD_TIMEOUT); - QVERIFY((ti1.timerId() & 0xffffff) != (ti2.timerId() & 0xffffff)); + QChronoTimer timer1{MoveToThread_Timeout}; + QChronoTimer timer2{MoveToThread_Timeout}; + timer1.setSingleShot(true); + timer1.start(); + timer2.start(); + QVERIFY((timer1.id() & 0xffffff) != (timer2.id() & 0xffffff)); QThread tr; - ti1.moveToThread(&tr); - connect(&ti1,SIGNAL(timeout()), &tr, SLOT(quit())); + timer1.moveToThread(&tr); + connect(&timer1, &QChronoTimer::timeout, &tr, &QThread::quit); tr.start(); - QTimer ti3; - ti3.start(MOVETOTHREAD_TIMEOUT); - QVERIFY((ti3.timerId() & 0xffffff) != (ti2.timerId() & 0xffffff)); - QVERIFY((ti3.timerId() & 0xffffff) != (ti1.timerId() & 0xffffff)); - QTest::qWait(MOVETOTHREAD_WAIT); + QChronoTimer ti3{MoveToThread_Timeout}; + ti3.start(); + QVERIFY((ti3.id() & 0xffffff) != (timer2.id() & 0xffffff)); + QVERIFY((ti3.id() & 0xffffff) != (timer1.id() & 0xffffff)); + QTest::qWait(MoveToThread_Wait); QVERIFY(tr.wait()); - ti2.stop(); - QTimer ti4; - ti4.start(MOVETOTHREAD_TIMEOUT); + timer2.stop(); + QChronoTimer ti4{MoveToThread_Timeout}; + ti4.start(); ti3.stop(); - ti2.start(MOVETOTHREAD_TIMEOUT); - ti3.start(MOVETOTHREAD_TIMEOUT); - QVERIFY((ti4.timerId() & 0xffffff) != (ti2.timerId() & 0xffffff)); - QVERIFY((ti3.timerId() & 0xffffff) != (ti2.timerId() & 0xffffff)); - QVERIFY((ti3.timerId() & 0xffffff) != (ti1.timerId() & 0xffffff)); + timer2.setInterval(MoveToThread_Timeout); + timer2.start(); + ti3.setInterval(MoveToThread_Timeout); + ti3.start(); + QVERIFY((ti4.id() & 0xffffff) != (timer2.id() & 0xffffff)); + QVERIFY((ti3.id() & 0xffffff) != (timer2.id() & 0xffffff)); + QVERIFY((ti3.id() & 0xffffff) != (timer1.id() & 0xffffff)); } class RestartedTimerFiresTooSoonObject : public QObject @@ -709,24 +622,22 @@ class RestartedTimerFiresTooSoonObject : public QObject public: QBasicTimer m_timer; - int m_interval; + std::chrono::milliseconds m_interval = 0ms; QElapsedTimer m_elapsedTimer; QEventLoop eventLoop; - inline RestartedTimerFiresTooSoonObject() - : QObject(), m_interval(0) - { } + RestartedTimerFiresTooSoonObject() = default; void timerFired() { - static int interval = 1000; + static std::chrono::milliseconds interval = 1s; m_interval = interval; m_elapsedTimer.start(); m_timer.start(interval, this); // alternate between single-shot and 1 sec - interval = interval ? 0 : 1000; + interval = interval > 0ms ? 0ms : 1s; } void timerEvent(QTimerEvent* ev) override @@ -736,7 +647,7 @@ public: m_timer.stop(); - int elapsed = m_elapsedTimer.elapsed(); + std::chrono::nanoseconds elapsed = m_elapsedTimer.durationElapsed(); if (elapsed < m_interval / 2) { // severely too early! @@ -757,7 +668,7 @@ public: } }; -void tst_QTimer::restartedTimerFiresTooSoon() +void tst_QChronoTimer::restartedTimerFiresTooSoon() { RestartedTimerFiresTooSoonObject object; object.timerFired(); @@ -769,16 +680,16 @@ class LongLastingSlotClass : public QObject Q_OBJECT public: - LongLastingSlotClass(QTimer *timer) : count(0), timer(timer) {} + LongLastingSlotClass(QChronoTimer *timer) : timer(timer) { } public slots: void longLastingSlot() { - // Don't use QTimer for this, because we are testing it. + // Don't use QChronoTimer for this, because we are testing it. QElapsedTimer control; control.start(); - while (control.elapsed() < 200) { - for (int c = 0; c < 100000; c++) {} // Mindless looping. + while (control.durationElapsed() < 200ms) { + for (int c = 0; c < 100'000; c++) {} // Mindless looping. } if (++count >= 2) { timer->stop(); @@ -786,29 +697,28 @@ public slots: } public: - int count; - QTimer *timer; + int count = 0; + QChronoTimer *timer; }; -void tst_QTimer::timerFiresOnlyOncePerProcessEvents_data() +void tst_QChronoTimer::timerFiresOnlyOncePerProcessEvents_data() { - QTest::addColumn<int>("interval"); - QTest::newRow("zero timer") << 0; - QTest::newRow("non-zero timer") << 10; + QTest::addColumn<std::chrono::nanoseconds>("interval"); + QTest::newRow("zero-timer") << 0ns; + QTest::newRow("non-zero-timer") << std::chrono::nanoseconds{10ms}; } -void tst_QTimer::timerFiresOnlyOncePerProcessEvents() +void tst_QChronoTimer::timerFiresOnlyOncePerProcessEvents() { - QFETCH(int, interval); + QFETCH(std::chrono::nanoseconds, interval); - QTimer t; + QChronoTimer t{interval}; LongLastingSlotClass longSlot(&t); - t.start(interval); - connect(&t, SIGNAL(timeout()), &longSlot, SLOT(longLastingSlot())); + t.start(); + connect(&t, &QChronoTimer::timeout, &longSlot, &LongLastingSlotClass::longLastingSlot); // Loop because there may be other events pending. - while (longSlot.count == 0) { + while (longSlot.count == 0) QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents); - } QCOMPARE(longSlot.count, 1); } @@ -816,49 +726,44 @@ void tst_QTimer::timerFiresOnlyOncePerProcessEvents() class TimerIdPersistsAfterThreadExitThread : public QThread { public: - QTimer *timer; - int timerId, returnValue; - - TimerIdPersistsAfterThreadExitThread() - : QThread(), timer(0), timerId(-1), returnValue(-1) - { } - ~TimerIdPersistsAfterThreadExitThread() - { - delete timer; - } + std::unique_ptr<QChronoTimer> timer; + int timerId = -1; + int returnValue = -1; void run() override { QEventLoop eventLoop; - timer = new QTimer; - connect(timer, SIGNAL(timeout()), &eventLoop, SLOT(quit())); - timer->start(100); - timerId = timer->timerId(); + timer = std::make_unique<QChronoTimer>(); + connect(timer.get(), &QChronoTimer::timeout, &eventLoop, &QEventLoop::quit); + timer->setInterval(100ms); + timer->start(); + timerId = timer->id(); returnValue = eventLoop.exec(); } }; -void tst_QTimer::timerIdPersistsAfterThreadExit() +void tst_QChronoTimer::timerIdPersistsAfterThreadExit() { TimerIdPersistsAfterThreadExitThread thread; thread.start(); - QVERIFY(thread.wait(30000)); + QVERIFY(thread.wait(30s)); QCOMPARE(thread.returnValue, 0); // even though the thread has exited, and the event dispatcher destroyed, the timer is still // "active", meaning the timer id should NOT be reused (i.e. the event dispatcher should not // have unregistered it) - int timerId = thread.startTimer(100); + int timerId = thread.startTimer(100ms); QVERIFY((timerId & 0xffffff) != (thread.timerId & 0xffffff)); } -void tst_QTimer::cancelLongTimer() +void tst_QChronoTimer::cancelLongTimer() { - QTimer timer; + QChronoTimer timer{1h}; timer.setSingleShot(true); - timer.start(1000 * 60 * 60); //set timer for 1 hour + timer.start(); QCoreApplication::processEvents(); - QVERIFY(timer.isActive()); //if the timer completes immediately with an error, then this will fail + // If the timer completes immediately with an error, then this will fail + QVERIFY(timer.isActive()); timer.stop(); QVERIFY(!timer.isActive()); } @@ -872,34 +777,13 @@ public: int count = 0; }; -void tst_QTimer::singleShotStaticFunctionZeroTimeout() -{ - { - TimeoutCounter counter; - - QTimer::singleShot(0, &counter, SLOT(timeout())); - QTRY_COMPARE(counter.count, 1); - QTest::qWait(500); - QCOMPARE(counter.count, 1); - } - - { - TimeoutCounter counter; - - QTimer::singleShot(0, &counter, &TimeoutCounter::timeout); - QTRY_COMPARE(counter.count, 1); - QTest::qWait(500); - QCOMPARE(counter.count, 1); - } -} - class RecursOnTimeoutAndStopTimerTimer : public QObject { Q_OBJECT public: - QTimer *one; - QTimer *two; + QChronoTimer *one; + QChronoTimer *two; public slots: void onetrigger() @@ -913,14 +797,14 @@ public slots: } }; -void tst_QTimer::recurseOnTimeoutAndStopTimer() +void tst_QChronoTimer::recurseOnTimeoutAndStopTimer() { QEventLoop eventLoop; - QTimer::singleShot(1000, &eventLoop, SLOT(quit())); + QChronoTimer::singleShot(1s, &eventLoop, &QEventLoop::quit); RecursOnTimeoutAndStopTimerTimer t; - t.one = new QTimer(&t); - t.two = new QTimer(&t); + t.one = new QChronoTimer(&t); + t.two = new QChronoTimer(&t); QObject::connect(t.one, SIGNAL(timeout()), &t, SLOT(onetrigger())); QObject::connect(t.two, SIGNAL(timeout()), &t, SLOT(twotrigger())); @@ -966,128 +850,6 @@ public: } }; -void tst_QTimer::singleShotToFunctors() -{ - int count = 0; - _e.reset(new QEventLoop); - QEventLoop e; - - QTimer::singleShot(0, CountedStruct(&count)); - QCoreApplication::processEvents(); - QCOMPARE(count, 1); - - QTimer::singleShot(0, &StaticEventLoop::quitEventLoop); - QCOMPARE(_e->exec(), 0); - - QTimer::singleShot(0, &StaticEventLoop::quitEventLoop_noexcept); - QCOMPARE(_e->exec(), 0); - - QThread t1; - QObject c1; - c1.moveToThread(&t1); - - QObject::connect(&t1, SIGNAL(started()), &e, SLOT(quit())); - t1.start(); - QCOMPARE(e.exec(), 0); - - QTimer::singleShot(0, &c1, CountedStruct(&count, &t1)); - QTRY_COMPARE(count, 2); - - t1.quit(); - t1.wait(); - - _t = new QThread; - QObject c2; - c2.moveToThread(_t); - - QObject::connect(_t, SIGNAL(started()), &e, SLOT(quit())); - _t->start(); - QCOMPARE(e.exec(), 0); - - QTimer::singleShot(0, &c2, &StaticEventLoop::quitEventLoop); - QCOMPARE(_e->exec(), 0); - - _t->quit(); - _t->wait(); - _t->deleteLater(); - _t = nullptr; - - { - QObject c3; - QTimer::singleShot(500, &c3, CountedStruct(&count)); - } - QTest::qWait(800); // Wait until the singleshot timer would have timed out - QCOMPARE(count, 2); - - QTimer::singleShot(0, [&count] { ++count; }); - QTRY_COMPARE(count, 3); - - QObject context; - QThread thread; - - context.moveToThread(&thread); - QObject::connect(&thread, SIGNAL(started()), &e, SLOT(quit())); - thread.start(); - QCOMPARE(e.exec(), 0); - - QTimer::singleShot(0, &context, [&count, &thread] { ++count; QCOMPARE(QThread::currentThread(), &thread); }); - QTRY_COMPARE(count, 4); - - thread.quit(); - thread.wait(); - - struct MoveOnly : CountedStruct { - Q_DISABLE_COPY(MoveOnly) - MoveOnly(MoveOnly &&o) : CountedStruct(std::move(o)) {}; - MoveOnly(int *c) : CountedStruct(c) {} - }; - QTimer::singleShot(0, MoveOnly(&count)); - QTRY_COMPARE(count, 5); - - _e.reset(); - _t = nullptr; -} - -void tst_QTimer::singleShot_chrono() -{ - // duplicates singleShotStaticFunctionZeroTimeout and singleShotToFunctors - using namespace std::chrono; - { - TimeoutCounter counter; - - QTimer::singleShot(hours(0), &counter, SLOT(timeout())); - QTRY_COMPARE(counter.count, 1); - QTest::qWait(500); - QCOMPARE(counter.count, 1); - } - - { - TimeoutCounter counter; - - QTimer::singleShot(hours(0), &counter, &TimeoutCounter::timeout); - QTRY_COMPARE(counter.count, 1); - QTest::qWait(500); - QCOMPARE(counter.count, 1); - } - - int count = 0; - QTimer::singleShot(to_ms(microseconds(0)), CountedStruct(&count)); - QTRY_COMPARE(count, 1); - - _e.reset(new QEventLoop); - QTimer::singleShot(0, &StaticEventLoop::quitEventLoop); - QCOMPARE(_e->exec(), 0); - - QObject c3; - QTimer::singleShot(milliseconds(500), &c3, CountedStruct(&count)); - QTRY_COMPARE(count, 2); - - QTimer::singleShot(0, [&count] { ++count; }); - QTRY_COMPARE(count, 3); - - _e.reset(); -} - class DontBlockEvents : public QObject { Q_OBJECT @@ -1109,22 +871,21 @@ DontBlockEvents::DontBlockEvents() count = 0; total = 0; + const std::chrono::milliseconds intervals[] = {2s, 2500ms, 3s, 5s, 1s, 2s}; // need a few unrelated timers running to reproduce the bug. - (new QTimer(this))->start(2000); - (new QTimer(this))->start(2500); - (new QTimer(this))->start(3000); - (new QTimer(this))->start(5000); - (new QTimer(this))->start(1000); - (new QTimer(this))->start(2000); - - m_timer.start(1, this); + for (auto dur : intervals) { + auto *t = new QChronoTimer(dur, this); + t->start(); + } + + m_timer.start(1ms, this); } void DontBlockEvents::timerEvent(QTimerEvent* event) { if (event->timerId() == m_timer.timerId()) { - QMetaObject::invokeMethod(this, "paintEvent", Qt::QueuedConnection); - m_timer.start(0, this); + QMetaObject::invokeMethod(this, &DontBlockEvents::paintEvent, Qt::QueuedConnection); + m_timer.start(0ms, this); count++; QCOMPARE(count, 1); total++; @@ -1139,10 +900,10 @@ void DontBlockEvents::paintEvent() // This is a regression test for QTBUG-13633, where a timer with a zero // timeout that was restarted by the event handler could starve other timers. -void tst_QTimer::dontBlockEvents() +void tst_QChronoTimer::dontBlockEvents() { DontBlockEvents t; - QTest::qWait(60); + QTest::qWait(60ms); QTRY_VERIFY(t.total > 2); } @@ -1154,16 +915,16 @@ public: public slots: void repeatThisSlot() { - QMetaObject::invokeMethod(this, "repeatThisSlot", Qt::QueuedConnection); + QMetaObject::invokeMethod(this, &SlotRepeater::repeatThisSlot, Qt::QueuedConnection); } }; -void tst_QTimer::postedEventsShouldNotStarveTimers() +void tst_QChronoTimer::postedEventsShouldNotStarveTimers() { - QTimer timer; - timer.setInterval(0); + QChronoTimer timer; + timer.setInterval(0ns); timer.setSingleShot(false); - QSignalSpy timeoutSpy(&timer, &QTimer::timeout); + QSignalSpy timeoutSpy(&timer, &QChronoTimer::timeout); timer.start(); SlotRepeater slotRepeater; slotRepeater.repeatThisSlot(); @@ -1179,75 +940,33 @@ struct DummyFunctor { }; QThread *DummyFunctor::callThread = nullptr; -void tst_QTimer::crossThreadSingleShotToFunctor_data() -{ - QTest::addColumn<int>("timeout"); - - QTest::addRow("zero-timer") << 0; - QTest::addRow("1ms") << 1; -} - -void tst_QTimer::crossThreadSingleShotToFunctor() -{ - QFETCH(int, timeout); - // We're also testing for crashes here, so the test simply running to - // completion is part of the success - DummyFunctor::callThread = nullptr; - - QThread t; - std::unique_ptr<QObject> o(new QObject()); - o->moveToThread(&t); - - QTimer::singleShot(timeout, o.get(), DummyFunctor()); - - // wait enough time for the timer to have timed out before the timer - // could be start in the receiver's thread. - QTest::qWait(10 + timeout * 10); - t.start(); - t.wait(); - QCOMPARE(DummyFunctor::callThread, &t); - - // continue with a stress test - the calling thread is busy, the - // timer should still fire and no crashes. - DummyFunctor::callThread = nullptr; - t.start(); - for (int i = 0; i < 10000; i++) - QTimer::singleShot(timeout, o.get(), DummyFunctor()); - - t.wait(); - o.reset(); - - QCOMPARE(DummyFunctor::callThread, &t); -} - -void tst_QTimer::callOnTimeout() +void tst_QChronoTimer::callOnTimeout() { - QTimer timer; - QSignalSpy timeoutSpy(&timer, &QTimer::timeout); - timer.setInterval(0); + QChronoTimer timer; + QSignalSpy timeoutSpy(&timer, &QChronoTimer::timeout); timer.start(); - auto context = new QObject(); + auto context = std::make_unique<QObject>(); int count = 0; timer.callOnTimeout([&count] { count++; }); - QMetaObject::Connection connection = timer.callOnTimeout(context, [&count] { count++; }); - timer.callOnTimeout(&timer, &QTimer::stop); + QMetaObject::Connection connection = timer.callOnTimeout(context.get(), [&count] { count++; }); + timer.callOnTimeout(&timer, &QChronoTimer::stop); - QTest::qWait(100); + QTest::qWait(100ms); QCOMPARE(count, 2); QCOMPARE(timeoutSpy.size(), 1); // Test that connection is bound to context lifetime QVERIFY(connection); - delete context; + context.reset(); QVERIFY(!connection); } -void tst_QTimer::bindToTimer() +void tst_QChronoTimer::bindToTimer() { - QTimer timer; + QChronoTimer timer; // singleShot property QProperty<bool> singleShot; @@ -1260,14 +979,14 @@ void tst_QTimer::bindToTimer() QVERIFY(!singleShot); // interval property - QProperty<int> interval; + QProperty<std::chrono::nanoseconds> interval; interval.setBinding([&](){ return timer.interval(); }); - QCOMPARE(timer.interval(), interval); + QCOMPARE(timer.interval(), interval.value()); - timer.setInterval(10); - QCOMPARE(interval, 10); - timer.setInterval(100); - QCOMPARE(interval, 100); + timer.setInterval(10ms); + QCOMPARE(interval.value(), 10ms); + timer.setInterval(100ms); + QCOMPARE(interval.value(), 100ms); // timerType property QProperty<Qt::TimerType> timerType; @@ -1285,33 +1004,43 @@ void tst_QTimer::bindToTimer() active.setBinding([&](){ return timer.isActive(); }); QCOMPARE(active, timer.isActive()); - timer.start(1000); + timer.setInterval(1s); + timer.start(); QVERIFY(active); timer.stop(); QVERIFY(!active); + // Also test that using negative interval updates the binding correctly + timer.setInterval(100ms); + timer.start(); + QVERIFY(active); + auto ignoreMsg = [] { QTest::ignoreMessage(QtWarningMsg, "QObject::startTimer: Timers cannot have negative intervals"); }; - // also test that using negative interval updates the binding correctly - timer.start(100); - QVERIFY(active); ignoreMsg(); - timer.setInterval(-100); + timer.setInterval(-100ms); + ignoreMsg(); + timer.start(); QVERIFY(!active); - timer.start(100); + + timer.setInterval(100ms); + timer.start(); QVERIFY(active); + + ignoreMsg(); + timer.setInterval(-100ms); ignoreMsg(); - timer.start(-100); + timer.start(); QVERIFY(!active); } -void tst_QTimer::bindTimer() +void tst_QChronoTimer::bindTimer() { - QTimer timer; + QChronoTimer timer; // singleShot property QVERIFY(!timer.isSingleShot()); @@ -1325,19 +1054,19 @@ void tst_QTimer::bindTimer() QVERIFY(!timer.isSingleShot()); // interval property - QCOMPARE(timer.interval(), 0); + QCOMPARE(timer.interval(), 0ns); - QProperty<int> interval; + QProperty<std::chrono::nanoseconds> interval; timer.bindableInterval().setBinding(Qt::makePropertyBinding(interval)); - interval = 10; - QCOMPARE(timer.interval(), 10); - interval = 100; - QCOMPARE(timer.interval(), 100); - timer.setInterval(50); - QCOMPARE(timer.interval(), 50); - interval = 30; - QCOMPARE(timer.interval(), 50); + interval = 10ms; + QCOMPARE(timer.interval(), 10ms); + interval = 100ms; + QCOMPARE(timer.interval(), 100ms); + timer.setInterval(50ms); + QCOMPARE(timer.interval(), 50ms); + interval = 30ms; + QCOMPARE(timer.interval(), 50ms); // timerType property QCOMPARE(timer.timerType(), Qt::CoarseTimer); @@ -1351,21 +1080,22 @@ void tst_QTimer::bindTimer() QCOMPARE(timer.timerType(), Qt::VeryCoarseTimer); } -void tst_QTimer::automatedBindingTests() +void tst_QChronoTimer::automatedBindingTests() { - QTimer timer; + QChronoTimer timer; QVERIFY(!timer.isSingleShot()); QTestPrivate::testReadWritePropertyBasics(timer, true, false, "singleShot"); if (QTest::currentTestFailed()) { - qDebug("Failed property test for QTimer::singleShot"); + qDebug("Failed property test for QChronoTimer::singleShot"); return; } - QCOMPARE_NE(timer.interval(), 10); - QTestPrivate::testReadWritePropertyBasics(timer, 10, 20, "interval"); + QCOMPARE_NE(timer.interval(), 10ms); + using NSec = std::chrono::nanoseconds; + QTestPrivate::testReadWritePropertyBasics(timer, NSec{10ms}, NSec{20ms}, "interval"); if (QTest::currentTestFailed()) { - qDebug("Failed property test for QTimer::interval"); + qDebug("Failed property test for QChronoTimer::interval"); return; } @@ -1373,48 +1103,50 @@ void tst_QTimer::automatedBindingTests() QTestPrivate::testReadWritePropertyBasics(timer, Qt::PreciseTimer, Qt::CoarseTimer, "timerType"); if (QTest::currentTestFailed()) { - qDebug("Failed property test for QTimer::timerType"); + qDebug("Failed property test for QChronoTimer::timerType"); return; } - timer.start(1000); + timer.setInterval(1s); + timer.start(); QVERIFY(timer.isActive()); QTestPrivate::testReadOnlyPropertyBasics(timer, true, false, "active", [&timer]() { timer.stop(); }); if (QTest::currentTestFailed()) { - qDebug("Failed property test for QTimer::active"); + qDebug("Failed property test for QChronoTimer::active"); return; } } -void tst_QTimer::negativeInterval() +void tst_QChronoTimer::negativeInterval() { + QChronoTimer timer; + auto ignoreMsg = [] { QTest::ignoreMessage(QtWarningMsg, "QObject::startTimer: Timers cannot have negative intervals"); }; - QTimer timer; - - // Starting with a negative interval does not change active state. ignoreMsg(); - timer.start(-100ms); + // Setting a negative interval does not change the active state. + timer.setInterval(-100ms); + ignoreMsg(); + timer.start(); QVERIFY(!timer.isActive()); - // Updating the interval to a negative value stops the timer and changes - // the active state. - timer.start(100ms); + // Starting a timer that has a positive interval, the active state is changed + timer.setInterval(100ms); + timer.start(); QVERIFY(timer.isActive()); + ignoreMsg(); - timer.setInterval(-100); + // Setting a negative interval on an already running timer... + timer.setInterval(-100ms); + // ... the timer is stopped and the active state is changed QVERIFY(!timer.isActive()); - // Starting with a negative interval when already started leads to stop - // and inactive state. - timer.start(100); - QVERIFY(timer.isActive()); - ignoreMsg(); - timer.start(-100ms); + // Calling start on a timer that has a negative interval, does not change the active state + timer.start(); QVERIFY(!timer.isActive()); } @@ -1437,16 +1169,16 @@ public: switch (callType) { case String: - QTimer::singleShot(0, this, SLOT(stringSlot())); + QChronoTimer::singleShot(0ns, this, SLOT(stringSlot())); break; case PMF: - QTimer::singleShot(0, this, &OrderHelper::pmfSlot); + QChronoTimer::singleShot(0ns, this, &OrderHelper::pmfSlot); break; case Functor: - QTimer::singleShot(0, this, [this]() { functorSlot(); }); + QChronoTimer::singleShot(0ns, this, [this]() { functorSlot(); }); break; case FunctorNoCtx: - QTimer::singleShot(0, [this]() { functorNoCtxSlot(); }); + QChronoTimer::singleShot(0ns, [this]() { functorNoCtxSlot(); }); break; } } @@ -1460,7 +1192,7 @@ public slots: Q_DECLARE_METATYPE(OrderHelper::CallType) -void tst_QTimer::timerOrder() +void tst_QChronoTimer::timerOrder() { QFETCH(QList<OrderHelper::CallType>, calls); @@ -1472,7 +1204,7 @@ void tst_QTimer::timerOrder() QTRY_COMPARE(helper.calls, calls); } -void tst_QTimer::timerOrder_data() +void tst_QChronoTimer::timerOrder_data() { QTest::addColumn<QList<OrderHelper::CallType>>("calls"); @@ -1489,7 +1221,7 @@ void tst_QTimer::timerOrder_data() } while (std::next_permutation(calls.begin(), calls.end())); } -void tst_QTimer::timerOrderBackgroundThread() +void tst_QChronoTimer::timerOrderBackgroundThread() { auto *thread = QThread::create([this]() { timerOrder(); }); thread->start(); @@ -1497,43 +1229,34 @@ void tst_QTimer::timerOrderBackgroundThread() delete thread; } -struct StaticSingleShotUser +void tst_QChronoTimer::timerPrecision() { - StaticSingleShotUser() - { - for (auto call : calls()) - helper.triggerCall(call); - } - OrderHelper helper; - - static QList<OrderHelper::CallType> calls() - { - return {OrderHelper::String, OrderHelper::PMF, - OrderHelper::Functor, OrderHelper::FunctorNoCtx}; - } -}; - -// NOTE: to prevent any static initialization order fiasco, we implement -// initMain() to instantiate staticSingleShotUser before qApp - -static StaticSingleShotUser *s_staticSingleShotUser = nullptr; + using namespace std::chrono; + steady_clock::time_point t1{}; + steady_clock::time_point t2{}; -void tst_QTimer::initMain() -{ - s_staticSingleShotUser = new StaticSingleShotUser; -} + QEventLoop loop; -void tst_QTimer::cleanupTestCase() -{ - delete s_staticSingleShotUser; -} + QChronoTimer zeroTimer{0ns}; + zeroTimer.setTimerType(Qt::PreciseTimer); + zeroTimer.setSingleShot(true); + connect(&zeroTimer, &QChronoTimer::timeout, this, [&t1] { t1 = steady_clock::now(); }); + + QChronoTimer oneNSecTimer{1ns}; + oneNSecTimer.setTimerType(Qt::PreciseTimer); + oneNSecTimer.setSingleShot(true); + connect(&oneNSecTimer, &QChronoTimer::timeout, this, [&t2, &loop] { + t2 = steady_clock::now(); + loop.quit(); + }); -void tst_QTimer::singleShot_static() -{ - QCoreApplication::processEvents(); - QCOMPARE(s_staticSingleShotUser->helper.calls, s_staticSingleShotUser->calls()); + zeroTimer.start(); + oneNSecTimer.start(); + loop.exec(); + QCOMPARE_GT(t2, t1); + // qDebug() << "t2 - t1" << duration<double, std::chrono::milliseconds::period>{t2 - t1}; } -QTEST_MAIN(tst_QTimer) +QTEST_MAIN(tst_QChronoTimer) -#include "tst_qtimer.moc" +#include "tst_qchronotimer.moc" diff --git a/tests/auto/gui/kernel/CMakeLists.txt b/tests/auto/gui/kernel/CMakeLists.txt index 5377a04a64..9acd817610 100644 --- a/tests/auto/gui/kernel/CMakeLists.txt +++ b/tests/auto/gui/kernel/CMakeLists.txt @@ -10,6 +10,7 @@ add_subdirectory(qcursor) add_subdirectory(qdrag) add_subdirectory(qevent) add_subdirectory(qfileopenevent) +add_subdirectory(qguichronotimer) add_subdirectory(qguieventdispatcher) add_subdirectory(qguitimer) if(NOT ANDROID AND NOT WASM) diff --git a/tests/auto/gui/kernel/qguichronotimer/CMakeLists.txt b/tests/auto/gui/kernel/qguichronotimer/CMakeLists.txt index bc292e133b..37848d8cec 100644 --- a/tests/auto/gui/kernel/qguichronotimer/CMakeLists.txt +++ b/tests/auto/gui/kernel/qguichronotimer/CMakeLists.txt @@ -2,19 +2,19 @@ # SPDX-License-Identifier: BSD-3-Clause ##################################################################### -## tst_qguitimer Test: +## tst_qguichronotimer Test: ##################################################################### if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) cmake_minimum_required(VERSION 3.16) - project(tst_qguitimer LANGUAGES CXX) + project(tst_qguichronotimer LANGUAGES CXX) find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) endif() -function(addGuiTimerTest test) +function(addGuiChronoTimerTest test) qt_internal_add_test(${test} SOURCES - ../../../corelib/kernel/qtimer/tst_qtimer.cpp + ../../../corelib/kernel/qchronotimer/tst_qchronotimer.cpp LIBRARIES Qt::CorePrivate Qt::Gui @@ -22,17 +22,17 @@ function(addGuiTimerTest test) ) endfunction() -addGuiTimerTest(tst_qguitimer) -qt_internal_extend_target(tst_qguitimer +addGuiChronoTimerTest(tst_qguichronotimer) +qt_internal_extend_target(tst_qguichronotimer DEFINES - tst_Qtimer=tst_QGuiTimer + tst_Qtimer=tst_QGuiChronoTimer ) if(QT_FEATURE_glib AND UNIX) - addGuiTimerTest(tst_qguitimer_no_glib) - qt_internal_extend_target(tst_qguitimer_no_glib + addGuiChronoTimerTest(tst_qguichronotimer_no_glib) + qt_internal_extend_target(tst_qguichronotimer_no_glib DEFINES DISABLE_GLIB - tst_QTimer=tst_QGuiTimer_no_glib # Class name in the unittest + tst_QTimer=tst_QGuiChronoTimer_no_glib # Class name in the unittest ) endif() |