Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit e28bcce

Browse files
committedAug 16, 2021
[Scheduler] Check for continuous input events
`isInputPending` supports a `includeContinuous` option. When set to `true`, the method will check for pending continuous inputs, like `mousemove`, in addition to discrete ones, like `click`. We will only check for pending continuous inputs if we've blocked the main thread for a non-neglible amount of time. If we've only blocked the main thread for, say, a few frames, then we'll only check for discrete inputs. I wrote a test for this but didn't include it because we haven't yet set up the `gate` flag infra to work with Scheduler feature flags. For now, I ran the test locally.
1 parent 4f7d257 commit e28bcce

File tree

1 file changed

+50
-34
lines changed

1 file changed

+50
-34
lines changed
 

‎packages/scheduler/src/forks/Scheduler.js

+50-34
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,13 @@ const localClearTimeout =
9191
const localSetImmediate =
9292
typeof setImmediate !== 'undefined' ? setImmediate : null; // IE and Node.js + jsdom
9393

94+
const isInputPending =
95+
typeof navigator !== 'undefined' &&
96+
navigator.scheduling !== undefined &&
97+
navigator.scheduling.isInputPending !== undefined
98+
? navigator.scheduling.isInputPending.bind(navigator.scheduling)
99+
: null;
100+
94101
function advanceTimers(currentTime) {
95102
// Check for tasks that are no longer delayed and add them to the queue.
96103
let timer = peek(timerQueue);
@@ -418,48 +425,57 @@ let taskTimeoutID = -1;
418425
// thread, like user events. By default, it yields multiple times per frame.
419426
// It does not attempt to align with frame boundaries, since most tasks don't
420427
// need to be frame aligned; for those that do, use requestAnimationFrame.
421-
let yieldInterval = 5;
428+
// TODO: Make these configurable
429+
let frameInterval = 5;
430+
const continuousInputInterval = 50;
431+
const maxInterval = 300;
422432
let startTime = -1;
423433

424-
// TODO: Make this configurable
425-
// TODO: Adjust this based on priority?
426-
const maxYieldInterval = 300;
427434
let needsPaint = false;
428435

429436
function shouldYieldToHost() {
430437
const timeElapsed = getCurrentTime() - startTime;
431-
if (
432-
enableIsInputPending &&
433-
navigator !== undefined &&
434-
navigator.scheduling !== undefined &&
435-
navigator.scheduling.isInputPending !== undefined
436-
) {
437-
const scheduling = navigator.scheduling;
438-
if (timeElapsed >= yieldInterval) {
439-
// There's no time left. We may want to yield control of the main
440-
// thread, so the browser can perform high priority tasks. The main ones
441-
// are painting and user input. If there's a pending paint or a pending
442-
// input, then we should yield. But if there's neither, then we can
443-
// yield less often while remaining responsive. We'll eventually yield
444-
// regardless, since there could be a pending paint that wasn't
445-
// accompanied by a call to `requestPaint`, or other main thread tasks
446-
// like network events.
447-
if (needsPaint || scheduling.isInputPending()) {
448-
// There is either a pending paint or a pending input.
449-
return true;
438+
if (timeElapsed < frameInterval) {
439+
// The main thread has only been blocked for a really short amount of time;
440+
// smaller than a single frame. Don't yield yet.
441+
return false;
442+
}
443+
444+
// The main thread has been blocked for a non-negligible amount of time. We
445+
// may want to yield control of the main thread, so the browser can perform
446+
// high priority tasks. The main ones are painting and user input. If there's
447+
// a pending paint or a pending input, then we should yield. But if there's
448+
// neither, then we can yield less often while remaining responsive. We'll
449+
// eventually yield regardless, since there could be a pending paint that
450+
// wasn't accompanied by a call to `requestPaint`, or other main thread tasks
451+
// like network events.
452+
if (enableIsInputPending) {
453+
if (needsPaint) {
454+
// There's a pending paint (signaled by `requestPaint`). Yield now.
455+
return true;
456+
}
457+
if (timeElapsed < continuousInputInterval) {
458+
// We haven't blocked the thread for that long. Only yield if there's a
459+
// pending discrete input (e.g. click). It's OK if there's pending
460+
// continuous input (e.g. mouseover).
461+
if (isInputPending !== null) {
462+
return isInputPending();
463+
}
464+
} else if (timeElapsed < maxInterval) {
465+
// Yield if there's either a pending discrete or continuous input.
466+
if (isInputPending !== null) {
467+
return isInputPending({includeContinuous: true});
450468
}
451-
// There's no pending input. Only yield if we've reached the max
452-
// yield interval.
453-
return timeElapsed >= maxYieldInterval;
454469
} else {
455-
// There's still time left in the frame.
456-
return false;
470+
// We've blocked the thread for a long time. Even if there's no pending
471+
// input, there may be some other scheduled work that we don't know about,
472+
// like a network event. Yield now.
473+
return true;
457474
}
458-
} else {
459-
// `isInputPending` is not available. Since we have no way of knowing if
460-
// there's pending input, always yield at the end of the frame.
461-
return timeElapsed >= yieldInterval;
462475
}
476+
477+
// `isInputPending` isn't available. Yield now.
478+
return true;
463479
}
464480

465481
function requestPaint() {
@@ -485,10 +501,10 @@ function forceFrameRate(fps) {
485501
return;
486502
}
487503
if (fps > 0) {
488-
yieldInterval = Math.floor(1000 / fps);
504+
frameInterval = Math.floor(1000 / fps);
489505
} else {
490506
// reset the framerate
491-
yieldInterval = 5;
507+
frameInterval = 5;
492508
}
493509
}
494510

0 commit comments

Comments
 (0)
Please sign in to comment.