diff --git a/libs/global/kis_relaxed_timer.h b/libs/global/kis_relaxed_timer.h --- a/libs/global/kis_relaxed_timer.h +++ b/libs/global/kis_relaxed_timer.h @@ -30,13 +30,17 @@ * time guarantees and minimizes internal timer restarts by keeping one long-running * repeating timer. * - * Users can use this just like a QTimer. The difference is that KisRelaxedTimer will - * relax the callback guarantee time as follows: timeouts will never happen earlier - * than \p interval ms, but may well happen only after 2 * \p interval ms (whereas - * QTimer guarantees a fixed interval of \p interval ms). + * Users can use this mostly like a QTimer. The difference is that KisRelaxedTimer will + * relax how precisely the callback happens. Using setInterval(), users of this class + * configure how much the emitted signals are allowed to be "off", i.e. up to how many + * ms a signal may be emitted too early (\p maxOffEarly), and up to how many ms a signal + * may be emitted too late (\p maxOffLate). + * + * Setting \p maxOffLate to \p interval + 1 will allow KisRelaxedTimer to always use the + * long running timer. * * The rationale for using this is that stopping and starting timers can produce a - * measurable performance overhead. KisRelaxedTimer removes that overhead. + * measurable performance overhead. KisRelaxedTimer can reduce that overhead. */ class KRITAGLOBAL_EXPORT KisRelaxedTimer : public QObject { @@ -51,7 +55,7 @@ m_emitOnTimeTick = 0; } - void setInterval(int interval); + void setInterval(int interval, int maxOffEarly, int maxOffLate); void setSingleShot(bool singleShot); inline bool isActive() const { @@ -64,10 +68,14 @@ void timeout(); protected: + void resync(); + void timerEvent(QTimerEvent *event) override; private: int m_interval; + int m_maxOffEarly; + int m_maxOffLate; bool m_singleShot; QBasicTimer m_timer; @@ -75,6 +83,7 @@ qint64 m_emitOnTimeTick; QElapsedTimer m_elapsed; + QElapsedTimer m_tick; protected: class IsEmitting { diff --git a/libs/global/kis_relaxed_timer.cpp b/libs/global/kis_relaxed_timer.cpp --- a/libs/global/kis_relaxed_timer.cpp +++ b/libs/global/kis_relaxed_timer.cpp @@ -28,10 +28,12 @@ { } -void KisRelaxedTimer::setInterval(int interval) +void KisRelaxedTimer::setInterval(int interval, int maxOffEarly, int maxOffLate) { - Q_ASSERT(!isActive()); + Q_ASSERT(!m_timer.isActive()); m_interval = interval; + m_maxOffEarly = maxOffEarly; + m_maxOffLate = maxOffLate; } void KisRelaxedTimer::setSingleShot(bool singleShot) @@ -63,8 +65,7 @@ // us a timeout event on the next possible tick which will be exactly // \p m_interval ms in the future. - m_emitOnTimeTick = m_nextTimerTickSeqNo; - m_timer.start(m_interval, this); + resync(); } else if (m_isEmitting) { // an internal timer is running and we are actually called from a // timeout event. so we know the next tick will happen in exactly @@ -72,15 +73,32 @@ m_emitOnTimeTick = m_nextTimerTickSeqNo; } else { - // an internal timer is already running, but we do not know when - // the next tick will happen. we need to skip next tick as it - // will be sooner than m_delay. the one after that will be good as - // it will be m_interval * (1 + err) in the future. - - m_emitOnTimeTick = m_nextTimerTickSeqNo + 1; + // an internal timer is already running. try to use it if we are + // in the tolerance limits given to us. + const int elapsed = m_tick.elapsed(); + + // how much too early are we if we take m_nextTimerTickSeqNo? + if (elapsed < m_maxOffEarly) { + m_emitOnTimeTick = m_nextTimerTickSeqNo; + } else { + // how much off too late we if we take m_nextTimerTickSeqNo + 1? + if (qMax(0, m_interval - elapsed) < m_maxOffLate) { + m_emitOnTimeTick = m_nextTimerTickSeqNo + 1; + } else { + // we are too much off, need to resync. + resync(); + } + } } } +void KisRelaxedTimer::resync() +{ + m_emitOnTimeTick = m_nextTimerTickSeqNo; + m_timer.start(m_interval, this); + m_tick.start(); +} + void KisRelaxedTimer::timerEvent(QTimerEvent *event) { Q_UNUSED(event); @@ -101,4 +119,6 @@ } else if (timerTickSeqNo - m_emitOnTimeTick > ticksStopThreshold) { m_timer.stop(); } + + m_tick.start(); } diff --git a/libs/global/kis_signal_compressor.cpp b/libs/global/kis_signal_compressor.cpp --- a/libs/global/kis_signal_compressor.cpp +++ b/libs/global/kis_signal_compressor.cpp @@ -16,30 +16,6 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -/** - * KisSignalCompressor will never trigger timeout more often than every \p delay ms, - * i.e. \p delay ms is a given lower limit defining the highest frequency. - * - * The current implementation uses a long-running monitor timer to eliminate the - * overhead incurred by restarting and stopping timers with each signal. The - * consequence of this is that the given \p delay ms is not always exactly followed. - * - * KisSignalCompressor makes the following callback guarantees (0 < err <= 1, with - * err == 0 if this is the first signal after a while): - * - * POSTPONE: - * - timeout after <= (1 + err) * \p delay ms. - * FIRST_ACTIVE_POSTPONE_NEXT: - * - first timeout immediately - * - postponed timeout after (1 + err) * \p delay ms - * FIRST_ACTIVE: - * - first timeout immediately - * - second timeout after (1 + err) * \p delay ms - * - after that: \p delay ms - * FIRST_INACTIVE: - * - timeout after (1 + err) * \p delay ms - */ - #include "kis_signal_compressor.h" #include "kis_relaxed_timer.h" @@ -60,13 +36,15 @@ m_gotSignals(false) { m_timer->setSingleShot(true); - m_timer->setInterval(delay); + setDelay(delay); connect(m_timer, SIGNAL(timeout()), SLOT(slotTimerExpired())); } void KisSignalCompressor::setDelay(int delay) { - m_timer->setInterval(delay); + // allow callbacks to happen 1 ms too early and up to 10% too late. for a + // 10 ms timer, this is 1 ms too late, which should not hurt any use case. + m_timer->setInterval(delay, 1, delay / 10); } void KisSignalCompressor::start()