@samitouri / QOS-React-2 / commits / 8d4ddd33ac

[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.

Andrew Clark committed Jun 22, 2019 at 00:05 UTC 8d4ddd33acabe139dea0c9c50386143e6bc994f4
1 file changed +31 -1
packages/scheduler/src/forks/SchedulerHostConfig.default.js
+31 -1
@@ -160,8 +160,38 @@ if (
160 let activeFrameTime = 33;
161 let fpsLocked = false;
162
163 + // TODO: Make this configurable
164 + // TODO: Adjust this based on priority?
165 + let maxFrameLength = 300;
166 +
167 + const isInputPending =
168 + navigator !== undefined &&
169 + navigator.scheduling !== undefined &&
170 + navigator.scheduling.isInputPending !== undefined
171 + ? navigator.scheduling.isInputPending
172 + : null;
173 +
174 shouldYieldToHost = function() {
164 - return frameDeadline <= getCurrentTime();
175 + const currentTime = getCurrentTime();
176 + if (currentTime < frameDeadline) {
177 + // There's still time left in the frame.
178 + return false;
179 + } else {
180 + // There's no time left in the frame. We may want to yield control of the
181 + // main thread, so the browser can perform high priority tasks. The main
182 + // ones are painting and user input. If we're certain there's no user
183 + // input, then we can yield less often without making the app less
184 + // responsive. We'll eventually yield regardless, since there could be
185 + // other main thread tasks that we don't know about.
186 + if (isInputPending !== null && !isInputPending()) {
187 + // There's no pending input. Only yield if we've reached the max
188 + // frame length.
189 + return currentTime >= frameDeadline + maxFrameLength;
190 + }
191 + // Either there is pending input, or there's no way for us to be sure
192 + // because `isInputPending` is not available.
193 + return true;
194 + }
195 };
196
197 forceFrameRate = function(fps) {