11
import {enableSchedulerDebugging} from './SchedulerFeatureFlags';
12
import {
13
requestHostCallback,
14
- cancelHostCallback,
14
shouldYieldToHost,
15
getCurrentTime,
16
forceFrameRate,
37
// Never times out
38
var IDLE_PRIORITY = maxSigned31BitInt;
39
41
-// Callbacks are stored as a circular, doubly linked list.
42
-var firstCallbackNode = null;
40
+// Tasks are stored as a circular, doubly linked list.
41
+var firstTask = null;
42
44
-var currentHostCallbackDidTimeout = false;
43
// Pausing the scheduler is useful for debugging.
44
var isSchedulerPaused = false;
45
46
+var currentTask = null;
47
var currentPriorityLevel = NormalPriority;
49
-var currentEventStartTime = -1;
50
-var currentExpirationTime = -1;
48
49
// This is set while performing work, to prevent re-entrancy.
50
var isPerformingWork = false;
51
52
var isHostCallbackScheduled = false;
53
57
-function scheduleHostCallbackIfNeeded() {
58
- if (isPerformingWork) {
59
- // Don't schedule work yet; wait until the next time we yield.
60
- return;
61
- }
62
- if (firstCallbackNode !== null) {
63
- // Schedule the host callback using the earliest expiration in the list.
64
- var expirationTime = firstCallbackNode.expirationTime;
65
- if (isHostCallbackScheduled) {
66
- // Cancel the existing host callback.
67
- cancelHostCallback();
68
- } else {
69
- isHostCallbackScheduled = true;
70
- }
71
- requestHostCallback(flushWork, expirationTime);
72
- }
73
-}
74
-
75
-function flushFirstCallback() {
76
- const currentlyFlushingCallback = firstCallbackNode;
77
-
78
- // Remove the node from the list before calling the callback. That way the
54
+function flushTask(task, currentTime) {
55
+ // Remove the task from the list before calling the callback. That way the
56
// list is in a consistent state even if the callback throws.
80
- var next = firstCallbackNode.next;
81
- if (firstCallbackNode === next) {
82
- // This is the last callback in the list.
83
- firstCallbackNode = null;
84
- next = null;
57
+ const next = task.next;
58
+ if (next === task) {
59
+ // This is the only scheduled task. Clear the list.
60
+ firstTask = null;
61
} else {
86
- var lastCallbackNode = firstCallbackNode.previous;
87
- firstCallbackNode = lastCallbackNode.next = next;
88
- next.previous = lastCallbackNode;
62
+ // Remove the task from its position in the list.
63
+ if (task === firstTask) {
64
+ firstTask = next;
65
+ }
66
+ const previous = task.previous;
67
+ previous.next = next;
68
+ next.previous = previous;
69
}
70
+ task.next = task.previous = null;
71
91
- currentlyFlushingCallback.next = currentlyFlushingCallback.previous = null;
92
-
93
- // Now it's safe to call the callback.
94
- var callback = currentlyFlushingCallback.callback;
95
- var expirationTime = currentlyFlushingCallback.expirationTime;
96
- var priorityLevel = currentlyFlushingCallback.priorityLevel;
72
+ // Now it's safe to execute the task.
73
+ var callback = task.callback;
74
var previousPriorityLevel = currentPriorityLevel;
98
- var previousExpirationTime = currentExpirationTime;
99
- currentPriorityLevel = priorityLevel;
100
- currentExpirationTime = expirationTime;
75
+ var previousTask = currentTask;
76
+ currentPriorityLevel = task.priorityLevel;
77
+ currentTask = task;
78
var continuationCallback;
79
try {
103
- const didUserCallbackTimeout =
104
- currentHostCallbackDidTimeout ||
105
- // Immediate priority callbacks are always called as if they timed out
106
- priorityLevel === ImmediatePriority;
80
+ var didUserCallbackTimeout = task.expirationTime <= currentTime;
81
continuationCallback = callback(didUserCallbackTimeout);
82
} catch (error) {
83
throw error;
84
} finally {
85
currentPriorityLevel = previousPriorityLevel;
112
- currentExpirationTime = previousExpirationTime;
86
+ currentTask = previousTask;
87
}
88
89
// A callback may return a continuation. The continuation should be scheduled
90
// with the same priority and expiration as the just-finished callback.
91
if (typeof continuationCallback === 'function') {
118
- var continuationNode: CallbackNode = {
92
+ var expirationTime = task.expirationTime;
93
+ var continuationTask = {
94
callback: continuationCallback,
120
- priorityLevel,
95
+ priorityLevel: task.priorityLevel,
96
+ startTime: task.startTime,
97
expirationTime,
98
next: null,
99
previous: null,
100
};
101
126
- // Insert the new callback into the list, sorted by its expiration. This is
102
+ // Insert the new callback into the list, sorted by its timeout. This is
103
// almost the same as the code in `scheduleCallback`, except the callback
128
- // is inserted into the list *before* callbacks of equal expiration instead
104
+ // is inserted into the list *before* callbacks of equal timeout instead
105
// of after.
130
- if (firstCallbackNode === null) {
106
+ if (firstTask === null) {
107
// This is the first callback in the list.
132
- firstCallbackNode = continuationNode.next = continuationNode.previous = continuationNode;
108
+ firstTask = continuationTask.next = continuationTask.previous = continuationTask;
109
} else {
110
var nextAfterContinuation = null;
135
- var node = firstCallbackNode;
111
+ var t = firstTask;
112
do {
137
- if (node.expirationTime >= expirationTime) {
138
- // This callback expires at or after the continuation. We will insert
139
- // the continuation *before* this callback.
140
- nextAfterContinuation = node;
113
+ if (expirationTime <= t.expirationTime) {
114
+ // This task times out at or after the continuation. We will insert
115
+ // the continuation *before* this task.
116
+ nextAfterContinuation = t;
117
break;
118
}
143
- node = node.next;
144
- } while (node !== firstCallbackNode);
145
-
119
+ t = t.next;
120
+ } while (t !== firstTask);
121
if (nextAfterContinuation === null) {
147
- // No equal or lower priority callback was found, which means the new
148
- // callback is the lowest priority callback in the list.
149
- nextAfterContinuation = firstCallbackNode;
150
- } else if (nextAfterContinuation === firstCallbackNode) {
151
- // The new callback is the highest priority callback in the list.
152
- firstCallbackNode = continuationNode;
153
- scheduleHostCallbackIfNeeded();
122
+ // No equal or lower priority task was found, which means the new task
123
+ // is the lowest priority task in the list.
124
+ nextAfterContinuation = firstTask;
125
+ } else if (nextAfterContinuation === firstTask) {
126
+ // The new task is the highest priority task in the list.
127
+ firstTask = continuationTask;
128
}
129
156
- var previous = nextAfterContinuation.previous;
157
- previous.next = nextAfterContinuation.previous = continuationNode;
158
- continuationNode.next = nextAfterContinuation;
159
- continuationNode.previous = previous;
130
+ const previous = nextAfterContinuation.previous;
131
+ previous.next = nextAfterContinuation.previous = continuationTask;
132
+ continuationTask.next = nextAfterContinuation;
133
+ continuationTask.previous = previous;
134
}
135
}
136
}
137
164
-function flushWork(didUserCallbackTimeout) {
138
+function flushWork(hasTimeRemaining, initialTime) {
139
// Exit right away if we're currently paused
140
if (enableSchedulerDebugging && isSchedulerPaused) {
141
return;
145
isHostCallbackScheduled = false;
146
147
isPerformingWork = true;
174
- const previousDidTimeout = currentHostCallbackDidTimeout;
175
- currentHostCallbackDidTimeout = didUserCallbackTimeout;
148
try {
177
- if (didUserCallbackTimeout) {
149
+ if (!hasTimeRemaining) {
150
// Flush all the expired callbacks without yielding.
151
+ // TODO: Split flushWork into two separate functions instead of using
152
+ // a boolean argument?
153
+ let currentTime = initialTime;
154
while (
180
- firstCallbackNode !== null &&
155
+ firstTask !== null &&
156
+ firstTask.expirationTime <= currentTime &&
157
!(enableSchedulerDebugging && isSchedulerPaused)
158
) {
183
- // TODO Wrap in feature flag
184
- // Read the current time. Flush all the callbacks that expire at or
185
- // earlier than that time. Then read the current time again and repeat.
186
- // This optimizes for as few performance.now calls as possible.
187
- var currentTime = getCurrentTime();
188
- if (firstCallbackNode.expirationTime <= currentTime) {
189
- do {
190
- flushFirstCallback();
191
- } while (
192
- firstCallbackNode !== null &&
193
- firstCallbackNode.expirationTime <= currentTime &&
194
- !(enableSchedulerDebugging && isSchedulerPaused)
195
- );
196
- continue;
197
- }
198
- break;
159
+ flushTask(firstTask, currentTime);
160
+ currentTime = getCurrentTime();
161
}
162
} else {
163
// Keep flushing callbacks until we run out of time in the frame.
202
- if (firstCallbackNode !== null) {
164
+ let currentTime = initialTime;
165
+ if (firstTask !== null) {
166
do {
204
- if (enableSchedulerDebugging && isSchedulerPaused) {
205
- break;
206
- }
207
- flushFirstCallback();
208
- } while (firstCallbackNode !== null && !shouldYieldToHost());
167
+ flushTask(firstTask, currentTime);
168
+ currentTime = getCurrentTime();
169
+ } while (
170
+ firstTask !== null &&
171
+ !shouldYieldToHost() &&
172
+ !(enableSchedulerDebugging && isSchedulerPaused)
173
+ );
174
}
175
}
176
+ // Return whether there's additional work
177
+ return firstTask !== null;
178
} finally {
179
isPerformingWork = false;
213
- currentHostCallbackDidTimeout = previousDidTimeout;
214
- // There's still work remaining. Request another callback.
215
- scheduleHostCallbackIfNeeded();
180
}
181
}
182
193
}
194
195
var previousPriorityLevel = currentPriorityLevel;
232
- var previousEventStartTime = currentEventStartTime;
196
currentPriorityLevel = priorityLevel;
234
- currentEventStartTime = getCurrentTime();
197
198
try {
199
return eventHandler();
238
- } catch (error) {
239
- // There's still work remaining. Request another callback.
240
- scheduleHostCallbackIfNeeded();
241
- throw error;
200
} finally {
201
currentPriorityLevel = previousPriorityLevel;
244
- currentEventStartTime = previousEventStartTime;
202
}
203
}
204
205
function unstable_next(eventHandler) {
249
- let priorityLevel;
206
+ var priorityLevel;
207
switch (currentPriorityLevel) {
208
case ImmediatePriority:
209
case UserBlockingPriority:
218
}
219
220
var previousPriorityLevel = currentPriorityLevel;
264
- var previousEventStartTime = currentEventStartTime;
221
currentPriorityLevel = priorityLevel;
266
- currentEventStartTime = getCurrentTime();
222
223
try {
224
return eventHandler();
270
- } catch (error) {
271
- // There's still work remaining. Request another callback.
272
- scheduleHostCallbackIfNeeded();
273
- throw error;
225
} finally {
226
currentPriorityLevel = previousPriorityLevel;
276
- currentEventStartTime = previousEventStartTime;
227
}
228
}
229
232
return function() {
233
// This is a fork of runWithPriority, inlined for performance.
234
var previousPriorityLevel = currentPriorityLevel;
285
- var previousEventStartTime = currentEventStartTime;
235
currentPriorityLevel = parentPriorityLevel;
287
- currentEventStartTime = getCurrentTime();
236
237
try {
238
return callback.apply(this, arguments);
291
- } catch (error) {
292
- // There's still work remaining. Request another callback.
293
- scheduleHostCallbackIfNeeded();
294
- throw error;
239
} finally {
240
currentPriorityLevel = previousPriorityLevel;
297
- currentEventStartTime = previousEventStartTime;
241
}
242
};
243
}
244
302
-function unstable_scheduleCallback(
303
- priorityLevel,
304
- callback,
305
- deprecated_options,
306
-) {
307
- var startTime =
308
- currentEventStartTime !== -1 ? currentEventStartTime : getCurrentTime();
245
+function unstable_scheduleCallback(priorityLevel, callback, options) {
246
+ var startTime = getCurrentTime();
247
310
- var expirationTime;
248
+ var timeout;
249
if (
312
- typeof deprecated_options === 'object' &&
313
- deprecated_options !== null &&
314
- typeof deprecated_options.timeout === 'number'
250
+ typeof options === 'object' &&
251
+ options !== null &&
252
+ typeof options.timeout === 'number'
253
) {
316
- // FIXME: Remove this branch once we lift expiration times out of React.
317
- expirationTime = startTime + deprecated_options.timeout;
254
+ timeout = options.timeout;
255
} else {
256
switch (priorityLevel) {
257
case ImmediatePriority:
321
- expirationTime = startTime + IMMEDIATE_PRIORITY_TIMEOUT;
258
+ timeout = IMMEDIATE_PRIORITY_TIMEOUT;
259
break;
260
case UserBlockingPriority:
324
- expirationTime = startTime + USER_BLOCKING_PRIORITY;
261
+ timeout = USER_BLOCKING_PRIORITY;
262
break;
263
case IdlePriority:
327
- expirationTime = startTime + IDLE_PRIORITY;
264
+ timeout = IDLE_PRIORITY;
265
break;
266
case LowPriority:
330
- expirationTime = startTime + LOW_PRIORITY_TIMEOUT;
267
+ timeout = LOW_PRIORITY_TIMEOUT;
268
break;
269
case NormalPriority:
270
default:
334
- expirationTime = startTime + NORMAL_PRIORITY_TIMEOUT;
271
+ timeout = NORMAL_PRIORITY_TIMEOUT;
272
}
273
}
274
338
- var newNode = {
275
+ var expirationTime = startTime + timeout;
276
+
277
+ var newTask = {
278
callback,
340
- priorityLevel: priorityLevel,
279
+ priorityLevel,
280
+ startTime,
281
expirationTime,
282
next: null,
283
previous: null,
284
};
285
346
- // Insert the new callback into the list, ordered first by expiration, then
347
- // by insertion. So the new callback is inserted after any other callback
348
- // with equal expiration.
349
- if (firstCallbackNode === null) {
350
- // This is the first callback in the list.
351
- firstCallbackNode = newNode.next = newNode.previous = newNode;
352
- scheduleHostCallbackIfNeeded();
286
+ // Insert the new task into the list, ordered first by its timeout, then by
287
+ // insertion. So the new task is inserted after any other task the
288
+ // same timeout
289
+ if (firstTask === null) {
290
+ // This is the first task in the list.
291
+ firstTask = newTask.next = newTask.previous = newTask;
292
} else {
293
var next = null;
355
- var node = firstCallbackNode;
294
+ var task = firstTask;
295
do {
357
- if (node.expirationTime > expirationTime) {
358
- // The new callback expires before this one.
359
- next = node;
296
+ if (expirationTime < task.expirationTime) {
297
+ // The new task times out before this one.
298
+ next = task;
299
break;
300
}
362
- node = node.next;
363
- } while (node !== firstCallbackNode);
301
+ task = task.next;
302
+ } while (task !== firstTask);
303
304
if (next === null) {
366
- // No callback with a later expiration was found, which means the new
367
- // callback has the latest expiration in the list.
368
- next = firstCallbackNode;
369
- } else if (next === firstCallbackNode) {
370
- // The new callback has the earliest expiration in the entire list.
371
- firstCallbackNode = newNode;
372
- scheduleHostCallbackIfNeeded();
305
+ // No task with a later timeout was found, which means the new task has
306
+ // the latest timeout in the list.
307
+ next = firstTask;
308
+ } else if (next === firstTask) {
309
+ // The new task has the earliest expiration in the entire list.
310
+ firstTask = newTask;
311
}
312
313
var previous = next.previous;
376
- previous.next = next.previous = newNode;
377
- newNode.next = next;
378
- newNode.previous = previous;
314
+ previous.next = next.previous = newTask;
315
+ newTask.next = next;
316
+ newTask.previous = previous;
317
+ }
318
+
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
}
325
381
- return newNode;
326
+ return newTask;
327
}
328
329
function unstable_pauseExecution() {
332
333
function unstable_continueExecution() {
334
isSchedulerPaused = false;
390
- if (firstCallbackNode !== null) {
391
- scheduleHostCallbackIfNeeded();
335
+ if (!isHostCallbackScheduled && !isPerformingWork) {
336
+ isHostCallbackScheduled = true;
337
+ requestHostCallback(flushWork);
338
}
339
}
340
341
function unstable_getFirstCallbackNode() {
396
- return firstCallbackNode;
342
+ return firstTask;
343
}
344
399
-function unstable_cancelCallback(callbackNode) {
400
- var next = callbackNode.next;
345
+function unstable_cancelCallback(task) {
346
+ var next = task.next;
347
if (next === null) {
348
// Already cancelled.
349
return;
350
}
351
406
- if (next === callbackNode) {
407
- // This is the only scheduled callback. Clear the list.
408
- firstCallbackNode = null;
352
+ if (next === task) {
353
+ // This is the only scheduled task. Clear the list.
354
+ firstTask = null;
355
} else {
410
- // Remove the callback from its position in the list.
411
- if (callbackNode === firstCallbackNode) {
412
- firstCallbackNode = next;
356
+ // Remove the task from its position in the list.
357
+ if (task === firstTask) {
358
+ firstTask = next;
359
}
414
- var previous = callbackNode.previous;
360
+ var previous = task.previous;
361
previous.next = next;
362
next.previous = previous;
363
}
364
419
- callbackNode.next = callbackNode.previous = null;
365
+ task.next = task.previous = null;
366
}
367
368
function unstable_getCurrentPriorityLevel() {
371
372
function unstable_shouldYield() {
373
return (
428
- !currentHostCallbackDidTimeout &&
429
- ((firstCallbackNode !== null &&
430
- firstCallbackNode.expirationTime < currentExpirationTime) ||
431
- shouldYieldToHost())
374
+ (currentTask !== null &&
375
+ firstTask !== null &&
376
+ firstTask.expirationTime < currentTask.expirationTime) ||
377
+ shouldYieldToHost()
378
);
379
}
380