Perceptual Color

asyncimagerenderthread.cpp
1// SPDX-FileCopyrightText: Lukas Sommer <sommerluk@gmail.com>
2// SPDX-License-Identifier: BSD-2-Clause OR MIT
3
4// Own headers
5// First the interface, which forces the header to be self-contained.
6#include "asyncimagerenderthread.h"
7
8#include <qglobal.h>
9#include <qmetatype.h>
10
11class QObject;
12
13namespace PerceptualColor
14{
15/** @brief The constructor.
16 *
17 * @param renderFunction Pointer to the render function that will be used.
18 * @param parent The widget’s parent widget. This parameter will be passed
19 * to the base class’s constructor. */
20AsyncImageRenderThread::AsyncImageRenderThread(const pointerToRenderFunction &renderFunction, QObject *parent)
21 : QThread(parent)
22 , m_renderFunction(renderFunction)
23{
24 qRegisterMetaType<PerceptualColor::AsyncImageRenderCallback::InterlacingState>();
25}
26
27/** @brief The destructor.
28 *
29 * This destructor might takes a little while because he has to
30 * stop the associated thread before destroying it: Possibly running
31 * rendering operations are aborted. */
32AsyncImageRenderThread::~AsyncImageRenderThread()
33{
34 m_loopMutex.lock();
35 m_loopAbort = true;
36 m_loopCondition.wakeOne();
37 m_loopMutex.unlock();
38
39 wait(); // Wait for the thread to terminate.
40
41 // We make sure no thread will stay blocked when this object is
42 // destroyed. However, given that this class itself is NOT thread-safe,
43 // anyway it isn’t allowed to execute the destructor and waitForIdle()
44 // in parallel. Therefore, this should be a no-operation. We stays
45 // here just to feel safe.
46 m_syncCondition.wakeAll();
47}
48
49/** @brief Asynchronously start rendering.
50 *
51 * As this function is asynchronous, it will return very fast.
52 *
53 * @param parameters The parameters of the requested rendering.
54 *
55 * @post If the <tt>parameters</tt> are different from those at the last
56 * call, a new rendering of the new parameters will be started. (If there
57 * is currently a rendering of other parameters in progress, this rendering
58 * will be requested to stop as soon as possible.) If the <tt>parameters</tt>
59 * are identical to those at the last call, nothing happens.
60 *
61 * The rendering will emit the signal @ref interlacingPassCompleted(). */
62void AsyncImageRenderThread::startRenderingAsync(const QVariant &parameters)
63{
64#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
65 QMutexLocker<QMutex> loopLocker(&m_loopMutex);
66#else
67 QMutexLocker loopLocker(&m_loopMutex);
68#endif
69
70 if (m_imageParameters == parameters) {
71 // Nothing to do here.
72 return;
73 }
74
75 m_imageParameters = parameters;
76
77 {
78#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
79 QMutexLocker<QMutex> syncLocker(&m_syncMutex);
80#else
81 QMutexLocker syncLocker(&m_syncMutex);
82#endif
83 m_syncIsIdle = false;
84 }
85 if (!isRunning()) {
86 start(LowPriority); // One priority level lower than normal priority.
87 } else {
88 m_loopRestart = true;
89 m_loopCondition.wakeOne();
90 }
91}
92
93/** @brief The code that will run within the thread.
94 *
95 * Reimplemented from base class.
96 *
97 * This is a wrapper that provides the thread-control (loops and so on).
98 * The actual rendering is done by calling @ref m_renderFunction. */
99void AsyncImageRenderThread::run()
100{
101 Q_FOREVER {
102 m_loopMutex.lock();
103 const QVariant parameters = m_imageParameters;
104 m_loopMutex.unlock();
105
106 if (m_loopAbort) {
107 return;
108 }
109
110 // From Qt Example’s documentation:
111 //
112 // “If we discover inside […] [this function call] that restart
113 // has been set to true (by render()), this function will return
114 // immediately, so that the control quickly returns to the very
115 // top of […] the forever loop […] and we fetch the new rendering
116 // parameters. Similarly, if we discover that abort has been set
117 // to true (by the RenderThread destructor), we return from the
118 // function immediately, terminating the thread.”
119 //
120 // Here, this is done by passing m_abortRun and m_restart (in form of
121 // shouldAbort())to the render function, which is supposed to return
122 // as fast as possible if indicated.
123 m_renderFunction(parameters, *this);
124
125 // cppcheck-suppress identicalConditionAfterEarlyExit // false positive
126 if (m_loopAbort) {
127 return;
128 }
129
130 // From Qt’s examples:
131 // “Once we're done with all the iterations, we call
132 // QWaitCondition::wait() to put the thread to sleep, unless
133 // restart is true. There's no use in keeping a worker thread
134 // looping indefinitely while there's nothing to do.”
135 m_loopMutex.lock();
136 if (!m_loopRestart && !m_loopAbort) {
137#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
138 QMutexLocker<QMutex> syncLocker(&m_syncMutex);
139#else
140 QMutexLocker syncLocker(&m_syncMutex);
141#endif
142 m_syncIsIdle = true;
143 m_syncCondition.wakeOne();
144 }
145 while (!m_loopRestart && !m_loopAbort) {
146 // QWaitCondition::wait() does the following things:
147 // 1.) Unlock mutex.
148 // 2.) Wait until another thread calls QWaitCondition::wakeOne()
149 // or QWaitCondition::wakeAll().
150 // 3.) Lock mutex again.
151 //
152 // As explained on the StackOverflow webpage at
153 // https://stackoverflow.com/questions/40445629
154 // using QWaitCondition::wait() alone can do spurious
155 // wake-up (wake-up without a reason). To prevent the
156 // rendering from continuing in this case, we put the
157 // QWaitCondition::wait() call into a while loop. This way
158 // we can go back to sleep if the wake-up was without
159 // reason (this is checked by the condition in the while loop).
160 m_loopCondition.wait(&m_loopMutex);
161 }
162 m_loopRestart = false;
163 m_loopMutex.unlock();
164 }
165}
166
167/** @brief Deliver the result of an interlacing pass of
168 * the <em>rendering</em> operation.
169 *
170 * This function is thread-safe.
171 *
172 * @param image The image
173 * @param parameters The parameters of the image
174 * @param state The interlacing state of the image. A render function
175 * must first return zero or more images with intermediate state. After
176 * that, it must return exactly one image with final state (unless it
177 * was aborted). After that, it must not return any more images. */
178void AsyncImageRenderThread::deliverInterlacingPass(const QImage &image, const QVariant &parameters, const AsyncImageRenderCallback::InterlacingState state)
179{
180 // interlacingPassCompleted() is documented as being possibly emitted
181 // by different threads, so this call is thread-safe within the
182 // restrictions mentioned in the documentation.
183 Q_EMIT interlacingPassCompleted(image, parameters, state);
184}
185
186/** @brief If the render function should abort.
187 *
188 * This function is thread-safe.
189 *
190 * @returns <tt>true</tt> if the render function should abort (and
191 * return). <tt>false</tt> otherwise.
192 *
193 * @internal
194 *
195 * @sa @ref m_renderFunction */
196bool AsyncImageRenderThread::shouldAbort() const
197{
198 // m_abortRun and m_restart are atomic, so this call is thread-safe.
199 return (m_loopAbort || m_loopRestart);
200}
201
202/** @brief Wait until the render thread is idle. */
203void AsyncImageRenderThread::waitForIdle()
204{
205 m_syncMutex.lock();
206 while (!m_syncIsIdle) {
207 // QWaitCondition::wait() does the following things:
208 // 1.) Unlock mutex.
209 // 2.) Wait until another thread calls QWaitCondition::wakeOne()
210 // or QWaitCondition::wakeAll().
211 // 3.) Lock mutex again.
212 //
213 // As explained on the StackOverflow webpage at
214 // https://stackoverflow.com/questions/40445629
215 // using QWaitCondition::wait() alone can do spurious
216 // wake-up (wake-up without a reason). To prevent the
217 // rendering from continuing in this case, we put the
218 // QWaitCondition::wait() call into a while loop. This way
219 // we can go back to sleep if the wake-up was without
220 // reason (this is checked by the condition in the while loop).
221 m_syncCondition.wait(&m_syncMutex);
222 }
223 m_syncMutex.unlock();
224}
225
226} // namespace PerceptualColor
Q_SCRIPTABLE Q_NOREPLY void start()
The namespace of this library.
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:18:38 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.