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 &parameters)
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 &parameters, 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