[Scheduler] Yield less if there's no pending input (#15959)

At the end of each frame, Scheduler yields control of the main thread so
the browser can execute important tasks; most importantly, painting the
screen and responding to user input. There's some overhead involved in
regaining control of the main thread, so we'd like to yield as
infrequently as possible to keep the UI responsive.

The reason we yield on every frame is because there's no way for us to
know whether we're blocking user input.

`isInputPending` is an experimental browser API that gives us this
information. It tells us whether there's a pending user input, which
also means it tells us if there's *not* a pending user input. We can use
this signal to decide whether it's OK not to yield.

There's a max frame length after which we'll yield regardless, as a
precaution against blocking non-input tasks that we don't know about.
This commit is contained in:
Andrew Clark
2019-06-22 00:05:38 -07:00
committed by GitHub
parent d77d12510b
commit 8d4ddd33ac
@@ -160,8 +160,38 @@ if (
let activeFrameTime = 33;
let fpsLocked = false;
// TODO: Make this configurable
// TODO: Adjust this based on priority?
let maxFrameLength = 300;
const isInputPending =
navigator !== undefined &&
navigator.scheduling !== undefined &&
navigator.scheduling.isInputPending !== undefined
? navigator.scheduling.isInputPending
: null;
shouldYieldToHost = function() {
return frameDeadline <= getCurrentTime();
const currentTime = getCurrentTime();
if (currentTime < frameDeadline) {
// There's still time left in the frame.
return false;
} else {
// There's no time left in the frame. We may want to yield control of the
// main thread, so the browser can perform high priority tasks. The main
// ones are painting and user input. If we're certain there's no user
// input, then we can yield less often without making the app less
// responsive. We'll eventually yield regardless, since there could be
// other main thread tasks that we don't know about.
if (isInputPending !== null && !isInputPending()) {
// There's no pending input. Only yield if we've reached the max
// frame length.
return currentTime >= frameDeadline + maxFrameLength;
}
// Either there is pending input, or there's no way for us to be sure
// because `isInputPending` is not available.
return true;
}
};
forceFrameRate = function(fps) {