flutter3d
Showcase Changelog 38 packages API reference

Pausing and stepping a running game

since 0.7.0 Widgets and the rest

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 real RunTimeline is not a dependency of this app, but every type in its body is: RewindBuffer, InputState, InputTapePlayback and Snapshot all come from flutter3d_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.