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;
|
int _debugFrameNumber = 0;
|
||||||
String? _debugBanner;
|
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) {
|
void _handleBeginFrame(Duration rawTimeStamp) {
|
||||||
if (_warmUpFrame) {
|
if (_warmUpFrame) {
|
||||||
assert(!_ignoreNextEngineDrawFrame);
|
// "begin frame" and "draw frame" must strictly alternate. Therefore
|
||||||
_ignoreNextEngineDrawFrame = true;
|
// _rescheduleAfterWarmUpFrame cannot possibly be true here as it is
|
||||||
|
// reset by _handleDrawFrame.
|
||||||
|
assert(!_rescheduleAfterWarmUpFrame);
|
||||||
|
_rescheduleAfterWarmUpFrame = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
handleBeginFrame(rawTimeStamp);
|
handleBeginFrame(rawTimeStamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleDrawFrame() {
|
void _handleDrawFrame() {
|
||||||
if (_ignoreNextEngineDrawFrame) {
|
if (_rescheduleAfterWarmUpFrame) {
|
||||||
_ignoreNextEngineDrawFrame = false;
|
_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;
|
return;
|
||||||
}
|
}
|
||||||
handleDrawFrame();
|
handleDrawFrame();
|
||||||
|
@ -129,6 +129,11 @@ void main() {
|
|||||||
// events are locked.
|
// events are locked.
|
||||||
expect(timerQueueTasks.length, 2);
|
expect(timerQueueTasks.length, 2);
|
||||||
expect(taskExecuted, false);
|
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 {
|
test('Flutter.Frame event fired', () async {
|
||||||
@ -165,6 +170,9 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('currentSystemFrameTimeStamp is the raw timestamp', () {
|
test('currentSystemFrameTimeStamp is the raw timestamp', () {
|
||||||
|
// Undo epoch set by previous tests.
|
||||||
|
scheduler.resetEpoch();
|
||||||
|
|
||||||
late Duration lastTimeStamp;
|
late Duration lastTimeStamp;
|
||||||
late Duration lastSystemTimeStamp;
|
late Duration lastSystemTimeStamp;
|
||||||
|
|
||||||
@ -195,6 +203,40 @@ void main() {
|
|||||||
expect(lastTimeStamp, const Duration(seconds: 3)); // 2s + (8 - 6)s / 2
|
expect(lastTimeStamp, const Duration(seconds: 3)); // 2s + (8 - 6)s / 2
|
||||||
expect(lastSystemTimeStamp, const Duration(seconds: 8));
|
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 {
|
class DummyTimer implements Timer {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user