File indexing completed on 2024-05-12 15:56:59

0001 /*
0002  *  SPDX-FileCopyrightText: 2013 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 /**
0008  * KisSignalCompressor will never trigger timeout more often than every \p delay ms,
0009  * i.e. \p delay ms is a given lower limit defining the highest frequency.
0010  *
0011  * The current implementation uses a long-running monitor timer to eliminate the
0012  * overhead incurred by restarting and stopping timers with each signal. The
0013  * consequence of this is that the given \p delay ms is not always exactly followed.
0014  *
0015  * KisSignalCompressor makes the following callback guarantees (0 < err <= 1, with
0016  * err == 0 if this is the first signal after a while):
0017  *
0018  * POSTPONE:
0019  *   - timeout after = [0.5 ... 1.0] * \p delay ms.
0020  * FIRST_ACTIVE_POSTPONE_NEXT:
0021  *   - first timeout immediately
0022  *   - postponed timeout after [0.5 ... 1.0] * \p delay ms
0023  * FIRST_ACTIVE:
0024  *   - first timeout immediately
0025  *   - after that [0.5 ... 1.5] * \p delay ms
0026   * FIRST_INACTIVE:
0027  *   - timeout after [0.5 ... 1.5] * \p delay ms
0028  */
0029 
0030 #include "kis_signal_compressor.h"
0031 
0032 #include <QTimer>
0033 #include "kis_assert.h"
0034 #include "kis_debug.h"
0035 
0036 
0037 KisSignalCompressor::KisSignalCompressor()
0038     : QObject(0)
0039     , m_timer(new QTimer(this))
0040 {
0041     m_timer->setSingleShot(false);
0042     connect(m_timer, SIGNAL(timeout()), SLOT(slotTimerExpired()));
0043 }
0044 
0045 KisSignalCompressor::KisSignalCompressor(int delay, Mode mode, QObject *parent)
0046     : KisSignalCompressor(delay, mode, PRECISE_INTERVAL, parent)
0047 {
0048 }
0049 
0050 KisSignalCompressor::KisSignalCompressor(int delay, Mode mode, SlowHandlerMode slowHandlerMode, QObject *parent)
0051     : QObject(parent),
0052       m_timer(new QTimer(this)),
0053       m_mode(mode),
0054       m_slowHandlerMode(slowHandlerMode),
0055       m_timeout(delay)
0056 {
0057     m_timer->setSingleShot(false);
0058     m_timer->setInterval(delay);
0059     connect(m_timer, SIGNAL(timeout()), SLOT(slotTimerExpired()));
0060 }
0061 
0062 void KisSignalCompressor::setDelayImpl(int delay)
0063 {
0064     const bool wasActive = m_timer->isActive();
0065 
0066     if (wasActive) {
0067         m_timer->stop();
0068     }
0069 
0070     m_timer->setInterval(delay);
0071 
0072     if (wasActive) {
0073         m_timer->start();
0074     }
0075 }
0076 
0077 void KisSignalCompressor::setDelay(int delay)
0078 {
0079     m_timeout = delay;
0080     m_idleCallback = {};
0081     setDelayImpl(delay);
0082 }
0083 
0084 void KisSignalCompressor::setDelay(std::function<bool ()> idleCallback, int idleDelay, int timeout)
0085 {
0086     m_timeout = timeout;
0087     m_idleCallback = idleCallback;
0088     setDelayImpl(idleDelay);
0089 }
0090 
0091 void KisSignalCompressor::start()
0092 {
0093     KIS_SAFE_ASSERT_RECOVER_RETURN(m_mode != UNDEFINED);
0094 
0095     const bool isFirstStart = !m_timer->isActive();
0096 
0097     KIS_SAFE_ASSERT_RECOVER_NOOP(!isFirstStart || !m_signalsPending);
0098 
0099     switch (m_mode) {
0100     case POSTPONE:
0101         if (isFirstStart) {
0102             m_timer->start();
0103         }
0104         m_lastEmittedTimer.restart();
0105         m_signalsPending = true;
0106         break;
0107     case FIRST_ACTIVE_POSTPONE_NEXT:
0108     case FIRST_ACTIVE:
0109         if (isFirstStart) {
0110             m_timer->start();
0111             if (m_slowHandlerMode == PRECISE_INTERVAL) {
0112                 m_lastEmittedTimer.restart();
0113             }
0114             m_signalsPending = false;
0115             if (!tryEmitSignalSafely()) {
0116                 m_signalsPending = true;
0117             }
0118             if (m_slowHandlerMode == ADDITIVE_INTERVAL) {
0119                 m_lastEmittedTimer.restart();
0120             }
0121         } else {
0122             if (m_mode == FIRST_ACTIVE) {
0123                 m_signalsPending = true;
0124                 tryEmitOnTick(false);
0125             } else {
0126                 m_lastEmittedTimer.restart();
0127                 m_signalsPending = true;
0128             }
0129         }
0130         break;
0131     case FIRST_INACTIVE:
0132         if (isFirstStart) {
0133             m_timer->start();
0134             m_lastEmittedTimer.restart();
0135             m_signalsPending = true;
0136         } else {
0137             m_signalsPending = true;
0138             tryEmitOnTick(false);
0139         }
0140     case UNDEFINED:
0141         ; // Should never happen, but do nothing
0142     };
0143 
0144     KIS_SAFE_ASSERT_RECOVER(m_timer->isActive()) {
0145         m_timer->start();
0146     }
0147 }
0148 
0149 bool KisSignalCompressor::tryEmitOnTick(bool isFromTimer)
0150 {
0151     bool wasEmitted = false;
0152 
0153     // we have different requirements for hi-frequency events (the mean
0154     // of the events rate must be min(compressorRate, eventsRate)
0155     const int realInterval = m_timeout;
0156     const int minInterval = realInterval < 100 ? 0.5 * realInterval : realInterval;
0157 
0158     // Enable for debugging:
0159     // ENTER_FUNCTION() << ppVar(isFromTimer) << ppVar(m_signalsPending) << m_lastEmittedTimer.elapsed() << ppVar((m_idleCallback && m_idleCallback()));
0160 
0161     if (m_signalsPending &&
0162             (m_lastEmittedTimer.elapsed() >= minInterval ||
0163              (m_idleCallback && m_idleCallback()))) {
0164 
0165         KIS_SAFE_ASSERT_RECOVER_NOOP(!isFromTimer || !m_isEmitting);
0166 
0167         if (m_slowHandlerMode == PRECISE_INTERVAL) {
0168             m_lastEmittedTimer.start();
0169         }
0170 
0171         m_signalsPending = false;
0172         if (!tryEmitSignalSafely()) {
0173             m_signalsPending = true;
0174         }
0175 
0176         if (m_slowHandlerMode == ADDITIVE_INTERVAL) {
0177             m_lastEmittedTimer.start();
0178         }
0179 
0180         wasEmitted = true;
0181     } else if (!isFromTimer) {
0182         m_signalsPending = true;
0183     }
0184 
0185     return wasEmitted;
0186 }
0187 
0188 bool KisSignalCompressor::tryEmitSignalSafely()
0189 {
0190     bool wasEmitted = false;
0191 
0192     m_isEmitting++;
0193 
0194     if (m_isEmitting == 1) {
0195         emit timeout();
0196         wasEmitted = true;
0197     }
0198 
0199     m_isEmitting--;
0200 
0201     return wasEmitted;
0202 }
0203 
0204 void KisSignalCompressor::slotTimerExpired()
0205 {
0206     KIS_ASSERT_RECOVER_NOOP(m_mode != UNDEFINED);
0207     if (!tryEmitOnTick(true)) {
0208         const int calmDownInterval = 5 * m_timeout;
0209 
0210         if (!m_lastEmittedTimer.isValid() ||
0211             m_lastEmittedTimer.elapsed() > calmDownInterval) {
0212 
0213             m_timer->stop();
0214         }
0215     }
0216 }
0217 
0218 void KisSignalCompressor::stop()
0219 {
0220     m_timer->stop();
0221     m_signalsPending = false;
0222     m_lastEmittedTimer.invalidate();
0223 }
0224 
0225 bool KisSignalCompressor::isActive() const
0226 {
0227     return m_signalsPending && m_timer->isActive();
0228 }
0229 
0230 void KisSignalCompressor::setMode(KisSignalCompressor::Mode mode)
0231 {
0232     m_mode = mode;
0233 }
0234 
0235 int KisSignalCompressor::delay() const
0236 {
0237     return m_timeout;
0238 }