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 /src/corelib/kernel | |
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>
Diffstat (limited to 'src/corelib/kernel')
-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 |
5 files changed, 333 insertions, 532 deletions
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 |