diff options
Diffstat (limited to 'src/corelib/kernel/qtimer.cpp')
-rw-r--r-- | src/corelib/kernel/qtimer.cpp | 449 |
1 files changed, 144 insertions, 305 deletions
diff --git a/src/corelib/kernel/qtimer.cpp b/src/corelib/kernel/qtimer.cpp index ddd1213f07..294369c1b3 100644 --- a/src/corelib/kernel/qtimer.cpp +++ b/src/corelib/kernel/qtimer.cpp @@ -1,70 +1,23 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Copyright (C) 2016 Intel Corporation. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtCore module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2022 The Qt Company Ltd. +// 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 "qtimer_p.h" +#include "qsingleshottimer_p.h" + #include "qabstracteventdispatcher.h" #include "qcoreapplication.h" -#include "qobject_p.h" -#include "qthread.h" #include "qcoreapplication_p.h" +#include "qdeadlinetimer.h" +#include "qmetaobject_p.h" +#include "qobject_p.h" #include "qproperty_p.h" +#include "qthread.h" -QT_BEGIN_NAMESPACE - -static constexpr int INV_TIMER = -1; // invalid timer id +using namespace std::chrono_literals; -class QTimerPrivate : public QObjectPrivate -{ - Q_DECLARE_PUBLIC(QTimer) -public: - void setInterval(int msec) { q_func()->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_BINDABLE_PROPERTY_WITH_ARGS(QTimerPrivate, bool, single, false) - bool nulltimer = false; - Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(QTimerPrivate, Qt::TimerType, type, Qt::CoarseTimer) - Q_OBJECT_COMPUTED_PROPERTY(QTimerPrivate, bool, isActiveData, - &QTimerPrivate::isActiveActualCalculation) -}; +QT_BEGIN_NAMESPACE /*! \class QTimer @@ -121,6 +74,13 @@ public: more and more platforms, we expect that zero-millisecond QTimer objects will gradually be replaced by \l{QThread}s. + \note Since Qt 6.7 this class is superseded by \l{QChronoTimer}. + The maximum interval QTimer supports is limited by the number of + milliseconds that would fit in an \c int (which is around 24 days); + whereas QChronoTimer stores its interval as \c std::chrono::nanoseconds + (which raises that limit to around 292 million years), that is, there is + less chance of integer overflow with QChronoTimer. + \section1 Accuracy and Timer Resolution The accuracy of timers depends on the underlying operating system @@ -157,7 +117,7 @@ public: used; Qt tries to work around these limitations. \sa QBasicTimer, QTimerEvent, QObject::timerEvent(), Timers, - {Analog Clock Example}, {Wiggly Example} + {Analog Clock} */ /*! @@ -165,8 +125,9 @@ public: */ QTimer::QTimer(QObject *parent) - : QObject(*new QTimerPrivate, parent) + : QObject(*new QTimerPrivate(this), parent) { + Q_ASSERT(d_func()->isQTimer); } @@ -176,7 +137,7 @@ QTimer::QTimer(QObject *parent) QTimer::~QTimer() { - if (d_func()->id != INV_TIMER) // stop running timer + if (d_func()->isActive()) // stop running timer stop(); } @@ -221,9 +182,21 @@ QBindable<bool> QTimer::bindableActive() */ int QTimer::timerId() const { - return d_func()->id; + auto v = qToUnderlying(id()); + return v == 0 ? -1 : v; } +/*! + \since 6.8 + Returns a Qt::TimerId representing the timer ID if the timer is running; + otherwise returns \c Qt::TimerId::Invalid. + + \sa Qt::TimerId +*/ +Qt::TimerId QTimer::id() const +{ + return d_func()->id; +} /*! \overload start() @@ -237,11 +210,14 @@ int QTimer::timerId() const void QTimer::start() { Q_D(QTimer); - if (d->id != INV_TIMER) // stop running timer + if (d->isActive()) // stop running timer stop(); - d->nulltimer = (!d->inter && d->single); - d->id = QObject::startTimer(d->inter, d->type); - d->isActiveData.notify(); + + Qt::TimerId newId{ QObject::startTimer(d->inter * 1ms, d->type) }; // overflow impossible + if (newId > Qt::TimerId::Invalid) { + d->id = newId; + d->isActiveData.notify(); + } } /*! @@ -251,14 +227,28 @@ void QTimer::start() 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. + 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(); @@ -277,9 +267,9 @@ void QTimer::start(int msec) void QTimer::stop() { Q_D(QTimer); - if (d->id != INV_TIMER) { + if (d->isActive()) { QObject::killTimer(d->id); - d->id = INV_TIMER; + d->id = Qt::TimerId::Invalid; d->isActiveData.notify(); } } @@ -291,84 +281,13 @@ void QTimer::stop() void QTimer::timerEvent(QTimerEvent *e) { Q_D(QTimer); - if (e->timerId() == d->id) { + if (Qt::TimerId{e->timerId()} == d->id) { if (d->single) stop(); emit timeout(QPrivateSignal()); } } -class QSingleShotTimer : public QObject -{ - Q_OBJECT - int timerId; - bool hasValidReceiver; - QPointer<const QObject> receiver; - QtPrivate::QSlotObjectBase *slotObj; -public: - ~QSingleShotTimer(); - QSingleShotTimer(int msec, Qt::TimerType timerType, const QObject *r, const char * m); - QSingleShotTimer(int msec, Qt::TimerType timerType, const QObject *r, QtPrivate::QSlotObjectBase *slotObj); - -Q_SIGNALS: - void timeout(); -protected: - void timerEvent(QTimerEvent *) override; -}; - -QSingleShotTimer::QSingleShotTimer(int msec, Qt::TimerType timerType, const QObject *r, const char *member) - : QObject(QAbstractEventDispatcher::instance()), hasValidReceiver(true), slotObj(nullptr) -{ - timerId = startTimer(msec, timerType); - connect(this, SIGNAL(timeout()), r, member); -} - -QSingleShotTimer::QSingleShotTimer(int msec, Qt::TimerType timerType, const QObject *r, QtPrivate::QSlotObjectBase *slotObj) - : QObject(QAbstractEventDispatcher::instance()), hasValidReceiver(r), receiver(r), slotObj(slotObj) -{ - timerId = startTimer(msec, timerType); - if (r && thread() != r->thread()) { - // Avoid leaking the QSingleShotTimer instance in case the application exits before the timer fires - connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &QObject::deleteLater); - setParent(nullptr); - moveToThread(r->thread()); - } -} - -QSingleShotTimer::~QSingleShotTimer() -{ - if (timerId > 0) - killTimer(timerId); - if (slotObj) - slotObj->destroyIfLastRef(); -} - -void QSingleShotTimer::timerEvent(QTimerEvent *) -{ - // need to kill the timer _before_ we emit timeout() in case the - // slot connected to timeout calls processEvents() - if (timerId > 0) - killTimer(timerId); - timerId = -1; - - if (slotObj) { - // If the receiver was destroyed, skip this part - if (Q_LIKELY(!receiver.isNull() || !hasValidReceiver)) { - // We allocate only the return type - we previously checked the function had - // no arguments. - void *args[1] = { nullptr }; - slotObj->call(const_cast<QObject*>(receiver.data()), args); - } - } else { - emit timeout(); - } - - // we would like to use delete later here, but it feels like a - // waste to post a new event to handle this event, so we just unset the flag - // and explicitly delete... - qDeleteInEventHandler(this); -} - /*! \internal @@ -378,14 +297,13 @@ void QSingleShotTimer::timerEvent(QTimerEvent *) \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 slot a pointer only used when using Qt::UniqueConnection \a slotObj the slot object - */ -void QTimer::singleShotImpl(int msec, Qt::TimerType timerType, +*/ +void QTimer::singleShotImpl(std::chrono::milliseconds msec, Qt::TimerType timerType, const QObject *receiver, QtPrivate::QSlotObjectBase *slotObj) { - if (msec == 0) { + 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. @@ -404,19 +322,23 @@ void QTimer::singleShotImpl(int msec, Qt::TimerType timerType, deleteReceiver = true; } + auto h = QtPrivate::invokeMethodHelper({}); QMetaObject::invokeMethodImpl(const_cast<QObject *>(receiver), slotObj, - Qt::QueuedConnection, nullptr); + 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); + new QSingleShotTimer(QSingleShotTimer::fromMsecs(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 @@ -435,16 +357,11 @@ void QTimer::singleShotImpl(int msec, Qt::TimerType timerType, \sa start() */ -void QTimer::singleShot(int msec, const QObject *receiver, const char *member) -{ - // 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 - singleShot(msec, msec >= 2000 ? Qt::CoarseTimer : Qt::PreciseTimer, receiver, member); -} - -/*! \overload +/*! + \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 @@ -457,147 +374,54 @@ void QTimer::singleShot(int msec, const QObject *receiver, const char *member) \sa start() */ -void QTimer::singleShot(int msec, Qt::TimerType timerType, const QObject *receiver, const char *member) + +void QTimer::singleShot(std::chrono::milliseconds msec, Qt::TimerType timerType, + const QObject *receiver, const char *member) { - if (Q_UNLIKELY(msec < 0)) { + if (Q_UNLIKELY(msec < 0ms)) { qWarning("QTimer::singleShot: Timers cannot have negative timeouts"); return; } if (receiver && member) { - if (msec == 0) { + 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; } - QByteArray methodName(member+1, bracketPosition - 1 - member); // extract method name - QMetaObject::invokeMethod(const_cast<QObject *>(receiver), methodName.constData(), Qt::QueuedConnection); + 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); + (void) new QSingleShotTimer(QSingleShotTimer::fromMsecs(msec), timerType, receiver, member); } } -/*! \fn template<typename PointerToMemberFunction> void QTimer::singleShot(int msec, const QObject *receiver, PointerToMemberFunction method) - - \since 5.4 - - \overload - \reentrant - This static function calls a member function of a QObject 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 method is the member function. The - time interval is \a msec milliseconds. - - If \a receiver is destroyed before the interval occurs, the method will not be called. - The function will be run in the thread of \a receiver. The receiver's thread must have - a running Qt event loop. - - \sa start() -*/ - -/*! \fn template<typename PointerToMemberFunction> void QTimer::singleShot(int msec, Qt::TimerType timerType, const QObject *receiver, PointerToMemberFunction method) - - \since 5.4 - - \overload - \reentrant - This static function calls a member function of a QObject 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 method is the member function. The - time interval is \a msec milliseconds. The \a timerType affects the - accuracy of the timer. - - If \a receiver is destroyed before the interval occurs, the method will not be called. - The function will be run in the thread of \a receiver. The receiver's thread must have - a running Qt event loop. - - \sa start() -*/ - -/*! \fn template<typename Functor> void QTimer::singleShot(int msec, Functor functor) - +/*! \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 - \overload \reentrant - This static function calls \a functor after a given time interval. + 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. - The time interval is \a msec milliseconds. + 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. - \sa start() -*/ + If \a functor is a member + function of \a context, then the function will be called on the object. -/*! \fn template<typename Functor> void QTimer::singleShot(int msec, Qt::TimerType timerType, Functor functor) - - \since 5.4 - - \overload - \reentrant - This static function calls \a functor 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 time interval is \a msec milliseconds. The \a timerType affects the - accuracy of the timer. - - \sa start() -*/ - -/*! \fn template<typename Functor> void QTimer::singleShot(int msec, const QObject *context, Functor functor) - - \since 5.4 - - \overload - \reentrant - This static function calls \a functor 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 time interval is \a msec milliseconds. - - If \a context is destroyed before the interval occurs, the method will not be called. - The function will be run in the thread of \a context. The context's thread must have - a running Qt event loop. - - \sa start() -*/ - -/*! \fn template<typename Functor> void QTimer::singleShot(int msec, Qt::TimerType timerType, const QObject *context, Functor functor) - - \since 5.4 - - \overload - \reentrant - This static function calls \a functor 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 time interval is \a msec milliseconds. The \a timerType affects the - accuracy of the timer. - - If \a context is destroyed before the interval occurs, the method will not be called. - The function will be run in the thread of \a context. The context's thread must have - a running Qt event loop. + The \a msec parameter can be an \c int or a \c std::chrono::milliseconds value. \sa start() */ @@ -640,43 +464,35 @@ void QTimer::singleShot(int msec, Qt::TimerType timerType, const QObject *receiv */ /*! - \fn template <typename Functor> QMetaObject::Connection QTimer::callOnTimeout(Functor slot, Qt::ConnectionType connectionType = Qt::AutoConnection) + \fn template <typename Functor> QMetaObject::Connection QTimer::callOnTimeout(Functor &&slot) \since 5.12 - \overload - Creates a connection of type \a connectionType from the timeout() signal - to \a slot, and returns a handle to the connection. + 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 \c {QObject::connect(timer, &QTimer::timeout, timer, slot, connectionType)}. + 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) + \fn template <typename Functor> QMetaObject::Connection QTimer::callOnTimeout(const QObject *context, Functor &&slot, Qt::ConnectionType connectionType = Qt::AutoConnection) \since 5.12 \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. - This method is provided for convenience. It's equivalent to calling - \c {QObject::connect(timer, &QTimer::timeout, context, slot, connectionType)}. - - \sa QObject::connect(), timeout() -*/ - -/*! - \fn template <typename MemberFunction> QMetaObject::Connection QTimer::callOnTimeout(const QObject *receiver, MemberFunction *slot, Qt::ConnectionType connectionType = Qt::AutoConnection) - \since 5.12 - \overload callOnTimeout() - - Creates a connection from the timeout() signal to the \a slot in the \a receiver object. Returns - a handle to the connection. - - This method is provided for convenience. It's equivalent to calling - \c {QObject::connect(timer, &QTimer::timeout, receiver, slot, connectionType)}. + This method is provided for convenience. It's equivalent to calling: + \code + QObject::connect(timer, &QTimer::timeout, context, slot, connectionType); + \endcode \sa QObject::connect(), timeout() */ @@ -691,7 +507,13 @@ void QTimer::singleShot(int msec, Qt::TimerType timerType, const QObject *receiv 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. + If \l singleShot is true, the timer will be activated only once. This is + equivalent to: + + \code + timer.setInterval(msec); + timer.start(); + \endcode */ /*! @@ -756,14 +578,30 @@ QBindable<bool> QTimer::bindableSingleShot() */ void QTimer::setInterval(int msec) { + setInterval(std::chrono::milliseconds{msec}); +} + +void QTimer::setInterval(std::chrono::milliseconds interval) +{ Q_D(QTimer); - const bool intervalChanged = msec != d->inter; - d->inter.setValue(msec); - if (d->id != INV_TIMER) { // create new timer + // 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->isActive()) { // create new timer QObject::killTimer(d->id); // restart timer - d->id = QObject::startTimer(msec, d->type); - // No need to call markDirty() for d->isActiveData here, - // as timer state actually does not change + Qt::TimerId newId{ QObject::startTimer(msec * 1ms, d->type) }; // overflow impossible + if (newId > Qt::TimerId::Invalid) { + // Restarted successfully. No need to update the active state. + d->id = newId; + } else { + // Failed to start the timer. + // Need to notify about active state change. + d->id = Qt::TimerId::Invalid; + d->isActiveData.notify(); + } } if (intervalChanged) d->inter.notify(); @@ -793,8 +631,10 @@ QBindable<int> QTimer::bindableInterval() int QTimer::remainingTime() const { Q_D(const QTimer); - if (d->id != INV_TIMER) { - return QAbstractEventDispatcher::instance()->remainingTime(d->id); + if (d->isActive()) { + using namespace std::chrono; + auto remaining = QAbstractEventDispatcher::instance()->remainingTime(d->id); + return ceil<milliseconds>(remaining).count(); } return -1; @@ -825,5 +665,4 @@ QBindable<Qt::TimerType> QTimer::bindableTimerType() QT_END_NAMESPACE -#include "qtimer.moc" #include "moc_qtimer.cpp" |