diff options
Diffstat (limited to 'src/corelib/kernel/qtimer.cpp')
-rw-r--r-- | src/corelib/kernel/qtimer.cpp | 255 |
1 files changed, 122 insertions, 133 deletions
diff --git a/src/corelib/kernel/qtimer.cpp b/src/corelib/kernel/qtimer.cpp index a1abe71052..8d3ea7865d 100644 --- a/src/corelib/kernel/qtimer.cpp +++ b/src/corelib/kernel/qtimer.cpp @@ -4,6 +4,7 @@ #include "qtimer.h" #include "qtimer_p.h" +#include "qsingleshottimer_p.h" #include "qabstracteventdispatcher.h" #include "qcoreapplication.h" @@ -14,6 +15,8 @@ #include "qproperty_p.h" #include "qthread.h" +using namespace std::chrono_literals; + QT_BEGIN_NAMESPACE /*! @@ -71,6 +74,13 @@ QT_BEGIN_NAMESPACE more and more platforms, we expect that zero-millisecond QTimer objects will gradually be replaced by \l{QThread}s. + \note Since Qt 6.8 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 @@ -107,7 +117,7 @@ QT_BEGIN_NAMESPACE used; Qt tries to work around these limitations. \sa QBasicTimer, QTimerEvent, QObject::timerEvent(), Timers, - {Analog Clock}, {Tetrix Example} + {Analog Clock} */ /*! @@ -115,8 +125,9 @@ QT_BEGIN_NAMESPACE */ QTimer::QTimer(QObject *parent) - : QObject(*new QTimerPrivate, parent) + : QObject(*new QTimerPrivate(this), parent) { + Q_ASSERT(d_func()->isQTimer); } @@ -126,7 +137,7 @@ QTimer::QTimer(QObject *parent) QTimer::~QTimer() { - if (d_func()->id != QTimerPrivate::INV_TIMER) // stop running timer + if (d_func()->isActive()) // stop running timer stop(); } @@ -171,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() @@ -187,10 +210,14 @@ int QTimer::timerId() const void QTimer::start() { Q_D(QTimer); - if (d->id != QTimerPrivate::INV_TIMER) // stop running timer + if (d->isActive()) // stop running timer stop(); - d->id = QObject::startTimer(std::chrono::milliseconds{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(); + } } /*! @@ -200,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(); @@ -226,9 +267,9 @@ void QTimer::start(int msec) void QTimer::stop() { Q_D(QTimer); - if (d->id != QTimerPrivate::INV_TIMER) { + if (d->isActive()) { QObject::killTimer(d->id); - d->id = QTimerPrivate::INV_TIMER; + d->id = Qt::TimerId::Invalid; d->isActiveData.notify(); } } @@ -240,97 +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 = -1; -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); - - void startTimerForReceiver(int msec, Qt::TimerType timerType, const QObject *receiver); - -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()) -{ - connect(this, SIGNAL(timeout()), r, member); - - startTimerForReceiver(msec, timerType, r); -} - -QSingleShotTimer::QSingleShotTimer(int msec, Qt::TimerType timerType, const QObject *r, QtPrivate::QSlotObjectBase *slotObj) - : QObject(QAbstractEventDispatcher::instance()) -{ - int signal_index = QMetaObjectPrivate::signalOffset(&staticMetaObject); - Q_ASSERT(QMetaObjectPrivate::signal(&staticMetaObject, signal_index).name() == "timeout"); - QObjectPrivate::connectImpl(this, signal_index, r ? r : this, nullptr, slotObj, - Qt::AutoConnection, nullptr, &staticMetaObject); - - startTimerForReceiver(msec, timerType, r); -} - -QSingleShotTimer::~QSingleShotTimer() -{ - if (timerId > 0) - killTimer(timerId); -} - -/* - Move the timer, and the dispatching and handling of the timer event, into - the same thread as where it will be handled, so that it fires reliably even - if the thread that set up the timer is busy. -*/ -void QSingleShotTimer::startTimerForReceiver(int msec, Qt::TimerType timerType, const QObject *receiver) -{ - if (receiver && receiver->thread() != 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(receiver->thread()); - - QDeadlineTimer deadline(std::chrono::milliseconds{msec}, timerType); - QMetaObject::invokeMethod(this, [this, deadline, timerType]{ - if (deadline.hasExpired()) - emit timeout(); - else - timerId = startTimer(std::chrono::milliseconds{deadline.remainingTime()}, timerType); - }, Qt::QueuedConnection); - } else { - timerId = startTimer(std::chrono::milliseconds{msec}, timerType); - } -} - - -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; - - 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 @@ -340,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. @@ -366,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 @@ -397,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 @@ -419,25 +374,29 @@ 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); } } @@ -505,14 +464,19 @@ 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 - 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() */ @@ -525,8 +489,10 @@ void QTimer::singleShot(int msec, Qt::TimerType timerType, const QObject *receiv 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)}. + 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() */ @@ -541,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 */ /*! @@ -606,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 != QTimerPrivate::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(std::chrono::milliseconds{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(); @@ -643,8 +631,10 @@ QBindable<int> QTimer::bindableInterval() int QTimer::remainingTime() const { Q_D(const QTimer); - if (d->id != QTimerPrivate::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; @@ -675,5 +665,4 @@ QBindable<Qt::TimerType> QTimer::bindableTimerType() QT_END_NAMESPACE -#include "qtimer.moc" #include "moc_qtimer.cpp" |