mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-05 01:23:24 -08:00
[Replay] Seek to the exact time of an event (#7610)
Timeline seeks only expose whole timer intervals, so jumping to an event could leave that event unprocessed until the next tick. Add an event-time seek that also applies events at the target timestamp. Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
parent
624163916f
commit
81a0b97240
4 changed files with 56 additions and 15 deletions
|
|
@ -57,7 +57,7 @@ ReplayManager::ReplayManager(QObject *parent, GameReplay *replay)
|
|||
|
||||
rewindBufferingTimer = new QTimer(this);
|
||||
rewindBufferingTimer->setSingleShot(true);
|
||||
connect(rewindBufferingTimer, &QTimer::timeout, this, &ReplayManager::processRewind);
|
||||
connect(rewindBufferingTimer, &QTimer::timeout, this, [this] { processRewind(); });
|
||||
}
|
||||
|
||||
ReplayManager::~ReplayManager()
|
||||
|
|
@ -65,7 +65,7 @@ ReplayManager::~ReplayManager()
|
|||
delete replay;
|
||||
}
|
||||
|
||||
void ReplayManager::skipToTime(int newTime, bool doRewindBuffering)
|
||||
void ReplayManager::skipToTime(int newTime, bool doRewindBuffering, bool includeEventsAtTime)
|
||||
{
|
||||
// check boundary conditions
|
||||
if (newTime < 0) {
|
||||
|
|
@ -75,15 +75,17 @@ void ReplayManager::skipToTime(int newTime, bool doRewindBuffering)
|
|||
newTime = maxTime;
|
||||
}
|
||||
|
||||
if (!includeEventsAtTime) {
|
||||
newTime -= newTime % TIMER_INTERVAL_MS; // Time should always be a multiple of the interval
|
||||
}
|
||||
|
||||
const bool isBackwardsSkip = newTime < currentProcessedTime;
|
||||
currentVisualTime = newTime;
|
||||
|
||||
if (isBackwardsSkip) {
|
||||
handleBackwardsSkip(doRewindBuffering);
|
||||
handleBackwardsSkip(doRewindBuffering, includeEventsAtTime);
|
||||
} else {
|
||||
processNewEvents(FORWARD_SKIP);
|
||||
processNewEvents(FORWARD_SKIP, includeEventsAtTime);
|
||||
}
|
||||
|
||||
timeChanged(currentVisualTime);
|
||||
|
|
@ -95,7 +97,7 @@ void ReplayManager::skipToTime(int newTime, bool doRewindBuffering)
|
|||
* @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 ReplayManager::handleBackwardsSkip(bool doRewindBuffering)
|
||||
void ReplayManager::handleBackwardsSkip(bool doRewindBuffering, bool includeEventsAtTime)
|
||||
{
|
||||
if (doRewindBuffering) {
|
||||
// We use a one-shot timer to implement the rewind buffering.
|
||||
|
|
@ -105,11 +107,11 @@ void ReplayManager::handleBackwardsSkip(bool doRewindBuffering)
|
|||
rewindBufferingTimer->start(SettingsCache::instance().userInterface().getRewindBufferingMs());
|
||||
} else {
|
||||
// otherwise, process the rewind immediately
|
||||
processRewind();
|
||||
processRewind(includeEventsAtTime);
|
||||
}
|
||||
}
|
||||
|
||||
void ReplayManager::processRewind()
|
||||
void ReplayManager::processRewind(bool includeEventsAtTime)
|
||||
{
|
||||
// stop any queued-up rewinds
|
||||
rewindBufferingTimer->stop();
|
||||
|
|
@ -117,7 +119,7 @@ void ReplayManager::processRewind()
|
|||
// process the rewind
|
||||
currentEvent = 0;
|
||||
emit rewound();
|
||||
processNewEvents(BACKWARD_SKIP);
|
||||
processNewEvents(BACKWARD_SKIP, includeEventsAtTime);
|
||||
}
|
||||
|
||||
void ReplayManager::replayTimerTimeout()
|
||||
|
|
@ -134,11 +136,13 @@ void ReplayManager::replayTimerTimeout()
|
|||
}
|
||||
|
||||
/** @brief Processes all unprocessed events up to the current time. */
|
||||
void ReplayManager::processNewEvents(PlaybackMode playbackMode)
|
||||
void ReplayManager::processNewEvents(PlaybackMode playbackMode, bool includeEventsAtTime)
|
||||
{
|
||||
currentProcessedTime = currentVisualTime;
|
||||
|
||||
while (currentEvent < replayTimeline.size() && replayTimeline[currentEvent] < currentProcessedTime) {
|
||||
while (currentEvent < replayTimeline.size() &&
|
||||
(replayTimeline[currentEvent] < currentProcessedTime ||
|
||||
(includeEventsAtTime && replayTimeline[currentEvent] == currentProcessedTime))) {
|
||||
EventProcessingOptions options;
|
||||
|
||||
// backwards skip => always skip reveal windows
|
||||
|
|
@ -236,6 +240,24 @@ void ReplayManager::setTime(int time)
|
|||
skipToTime(time, false);
|
||||
}
|
||||
|
||||
void ReplayManager::setTimeToEvent(int eventIndex)
|
||||
{
|
||||
if (eventIndex < 0 || eventIndex >= replayTimeline.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Flush a pending buffered rewind before seeking, otherwise its timer fires after this seek and rebuilds the
|
||||
// replay state from an outdated currentVisualTime, dropping the event we seek to. Stopping the timer instead
|
||||
// of processing it is not enough: playback ticks advance currentProcessedTime without rewinding currentEvent,
|
||||
// so the board could still be ahead of the target.
|
||||
if (rewindBufferingTimer->isActive()) {
|
||||
processRewind();
|
||||
}
|
||||
|
||||
// Unlike a timeline seek, jumping to an event should apply that event immediately.
|
||||
skipToTime(replayTimeline.at(eventIndex), false, true);
|
||||
}
|
||||
|
||||
void ReplayManager::skipByAmount(int amount)
|
||||
{
|
||||
skipToTime(currentVisualTime + amount, amount < 0);
|
||||
|
|
|
|||
|
|
@ -40,10 +40,10 @@ class ReplayManager : public QObject
|
|||
///< to rewind buffering
|
||||
int currentEvent = 0; ///< current event's index
|
||||
|
||||
void skipToTime(int newTime, bool doRewindBuffering);
|
||||
void handleBackwardsSkip(bool doRewindBuffering);
|
||||
void processRewind();
|
||||
void processNewEvents(PlaybackMode playbackMode);
|
||||
void skipToTime(int newTime, bool doRewindBuffering, bool includeEventsAtTime = false);
|
||||
void handleBackwardsSkip(bool doRewindBuffering, bool includeEventsAtTime = false);
|
||||
void processRewind(bool includeEventsAtTime = false);
|
||||
void processNewEvents(PlaybackMode playbackMode, bool includeEventsAtTime = false);
|
||||
void handleSkipEmptySection();
|
||||
|
||||
private slots:
|
||||
|
|
@ -73,6 +73,7 @@ public slots:
|
|||
void startReplay();
|
||||
void stopReplay();
|
||||
void setTime(int time);
|
||||
void setTimeToEvent(int eventIndex);
|
||||
void skipByAmount(int amount); // use a negative amount to skip backwards
|
||||
|
||||
signals:
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ ReplayWidget::ReplayWidget(QWidget *parent, GameReplay *replay)
|
|||
connect(replayManager, &ReplayManager::eventReplayed, this, &ReplayWidget::eventReplayed);
|
||||
connect(replayManager, &ReplayManager::replayFinished, this, &ReplayWidget::replayFinished);
|
||||
connect(replayManager, &ReplayManager::rewound, this, &ReplayWidget::rewound);
|
||||
connect(replayManager, &ReplayManager::timeChanged, this, &ReplayWidget::timeChanged);
|
||||
|
||||
// timeline widget
|
||||
timelineWidget = new ReplayTimelineWidget;
|
||||
|
|
@ -96,6 +97,16 @@ ReplayWidget::ReplayWidget(QWidget *parent, GameReplay *replay)
|
|||
refreshShortcuts();
|
||||
}
|
||||
|
||||
const QList<int> &ReplayWidget::getReplayTimeline() const
|
||||
{
|
||||
return replayManager->getReplayTimeline();
|
||||
}
|
||||
|
||||
void ReplayWidget::seekToEvent(int eventIndex)
|
||||
{
|
||||
replayManager->setTimeToEvent(eventIndex);
|
||||
}
|
||||
|
||||
void ReplayWidget::replayFinished()
|
||||
{
|
||||
replayPlayButton->setChecked(false);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#ifndef REPLAY_WIDGET_H
|
||||
#define REPLAY_WIDGET_H
|
||||
|
||||
#include <QList>
|
||||
#include <QWidget>
|
||||
#include <libcockatrice/game/player/event_processing_options.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
|
@ -35,8 +36,14 @@ public:
|
|||
*/
|
||||
ReplayWidget(QWidget *parent, GameReplay *replay);
|
||||
|
||||
[[nodiscard]] const QList<int> &getReplayTimeline() const;
|
||||
|
||||
public slots:
|
||||
void seekToEvent(int eventIndex);
|
||||
|
||||
signals:
|
||||
void rewound();
|
||||
void timeChanged(int time);
|
||||
void eventReplayed(const GameEventContainer &cont, EventProcessingOptions options);
|
||||
|
||||
private:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue