Buffer rewinds from backward skips in replays (#5141)

* split event processing to own method

* implement rewind throttling

* don't throttle backward skips from clicks

* use the term 'buffering' instead

* remove initial backup logic; just always buffer shortcut backward skips

* prevent segfault

* turns out you can just reuse the same one-shot timer

* try scaling timeout based on event count

* rewrite timeout calculation code

* fix linker error
This commit is contained in:
RickyRister 2024-11-05 09:45:42 -08:00 committed by GitHub
parent 6652012f4c
commit e43a21866c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 69 additions and 13 deletions

View file

@ -10,6 +10,10 @@ ReplayTimelineWidget::ReplayTimelineWidget(QWidget *parent)
{ {
replayTimer = new QTimer(this); replayTimer = new QTimer(this);
connect(replayTimer, SIGNAL(timeout()), this, SLOT(replayTimerTimeout())); connect(replayTimer, SIGNAL(timeout()), this, SLOT(replayTimerTimeout()));
rewindBufferingTimer = new QTimer(this);
rewindBufferingTimer->setSingleShot(true);
connect(rewindBufferingTimer, &QTimer::timeout, this, &ReplayTimelineWidget::processRewind);
} }
const int ReplayTimelineWidget::binLength = 5000; const int ReplayTimelineWidget::binLength = 5000;
@ -67,10 +71,11 @@ void ReplayTimelineWidget::mousePressEvent(QMouseEvent *event)
#else #else
int newTime = static_cast<int>((qint64)maxTime * (qint64)event->x() / width()); int newTime = static_cast<int>((qint64)maxTime * (qint64)event->x() / width());
#endif #endif
skipToTime(newTime); // don't buffer rewinds from clicks, since clicks usually don't happen fast enough to require buffering
skipToTime(newTime, false);
} }
void ReplayTimelineWidget::skipToTime(int newTime) void ReplayTimelineWidget::skipToTime(int newTime, bool doRewindBuffering)
{ {
// check boundary conditions // check boundary conditions
if (newTime < 0) { if (newTime < 0) {
@ -81,16 +86,53 @@ void ReplayTimelineWidget::skipToTime(int newTime)
} }
newTime -= newTime % 200; // Time should always be a multiple of 200 newTime -= newTime % 200; // Time should always be a multiple of 200
if (newTime < currentTime) {
currentTime = 0; const bool isBackwardsSkip = newTime < currentTime;
currentEvent = 0; currentTime = newTime;
emit rewound();
if (isBackwardsSkip) {
handleBackwardsSkip(doRewindBuffering);
} else {
processNewEvents();
} }
currentTime = newTime - 200; // 200 is added back in replayTimerTimeout
replayTimerTimeout();
update(); update();
} }
/// @param doRewindBuffering When true, if multiple backward skips are made in quick succession, only a single rewind
/// is processed at the end. When false, the backwards skip will always cause an immediate rewind
void ReplayTimelineWidget::handleBackwardsSkip(bool doRewindBuffering)
{
if (doRewindBuffering) {
// We use a one-shot timer to implement the rewind buffering.
// The rewind only happens once the timer runs out.
// If another backwards skip happens, the timer will just get reset instead of rewinding.
rewindBufferingTimer->stop();
rewindBufferingTimer->start(calcRewindBufferingTimeout());
} else {
// otherwise, process the rewind immediately
processRewind();
}
}
/// The timeout scales based on the current event number, up to a limit
int ReplayTimelineWidget::calcRewindBufferingTimeout() const
{
int extraTime = currentEvent / 100;
return std::min(BASE_REWIND_BUFFERING_TIMEOUT_MS + extraTime, MAX_REWIND_BUFFERING_TIMEOUT_MS);
}
void ReplayTimelineWidget::processRewind()
{
// stop any queued-up rewinds
rewindBufferingTimer->stop();
// process the rewind
currentEvent = 0;
emit rewound();
processNewEvents();
}
QSize ReplayTimelineWidget::sizeHint() const QSize ReplayTimelineWidget::sizeHint() const
{ {
return {-1, 50}; return {-1, 50};
@ -104,6 +146,16 @@ QSize ReplayTimelineWidget::minimumSizeHint() const
void ReplayTimelineWidget::replayTimerTimeout() void ReplayTimelineWidget::replayTimerTimeout()
{ {
currentTime += 200; currentTime += 200;
processNewEvents();
if (!(currentTime % 1000))
update();
}
/// Processes all unprocessed events up to the current time.
void ReplayTimelineWidget::processNewEvents()
{
while ((currentEvent < replayTimeline.size()) && (replayTimeline[currentEvent] < currentTime)) { while ((currentEvent < replayTimeline.size()) && (replayTimeline[currentEvent] < currentTime)) {
emit processNextEvent(); emit processNextEvent();
++currentEvent; ++currentEvent;
@ -112,9 +164,6 @@ void ReplayTimelineWidget::replayTimerTimeout()
emit replayFinished(); emit replayFinished();
replayTimer->stop(); replayTimer->stop();
} }
if (!(currentTime % 1000))
update();
} }
void ReplayTimelineWidget::setTimeScaleFactor(qreal _timeScaleFactor) void ReplayTimelineWidget::setTimeScaleFactor(qreal _timeScaleFactor)
@ -135,5 +184,5 @@ void ReplayTimelineWidget::stopReplay()
void ReplayTimelineWidget::skipByAmount(int amount) void ReplayTimelineWidget::skipByAmount(int amount)
{ {
skipToTime(currentTime + amount); skipToTime(currentTime + amount, true);
} }

View file

@ -18,6 +18,9 @@ signals:
private: private:
QTimer *replayTimer; QTimer *replayTimer;
static constexpr int BASE_REWIND_BUFFERING_TIMEOUT_MS = 180;
static constexpr int MAX_REWIND_BUFFERING_TIMEOUT_MS = 280;
QTimer *rewindBufferingTimer;
QList<int> replayTimeline; QList<int> replayTimeline;
QList<int> histogram; QList<int> histogram;
static const int binLength; static const int binLength;
@ -26,7 +29,11 @@ private:
int currentTime; int currentTime;
int currentEvent; int currentEvent;
void skipToTime(int newTime); void skipToTime(int newTime, bool doRewindBuffering);
void handleBackwardsSkip(bool doRewindBuffering);
int calcRewindBufferingTimeout() const;
void processRewind();
void processNewEvents();
private slots: private slots:
void replayTimerTimeout(); void replayTimerTimeout();