Don't flush touch events during first launch frame pre-warm (#13990)
* Lock events during runApp's warm up frame * move to scheduler binding * let the one scheduleWarmUpFrame api always lock * tweak test
This commit is contained in:
parent
8a6e973739
commit
c5c63dfd47
@ -161,21 +161,25 @@ abstract class BindingBase {
|
|||||||
/// callback's future completes.
|
/// callback's future completes.
|
||||||
///
|
///
|
||||||
/// This causes input lag and should therefore be avoided when possible. It is
|
/// This causes input lag and should therefore be avoided when possible. It is
|
||||||
/// primarily intended for development features, in particular to allow
|
/// primarily intended for use during non-user-interactive time such as to
|
||||||
/// [reassembleApplication] to block input while it walks the tree (which it
|
/// allow [reassembleApplication] to block input while it walks the tree
|
||||||
/// partially does asynchronously).
|
/// (which it partially does asynchronously).
|
||||||
///
|
///
|
||||||
/// The [Future] returned by the `callback` argument is returned by [lockEvents].
|
/// The [Future] returned by the `callback` argument is returned by [lockEvents].
|
||||||
@protected
|
@protected
|
||||||
Future<Null> lockEvents(Future<Null> callback()) {
|
Future<Null> lockEvents(Future<Null> callback()) {
|
||||||
|
developer.Timeline.startSync('Lock events');
|
||||||
|
|
||||||
assert(callback != null);
|
assert(callback != null);
|
||||||
_lockCount += 1;
|
_lockCount += 1;
|
||||||
final Future<Null> future = callback();
|
final Future<Null> future = callback();
|
||||||
assert(future != null, 'The lockEvents() callback returned null; it should return a Future<Null> that completes when the lock is to expire.');
|
assert(future != null, 'The lockEvents() callback returned null; it should return a Future<Null> that completes when the lock is to expire.');
|
||||||
future.whenComplete(() {
|
future.whenComplete(() {
|
||||||
_lockCount -= 1;
|
_lockCount -= 1;
|
||||||
if (!locked)
|
if (!locked) {
|
||||||
|
developer.Timeline.finishSync();
|
||||||
unlocked();
|
unlocked();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
|
@ -666,6 +666,8 @@ abstract class SchedulerBinding extends BindingBase with ServicesBinding {
|
|||||||
/// This is used during application startup so that the first frame (which is
|
/// 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.
|
/// 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
|
/// If a frame has already been scheduled with [scheduleFrame] or
|
||||||
/// [scheduleForcedFrame], this call may delay that frame.
|
/// [scheduleForcedFrame], this call may delay that frame.
|
||||||
///
|
///
|
||||||
@ -677,8 +679,9 @@ abstract class SchedulerBinding extends BindingBase with ServicesBinding {
|
|||||||
if (_warmUpFrame || schedulerPhase != SchedulerPhase.idle)
|
if (_warmUpFrame || schedulerPhase != SchedulerPhase.idle)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
final bool hadScheduledFrame = _hasScheduledFrame;
|
|
||||||
_warmUpFrame = true;
|
_warmUpFrame = true;
|
||||||
|
Timeline.startSync('Warm-up frame');
|
||||||
|
final bool hadScheduledFrame = _hasScheduledFrame;
|
||||||
// We use timers here to ensure that microtasks flush in between.
|
// We use timers here to ensure that microtasks flush in between.
|
||||||
Timer.run(() {
|
Timer.run(() {
|
||||||
assert(_warmUpFrame);
|
assert(_warmUpFrame);
|
||||||
@ -700,6 +703,13 @@ abstract class SchedulerBinding extends BindingBase with ServicesBinding {
|
|||||||
if (hadScheduledFrame)
|
if (hadScheduledFrame)
|
||||||
scheduleFrame();
|
scheduleFrame();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Lock events so touch events etc don't insert themselves until the
|
||||||
|
// scheduled frame has finished.
|
||||||
|
lockEvents(() async {
|
||||||
|
await endOfFrame;
|
||||||
|
Timeline.finishSync();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Duration _firstRawTimeStampInEpoch;
|
Duration _firstRawTimeStampInEpoch;
|
||||||
|
@ -92,11 +92,13 @@ void main() {
|
|||||||
|
|
||||||
test('2 calls to scheduleWarmUpFrame just schedules it once', () {
|
test('2 calls to scheduleWarmUpFrame just schedules it once', () {
|
||||||
final List<VoidCallback> timerQueueTasks = <VoidCallback>[];
|
final List<VoidCallback> timerQueueTasks = <VoidCallback>[];
|
||||||
|
bool taskExecuted = false;
|
||||||
runZoned(
|
runZoned(
|
||||||
() {
|
() {
|
||||||
// Run it twice without processing the queued tasks.
|
// Run it twice without processing the queued tasks.
|
||||||
scheduler.scheduleWarmUpFrame();
|
scheduler.scheduleWarmUpFrame();
|
||||||
scheduler.scheduleWarmUpFrame();
|
scheduler.scheduleWarmUpFrame();
|
||||||
|
scheduler.scheduleTask(() { taskExecuted = true; }, Priority.touch);
|
||||||
},
|
},
|
||||||
zoneSpecification: new ZoneSpecification(
|
zoneSpecification: new ZoneSpecification(
|
||||||
createTimer: (Zone self, ZoneDelegate parent, Zone zone, Duration duration, void f()) {
|
createTimer: (Zone self, ZoneDelegate parent, Zone zone, Duration duration, void f()) {
|
||||||
@ -107,7 +109,9 @@ void main() {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// A single call to scheduleWarmUpFrame queues up 2 Timer tasks.
|
// scheduleWarmUpFrame scheduled 2 Timers, scheduleTask scheduled 0 because
|
||||||
|
// events are locked.
|
||||||
expect(timerQueueTasks.length, 2);
|
expect(timerQueueTasks.length, 2);
|
||||||
|
expect(taskExecuted, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user