mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-09 03: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 = new QTimer(this);
|
||||||
rewindBufferingTimer->setSingleShot(true);
|
rewindBufferingTimer->setSingleShot(true);
|
||||||
connect(rewindBufferingTimer, &QTimer::timeout, this, &ReplayManager::processRewind);
|
connect(rewindBufferingTimer, &QTimer::timeout, this, [this] { processRewind(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
ReplayManager::~ReplayManager()
|
ReplayManager::~ReplayManager()
|
||||||
|
|
@ -65,7 +65,7 @@ ReplayManager::~ReplayManager()
|
||||||
delete replay;
|
delete replay;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ReplayManager::skipToTime(int newTime, bool doRewindBuffering)
|
void ReplayManager::skipToTime(int newTime, bool doRewindBuffering, bool includeEventsAtTime)
|
||||||
{
|
{
|
||||||
// check boundary conditions
|
// check boundary conditions
|
||||||
if (newTime < 0) {
|
if (newTime < 0) {
|
||||||
|
|
@ -75,15 +75,17 @@ void ReplayManager::skipToTime(int newTime, bool doRewindBuffering)
|
||||||
newTime = maxTime;
|
newTime = maxTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
newTime -= newTime % TIMER_INTERVAL_MS; // Time should always be a multiple of the interval
|
if (!includeEventsAtTime) {
|
||||||
|
newTime -= newTime % TIMER_INTERVAL_MS; // Time should always be a multiple of the interval
|
||||||
|
}
|
||||||
|
|
||||||
const bool isBackwardsSkip = newTime < currentProcessedTime;
|
const bool isBackwardsSkip = newTime < currentProcessedTime;
|
||||||
currentVisualTime = newTime;
|
currentVisualTime = newTime;
|
||||||
|
|
||||||
if (isBackwardsSkip) {
|
if (isBackwardsSkip) {
|
||||||
handleBackwardsSkip(doRewindBuffering);
|
handleBackwardsSkip(doRewindBuffering, includeEventsAtTime);
|
||||||
} else {
|
} else {
|
||||||
processNewEvents(FORWARD_SKIP);
|
processNewEvents(FORWARD_SKIP, includeEventsAtTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
timeChanged(currentVisualTime);
|
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
|
* @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.
|
* 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) {
|
if (doRewindBuffering) {
|
||||||
// We use a one-shot timer to implement the rewind buffering.
|
// 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());
|
rewindBufferingTimer->start(SettingsCache::instance().userInterface().getRewindBufferingMs());
|
||||||
} else {
|
} else {
|
||||||
// otherwise, process the rewind immediately
|
// otherwise, process the rewind immediately
|
||||||
processRewind();
|
processRewind(includeEventsAtTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ReplayManager::processRewind()
|
void ReplayManager::processRewind(bool includeEventsAtTime)
|
||||||
{
|
{
|
||||||
// stop any queued-up rewinds
|
// stop any queued-up rewinds
|
||||||
rewindBufferingTimer->stop();
|
rewindBufferingTimer->stop();
|
||||||
|
|
@ -117,7 +119,7 @@ void ReplayManager::processRewind()
|
||||||
// process the rewind
|
// process the rewind
|
||||||
currentEvent = 0;
|
currentEvent = 0;
|
||||||
emit rewound();
|
emit rewound();
|
||||||
processNewEvents(BACKWARD_SKIP);
|
processNewEvents(BACKWARD_SKIP, includeEventsAtTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ReplayManager::replayTimerTimeout()
|
void ReplayManager::replayTimerTimeout()
|
||||||
|
|
@ -134,11 +136,13 @@ void ReplayManager::replayTimerTimeout()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @brief Processes all unprocessed events up to the current time. */
|
/** @brief Processes all unprocessed events up to the current time. */
|
||||||
void ReplayManager::processNewEvents(PlaybackMode playbackMode)
|
void ReplayManager::processNewEvents(PlaybackMode playbackMode, bool includeEventsAtTime)
|
||||||
{
|
{
|
||||||
currentProcessedTime = currentVisualTime;
|
currentProcessedTime = currentVisualTime;
|
||||||
|
|
||||||
while (currentEvent < replayTimeline.size() && replayTimeline[currentEvent] < currentProcessedTime) {
|
while (currentEvent < replayTimeline.size() &&
|
||||||
|
(replayTimeline[currentEvent] < currentProcessedTime ||
|
||||||
|
(includeEventsAtTime && replayTimeline[currentEvent] == currentProcessedTime))) {
|
||||||
EventProcessingOptions options;
|
EventProcessingOptions options;
|
||||||
|
|
||||||
// backwards skip => always skip reveal windows
|
// backwards skip => always skip reveal windows
|
||||||
|
|
@ -236,6 +240,24 @@ void ReplayManager::setTime(int time)
|
||||||
skipToTime(time, false);
|
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)
|
void ReplayManager::skipByAmount(int amount)
|
||||||
{
|
{
|
||||||
skipToTime(currentVisualTime + amount, amount < 0);
|
skipToTime(currentVisualTime + amount, amount < 0);
|
||||||
|
|
|
||||||
|
|
@ -40,10 +40,10 @@ class ReplayManager : public QObject
|
||||||
///< to rewind buffering
|
///< to rewind buffering
|
||||||
int currentEvent = 0; ///< current event's index
|
int currentEvent = 0; ///< current event's index
|
||||||
|
|
||||||
void skipToTime(int newTime, bool doRewindBuffering);
|
void skipToTime(int newTime, bool doRewindBuffering, bool includeEventsAtTime = false);
|
||||||
void handleBackwardsSkip(bool doRewindBuffering);
|
void handleBackwardsSkip(bool doRewindBuffering, bool includeEventsAtTime = false);
|
||||||
void processRewind();
|
void processRewind(bool includeEventsAtTime = false);
|
||||||
void processNewEvents(PlaybackMode playbackMode);
|
void processNewEvents(PlaybackMode playbackMode, bool includeEventsAtTime = false);
|
||||||
void handleSkipEmptySection();
|
void handleSkipEmptySection();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
|
@ -73,6 +73,7 @@ public slots:
|
||||||
void startReplay();
|
void startReplay();
|
||||||
void stopReplay();
|
void stopReplay();
|
||||||
void setTime(int time);
|
void setTime(int time);
|
||||||
|
void setTimeToEvent(int eventIndex);
|
||||||
void skipByAmount(int amount); // use a negative amount to skip backwards
|
void skipByAmount(int amount); // use a negative amount to skip backwards
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ ReplayWidget::ReplayWidget(QWidget *parent, GameReplay *replay)
|
||||||
connect(replayManager, &ReplayManager::eventReplayed, this, &ReplayWidget::eventReplayed);
|
connect(replayManager, &ReplayManager::eventReplayed, this, &ReplayWidget::eventReplayed);
|
||||||
connect(replayManager, &ReplayManager::replayFinished, this, &ReplayWidget::replayFinished);
|
connect(replayManager, &ReplayManager::replayFinished, this, &ReplayWidget::replayFinished);
|
||||||
connect(replayManager, &ReplayManager::rewound, this, &ReplayWidget::rewound);
|
connect(replayManager, &ReplayManager::rewound, this, &ReplayWidget::rewound);
|
||||||
|
connect(replayManager, &ReplayManager::timeChanged, this, &ReplayWidget::timeChanged);
|
||||||
|
|
||||||
// timeline widget
|
// timeline widget
|
||||||
timelineWidget = new ReplayTimelineWidget;
|
timelineWidget = new ReplayTimelineWidget;
|
||||||
|
|
@ -96,6 +97,16 @@ ReplayWidget::ReplayWidget(QWidget *parent, GameReplay *replay)
|
||||||
refreshShortcuts();
|
refreshShortcuts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const QList<int> &ReplayWidget::getReplayTimeline() const
|
||||||
|
{
|
||||||
|
return replayManager->getReplayTimeline();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReplayWidget::seekToEvent(int eventIndex)
|
||||||
|
{
|
||||||
|
replayManager->setTimeToEvent(eventIndex);
|
||||||
|
}
|
||||||
|
|
||||||
void ReplayWidget::replayFinished()
|
void ReplayWidget::replayFinished()
|
||||||
{
|
{
|
||||||
replayPlayButton->setChecked(false);
|
replayPlayButton->setChecked(false);
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
#ifndef REPLAY_WIDGET_H
|
#ifndef REPLAY_WIDGET_H
|
||||||
#define REPLAY_WIDGET_H
|
#define REPLAY_WIDGET_H
|
||||||
|
|
||||||
|
#include <QList>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <libcockatrice/game/player/event_processing_options.h>
|
#include <libcockatrice/game/player/event_processing_options.h>
|
||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
|
|
@ -35,8 +36,14 @@ public:
|
||||||
*/
|
*/
|
||||||
ReplayWidget(QWidget *parent, GameReplay *replay);
|
ReplayWidget(QWidget *parent, GameReplay *replay);
|
||||||
|
|
||||||
|
[[nodiscard]] const QList<int> &getReplayTimeline() const;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void seekToEvent(int eventIndex);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void rewound();
|
void rewound();
|
||||||
|
void timeChanged(int time);
|
||||||
void eventReplayed(const GameEventContainer &cont, EventProcessingOptions options);
|
void eventReplayed(const GameEventContainer &cont, EventProcessingOptions options);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue