Reschedule engine frame if it arrives in the middle of warm-up (#72115)
* Reschedule engine frame if it arrives in the middle of warm-up * user post-frame callback instead
This commit is contained in:
parent
795b23ef44
commit
67908dfb80
@ -954,20 +954,45 @@ mixin SchedulerBinding on BindingBase {
|
||||
|
||||
int _debugFrameNumber = 0;
|
||||
String? _debugBanner;
|
||||
bool _ignoreNextEngineDrawFrame = false;
|
||||
|
||||
// Whether the current engine frame needs to be postponed till after the
|
||||
// warm-up frame.
|
||||
//
|
||||
// Engine may begin a frame in the middle of the warm-up frame because the
|
||||
// warm-up frame is scheduled by timers while the engine frame is scheduled
|
||||
// by platform specific frame scheduler (e.g. `requestAnimationFrame` on the
|
||||
// web). When this happens, we let the warm-up frame finish, and postpone the
|
||||
// engine frame.
|
||||
bool _rescheduleAfterWarmUpFrame = false;
|
||||
|
||||
void _handleBeginFrame(Duration rawTimeStamp) {
|
||||
if (_warmUpFrame) {
|
||||
assert(!_ignoreNextEngineDrawFrame);
|
||||
_ignoreNextEngineDrawFrame = true;
|
||||
// "begin frame" and "draw frame" must strictly alternate. Therefore
|
||||
// _rescheduleAfterWarmUpFrame cannot possibly be true here as it is
|
||||
// reset by _handleDrawFrame.
|
||||
assert(!_rescheduleAfterWarmUpFrame);
|
||||
_rescheduleAfterWarmUpFrame = true;
|
||||
return;
|
||||
}
|
||||
handleBeginFrame(rawTimeStamp);
|
||||
}
|
||||
|
||||
void _handleDrawFrame() {
|
||||
if (_ignoreNextEngineDrawFrame) {
|
||||
_ignoreNextEngineDrawFrame = false;
|
||||
if (_rescheduleAfterWarmUpFrame) {
|
||||
_rescheduleAfterWarmUpFrame = false;
|
||||
// Reschedule in a post-frame callback to allow the draw-frame phase of
|
||||
// the warm-up frame to finish.
|
||||
addPostFrameCallback((Duration timeStamp) {
|
||||
// Force an engine frame.
|
||||
//
|
||||
// We need to reset _hasScheduledFrame here because we cancelled the
|
||||
// original engine frame, and therefore did not run handleBeginFrame
|
||||
// who is responsible for resetting it. So if a frame callback set this
|
||||
// to true in the "begin frame" part of the warm-up frame, it will
|
||||
// still be true here and cause us to skip scheduling an engine frame.
|
||||
_hasScheduledFrame = false;
|
||||
scheduleFrame();
|
||||
});
|
||||
return;
|
||||
}
|
||||
handleDrawFrame();
|
||||
|
@ -129,6 +129,11 @@ void main() {
|
||||
// events are locked.
|
||||
expect(timerQueueTasks.length, 2);
|
||||
expect(taskExecuted, false);
|
||||
|
||||
// Run the timers so that the scheduler is no longer in warm-up state.
|
||||
for (final VoidCallback timer in timerQueueTasks) {
|
||||
timer();
|
||||
}
|
||||
});
|
||||
|
||||
test('Flutter.Frame event fired', () async {
|
||||
@ -165,6 +170,9 @@ void main() {
|
||||
});
|
||||
|
||||
test('currentSystemFrameTimeStamp is the raw timestamp', () {
|
||||
// Undo epoch set by previous tests.
|
||||
scheduler.resetEpoch();
|
||||
|
||||
late Duration lastTimeStamp;
|
||||
late Duration lastSystemTimeStamp;
|
||||
|
||||
@ -195,6 +203,40 @@ void main() {
|
||||
expect(lastTimeStamp, const Duration(seconds: 3)); // 2s + (8 - 6)s / 2
|
||||
expect(lastSystemTimeStamp, const Duration(seconds: 8));
|
||||
});
|
||||
|
||||
test('Animation frame scheduled in the middle of the warm-up frame', () {
|
||||
expect(scheduler.schedulerPhase, SchedulerPhase.idle);
|
||||
final List<VoidCallback> timers = <VoidCallback>[];
|
||||
final ZoneSpecification timerInterceptor = ZoneSpecification(
|
||||
createTimer: (Zone self, ZoneDelegate parent, Zone zone, Duration duration, void Function() callback) {
|
||||
timers.add(callback);
|
||||
return DummyTimer();
|
||||
},
|
||||
);
|
||||
|
||||
// Schedule a warm-up frame.
|
||||
// Expect two timers, one for begin frame, and one for draw frame.
|
||||
runZoned<void>(scheduler.scheduleWarmUpFrame, zoneSpecification: timerInterceptor);
|
||||
expect(timers.length, 2);
|
||||
final VoidCallback warmUpBeginFrame = timers.first;
|
||||
final VoidCallback warmUpDrawFrame = timers.last;
|
||||
timers.clear();
|
||||
|
||||
warmUpBeginFrame();
|
||||
|
||||
// Simulate an animation frame firing between warm-up begin frame and warm-up draw frame.
|
||||
// Expect a timer that reschedules the frame.
|
||||
expect(scheduler.hasScheduledFrame, isFalse);
|
||||
window.onBeginFrame!(Duration.zero);
|
||||
expect(scheduler.hasScheduledFrame, isFalse);
|
||||
window.onDrawFrame!();
|
||||
expect(scheduler.hasScheduledFrame, isFalse);
|
||||
|
||||
// The draw frame part of the warm-up frame will run the post-frame
|
||||
// callback that reschedules the engine frame.
|
||||
warmUpDrawFrame();
|
||||
expect(scheduler.hasScheduledFrame, isTrue);
|
||||
});
|
||||
}
|
||||
|
||||
class DummyTimer implements Timer {
|
||||
|
Loading…
x
Reference in New Issue
Block a user