8
8
9
9
/* eslint-disable no-var */
10
10
11
- import { enableSchedulerDebugging } from './SchedulerFeatureFlags' ;
11
+ import {
12
+ enableSchedulerDebugging ,
13
+ enableProfiling ,
14
+ } from './SchedulerFeatureFlags' ;
12
15
import {
13
16
requestHostCallback ,
14
17
requestHostTimeout ,
@@ -21,11 +24,26 @@ import {
21
24
import { push , pop , peek } from './SchedulerMinHeap' ;
22
25
23
26
// TODO: Use symbols?
24
- var ImmediatePriority = 1 ;
25
- var UserBlockingPriority = 2 ;
26
- var NormalPriority = 3 ;
27
- var LowPriority = 4 ;
28
- var IdlePriority = 5 ;
27
+ import {
28
+ ImmediatePriority ,
29
+ UserBlockingPriority ,
30
+ NormalPriority ,
31
+ LowPriority ,
32
+ IdlePriority ,
33
+ } from './SchedulerPriorities' ;
34
+ import {
35
+ sharedProfilingBuffer ,
36
+ markTaskRun ,
37
+ markTaskYield ,
38
+ markTaskCompleted ,
39
+ markTaskCanceled ,
40
+ markTaskErrored ,
41
+ markSchedulerSuspended ,
42
+ markSchedulerUnsuspended ,
43
+ markTaskStart ,
44
+ stopLoggingProfilingEvents ,
45
+ startLoggingProfilingEvents ,
46
+ } from './SchedulerProfiling' ;
29
47
30
48
// Max 31 bit integer. The max integer size in V8 for 32-bit systems.
31
49
// Math.pow(2, 30) - 1
@@ -46,7 +64,7 @@ var taskQueue = [];
46
64
var timerQueue = [ ] ;
47
65
48
66
// Incrementing id counter. Used to maintain insertion order.
49
- var taskIdCounter = 0 ;
67
+ var taskIdCounter = 1 ;
50
68
51
69
// Pausing the scheduler is useful for debugging.
52
70
var isSchedulerPaused = false ;
@@ -60,15 +78,6 @@ var isPerformingWork = false;
60
78
var isHostCallbackScheduled = false ;
61
79
var isHostTimeoutScheduled = false ;
62
80
63
- function flushTask ( task , callback , currentTime ) {
64
- currentPriorityLevel = task . priorityLevel ;
65
- var didUserCallbackTimeout = task . expirationTime <= currentTime ;
66
- var continuationCallback = callback ( didUserCallbackTimeout ) ;
67
- return typeof continuationCallback === 'function'
68
- ? continuationCallback
69
- : null ;
70
- }
71
-
72
81
function advanceTimers ( currentTime ) {
73
82
// Check for tasks that are no longer delayed and add them to the queue.
74
83
let timer = peek ( timerQueue ) ;
@@ -81,6 +90,10 @@ function advanceTimers(currentTime) {
81
90
pop ( timerQueue ) ;
82
91
timer . sortIndex = timer . expirationTime ;
83
92
push ( taskQueue , timer ) ;
93
+ if ( enableProfiling ) {
94
+ markTaskStart ( timer ) ;
95
+ timer . isQueued = true ;
96
+ }
84
97
} else {
85
98
// Remaining timers are pending.
86
99
return ;
@@ -107,6 +120,10 @@ function handleTimeout(currentTime) {
107
120
}
108
121
109
122
function flushWork ( hasTimeRemaining , initialTime ) {
123
+ if ( enableProfiling ) {
124
+ markSchedulerUnsuspended ( initialTime ) ;
125
+ }
126
+
110
127
// We'll need a host callback the next time work is scheduled.
111
128
isHostCallbackScheduled = false ;
112
129
if ( isHostTimeoutScheduled ) {
@@ -118,52 +135,82 @@ function flushWork(hasTimeRemaining, initialTime) {
118
135
isPerformingWork = true ;
119
136
const previousPriorityLevel = currentPriorityLevel ;
120
137
try {
121
- let currentTime = initialTime ;
122
- advanceTimers ( currentTime ) ;
123
- currentTask = peek ( taskQueue ) ;
124
- while (
125
- currentTask !== null &&
126
- ! ( enableSchedulerDebugging && isSchedulerPaused )
127
- ) {
128
- if (
129
- currentTask . expirationTime > currentTime &&
130
- ( ! hasTimeRemaining || shouldYieldToHost ( ) )
131
- ) {
132
- // This currentTask hasn't expired, and we've reached the deadline.
133
- break ;
134
- }
135
- const callback = currentTask . callback ;
136
- if ( callback !== null ) {
137
- currentTask . callback = null ;
138
- const continuation = flushTask ( currentTask , callback , currentTime ) ;
139
- if ( continuation !== null ) {
140
- currentTask . callback = continuation ;
141
- } else {
142
- if ( currentTask === peek ( taskQueue ) ) {
143
- pop ( taskQueue ) ;
144
- }
138
+ if ( enableProfiling ) {
139
+ try {
140
+ return workLoop ( hasTimeRemaining , initialTime ) ;
141
+ } catch ( error ) {
142
+ if ( currentTask !== null ) {
143
+ const currentTime = getCurrentTime ( ) ;
144
+ markTaskErrored ( currentTask , currentTime ) ;
145
+ currentTask . isQueued = false ;
145
146
}
146
- currentTime = getCurrentTime ( ) ;
147
- advanceTimers ( currentTime ) ;
148
- } else {
149
- pop ( taskQueue ) ;
147
+ throw error ;
150
148
}
151
- currentTask = peek ( taskQueue ) ;
152
- }
153
- // Return whether there's additional work
154
- if ( currentTask !== null ) {
155
- return true ;
156
149
} else {
157
- let firstTimer = peek ( timerQueue ) ;
158
- if ( firstTimer !== null ) {
159
- requestHostTimeout ( handleTimeout , firstTimer . startTime - currentTime ) ;
160
- }
161
- return false ;
150
+ // No catch in prod codepath.
151
+ return workLoop ( hasTimeRemaining , initialTime ) ;
162
152
}
163
153
} finally {
164
154
currentTask = null ;
165
155
currentPriorityLevel = previousPriorityLevel ;
166
156
isPerformingWork = false ;
157
+ if ( enableProfiling ) {
158
+ const currentTime = getCurrentTime ( ) ;
159
+ markSchedulerSuspended ( currentTime ) ;
160
+ }
161
+ }
162
+ }
163
+
164
+ function workLoop ( hasTimeRemaining , initialTime ) {
165
+ let currentTime = initialTime ;
166
+ advanceTimers ( currentTime ) ;
167
+ currentTask = peek ( taskQueue ) ;
168
+ while (
169
+ currentTask !== null &&
170
+ ! ( enableSchedulerDebugging && isSchedulerPaused )
171
+ ) {
172
+ if (
173
+ currentTask . expirationTime > currentTime &&
174
+ ( ! hasTimeRemaining || shouldYieldToHost ( ) )
175
+ ) {
176
+ // This currentTask hasn't expired, and we've reached the deadline.
177
+ break ;
178
+ }
179
+ const callback = currentTask . callback ;
180
+ if ( callback !== null ) {
181
+ currentTask . callback = null ;
182
+ currentPriorityLevel = currentTask . priorityLevel ;
183
+ const didUserCallbackTimeout = currentTask . expirationTime <= currentTime ;
184
+ markTaskRun ( currentTask , currentTime ) ;
185
+ const continuationCallback = callback ( didUserCallbackTimeout ) ;
186
+ currentTime = getCurrentTime ( ) ;
187
+ if ( typeof continuationCallback === 'function' ) {
188
+ currentTask . callback = continuationCallback ;
189
+ markTaskYield ( currentTask , currentTime ) ;
190
+ } else {
191
+ if ( enableProfiling ) {
192
+ markTaskCompleted ( currentTask , currentTime ) ;
193
+ currentTask . isQueued = false ;
194
+ }
195
+ if ( currentTask === peek ( taskQueue ) ) {
196
+ pop ( taskQueue ) ;
197
+ }
198
+ }
199
+ advanceTimers ( currentTime ) ;
200
+ } else {
201
+ pop ( taskQueue ) ;
202
+ }
203
+ currentTask = peek ( taskQueue ) ;
204
+ }
205
+ // Return whether there's additional work
206
+ if ( currentTask !== null ) {
207
+ return true ;
208
+ } else {
209
+ let firstTimer = peek ( timerQueue ) ;
210
+ if ( firstTimer !== null ) {
211
+ requestHostTimeout ( handleTimeout , firstTimer . startTime - currentTime ) ;
212
+ }
213
+ return false ;
167
214
}
168
215
}
169
216
@@ -276,6 +323,9 @@ function unstable_scheduleCallback(priorityLevel, callback, options) {
276
323
expirationTime,
277
324
sortIndex : - 1 ,
278
325
} ;
326
+ if ( enableProfiling ) {
327
+ newTask . isQueued = false ;
328
+ }
279
329
280
330
if ( startTime > currentTime ) {
281
331
// This is a delayed task.
@@ -295,6 +345,10 @@ function unstable_scheduleCallback(priorityLevel, callback, options) {
295
345
} else {
296
346
newTask . sortIndex = expirationTime ;
297
347
push ( taskQueue , newTask ) ;
348
+ if ( enableProfiling ) {
349
+ markTaskStart ( newTask , currentTime ) ;
350
+ newTask . isQueued = true ;
351
+ }
298
352
// Schedule a host callback, if needed. If we're already performing work,
299
353
// wait until the next time we yield.
300
354
if ( ! isHostCallbackScheduled && ! isPerformingWork ) {
@@ -323,9 +377,17 @@ function unstable_getFirstCallbackNode() {
323
377
}
324
378
325
379
function unstable_cancelCallback ( task ) {
326
- // Null out the callback to indicate the task has been canceled. (Can't remove
327
- // from the queue because you can't remove arbitrary nodes from an array based
328
- // heap, only the first one.)
380
+ if ( enableProfiling ) {
381
+ if ( task . isQueued ) {
382
+ const currentTime = getCurrentTime ( ) ;
383
+ markTaskCanceled ( task , currentTime ) ;
384
+ task . isQueued = false ;
385
+ }
386
+ }
387
+
388
+ // Null out the callback to indicate the task has been canceled. (Can't
389
+ // remove from the queue because you can't remove arbitrary nodes from an
390
+ // array based heap, only the first one.)
329
391
task . callback = null ;
330
392
}
331
393
@@ -370,3 +432,11 @@ export {
370
432
getCurrentTime as unstable_now ,
371
433
forceFrameRate as unstable_forceFrameRate ,
372
434
} ;
435
+
436
+ export const unstable_Profiling = enableProfiling
437
+ ? {
438
+ startLoggingProfilingEvents,
439
+ stopLoggingProfilingEvents,
440
+ sharedProfilingBuffer,
441
+ }
442
+ : null ;
0 commit comments