11
import {enableSchedulerDebugging} from './SchedulerFeatureFlags';
12
import {
13
requestHostCallback,
14
+ requestHostTimeout,
15
+ cancelHostTimeout,
16
shouldYieldToHost,
17
getCurrentTime,
18
forceFrameRate,
41
42
// Tasks are stored as a circular, doubly linked list.
43
var firstTask = null;
44
+var firstDelayedTask = null;
45
46
// Pausing the scheduler is useful for debugging.
47
var isSchedulerPaused = false;
53
var isPerformingWork = false;
54
55
var isHostCallbackScheduled = false;
56
+var isHostTimeoutScheduled = false;
57
58
function flushTask(task, currentTime) {
59
// Remove the task from the list before calling the callback. That way the
139
}
140
}
141
142
+function advanceTimers(currentTime) {
143
+ // Check for tasks that are no longer delayed and add them to the queue.
144
+ if (firstDelayedTask !== null && firstDelayedTask.startTime <= currentTime) {
145
+ do {
146
+ const task = firstDelayedTask;
147
+ const next = task.next;
148
+ if (task === next) {
149
+ firstDelayedTask = null;
150
+ } else {
151
+ firstDelayedTask = next;
152
+ const previous = task.previous;
153
+ previous.next = next;
154
+ next.previous = previous;
155
+ }
156
+ task.next = task.previous = null;
157
+ insertScheduledTask(task, task.expirationTime);
158
+ } while (
159
+ firstDelayedTask !== null &&
160
+ firstDelayedTask.startTime <= currentTime
161
+ );
162
+ }
163
+}
164
+
165
+function handleTimeout(currentTime) {
166
+ isHostTimeoutScheduled = false;
167
+ advanceTimers(currentTime);
168
+
169
+ if (!isHostCallbackScheduled) {
170
+ if (firstTask !== null) {
171
+ isHostCallbackScheduled = true;
172
+ requestHostCallback(flushWork);
173
+ } else if (firstDelayedTask !== null) {
174
+ requestHostTimeout(
175
+ handleTimeout,
176
+ firstDelayedTask.startTime - currentTime,
177
+ );
178
+ }
179
+ }
180
+}
181
+
182
function flushWork(hasTimeRemaining, initialTime) {
183
// Exit right away if we're currently paused
184
if (enableSchedulerDebugging && isSchedulerPaused) {
185
return;
186
}
187
144
- // We'll need a new host callback the next time work is scheduled.
188
+ // We'll need a host callback the next time work is scheduled.
189
isHostCallbackScheduled = false;
190
+ if (isHostTimeoutScheduled) {
191
+ // We scheduled a timeout but it's no longer needed. Cancel it.
192
+ isHostTimeoutScheduled = false;
193
+ cancelHostTimeout();
194
+ }
195
+
196
+ let currentTime = initialTime;
197
+ advanceTimers(currentTime);
198
199
isPerformingWork = true;
200
try {
202
// Flush all the expired callbacks without yielding.
203
// TODO: Split flushWork into two separate functions instead of using
204
// a boolean argument?
153
- let currentTime = initialTime;
205
while (
206
firstTask !== null &&
207
firstTask.expirationTime <= currentTime &&
209
) {
210
flushTask(firstTask, currentTime);
211
currentTime = getCurrentTime();
212
+ advanceTimers(currentTime);
213
}
214
} else {
215
// Keep flushing callbacks until we run out of time in the frame.
164
- let currentTime = initialTime;
216
if (firstTask !== null) {
217
do {
218
flushTask(firstTask, currentTime);
219
currentTime = getCurrentTime();
220
+ advanceTimers(currentTime);
221
} while (
222
firstTask !== null &&
223
!shouldYieldToHost() &&
226
}
227
}
228
// Return whether there's additional work
177
- return firstTask !== null;
229
+ if (firstTask !== null) {
230
+ return true;
231
+ } else {
232
+ if (firstDelayedTask !== null) {
233
+ requestHostTimeout(
234
+ handleTimeout,
235
+ firstDelayedTask.startTime - currentTime,
236
+ );
237
+ }
238
+ return false;
239
+ }
240
} finally {
241
isPerformingWork = false;
242
}
304
};
305
}
306
307
+function timeoutForPriorityLevel(priorityLevel) {
308
+ switch (priorityLevel) {
309
+ case ImmediatePriority:
310
+ return IMMEDIATE_PRIORITY_TIMEOUT;
311
+ case UserBlockingPriority:
312
+ return USER_BLOCKING_PRIORITY;
313
+ case IdlePriority:
314
+ return IDLE_PRIORITY;
315
+ case LowPriority:
316
+ return LOW_PRIORITY_TIMEOUT;
317
+ case NormalPriority:
318
+ default:
319
+ return NORMAL_PRIORITY_TIMEOUT;
320
+ }
321
+}
322
+
323
function unstable_scheduleCallback(priorityLevel, callback, options) {
246
- var startTime = getCurrentTime();
324
+ var currentTime = getCurrentTime();
325
326
+ var startTime;
327
var timeout;
249
- if (
250
- typeof options === 'object' &&
251
- options !== null &&
252
- typeof options.timeout === 'number'
253
- ) {
254
- timeout = options.timeout;
255
- } else {
256
- switch (priorityLevel) {
257
- case ImmediatePriority:
258
- timeout = IMMEDIATE_PRIORITY_TIMEOUT;
259
- break;
260
- case UserBlockingPriority:
261
- timeout = USER_BLOCKING_PRIORITY;
262
- break;
263
- case IdlePriority:
264
- timeout = IDLE_PRIORITY;
265
- break;
266
- case LowPriority:
267
- timeout = LOW_PRIORITY_TIMEOUT;
268
- break;
269
- case NormalPriority:
270
- default:
271
- timeout = NORMAL_PRIORITY_TIMEOUT;
328
+ if (typeof options === 'object' && options !== null) {
329
+ var delay = options.delay;
330
+ if (typeof delay === 'number' && delay > 0) {
331
+ startTime = currentTime + delay;
332
+ } else {
333
+ startTime = currentTime;
334
}
335
+ timeout =
336
+ typeof options.timeout === 'number'
337
+ ? options.timeout
338
+ : timeoutForPriorityLevel(priorityLevel);
339
+ } else {
340
+ timeout = timeoutForPriorityLevel(priorityLevel);
341
+ startTime = currentTime;
342
}
343
344
var expirationTime = startTime + timeout;
352
previous: null,
353
};
354
355
+ if (startTime > currentTime) {
356
+ // This is a delayed task.
357
+ insertDelayedTask(newTask, startTime);
358
+ if (firstTask === null && firstDelayedTask === newTask) {
359
+ // All tasks are delayed, and this is the task with the earliest delay.
360
+ if (isHostTimeoutScheduled) {
361
+ // Cancel an existing timeout.
362
+ cancelHostTimeout();
363
+ } else {
364
+ isHostTimeoutScheduled = true;
365
+ }
366
+ // Schedule a timeout.
367
+ requestHostTimeout(handleTimeout, startTime - currentTime);
368
+ }
369
+ } else {
370
+ insertScheduledTask(newTask, expirationTime);
371
+ // Schedule a host callback, if needed. If we're already performing work,
372
+ // wait until the next time we yield.
373
+ if (!isHostCallbackScheduled && !isPerformingWork) {
374
+ isHostCallbackScheduled = true;
375
+ requestHostCallback(flushWork);
376
+ }
377
+ }
378
+
379
+ return newTask;
380
+}
381
+
382
+function insertScheduledTask(newTask, expirationTime) {
383
// Insert the new task into the list, ordered first by its timeout, then by
384
// insertion. So the new task is inserted after any other task the
385
// same timeout
412
newTask.next = next;
413
newTask.previous = previous;
414
}
415
+}
416
319
- // Schedule a host callback, if needed. If we're already performing work, wait
320
- // until the next time we yield.
321
- if (!isHostCallbackScheduled && !isPerformingWork) {
322
- isHostCallbackScheduled = true;
323
- requestHostCallback(flushWork);
324
- }
417
+function insertDelayedTask(newTask, startTime) {
418
+ // Insert the new task into the list, ordered by its start time.
419
+ if (firstDelayedTask === null) {
420
+ // This is the first task in the list.
421
+ firstDelayedTask = newTask.next = newTask.previous = newTask;
422
+ } else {
423
+ var next = null;
424
+ var task = firstDelayedTask;
425
+ do {
426
+ if (startTime < task.startTime) {
427
+ // The new task times out before this one.
428
+ next = task;
429
+ break;
430
+ }
431
+ task = task.next;
432
+ } while (task !== firstDelayedTask);
433
326
- return newTask;
434
+ if (next === null) {
435
+ // No task with a later timeout was found, which means the new task has
436
+ // the latest timeout in the list.
437
+ next = firstDelayedTask;
438
+ } else if (next === firstDelayedTask) {
439
+ // The new task has the earliest expiration in the entire list.
440
+ firstDelayedTask = newTask;
441
+ }
442
+
443
+ var previous = next.previous;
444
+ previous.next = next.previous = newTask;
445
+ newTask.next = next;
446
+ newTask.previous = previous;
447
+ }
448
}
449
450
function unstable_pauseExecution() {
470
return;
471
}
472
352
- if (next === task) {
353
- // This is the only scheduled task. Clear the list.
354
- firstTask = null;
473
+ if (task === next) {
474
+ if (task === firstTask) {
475
+ firstTask = null;
476
+ } else if (task === firstDelayedTask) {
477
+ firstDelayedTask = null;
478
+ }
479
} else {
356
- // Remove the task from its position in the list.
480
if (task === firstTask) {
481
firstTask = next;
482
+ } else if (task === firstDelayedTask) {
483
+ firstDelayedTask = next;
484
}
485
var previous = task.previous;
486
previous.next = next;
495
}
496
497
function unstable_shouldYield() {
498
+ const currentTime = getCurrentTime();
499
+ advanceTimers(currentTime);
500
return (
501
(currentTask !== null &&
502
firstTask !== null &&
503
+ firstTask.startTime <= currentTime &&
504
firstTask.expirationTime < currentTask.expirationTime) ||
505
shouldYieldToHost()
506
);