Render the warm up frame in a proper rendering process (#143290)

_This PR requires https://github.com/flutter/engine/pull/50570._

This PR uses the new `PlatformDispatcher.scheduleWarmUpFrame` API to render warm up frames.

For why the warm up frame must involve the engine to render, see https://github.com/flutter/flutter/issues/142851.
This commit is contained in:
Tong Mu 2024-02-23 13:30:14 -08:00 committed by GitHub
parent 01963ea21a
commit be2544ab59
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 62 additions and 22 deletions

View file

@ -1025,28 +1025,29 @@ mixin SchedulerBinding on BindingBase {
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();
}
});
PlatformDispatcher.instance.scheduleWarmUpFrame(
beginFrame: () {
assert(_warmUpFrame);
handleBeginFrame(null);
},
drawFrame: () {
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.

View file

@ -14,6 +14,21 @@ import 'scheduler_tester.dart';
class TestSchedulerBinding extends BindingBase with SchedulerBinding, ServicesBinding {
final Map<String, List<Map<String, dynamic>>> eventsDispatched = <String, List<Map<String, dynamic>>>{};
VoidCallback? additionalHandleBeginFrame;
VoidCallback? additionalHandleDrawFrame;
@override
void handleBeginFrame(Duration? rawTimeStamp) {
additionalHandleBeginFrame?.call();
super.handleBeginFrame(rawTimeStamp);
}
@override
void handleDrawFrame() {
additionalHandleDrawFrame?.call();
super.handleDrawFrame();
}
@override
void postEvent(String eventKind, Map<String, dynamic> eventData) {
getEventsDispatched(eventKind).add(eventData);
@ -39,6 +54,11 @@ void main() {
scheduler = TestSchedulerBinding();
});
tearDown(() {
scheduler.additionalHandleBeginFrame = null;
scheduler.additionalHandleDrawFrame = null;
});
test('Tasks are executed in the right order', () {
final TestStrategy strategy = TestStrategy();
scheduler.schedulingStrategy = strategy.shouldRunTaskWithPriority;
@ -111,6 +131,25 @@ void main() {
expect(executedTasks[0], equals(0));
});
test('scheduleWarmUpFrame should flush microtasks between callbacks', () async {
addTearDown(() => scheduler.handleEventLoopCallback());
bool microtaskDone = false;
final Completer<void> drawFrameDone = Completer<void>();
scheduler.additionalHandleBeginFrame = () {
expect(microtaskDone, false);
scheduleMicrotask(() {
microtaskDone = true;
});
};
scheduler.additionalHandleDrawFrame = () {
expect(microtaskDone, true);
drawFrameDone.complete();
};
scheduler.scheduleWarmUpFrame();
await drawFrameDone.future;
});
test('2 calls to scheduleWarmUpFrame just schedules it once', () {
final List<VoidCallback> timerQueueTasks = <VoidCallback>[];
bool taskExecuted = false;