1111import { enableSchedulerDebugging } from './SchedulerFeatureFlags' ;
1212import {
1313 requestHostCallback ,
14+ requestHostTimeout ,
15+ cancelHostTimeout ,
1416 shouldYieldToHost ,
1517 getCurrentTime ,
1618 forceFrameRate ,
@@ -39,6 +41,7 @@ var IDLE_PRIORITY = maxSigned31BitInt;
3941
4042// Tasks are stored as a circular, doubly linked list.
4143var firstTask = null ;
44+ var firstDelayedTask = null ;
4245
4346// Pausing the scheduler is useful for debugging.
4447var isSchedulerPaused = false ;
@@ -50,6 +53,7 @@ var currentPriorityLevel = NormalPriority;
5053var isPerformingWork = false ;
5154
5255var isHostCallbackScheduled = false ;
56+ var isHostTimeoutScheduled = false ;
5357
5458function flushTask ( task , currentTime ) {
5559 // Remove the task from the list before calling the callback. That way the
@@ -135,37 +139,85 @@ function flushTask(task, currentTime) {
135139 }
136140}
137141
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+
138182function flushWork ( hasTimeRemaining , initialTime ) {
139183 // Exit right away if we're currently paused
140184 if ( enableSchedulerDebugging && isSchedulerPaused ) {
141185 return ;
142186 }
143187
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.
145189 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 ) ;
146198
147199 isPerformingWork = true ;
148200 try {
149201 if ( ! hasTimeRemaining ) {
150202 // Flush all the expired callbacks without yielding.
151203 // TODO: Split flushWork into two separate functions instead of using
152204 // a boolean argument?
153- let currentTime = initialTime ;
154205 while (
155206 firstTask !== null &&
156207 firstTask . expirationTime <= currentTime &&
157208 ! ( enableSchedulerDebugging && isSchedulerPaused )
158209 ) {
159210 flushTask ( firstTask , currentTime ) ;
160211 currentTime = getCurrentTime ( ) ;
212+ advanceTimers ( currentTime ) ;
161213 }
162214 } else {
163215 // Keep flushing callbacks until we run out of time in the frame.
164- let currentTime = initialTime ;
165216 if ( firstTask !== null ) {
166217 do {
167218 flushTask ( firstTask , currentTime ) ;
168219 currentTime = getCurrentTime ( ) ;
220+ advanceTimers ( currentTime ) ;
169221 } while (
170222 firstTask !== null &&
171223 ! shouldYieldToHost ( ) &&
@@ -174,7 +226,17 @@ function flushWork(hasTimeRemaining, initialTime) {
174226 }
175227 }
176228 // 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+ }
178240 } finally {
179241 isPerformingWork = false ;
180242 }
@@ -242,34 +304,41 @@ function unstable_wrapCallback(callback) {
242304 } ;
243305}
244306
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+
245323function unstable_scheduleCallback ( priorityLevel , callback , options ) {
246- var startTime = getCurrentTime ( ) ;
324+ var currentTime = getCurrentTime ( ) ;
247325
326+ var startTime ;
248327 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 ;
272334 }
335+ timeout =
336+ typeof options . timeout === 'number'
337+ ? options . timeout
338+ : timeoutForPriorityLevel ( priorityLevel ) ;
339+ } else {
340+ timeout = timeoutForPriorityLevel ( priorityLevel ) ;
341+ startTime = currentTime ;
273342 }
274343
275344 var expirationTime = startTime + timeout ;
@@ -283,6 +352,34 @@ function unstable_scheduleCallback(priorityLevel, callback, options) {
283352 previous : null ,
284353 } ;
285354
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 ) {
286383 // Insert the new task into the list, ordered first by its timeout, then by
287384 // insertion. So the new task is inserted after any other task the
288385 // same timeout
@@ -315,15 +412,39 @@ function unstable_scheduleCallback(priorityLevel, callback, options) {
315412 newTask . next = next ;
316413 newTask . previous = previous ;
317414 }
415+ }
318416
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 ) ;
325433
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+ }
327448}
328449
329450function unstable_pauseExecution ( ) {
@@ -349,13 +470,17 @@ function unstable_cancelCallback(task) {
349470 return ;
350471 }
351472
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+ }
355479 } else {
356- // Remove the task from its position in the list.
357480 if ( task === firstTask ) {
358481 firstTask = next ;
482+ } else if ( task === firstDelayedTask ) {
483+ firstDelayedTask = next ;
359484 }
360485 var previous = task . previous ;
361486 previous . next = next ;
@@ -370,9 +495,12 @@ function unstable_getCurrentPriorityLevel() {
370495}
371496
372497function unstable_shouldYield ( ) {
498+ const currentTime = getCurrentTime ( ) ;
499+ advanceTimers ( currentTime ) ;
373500 return (
374501 ( currentTask !== null &&
375502 firstTask !== null &&
503+ firstTask . startTime <= currentTime &&
376504 firstTask . expirationTime < currentTask . expirationTime ) ||
377505 shouldYieldToHost ( )
378506 ) ;
0 commit comments