summaryrefslogtreecommitdiffstats
path: root/src/corelib/kernel
diff options
context:
space:
mode:
authorAhmad Samir <a.samirh78@gmail.com>2023-07-11 16:32:39 +0300
committerAhmad Samir <a.samirh78@gmail.com>2024-03-03 19:56:55 +0200
commitbd764cc1ca578759f16fbe292fbe14a243b7d263 (patch)
tree9e270f40316c60a3b536e2019fc70d63cadc0224 /src/corelib/kernel
parent4fa9034d0c592e5f07531d41463c8c462f5e8895 (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.cpp645
-rw-r--r--src/corelib/kernel/qchronotimer.h178
-rw-r--r--src/corelib/kernel/qobjectdefs.h1
-rw-r--r--src/corelib/kernel/qtimer.cpp3
-rw-r--r--src/corelib/kernel/qtimer_p.h38
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