@@ -21,14 +21,13 @@ import {
2121 is_updating_effect ,
2222 set_is_updating_effect ,
2323 set_signal_status ,
24- update_effect ,
25- write_version
24+ update_effect
2625} from '../runtime.js' ;
2726import * as e from '../errors.js' ;
2827import { flush_tasks } from '../dom/task.js' ;
2928import { DEV } from 'esm-env' ;
3029import { invoke_error_boundary } from '../error-handling.js' ;
31- import { old_values } from './sources.js' ;
30+ import { mark_reactions , old_values } from './sources.js' ;
3231import { unlink_effect } from './effects.js' ;
3332import { unset_context } from './async.js' ;
3433
@@ -70,13 +69,15 @@ let last_scheduled_effect = null;
7069
7170let is_flushing = false ;
7271
72+ let is_flushing_sync = false ;
73+
7374export class Batch {
7475 /**
7576 * The current values of any sources that are updated in this batch
7677 * They keys of this map are identical to `this.#previous`
7778 * @type {Map<Source, any> }
7879 */
79- # current = new Map ( ) ;
80+ current = new Map ( ) ;
8081
8182 /**
8283 * The values of any sources that are updated in this batch _before_ those updates took place.
@@ -156,7 +157,7 @@ export class Batch {
156157 *
157158 * @param {Effect[] } root_effects
158159 */
159- # process( root_effects ) {
160+ process ( root_effects ) {
160161 queued_root_effects = [ ] ;
161162
162163 /** @type {Map<Source, { v: unknown, wv: number }> | null } */
@@ -169,7 +170,7 @@ export class Batch {
169170 current_values = new Map ( ) ;
170171 batch_deriveds = new Map ( ) ;
171172
172- for ( const [ source , current ] of this . # current) {
173+ for ( const [ source , current ] of this . current ) {
173174 current_values . set ( source , { v : source . v , wv : source . wv } ) ;
174175 source . v = current ;
175176 }
@@ -202,9 +203,22 @@ export class Batch {
202203 this . #effects = [ ] ;
203204 this . #block_effects = [ ] ;
204205
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+
205210 flush_queued_effects ( render_effects ) ;
206211 flush_queued_effects ( effects ) ;
207212
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+
208222 this . #deferred?. resolve ( ) ;
209223 } else {
210224 // otherwise mark effects clean so they get scheduled on the next run
@@ -300,7 +314,7 @@ export class Batch {
300314 this . #previous. set ( source , value ) ;
301315 }
302316
303- this . # current. set ( source , source . v ) ;
317+ this . current . set ( source , source . v ) ;
304318 }
305319
306320 activate ( ) {
@@ -327,13 +341,13 @@ export class Batch {
327341
328342 flush ( ) {
329343 if ( queued_root_effects . length > 0 ) {
330- this . flush_effects ( ) ;
344+ flush_effects ( ) ;
331345 } else {
332346 this . #commit( ) ;
333347 }
334348
335349 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()`,
337351 // which is permitted in legacy mode despite being a terrible idea
338352 return ;
339353 }
@@ -345,52 +359,6 @@ export class Batch {
345359 this . deactivate ( ) ;
346360 }
347361
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-
394362 /**
395363 * Append and remove branches to/from the DOM
396364 */
@@ -412,19 +380,8 @@ export class Batch {
412380 this . #pending -= 1 ;
413381
414382 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 ) ;
428385 }
429386
430387 this . #render_effects = [ ] ;
@@ -445,12 +402,12 @@ export class Batch {
445402 return ( this . #deferred ??= deferred ( ) ) . promise ;
446403 }
447404
448- static ensure ( autoflush = true ) {
405+ static ensure ( ) {
449406 if ( current_batch === null ) {
450407 const batch = ( current_batch = new Batch ( ) ) ;
451408 batches . add ( current_batch ) ;
452409
453- if ( autoflush ) {
410+ if ( ! is_flushing_sync ) {
454411 Batch . enqueue ( ( ) => {
455412 if ( current_batch !== batch ) {
456413 // a flushSync happened in the meantime
@@ -487,32 +444,85 @@ export function flushSync(fn) {
487444 e . flush_sync_in_effect ( ) ;
488445 }
489446
490- var result ;
447+ var was_flushing_sync = is_flushing_sync ;
448+ is_flushing_sync = true ;
491449
492- const batch = Batch . ensure ( false ) ;
450+ try {
451+ var result ;
493452
494- if ( fn ) {
495- batch . flush_effects ( ) ;
453+ if ( fn ) {
454+ flush_effects ( ) ;
455+ result = fn ( ) ;
456+ }
496457
497- result = fn ( ) ;
498- }
458+ while ( true ) {
459+ flush_tasks ( ) ;
499460
500- while ( true ) {
501- flush_tasks ( ) ;
461+ if ( queued_root_effects . length === 0 ) {
462+ current_batch ?. flush ( ) ;
502463
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+ }
506472 }
507473
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+ }
511517
512- return /** @type {T } */ ( result ) ;
518+ batch . process ( queued_root_effects ) ;
519+ old_values . clear ( ) ;
513520 }
521+ } finally {
522+ is_flushing = false ;
523+ set_is_updating_effect ( was_updating_effect ) ;
514524
515- batch . flush_effects ( ) ;
525+ last_scheduled_effect = null ;
516526 }
517527}
518528
@@ -545,7 +555,7 @@ function flush_queued_effects(effects) {
545555 var effect = effects [ i ++ ] ;
546556
547557 if ( ( effect . f & ( DESTROYED | INERT ) ) === 0 && is_dirty ( effect ) ) {
548- var wv = write_version ;
558+ var n = current_batch ? current_batch . current . size : 0 ;
549559
550560 update_effect ( effect ) ;
551561
@@ -568,7 +578,11 @@ function flush_queued_effects(effects) {
568578
569579 // if state is written in a user effect, abort and re-schedule, lest we run
570580 // 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+ ) {
572586 break ;
573587 }
574588 }
0 commit comments