File indexing completed on 2024-05-12 04:44:29
0001 // SPDX-FileCopyrightText: Lukas Sommer <sommerluk@gmail.com> 0002 // SPDX-License-Identifier: BSD-2-Clause OR MIT 0003 0004 // Own headers 0005 // First the interface, which forces the header to be self-contained. 0006 #include "asyncimagerenderthread.h" 0007 0008 #include <qglobal.h> 0009 #include <qmetatype.h> 0010 0011 class QObject; 0012 0013 namespace PerceptualColor 0014 { 0015 /** @brief The constructor. 0016 * 0017 * @param renderFunction Pointer to the render function that will be used. 0018 * @param parent The widget’s parent widget. This parameter will be passed 0019 * to the base class’s constructor. */ 0020 AsyncImageRenderThread::AsyncImageRenderThread(const pointerToRenderFunction &renderFunction, QObject *parent) 0021 : QThread(parent) 0022 , m_renderFunction(renderFunction) 0023 { 0024 qRegisterMetaType<PerceptualColor::AsyncImageRenderCallback::InterlacingState>(); 0025 } 0026 0027 /** @brief The destructor. 0028 * 0029 * This destructor might takes a little while because he has to 0030 * stop the associated thread before destroying it: Possibly running 0031 * rendering operations are aborted. */ 0032 AsyncImageRenderThread::~AsyncImageRenderThread() 0033 { 0034 m_loopMutex.lock(); 0035 m_loopAbort = true; 0036 m_loopCondition.wakeOne(); 0037 m_loopMutex.unlock(); 0038 0039 wait(); // Wait for the thread to terminate. 0040 0041 // We make sure no thread will stay blocked when this object is 0042 // destroyed. However, given that this class itself is NOT thread-safe, 0043 // anyway it isn’t allowed to execute the destructor and waitForIdle() 0044 // in parallel. Therefore, this should be a no-operation. We stays 0045 // here just to feel safe. 0046 m_syncCondition.wakeAll(); 0047 } 0048 0049 /** @brief Asynchronously start rendering. 0050 * 0051 * As this function is asynchronous, it will return very fast. 0052 * 0053 * @param parameters The parameters of the requested rendering. 0054 * 0055 * @post If the <tt>parameters</tt> are different from those at the last 0056 * call, a new rendering of the new parameters will be started. (If there 0057 * is currently a rendering of other parameters in progress, this rendering 0058 * will be requested to stop as soon as possible.) If the <tt>parameters</tt> 0059 * are identical to those at the last call, nothing happens. 0060 * 0061 * The rendering will emit the signal @ref interlacingPassCompleted(). */ 0062 void AsyncImageRenderThread::startRenderingAsync(const QVariant ¶meters) 0063 { 0064 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0065 QMutexLocker<QMutex> loopLocker(&m_loopMutex); 0066 #else 0067 QMutexLocker loopLocker(&m_loopMutex); 0068 #endif 0069 0070 if (m_imageParameters == parameters) { 0071 // Nothing to do here. 0072 return; 0073 } 0074 0075 m_imageParameters = parameters; 0076 0077 { 0078 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0079 QMutexLocker<QMutex> syncLocker(&m_syncMutex); 0080 #else 0081 QMutexLocker syncLocker(&m_syncMutex); 0082 #endif 0083 m_syncIsIdle = false; 0084 } 0085 if (!isRunning()) { 0086 start(LowPriority); // One priority level lower than normal priority. 0087 } else { 0088 m_loopRestart = true; 0089 m_loopCondition.wakeOne(); 0090 } 0091 } 0092 0093 /** @brief The code that will run within the thread. 0094 * 0095 * Reimplemented from base class. 0096 * 0097 * This is a wrapper that provides the thread-control (loops and so on). 0098 * The actual rendering is done by calling @ref m_renderFunction. */ 0099 void AsyncImageRenderThread::run() 0100 { 0101 Q_FOREVER { 0102 m_loopMutex.lock(); 0103 const QVariant parameters = m_imageParameters; 0104 m_loopMutex.unlock(); 0105 0106 if (m_loopAbort) { 0107 return; 0108 } 0109 0110 // From Qt Example’s documentation: 0111 // 0112 // “If we discover inside […] [this function call] that restart 0113 // has been set to true (by render()), this function will return 0114 // immediately, so that the control quickly returns to the very 0115 // top of […] the forever loop […] and we fetch the new rendering 0116 // parameters. Similarly, if we discover that abort has been set 0117 // to true (by the RenderThread destructor), we return from the 0118 // function immediately, terminating the thread.” 0119 // 0120 // Here, this is done by passing m_abortRun and m_restart (in form of 0121 // shouldAbort())to the render function, which is supposed to return 0122 // as fast as possible if indicated. 0123 m_renderFunction(parameters, *this); 0124 0125 // cppcheck-suppress identicalConditionAfterEarlyExit // false positive 0126 if (m_loopAbort) { 0127 return; 0128 } 0129 0130 // From Qt’s examples: 0131 // “Once we're done with all the iterations, we call 0132 // QWaitCondition::wait() to put the thread to sleep, unless 0133 // restart is true. There's no use in keeping a worker thread 0134 // looping indefinitely while there's nothing to do.” 0135 m_loopMutex.lock(); 0136 if (!m_loopRestart && !m_loopAbort) { 0137 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0138 QMutexLocker<QMutex> syncLocker(&m_syncMutex); 0139 #else 0140 QMutexLocker syncLocker(&m_syncMutex); 0141 #endif 0142 m_syncIsIdle = true; 0143 m_syncCondition.wakeOne(); 0144 } 0145 while (!m_loopRestart && !m_loopAbort) { 0146 // QWaitCondition::wait() does the following things: 0147 // 1.) Unlock mutex. 0148 // 2.) Wait until another thread calls QWaitCondition::wakeOne() 0149 // or QWaitCondition::wakeAll(). 0150 // 3.) Lock mutex again. 0151 // 0152 // As explained on the StackOverflow webpage at 0153 // https://stackoverflow.com/questions/40445629 0154 // using QWaitCondition::wait() alone can do spurious 0155 // wake-up (wake-up without a reason). To prevent the 0156 // rendering from continuing in this case, we put the 0157 // QWaitCondition::wait() call into a while loop. This way 0158 // we can go back to sleep if the wake-up was without 0159 // reason (this is checked by the condition in the while loop). 0160 m_loopCondition.wait(&m_loopMutex); 0161 } 0162 m_loopRestart = false; 0163 m_loopMutex.unlock(); 0164 } 0165 } 0166 0167 /** @brief Deliver the result of an interlacing pass of 0168 * the <em>rendering</em> operation. 0169 * 0170 * This function is thread-safe. 0171 * 0172 * @param image The image 0173 * @param parameters The parameters of the image 0174 * @param state The interlacing state of the image. A render function 0175 * must first return zero or more images with intermediate state. After 0176 * that, it must return exactly one image with final state (unless it 0177 * was aborted). After that, it must not return any more images. */ 0178 void AsyncImageRenderThread::deliverInterlacingPass(const QImage &image, const QVariant ¶meters, const AsyncImageRenderCallback::InterlacingState state) 0179 { 0180 // interlacingPassCompleted() is documented as being possibly emitted 0181 // by different threads, so this call is thread-safe within the 0182 // restrictions mentioned in the documentation. 0183 Q_EMIT interlacingPassCompleted(image, parameters, state); 0184 } 0185 0186 /** @brief If the render function should abort. 0187 * 0188 * This function is thread-safe. 0189 * 0190 * @returns <tt>true</tt> if the render function should abort (and 0191 * return). <tt>false</tt> otherwise. 0192 * 0193 * @internal 0194 * 0195 * @sa @ref m_renderFunction */ 0196 bool AsyncImageRenderThread::shouldAbort() const 0197 { 0198 // m_abortRun and m_restart are atomic, so this call is thread-safe. 0199 return (m_loopAbort || m_loopRestart); 0200 } 0201 0202 /** @brief Wait until the render thread is idle. */ 0203 void AsyncImageRenderThread::waitForIdle() 0204 { 0205 m_syncMutex.lock(); 0206 while (!m_syncIsIdle) { 0207 // QWaitCondition::wait() does the following things: 0208 // 1.) Unlock mutex. 0209 // 2.) Wait until another thread calls QWaitCondition::wakeOne() 0210 // or QWaitCondition::wakeAll(). 0211 // 3.) Lock mutex again. 0212 // 0213 // As explained on the StackOverflow webpage at 0214 // https://stackoverflow.com/questions/40445629 0215 // using QWaitCondition::wait() alone can do spurious 0216 // wake-up (wake-up without a reason). To prevent the 0217 // rendering from continuing in this case, we put the 0218 // QWaitCondition::wait() call into a while loop. This way 0219 // we can go back to sleep if the wake-up was without 0220 // reason (this is checked by the condition in the while loop). 0221 m_syncCondition.wait(&m_syncMutex); 0222 } 0223 m_syncMutex.unlock(); 0224 } 0225 0226 } // namespace PerceptualColor