@@ -21,14 +21,13 @@ import {
21
21
is_updating_effect ,
22
22
set_is_updating_effect ,
23
23
set_signal_status ,
24
- update_effect ,
25
- write_version
24
+ update_effect
26
25
} from '../runtime.js' ;
27
26
import * as e from '../errors.js' ;
28
27
import { flush_tasks } from '../dom/task.js' ;
29
28
import { DEV } from 'esm-env' ;
30
29
import { invoke_error_boundary } from '../error-handling.js' ;
31
- import { old_values } from './sources.js' ;
30
+ import { mark_reactions , old_values } from './sources.js' ;
32
31
import { unlink_effect } from './effects.js' ;
33
32
import { unset_context } from './async.js' ;
34
33
@@ -70,13 +69,15 @@ let last_scheduled_effect = null;
70
69
71
70
let is_flushing = false ;
72
71
72
+ let is_flushing_sync = false ;
73
+
73
74
export class Batch {
74
75
/**
75
76
* The current values of any sources that are updated in this batch
76
77
* They keys of this map are identical to `this.#previous`
77
78
* @type {Map<Source, any> }
78
79
*/
79
- # current = new Map ( ) ;
80
+ current = new Map ( ) ;
80
81
81
82
/**
82
83
* The values of any sources that are updated in this batch _before_ those updates took place.
@@ -156,7 +157,7 @@ export class Batch {
156
157
*
157
158
* @param {Effect[] } root_effects
158
159
*/
159
- # process( root_effects ) {
160
+ process ( root_effects ) {
160
161
queued_root_effects = [ ] ;
161
162
162
163
/** @type {Map<Source, { v: unknown, wv: number }> | null } */
@@ -169,7 +170,7 @@ export class Batch {
169
170
current_values = new Map ( ) ;
170
171
batch_deriveds = new Map ( ) ;
171
172
172
- for ( const [ source , current ] of this . # current) {
173
+ for ( const [ source , current ] of this . current ) {
173
174
current_values . set ( source , { v : source . v , wv : source . wv } ) ;
174
175
source . v = current ;
175
176
}
@@ -202,9 +203,22 @@ export class Batch {
202
203
this . #effects = [ ] ;
203
204
this . #block_effects = [ ] ;
204
205
206
+ // If sources are written to, then work needs to happen in a separate batch, else prior sources would be mixed with
207
+ // newly updated sources, which could lead to infinite loops when effects run over and over again.
208
+ current_batch = null ;
209
+
205
210
flush_queued_effects ( render_effects ) ;
206
211
flush_queued_effects ( effects ) ;
207
212
213
+ // Reinstate the current batch if there was no new one created, as `process()` runs in a loop in `flush_effects()`.
214
+ // That method expects `current_batch` to be set, and could run the loop again if effects result in new effects
215
+ // being scheduled but without writes happening in which case no new batch is created.
216
+ if ( current_batch === null ) {
217
+ current_batch = this ;
218
+ } else {
219
+ batches . delete ( this ) ;
220
+ }
221
+
208
222
this . #deferred?. resolve ( ) ;
209
223
} else {
210
224
// otherwise mark effects clean so they get scheduled on the next run
@@ -300,7 +314,7 @@ export class Batch {
300
314
this . #previous. set ( source , value ) ;
301
315
}
302
316
303
- this . # current. set ( source , source . v ) ;
317
+ this . current . set ( source , source . v ) ;
304
318
}
305
319
306
320
activate ( ) {
@@ -327,13 +341,13 @@ export class Batch {
327
341
328
342
flush ( ) {
329
343
if ( queued_root_effects . length > 0 ) {
330
- this . flush_effects ( ) ;
344
+ flush_effects ( ) ;
331
345
} else {
332
346
this . #commit( ) ;
333
347
}
334
348
335
349
if ( current_batch !== this ) {
336
- // this can happen if a `flushSync` occurred during `this. flush_effects()`,
350
+ // this can happen if a `flushSync` occurred during `flush_effects()`,
337
351
// which is permitted in legacy mode despite being a terrible idea
338
352
return ;
339
353
}
@@ -345,52 +359,6 @@ export class Batch {
345
359
this . deactivate ( ) ;
346
360
}
347
361
348
- flush_effects ( ) {
349
- var was_updating_effect = is_updating_effect ;
350
- is_flushing = true ;
351
-
352
- try {
353
- var flush_count = 0 ;
354
- set_is_updating_effect ( true ) ;
355
-
356
- while ( queued_root_effects . length > 0 ) {
357
- if ( flush_count ++ > 1000 ) {
358
- if ( DEV ) {
359
- var updates = new Map ( ) ;
360
-
361
- for ( const source of this . #current. keys ( ) ) {
362
- for ( const [ stack , update ] of source . updated ?? [ ] ) {
363
- var entry = updates . get ( stack ) ;
364
-
365
- if ( ! entry ) {
366
- entry = { error : update . error , count : 0 } ;
367
- updates . set ( stack , entry ) ;
368
- }
369
-
370
- entry . count += update . count ;
371
- }
372
- }
373
-
374
- for ( const update of updates . values ( ) ) {
375
- // eslint-disable-next-line no-console
376
- console . error ( update . error ) ;
377
- }
378
- }
379
-
380
- infinite_loop_guard ( ) ;
381
- }
382
-
383
- this . #process( queued_root_effects ) ;
384
- old_values . clear ( ) ;
385
- }
386
- } finally {
387
- is_flushing = false ;
388
- set_is_updating_effect ( was_updating_effect ) ;
389
-
390
- last_scheduled_effect = null ;
391
- }
392
- }
393
-
394
362
/**
395
363
* Append and remove branches to/from the DOM
396
364
*/
@@ -412,19 +380,8 @@ export class Batch {
412
380
this . #pending -= 1 ;
413
381
414
382
if ( this . #pending === 0 ) {
415
- for ( const e of this . #render_effects) {
416
- set_signal_status ( e , DIRTY ) ;
417
- schedule_effect ( e ) ;
418
- }
419
-
420
- for ( const e of this . #effects) {
421
- set_signal_status ( e , DIRTY ) ;
422
- schedule_effect ( e ) ;
423
- }
424
-
425
- for ( const e of this . #block_effects) {
426
- set_signal_status ( e , DIRTY ) ;
427
- schedule_effect ( e ) ;
383
+ for ( const source of this . current . keys ( ) ) {
384
+ mark_reactions ( source , DIRTY , false ) ;
428
385
}
429
386
430
387
this . #render_effects = [ ] ;
@@ -445,12 +402,12 @@ export class Batch {
445
402
return ( this . #deferred ??= deferred ( ) ) . promise ;
446
403
}
447
404
448
- static ensure ( autoflush = true ) {
405
+ static ensure ( ) {
449
406
if ( current_batch === null ) {
450
407
const batch = ( current_batch = new Batch ( ) ) ;
451
408
batches . add ( current_batch ) ;
452
409
453
- if ( autoflush ) {
410
+ if ( ! is_flushing_sync ) {
454
411
Batch . enqueue ( ( ) => {
455
412
if ( current_batch !== batch ) {
456
413
// a flushSync happened in the meantime
@@ -487,32 +444,85 @@ export function flushSync(fn) {
487
444
e . flush_sync_in_effect ( ) ;
488
445
}
489
446
490
- var result ;
447
+ var was_flushing_sync = is_flushing_sync ;
448
+ is_flushing_sync = true ;
491
449
492
- const batch = Batch . ensure ( false ) ;
450
+ try {
451
+ var result ;
493
452
494
- if ( fn ) {
495
- batch . flush_effects ( ) ;
453
+ if ( fn ) {
454
+ flush_effects ( ) ;
455
+ result = fn ( ) ;
456
+ }
496
457
497
- result = fn ( ) ;
498
- }
458
+ while ( true ) {
459
+ flush_tasks ( ) ;
499
460
500
- while ( true ) {
501
- flush_tasks ( ) ;
461
+ if ( queued_root_effects . length === 0 ) {
462
+ current_batch ?. flush ( ) ;
502
463
503
- if ( queued_root_effects . length === 0 ) {
504
- if ( batch === current_batch ) {
505
- batch . flush ( ) ;
464
+ // we need to check again, in case we just updated an `$effect.pending()`
465
+ if ( queued_root_effects . length === 0 ) {
466
+ // this would be reset in `flush_effects()` but since we are early returning here,
467
+ // we need to reset it here as well in case the first time there's 0 queued root effects
468
+ last_scheduled_effect = null ;
469
+
470
+ return /** @type {T } */ ( result ) ;
471
+ }
506
472
}
507
473
508
- // this would be reset in `batch.flush_effects()` but since we are early returning here,
509
- // we need to reset it here as well in case the first time there's 0 queued root effects
510
- last_scheduled_effect = null ;
474
+ flush_effects ( ) ;
475
+ }
476
+ } finally {
477
+ is_flushing_sync = was_flushing_sync ;
478
+ }
479
+ }
480
+
481
+ function flush_effects ( ) {
482
+ var was_updating_effect = is_updating_effect ;
483
+ is_flushing = true ;
484
+
485
+ try {
486
+ var flush_count = 0 ;
487
+ set_is_updating_effect ( true ) ;
488
+
489
+ while ( queued_root_effects . length > 0 ) {
490
+ var batch = Batch . ensure ( ) ;
491
+
492
+ if ( flush_count ++ > 1000 ) {
493
+ if ( DEV ) {
494
+ var updates = new Map ( ) ;
495
+
496
+ for ( const source of batch . current . keys ( ) ) {
497
+ for ( const [ stack , update ] of source . updated ?? [ ] ) {
498
+ var entry = updates . get ( stack ) ;
499
+
500
+ if ( ! entry ) {
501
+ entry = { error : update . error , count : 0 } ;
502
+ updates . set ( stack , entry ) ;
503
+ }
504
+
505
+ entry . count += update . count ;
506
+ }
507
+ }
508
+
509
+ for ( const update of updates . values ( ) ) {
510
+ // eslint-disable-next-line no-console
511
+ console . error ( update . error ) ;
512
+ }
513
+ }
514
+
515
+ infinite_loop_guard ( ) ;
516
+ }
511
517
512
- return /** @type {T } */ ( result ) ;
518
+ batch . process ( queued_root_effects ) ;
519
+ old_values . clear ( ) ;
513
520
}
521
+ } finally {
522
+ is_flushing = false ;
523
+ set_is_updating_effect ( was_updating_effect ) ;
514
524
515
- batch . flush_effects ( ) ;
525
+ last_scheduled_effect = null ;
516
526
}
517
527
}
518
528
@@ -545,7 +555,7 @@ function flush_queued_effects(effects) {
545
555
var effect = effects [ i ++ ] ;
546
556
547
557
if ( ( effect . f & ( DESTROYED | INERT ) ) === 0 && is_dirty ( effect ) ) {
548
- var wv = write_version ;
558
+ var n = current_batch ? current_batch . current . size : 0 ;
549
559
550
560
update_effect ( effect ) ;
551
561
@@ -568,7 +578,11 @@ function flush_queued_effects(effects) {
568
578
569
579
// if state is written in a user effect, abort and re-schedule, lest we run
570
580
// effects that should be removed as a result of the state change
571
- if ( write_version > wv && ( effect . f & USER_EFFECT ) !== 0 ) {
581
+ if (
582
+ current_batch !== null &&
583
+ current_batch . current . size > n &&
584
+ ( effect . f & USER_EFFECT ) !== 0
585
+ ) {
572
586
break ;
573
587
}
574
588
}
0 commit comments