scheduleWarmUpFrame method

void scheduleWarmUpFrame()

Schedule a frame to run as soon as possible, rather than waiting for the engine to request a frame in response to a system "Vsync" signal.

This is used during application startup so that the first frame (which is likely to be quite expensive) gets a few extra milliseconds to run.

Locks events dispatching until the scheduled frame has completed.

If a frame has already been scheduled with scheduleFrame or scheduleForcedFrame, this call may delay that frame.

If any scheduled frame has already begun or if another scheduleWarmUpFrame was already called, this call will be ignored.

Prefer scheduleFrame to update the display in normal operation.

Design discussion

The Flutter engine prompts the framework to generate frames when it receives a request from the operating system (known for historical reasons as a vsync). However, this may not happen for several milliseconds after the app starts (or after a hot reload). To make use of the time between when the widget tree is first configured and when the engine requests an update, the framework schedules a warm-up frame.

A warm-up frame may never actually render (as the engine did not request it and therefore does not have a valid context in which to paint), but it will cause the framework to go through the steps of building, laying out, and painting, which can together take several milliseconds. Thus, when the engine requests a real frame, much of the work will already have been completed, and the framework can generate the frame with minimal additional effort.

Warm-up frames are scheduled by runApp on startup, and by RendererBinding.performReassemble during a hot reload.

Warm-up frames are also scheduled when the framework is unblocked by a call to RendererBinding.allowFirstFrame (corresponding to a call to RendererBinding.deferFirstFrame that blocked the rendering).

Implementation

void scheduleWarmUpFrame() {
  if (_warmUpFrame || schedulerPhase != SchedulerPhase.idle) {
    return;
  }

  _warmUpFrame = true;
  TimelineTask? debugTimelineTask;
  if (!kReleaseMode) {
    debugTimelineTask = TimelineTask()..start('Warm-up frame');
  }
  final bool hadScheduledFrame = _hasScheduledFrame;
  // We use timers here to ensure that microtasks flush in between.
  Timer.run(() {
    assert(_warmUpFrame);
    handleBeginFrame(null);
  });
  Timer.run(() {
    assert(_warmUpFrame);
    handleDrawFrame();
    // We call resetEpoch after this frame so that, in the hot reload case,
    // the very next frame pretends to have occurred immediately after this
    // warm-up frame. The warm-up frame's timestamp will typically be far in
    // the past (the time of the last real frame), so if we didn't reset the
    // epoch we would see a sudden jump from the old time in the warm-up frame
    // to the new time in the "real" frame. The biggest problem with this is
    // that implicit animations end up being triggered at the old time and
    // then skipping every frame and finishing in the new time.
    resetEpoch();
    _warmUpFrame = false;
    if (hadScheduledFrame) {
      scheduleFrame();
    }
  });

  // Lock events so touch events etc don't insert themselves until the
  // scheduled frame has finished.
  lockEvents(() async {
    await endOfFrame;
    if (!kReleaseMode) {
      debugTimelineTask!.finish();
    }
  });
}