summaryrefslogtreecommitdiffstats
path: root/src/corelib/kernel/qchronotimer.cpp
blob: a517c4446b4acf72ccb1f0f47eef9459172feded (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
// 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 "qchronotimer.h"
#include "qtimer_p.h"
#include "qsingleshottimer_p.h"

#include "qabstracteventdispatcher.h"
#include "qcoreapplication.h"
#include "qcoreapplication_p.h"
#include "qdeadlinetimer.h"
#include "qmetaobject_p.h"
#include "qobject_p.h"
#include "qproperty_p.h"
#include "qthread.h"

using namespace std::chrono_literals;

QT_BEGIN_NAMESPACE

/*!
    \class QChronoTimer
    \inmodule QtCore
    \since 6.8
    \ingroup events

    \brief The QChronoTimer class provides repetitive and single-shot timers.

    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 timers/timers.cpp timer-interval-in-ctor
    \snippet timers/timers.cpp timer-setinterval

    You can set a timer to time out only once by calling setSingleShot(true).

    QChronoTimer also has singleShot() static methods:

    \snippet timers/timers.cpp qchronotimer-singleshot

    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{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 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 zero-timer

    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 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.

    You can set the \l{Qt::TimerType}{timer type} to tell QChronoTimer which
    precision to request from the system.

    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 QChronoTimer

    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.

    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, using the default interval,
    \c 0ns.
*/
QChronoTimer::QChronoTimer(QObject *parent)
    : QChronoTimer(0ns, parent)
{
}

/*!
    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.
*/
QChronoTimer::~QChronoTimer()
{
    if (d_func()->isActive()) // stop running timer
        stop();
}

/*!
    \fn void QChronoTimer::timeout()

    This signal is emitted when the timer times out.

    \sa interval, start(), stop()
*/

/*!
    \property QChronoTimer::active

    This boolean property is \c true if the timer is running; otherwise
    \c false.
*/

/*!
    Returns \c true if the timer is running (pending); otherwise returns
    false.
*/
bool QChronoTimer::isActive() const
{
    return d_func()->isActiveData.value();
}

QBindable<bool> QChronoTimer::bindableActive()
{
    return QBindable<bool>(&d_func()->isActiveData);
}

/*!
    Returns a Qt::TimerId representing the timer ID if the timer is running;
    otherwise returns \c Qt::TimerId::Invalid.

    \sa Qt::TimerId
*/
Qt::TimerId 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{QChronoTimer::stop()}{stopped} and restarted.

    If \l singleShot is true, the timer will be activated only once.
*/
void QChronoTimer::start()
{
    auto *d = d_func();
    if (d->isActive()) // stop running timer
        stop();
    const auto id = Qt::TimerId{QObject::startTimer(d->intervalDuration, d->type)};
    if (id > Qt::TimerId::Invalid) {
        d->id = id;
        d->isActiveData.notify();
    }
}

/*!
    Stops the timer.

    \sa start()
*/
void QChronoTimer::stop()
{
    auto *d = d_func();
    if (d->isActive()) {
        QObject::killTimer(d->id);
        d->id = Qt::TimerId::Invalid;
        d->isActiveData.notify();
    }
}

/*!
  \reimp
*/
void QChronoTimer::timerEvent(QTimerEvent *e)
{
    auto *d = d_func();
    if (Qt::TimerId{e->timerId()} == d->id) {
        if (d->single)
            stop();
        Q_EMIT timeout(QPrivateSignal());
    }
}

/*!
    \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, with connection type \a connectionType,
    and returns a handle to the connection.

    This method is provided as a convenience. It's equivalent to calling:
    \code
    QObject::connect(timer, &QChronoTimer::timeout, context, slot, connectionType);
    \endcode

    \sa QObject::connect(), timeout()
*/

/*!
    \property QChronoTimer::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.

    The default value for this property is \c false.

    \sa interval, singleShot()
*/
void QChronoTimer::setSingleShot(bool singleShot)
{
    d_func()->single = singleShot;
}

bool QChronoTimer::isSingleShot() const
{
    return d_func()->single;
}

QBindable<bool> QChronoTimer::bindableSingleShot()
{
    return QBindable<bool>(&d_func()->single);
}

/*!
    \property QChronoTimer::interval
    \brief The timeout interval

    The default value for this property is \c 0ns.

    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 QChronoTimer::setInterval(std::chrono::nanoseconds nsec)
{
    auto *d = d_func();
    d->intervalDuration.removeBindingUnlessInWrapper();
    const bool intervalChanged = nsec != d->intervalDuration.valueBypassingBindings();
    d->intervalDuration.setValueBypassingBindings(nsec);
    if (d->isActive()) { // Create new timer
        QObject::killTimer(d->id); // Restart timer
        const auto newId = Qt::TimerId{QObject::startTimer(nsec, d->type)};
        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->intervalDuration.notify();
}

std::chrono::nanoseconds QChronoTimer::interval() const
{
    return d_func()->intervalDuration.value();
}

QBindable<std::chrono::nanoseconds> QChronoTimer::bindableInterval()
{
    return {&d_func()->intervalDuration};
}

/*!
    \property QChronoTimer::remainingTime
    \brief The remaining time

    Returns the remaining duration until the timeout.

    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
*/
std::chrono::nanoseconds QChronoTimer::remainingTime() const
{
    if (isActive())
        return QAbstractEventDispatcher::instance()->remainingTime(d_func()->id);
    return std::chrono::nanoseconds::min();
}

/*!
    \property QChronoTimer::timerType
    \brief Controls the accuracy of the timer

    The default value for this property is \c Qt::CoarseTimer.

    \sa Qt::TimerType
*/
void QChronoTimer::setTimerType(Qt::TimerType atype)
{
    d_func()->type = atype;
}

Qt::TimerType QChronoTimer::timerType() const
{
    return d_func()->type;
}

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 QChronoTimer
    object.

    \sa start(), Qt::TimerType
*/
void QChronoTimer::singleShot(std::chrono::nanoseconds interval, Qt::TimerType timerType,
                              const QObject *receiver, const char *member)
{
    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_qchronotimer.cpp"