@@ -115,7 +115,7 @@ export type Update<State> = {|
115
115
payload : any ,
116
116
callback : ( ( ) => mixed ) | null ,
117
117
118
- next : Update < State > ,
118
+ next : Update < State > | null ,
119
119
120
120
// DEV only
121
121
priority ?: ReactPriorityLevel ,
@@ -125,7 +125,8 @@ type SharedQueue<State> = {|pending: Update<State> | null|};
125
125
126
126
export type UpdateQueue < State > = { |
127
127
baseState : State ,
128
- baseQueue : Update < State > | null ,
128
+ firstBaseUpdate : Update < State > | null ,
129
+ lastBaseUpdate : Update < State > | null ,
129
130
shared : SharedQueue < State > ,
130
131
effects : Array < Update < State >> | null ,
131
132
| } ;
@@ -154,7 +155,8 @@ if (__DEV__) {
154
155
export function initializeUpdateQueue < State > ( fiber : Fiber ) : void {
155
156
const queue : UpdateQueue < State > = {
156
157
baseState : fiber . memoizedState ,
157
- baseQueue : null ,
158
+ firstBaseUpdate : null ,
159
+ lastBaseUpdate : null ,
158
160
shared : {
159
161
pending : null ,
160
162
} ,
@@ -173,7 +175,8 @@ export function cloneUpdateQueue<State>(
173
175
if (queue === currentQueue) {
174
176
const clone : UpdateQueue < State > = {
175
177
baseState : currentQueue . baseState ,
176
- baseQueue : currentQueue . baseQueue ,
178
+ firstBaseUpdate : currentQueue . firstBaseUpdate ,
179
+ lastBaseUpdate : currentQueue . lastBaseUpdate ,
177
180
shared : currentQueue . shared ,
178
181
effects : currentQueue . effects ,
179
182
} ;
@@ -193,9 +196,8 @@ export function createUpdate(
193
196
payload : null ,
194
197
callback : null ,
195
198
196
- next : ( null : any ) ,
199
+ next : null ,
197
200
} ;
198
- update . next = update ;
199
201
if ( __DEV__ ) {
200
202
update . priority = getCurrentPriorityLevel ( ) ;
201
203
}
@@ -249,14 +251,13 @@ export function enqueueCapturedUpdate<State>(
249
251
// Captured updates go only on the work-in-progress queue.
250
252
const queue : UpdateQueue < State > = ( workInProgress . updateQueue : any ) ;
251
253
// Append the update to the end of the list.
252
- const last = queue . baseQueue ;
254
+ const last = queue . lastBaseUpdate ;
253
255
if ( last === null ) {
254
- queue . baseQueue = update . next = update ;
255
- update . next = update ;
256
+ queue . firstBaseUpdate = update ;
256
257
} else {
257
- update . next = last . next ;
258
258
last . next = update ;
259
259
}
260
+ queue.lastBaseUpdate = update;
260
261
}
261
262
262
263
function getStateFromUpdate < State > (
@@ -347,147 +348,160 @@ export function processUpdateQueue<State>(
347
348
currentlyProcessingQueue = queue . shared ;
348
349
}
349
350
350
- // The last rebase update that is NOT part of the base state.
351
- let baseQueue = queue . baseQueue ;
351
+ let firstBaseUpdate = queue . firstBaseUpdate ;
352
+ let lastBaseUpdate = queue . lastBaseUpdate ;
352
353
353
- // The last pending update that hasn't been processed yet .
354
+ // Check if there are pending updates. If so, transfer them to the base queue .
354
355
let pendingQueue = queue . shared . pending ;
355
356
if ( pendingQueue !== null ) {
356
- // We have new updates that haven't been processed yet.
357
- // We'll add them to the base queue.
358
- if ( baseQueue !== null ) {
359
- // Merge the pending queue and the base queue.
360
- let baseFirst = baseQueue . next ;
361
- let pendingFirst = pendingQueue . next ;
362
- baseQueue . next = pendingFirst ;
363
- pendingQueue . next = baseFirst ;
364
- }
357
+ queue . shared . pending = null ;
365
358
366
- baseQueue = pendingQueue ;
359
+ // The pending queue is circular. Disconnect the pointer between first
360
+ // and last so that it's non-circular.
361
+ const lastPendingUpdate = pendingQueue ;
362
+ const firstPendingUpdate = lastPendingUpdate . next ;
363
+ lastPendingUpdate . next = null ;
364
+ // Append pending updates to base queue
365
+ if ( lastBaseUpdate === null ) {
366
+ firstBaseUpdate = firstPendingUpdate ;
367
+ } else {
368
+ lastBaseUpdate. next = firstPendingUpdate ;
369
+ }
370
+ lastBaseUpdate = lastPendingUpdate;
367
371
368
- queue . shared . pending = null ;
372
+ // If there's a current queue, and it's different from the base queue, then
373
+ // we need to transfer the updates to that queue, too. Because the base
374
+ // queue is a singly-linked list with no cycles, we can append to both
375
+ // lists and take advantage of structural sharing.
369
376
// TODO: Pass `current` as argument
370
377
const current = workInProgress.alternate;
371
378
if (current !== null) {
372
- const currentQueue = current . updateQueue ;
373
- if ( currentQueue !== null ) {
374
- currentQueue . baseQueue = pendingQueue ;
379
+ // This is always non-null on a ClassComponent or HostRoot
380
+ const currentQueue : UpdateQueue < State > = ( current . updateQueue : any ) ;
381
+ const currentLastBaseUpdate = currentQueue . lastBaseUpdate ;
382
+ if ( currentLastBaseUpdate !== lastBaseUpdate ) {
383
+ if ( currentLastBaseUpdate === null ) {
384
+ currentQueue . firstBaseUpdate = firstPendingUpdate ;
385
+ } else {
386
+ currentLastBaseUpdate . next = firstPendingUpdate ;
387
+ }
388
+ currentQueue . lastBaseUpdate = lastPendingUpdate ;
375
389
}
376
390
}
377
391
}
378
392
379
393
// These values may change as we process the queue.
380
- if (baseQueue !== null) {
381
- let first = baseQueue . next ;
394
+ if ( firstBaseUpdate !== null ) {
382
395
// Iterate through the list of updates to compute the result.
383
396
let newState = queue . baseState ;
384
397
let newExpirationTime = NoWork ;
385
398
386
399
let newBaseState = null ;
387
- let newBaseQueueFirst = null ;
388
- let newBaseQueueLast = null ;
389
-
390
- if ( first !== null ) {
391
- let update = first ;
392
- do {
393
- const updateExpirationTime = update . expirationTime ;
394
- if ( updateExpirationTime < renderExpirationTime ) {
395
- // Priority is insufficient. Skip this update. If this is the first
396
- // skipped update, the previous update/state is the new base
397
- // update/state.
400
+ let newFirstBaseUpdate = null ;
401
+ let newLastBaseUpdate = null ;
402
+
403
+ let update = firstBaseUpdate ;
404
+ do {
405
+ const updateExpirationTime = update . expirationTime ;
406
+ if ( updateExpirationTime < renderExpirationTime ) {
407
+ // Priority is insufficient. Skip this update. If this is the first
408
+ // skipped update, the previous update/state is the new base
409
+ // update/state.
410
+ const clone : Update < State > = {
411
+ expirationTime : update . expirationTime ,
412
+ suspenseConfig : update . suspenseConfig ,
413
+
414
+ tag : update . tag ,
415
+ payload : update . payload ,
416
+ callback : update . callback ,
417
+
418
+ next : null ,
419
+ } ;
420
+ if ( newLastBaseUpdate === null ) {
421
+ newFirstBaseUpdate = newLastBaseUpdate = clone ;
422
+ newBaseState = newState ;
423
+ } else {
424
+ newLastBaseUpdate = newLastBaseUpdate . next = clone ;
425
+ }
426
+ // Update the remaining priority in the queue.
427
+ if ( updateExpirationTime > newExpirationTime ) {
428
+ newExpirationTime = updateExpirationTime ;
429
+ }
430
+ } else {
431
+ // This update does have sufficient priority.
432
+
433
+ if ( newLastBaseUpdate !== null ) {
398
434
const clone : Update < State > = {
399
- expirationTime : update . expirationTime ,
435
+ expirationTime : Sync , // This update is going to be committed so we never want uncommit it.
400
436
suspenseConfig : update . suspenseConfig ,
401
437
402
438
tag : update . tag ,
403
439
payload : update . payload ,
404
440
callback : update . callback ,
405
441
406
- next : ( null : any ) ,
442
+ next : null ,
407
443
} ;
408
- if ( newBaseQueueLast === null ) {
409
- newBaseQueueFirst = newBaseQueueLast = clone ;
410
- newBaseState = newState ;
411
- } else {
412
- newBaseQueueLast = newBaseQueueLast . next = clone ;
413
- }
414
- // Update the remaining priority in the queue.
415
- if ( updateExpirationTime > newExpirationTime ) {
416
- newExpirationTime = updateExpirationTime ;
417
- }
418
- } else {
419
- // This update does have sufficient priority.
420
-
421
- if ( newBaseQueueLast !== null ) {
422
- const clone : Update < State > = {
423
- expirationTime : Sync , // This update is going to be committed so we never want uncommit it.
424
- suspenseConfig : update . suspenseConfig ,
425
-
426
- tag : update . tag ,
427
- payload : update . payload ,
428
- callback : update . callback ,
429
-
430
- next : ( null : any ) ,
431
- } ;
432
- newBaseQueueLast = newBaseQueueLast . next = clone ;
433
- }
434
-
435
- // Mark the event time of this update as relevant to this render pass.
436
- // TODO: This should ideally use the true event time of this update rather than
437
- // its priority which is a derived and not reverseable value.
438
- // TODO: We should skip this update if it was already committed but currently
439
- // we have no way of detecting the difference between a committed and suspended
440
- // update here.
441
- markRenderEventTimeAndConfig(
442
- updateExpirationTime,
443
- update.suspenseConfig,
444
- );
445
-
446
- // Process this update.
447
- newState = getStateFromUpdate(
448
- workInProgress,
449
- queue,
450
- update,
451
- newState,
452
- props,
453
- instance,
454
- );
455
- const callback = update.callback;
456
- if (callback !== null) {
457
- workInProgress . effectTag |= Callback ;
458
- let effects = queue . effects ;
459
- if ( effects === null ) {
460
- queue . effects = [ update ] ;
461
- } else {
462
- effects . push ( update ) ;
463
- }
464
- }
444
+ newLastBaseUpdate = newLastBaseUpdate . next = clone ;
465
445
}
466
- update = update . next ;
467
- if ( update === null || update === first ) {
468
- pendingQueue = queue . shared . pending ;
469
- if ( pendingQueue === null ) {
470
- break ;
446
+
447
+ // Mark the event time of this update as relevant to this render pass.
448
+ // TODO: This should ideally use the true event time of this update rather than
449
+ // its priority which is a derived and not reverseable value.
450
+ // TODO: We should skip this update if it was already committed but currently
451
+ // we have no way of detecting the difference between a committed and suspended
452
+ // update here.
453
+ markRenderEventTimeAndConfig(
454
+ updateExpirationTime,
455
+ update.suspenseConfig,
456
+ );
457
+
458
+ // Process this update.
459
+ newState = getStateFromUpdate(
460
+ workInProgress,
461
+ queue,
462
+ update,
463
+ newState,
464
+ props,
465
+ instance,
466
+ );
467
+ const callback = update.callback;
468
+ if (callback !== null) {
469
+ workInProgress . effectTag |= Callback ;
470
+ let effects = queue . effects ;
471
+ if ( effects === null ) {
472
+ queue . effects = [ update ] ;
471
473
} else {
472
- // An update was scheduled from inside a reducer. Add the new
473
- // pending updates to the end of the list and keep processing.
474
- update = baseQueue . next = pendingQueue . next ;
475
- pendingQueue . next = first ;
476
- queue . baseQueue = baseQueue = pendingQueue ;
477
- queue . shared . pending = null ;
474
+ effects . push ( update ) ;
478
475
}
479
476
}
480
- } while ( true ) ;
481
- }
477
+ }
478
+ update = update . next ;
479
+ if ( update === null ) {
480
+ pendingQueue = queue . shared . pending ;
481
+ if ( pendingQueue === null ) {
482
+ break ;
483
+ } else {
484
+ // An update was scheduled from inside a reducer. Add the new
485
+ // pending updates to the end of the list and keep processing.
486
+ const lastPendingUpdate = pendingQueue ;
487
+ // Intentionally unsound. Pending updates form a circular list, but we
488
+ // unravel them when transferring them to the base queue.
489
+ const firstPendingUpdate = ( ( lastPendingUpdate . next : any ) : Update < State > ) ;
490
+ lastPendingUpdate . next = null ;
491
+ update = firstPendingUpdate ;
492
+ queue . lastBaseUpdate = lastPendingUpdate ;
493
+ queue . shared . pending = null ;
494
+ }
495
+ }
496
+ } while (true);
482
497
483
- if ( newBaseQueueLast === null ) {
498
+ if (newLastBaseUpdate === null) {
484
499
newBaseState = newState ;
485
- } else {
486
- newBaseQueueLast . next = ( newBaseQueueFirst : any ) ;
487
500
}
488
501
489
502
queue.baseState = ((newBaseState: any): State);
490
- queue.baseQueue = newBaseQueueLast;
503
+ queue.firstBaseUpdate = newFirstBaseUpdate;
504
+ queue.lastBaseUpdate = newLastBaseUpdate;
491
505
492
506
// Set the remaining expiration time to be whatever is remaining in the queue.
493
507
// This should be fine because the only two other things that contribute to
0 commit comments