1 | // Copyright (C) 2022 The Qt Company Ltd. |
2 | // Copyright (C) 2016 Intel Corporation. |
3 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
4 | |
5 | #include "qchronotimer.h" |
6 | #include "qtimer_p.h" |
7 | #include "qsingleshottimer_p.h" |
8 | |
9 | #include "qabstracteventdispatcher.h" |
10 | #include "qcoreapplication.h" |
11 | #include "qcoreapplication_p.h" |
12 | #include "qdeadlinetimer.h" |
13 | #include "qmetaobject_p.h" |
14 | #include "qobject_p.h" |
15 | #include "qproperty_p.h" |
16 | #include "qthread.h" |
17 | |
18 | using namespace std::chrono_literals; |
19 | |
20 | QT_BEGIN_NAMESPACE |
21 | |
22 | /*! |
23 | \class QChronoTimer |
24 | \inmodule QtCore |
25 | \since 6.8 |
26 | \ingroup events |
27 | |
28 | \brief The QChronoTimer class provides repetitive and single-shot timers. |
29 | |
30 | The QChronoTimer class provides a high-level programming interface for |
31 | timers. To use it, create a QChronoTimer, either passing the interval to the |
32 | constructor, or setting it after construction using setInterval(), connect |
33 | its timeout() signal to the appropriate slots, and call start(). From then |
34 | on, it will emit the timeout() signal at constant intervals. For example: |
35 | |
36 | \snippet timers/timers.cpp timer-interval-in-ctor |
37 | \snippet timers/timers.cpp timer-setinterval |
38 | |
39 | You can set a timer to time out only once by calling setSingleShot(true). |
40 | |
41 | \note QChronoTimer has no singleShot() static methods, as the ones on |
42 | QTimer already work with chrono types and nanoseconds resolution. |
43 | |
44 | In multithreaded applications, you can use QChronoTimer in any thread |
45 | that has an event loop. To start an event loop from a non-GUI |
46 | thread, use QThread::exec(). Qt uses the timer's |
47 | \l{QObject::thread()}{thread affinity} to determine which thread |
48 | will emit the \l{QChronoTimer::}{timeout()} signal. Because of this, you |
49 | must start and stop the timer in its thread; it is not possible to |
50 | start a timer from another thread. |
51 | |
52 | As a special case, a QChronoTimer with a timeout of \c 0ns will time out |
53 | as soon as possible, though the ordering between zero timers and other |
54 | sources of events is unspecified. Zero timers can be used to do some |
55 | work while still providing a responsive user interface: |
56 | |
57 | \snippet timers/timers.cpp zero-timer |
58 | |
59 | From then on, \c processOneThing() will be called repeatedly. It should |
60 | be written in such a way that it always returns quickly (for example, |
61 | after processing one data item) so that Qt can deliver events to the user |
62 | interface and stop the timer as soon as it has done all its work. This |
63 | is the traditional way of implementing heavy work in GUI applications, |
64 | but as multithreading is becoming available on more platforms, a modern |
65 | alternative is doing the heavy work in a thread other than the GUI (main) |
66 | thread. Qt has the QThread class, which can be used to achieve that. |
67 | |
68 | \section1 Accuracy and Timer Resolution |
69 | |
70 | The accuracy of timers depends on the underlying operating system and |
71 | hardware. Most platforms support requesting nano-second precision for |
72 | timers (for example, libc's \c nanosleep), though the accuracy of the |
73 | timer will not equal this resolution in many real-world situations. |
74 | |
75 | You can set the \l{Qt::TimerType}{timer type} to tell QChronoTimer which |
76 | precision to request from the system. |
77 | |
78 | For Qt::PreciseTimer, QChronoTimer will try to keep the precision at |
79 | \c 1ns. Precise timers will never time out earlier than expected. |
80 | |
81 | For Qt::CoarseTimer and Qt::VeryCoarseTimer types, QChronoTimer may wake |
82 | up earlier than expected, within the margins for those types: |
83 | \list |
84 | \li 5% of the interval for Qt::CoarseTimer |
85 | \li \c 500ms for Qt::VeryCoarseTimer |
86 | \endlist |
87 | |
88 | All timer types may time out later than expected if the system is busy or |
89 | unable to provide the requested accuracy. In such a case of timeout |
90 | overrun, Qt will emit timeout() only once, even if multiple timeouts have |
91 | expired, and then will resume the original interval. |
92 | |
93 | \section1 Alternatives to QChronoTimer |
94 | |
95 | QChronoTimer provides nanosecond resolution and a ±292 years range |
96 | (less chances of integer overflow if the interval is longer than \c |
97 | std::numeric_limits<int>::max()). If you only need millisecond resolution |
98 | and ±24 days range, you can continue to use the classical QTimer class |
99 | |
100 | \include timers-common.qdocinc q-chrono-timer-alternatives |
101 | |
102 | Some operating systems limit the number of timers that may be used; |
103 | Qt does its best to work around these limitations. |
104 | |
105 | \sa QBasicTimer, QTimerEvent, QObject::timerEvent(), Timers, |
106 | {Analog Clock} |
107 | */ |
108 | |
109 | /*! |
110 | Constructs a timer with the given \a parent, using the default interval, |
111 | \c 0ns. |
112 | */ |
113 | QChronoTimer::QChronoTimer(QObject *parent) |
114 | : QChronoTimer(0ns, parent) |
115 | { |
116 | } |
117 | |
118 | /*! |
119 | Constructs a timer with the given \a parent, using an interval of \a nsec. |
120 | */ |
121 | QChronoTimer::QChronoTimer(std::chrono::nanoseconds nsec, QObject *parent) |
122 | : QObject(*new QTimerPrivate(nsec, this), parent) |
123 | { |
124 | Q_ASSERT(!d_func()->isQTimer); |
125 | } |
126 | |
127 | /*! |
128 | Destroys the timer. |
129 | */ |
130 | QChronoTimer::~QChronoTimer() |
131 | { |
132 | if (d_func()->isActive()) // stop running timer |
133 | stop(); |
134 | } |
135 | |
136 | /*! |
137 | \fn void QChronoTimer::timeout() |
138 | |
139 | This signal is emitted when the timer times out. |
140 | |
141 | \sa interval, start(), stop() |
142 | */ |
143 | |
144 | /*! |
145 | \property QChronoTimer::active |
146 | |
147 | This boolean property is \c true if the timer is running; otherwise |
148 | \c false. |
149 | */ |
150 | |
151 | /*! |
152 | Returns \c true if the timer is running; otherwise returns \c false. |
153 | */ |
154 | bool QChronoTimer::isActive() const |
155 | { |
156 | return d_func()->isActiveData.value(); |
157 | } |
158 | |
159 | QBindable<bool> QChronoTimer::bindableActive() |
160 | { |
161 | return QBindable<bool>(&d_func()->isActiveData); |
162 | } |
163 | |
164 | /*! |
165 | Returns a Qt::TimerId representing the timer ID if the timer is running; |
166 | otherwise returns \c Qt::TimerId::Invalid. |
167 | |
168 | \sa Qt::TimerId |
169 | */ |
170 | Qt::TimerId QChronoTimer::id() const |
171 | { |
172 | return d_func()->id; |
173 | } |
174 | |
175 | /*! \overload start() |
176 | |
177 | Starts or restarts the timer with the timeout specified in \l interval. |
178 | |
179 | //! [stop-restart-timer] |
180 | If the timer is already running, it will be |
181 | \l{QChronoTimer::stop()}{stopped} and restarted. This will also change its |
182 | id(). |
183 | //! [stop-restart-timer] |
184 | |
185 | If \l singleShot is true, the timer will be activated only once. |
186 | */ |
187 | void QChronoTimer::start() |
188 | { |
189 | auto *d = d_func(); |
190 | if (d->isActive()) // stop running timer |
191 | stop(); |
192 | const auto id = Qt::TimerId{QObject::startTimer(time: d->intervalDuration, timerType: d->type)}; |
193 | if (id != Qt::TimerId::Invalid) { |
194 | d->id = id; |
195 | d->isActiveData.notify(); |
196 | } |
197 | } |
198 | |
199 | /*! |
200 | Stops the timer. |
201 | |
202 | \sa start() |
203 | */ |
204 | void QChronoTimer::stop() |
205 | { |
206 | auto *d = d_func(); |
207 | if (d->isActive()) { |
208 | QObject::killTimer(id: d->id); |
209 | d->id = Qt::TimerId::Invalid; |
210 | d->isActiveData.notify(); |
211 | } |
212 | } |
213 | |
214 | /*! |
215 | \reimp |
216 | */ |
217 | void QChronoTimer::timerEvent(QTimerEvent *e) |
218 | { |
219 | auto *d = d_func(); |
220 | if (Qt::TimerId{e->timerId()} == d->id) { |
221 | if (d->single) |
222 | stop(); |
223 | Q_EMIT timeout(QPrivateSignal()); |
224 | } |
225 | } |
226 | |
227 | /*! |
228 | \fn template <typename Functor> QMetaObject::Connection QChronoTimer::callOnTimeout(const QObject *context, Functor &&slot, Qt::ConnectionType connectionType = Qt::AutoConnection) |
229 | \overload callOnTimeout() |
230 | |
231 | Creates a connection from the timeout() signal to \a slot to be placed in a |
232 | specific event loop of \a context, with connection type \a connectionType, |
233 | and returns a handle to the connection. |
234 | |
235 | This method is provided as a convenience. It's equivalent to calling: |
236 | \code |
237 | QObject::connect(timer, &QChronoTimer::timeout, context, slot, connectionType); |
238 | \endcode |
239 | |
240 | \sa QObject::connect(), timeout() |
241 | */ |
242 | |
243 | /*! |
244 | \property QChronoTimer::singleShot |
245 | \brief Whether the timer is a single-shot timer |
246 | |
247 | A single-shot timer fires only once, non-single-shot timers fire every |
248 | \l interval. |
249 | |
250 | The default value for this property is \c false. |
251 | |
252 | \sa interval, QChronoTimer::singleShot() |
253 | */ |
254 | void QChronoTimer::setSingleShot(bool singleShot) |
255 | { |
256 | d_func()->single = singleShot; |
257 | } |
258 | |
259 | bool QChronoTimer::isSingleShot() const |
260 | { |
261 | return d_func()->single; |
262 | } |
263 | |
264 | QBindable<bool> QChronoTimer::bindableSingleShot() |
265 | { |
266 | return QBindable<bool>(&d_func()->single); |
267 | } |
268 | |
269 | /*! |
270 | \property QChronoTimer::interval |
271 | \brief The timeout interval |
272 | |
273 | The default value for this property is \c 0ns. |
274 | |
275 | A QChronoTimer with a timeout of \c 0ns will time out as soon as all |
276 | the events in the window system's event queue have been processed. |
277 | |
278 | Setting the interval of a running timer will change the interval, |
279 | stop() and then start() the timer, and acquire a new id(). |
280 | If the timer is not running, only the interval is changed. |
281 | |
282 | \sa singleShot |
283 | */ |
284 | void QChronoTimer::setInterval(std::chrono::nanoseconds nsec) |
285 | { |
286 | auto *d = d_func(); |
287 | d->intervalDuration.removeBindingUnlessInWrapper(); |
288 | const bool intervalChanged = nsec != d->intervalDuration.valueBypassingBindings(); |
289 | d->intervalDuration.setValueBypassingBindings(nsec); |
290 | if (d->isActive()) { // Create new timer |
291 | QObject::killTimer(id: d->id); // Restart timer |
292 | const auto newId = Qt::TimerId{QObject::startTimer(time: nsec, timerType: d->type)}; |
293 | if (newId != Qt::TimerId::Invalid) { |
294 | // Restarted successfully. No need to update the active state. |
295 | d->id = newId; |
296 | } else { |
297 | // Failed to start the timer. |
298 | // Need to notify about active state change. |
299 | d->id = Qt::TimerId::Invalid; |
300 | d->isActiveData.notify(); |
301 | } |
302 | } |
303 | if (intervalChanged) |
304 | d->intervalDuration.notify(); |
305 | } |
306 | |
307 | std::chrono::nanoseconds QChronoTimer::interval() const |
308 | { |
309 | return d_func()->intervalDuration.value(); |
310 | } |
311 | |
312 | QBindable<std::chrono::nanoseconds> QChronoTimer::bindableInterval() |
313 | { |
314 | return {&d_func()->intervalDuration}; |
315 | } |
316 | |
317 | /*! |
318 | \property QChronoTimer::remainingTime |
319 | \brief The remaining time |
320 | |
321 | Returns the remaining duration until the timeout. |
322 | |
323 | If the timer is inactive, the returned duration will be negative. |
324 | |
325 | If the timer is overdue, the returned duration will be \c 0ns. |
326 | |
327 | \sa interval |
328 | */ |
329 | std::chrono::nanoseconds QChronoTimer::remainingTime() const |
330 | { |
331 | if (isActive()) |
332 | return QAbstractEventDispatcher::instance()->remainingTime(timerId: d_func()->id); |
333 | return std::chrono::nanoseconds::min(); |
334 | } |
335 | |
336 | /*! |
337 | \property QChronoTimer::timerType |
338 | \brief Controls the accuracy of the timer |
339 | |
340 | The default value for this property is \c Qt::CoarseTimer. |
341 | |
342 | \sa Qt::TimerType |
343 | */ |
344 | void QChronoTimer::setTimerType(Qt::TimerType atype) |
345 | { |
346 | d_func()->type = atype; |
347 | } |
348 | |
349 | Qt::TimerType QChronoTimer::timerType() const |
350 | { |
351 | return d_func()->type; |
352 | } |
353 | |
354 | QBindable<Qt::TimerType> QChronoTimer::bindableTimerType() |
355 | { |
356 | return {&d_func()->type}; |
357 | } |
358 | |
359 | QT_END_NAMESPACE |
360 | |
361 | #include "moc_qchronotimer.cpp" |
362 | |