Pausing and stepping a running game
Open the live demo · Read the source · View on GitHub
RunTimeline is the mechanism underneath an editor's transport controls:
pause, step one fixed frame at a time, preview a rewind before committing to
it, and release into the past so the run continues from there instead of
snapping back.
Note.
flutter3d_game's realRunTimelineis not a dependency of this app, but every type in its body is:RewindBuffer,InputState,InputTapePlaybackandSnapshotall come fromflutter3d_sim. This page carries the class over whole rather than approximating it.
Step 1: The timeline itself #
/// Pause, step and rewind, over a live [RewindBuffer] — carried over from
/// `flutter3d_game`'s real class, whose whole body is `flutter3d_sim` types.
final class _RunTimeline {
_RunTimeline({
required this.rewind,
required this.input,
required this.stepSim,
required this.restore,
this.stepSeconds = 1.0 / 60.0,
});
final RewindBuffer rewind;
final InputState input;
final void Function(double dt) stepSim;
final void Function(Snapshot snapshot) restore;
final double stepSeconds;
bool _paused = false;
bool get isPaused => _paused;
void pause() => _paused = true;
void resume() => _paused = false;
void stepOnce() {
if (!_paused) {
throw StateError('stepOnce is only valid while the timeline is paused');
}
stepSim(stepSeconds);
}
RewindPoint? preview(double secondsAgo) => rewind.rewindBy(secondsAgo);
void releaseAt(RewindPoint point) {
restore(point.snapshot);
final toPoint = InputTapePlayback(point.tapeToPoint);
while (!toPoint.isFinished) {
toPoint.applyTo(input);
stepSim(stepSeconds);
}
rewind.cut(point);
_paused = false;
}
}
Step 2: Play normally, then pause and step by hand #
Twenty ordinary steps, recorded the way a game loop already records every step for the rewind buffer.
// Twenty steps of ordinary play, recorded the way a game loop already
// records every step for the rewind buffer.
for (var i = 0; i < 20; i++) {
buffer.recorder.tape.frames.add(InputFrame(stickX: 1.0));
if (buffer.keyframeDue) {
buffer.keyframe(Snapshot(<String, Object?>{'x': x}));
}
timeline.stepSim(1 / 10);
}
final beforePause = x;
Paused, stepOnce advances the simulation by exactly one fixed step —
refused if the timeline is not paused, since a step taken on a running
timeline would be a second step nobody asked for.
timeline.pause();
timeline.stepOnce();
final afterOneStep = x;
Step 3: Rewind and release #
final point = timeline.preview(1.0)!;
timeline.releaseAt(point);
final afterRelease = x;
Rewinding one second on a buffer stepping ten times a second lands on the recorded state ten steps back — not on the manual step from the previous section, because that step was never written to the tape. Releasing leaves the timeline running, not paused: a release is asking to keep playing from here.