@@ -28,7 +28,7 @@ import * as e from '../errors.js';
2828import { flush_tasks } from '../dom/task.js' ;
2929import { DEV } from 'esm-env' ;
3030import { invoke_error_boundary } from '../error-handling.js' ;
31- import { old_values } from './sources.js' ;
31+ import { mark_reactions , old_values } from './sources.js' ;
3232import { unlink_effect } from './effects.js' ;
3333import { unset_context } from './async.js' ;
3434
@@ -70,13 +70,15 @@ let last_scheduled_effect = null;
7070
7171let is_flushing = false ;
7272
73+ let flushing_sync = false ;
74+
7375export class Batch {
7476 /**
7577 * The current values of any sources that are updated in this batch
7678 * They keys of this map are identical to `this.#previous`
7779 * @type {Map<Source, any> }
7880 */
79- # current = new Map ( ) ;
81+ current = new Map ( ) ;
8082
8183 /**
8284 * The values of any sources that are updated in this batch _before_ those updates took place.
@@ -156,7 +158,7 @@ export class Batch {
156158 *
157159 * @param {Effect[] } root_effects
158160 */
159- # process( root_effects ) {
161+ process ( root_effects ) {
160162 queued_root_effects = [ ] ;
161163
162164 /** @type {Map<Source, { v: unknown, wv: number }> | null } */
@@ -169,7 +171,7 @@ export class Batch {
169171 current_values = new Map ( ) ;
170172 batch_deriveds = new Map ( ) ;
171173
172- for ( const [ source , current ] of this . # current) {
174+ for ( const [ source , current ] of this . current ) {
173175 current_values . set ( source , { v : source . v , wv : source . wv } ) ;
174176 source . v = current ;
175177 }
@@ -300,7 +302,7 @@ export class Batch {
300302 this . #previous. set ( source , value ) ;
301303 }
302304
303- this . # current. set ( source , source . v ) ;
305+ this . current . set ( source , source . v ) ;
304306 }
305307
306308 activate ( ) {
@@ -346,49 +348,7 @@ export class Batch {
346348 }
347349
348350 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- }
351+ flush_effects ( ) ;
392352 }
393353
394354 /**
@@ -412,19 +372,8 @@ export class Batch {
412372 this . #pending -= 1 ;
413373
414374 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 ) ;
375+ for ( const source of this . current . keys ( ) ) {
376+ mark_reactions ( source , DIRTY , false ) ;
428377 }
429378
430379 this . #render_effects = [ ] ;
@@ -487,32 +436,88 @@ export function flushSync(fn) {
487436 e . flush_sync_in_effect ( ) ;
488437 }
489438
490- var result ;
439+ var prev_flushing_sync = flushing_sync ;
440+ flushing_sync = true ;
491441
492- const batch = Batch . ensure ( false ) ;
442+ try {
443+ var result ;
493444
494- if ( fn ) {
495- batch . flush_effects ( ) ;
445+ const batch = Batch . ensure ( false ) ;
496446
497- result = fn ( ) ;
498- }
447+ if ( fn ) {
448+ batch . flush_effects ( ) ;
499449
500- while ( true ) {
501- flush_tasks ( ) ;
450+ result = fn ( ) ;
451+ }
452+
453+ while ( true ) {
454+ flush_tasks ( ) ;
455+
456+ if ( queued_root_effects . length === 0 ) {
457+ // TODO this might need adjustment
458+ if ( batch === current_batch ) {
459+ batch . flush ( ) ;
460+ }
461+
462+ // this would be reset in `batch.flush_effects()` but since we are early returning here,
463+ // we need to reset it here as well in case the first time there's 0 queued root effects
464+ last_scheduled_effect = null ;
502465
503- if ( queued_root_effects . length === 0 ) {
504- if ( batch === current_batch ) {
505- batch . flush ( ) ;
466+ return /** @type {T } */ ( result ) ;
506467 }
507468
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 ;
469+ batch . flush_effects ( ) ;
470+ }
471+ } finally {
472+ flushing_sync = prev_flushing_sync ;
473+ }
474+ }
511475
512- return /** @type {T } */ ( result ) ;
476+ function flush_effects ( ) {
477+ var was_updating_effect = is_updating_effect ;
478+ is_flushing = true ;
479+
480+ try {
481+ var flush_count = 0 ;
482+ var batch = /** @type {Batch } */ ( current_batch ) ;
483+ set_is_updating_effect ( true ) ;
484+
485+ while ( queued_root_effects . length > 0 ) {
486+ if ( flush_count ++ > 1000 ) {
487+ if ( DEV ) {
488+ var updates = new Map ( ) ;
489+
490+ for ( const source of batch . current . keys ( ) ) {
491+ for ( const [ stack , update ] of source . updated ?? [ ] ) {
492+ var entry = updates . get ( stack ) ;
493+
494+ if ( ! entry ) {
495+ entry = { error : update . error , count : 0 } ;
496+ updates . set ( stack , entry ) ;
497+ }
498+
499+ entry . count += update . count ;
500+ }
501+ }
502+
503+ for ( const update of updates . values ( ) ) {
504+ // eslint-disable-next-line no-console
505+ console . error ( update . error ) ;
506+ }
507+ }
508+
509+ infinite_loop_guard ( ) ;
510+ }
511+
512+ batch = /** @type {Batch } */ ( current_batch ) ;
513+ batch . process ( queued_root_effects ) ;
514+ old_values . clear ( ) ;
513515 }
516+ } finally {
517+ is_flushing = false ;
518+ set_is_updating_effect ( was_updating_effect ) ;
514519
515- batch . flush_effects ( ) ;
520+ last_scheduled_effect = null ;
516521 }
517522}
518523
@@ -545,6 +550,7 @@ function flush_queued_effects(effects) {
545550 if ( ( effect . f & ( DESTROYED | INERT ) ) === 0 ) {
546551 if ( is_dirty ( effect ) ) {
547552 var wv = write_version ;
553+ var current_size = /** @type {Batch } */ ( current_batch ) . current . size ;
548554
549555 update_effect ( effect ) ;
550556
@@ -568,6 +574,22 @@ function flush_queued_effects(effects) {
568574 // if state is written in a user effect, abort and re-schedule, lest we run
569575 // effects that should be removed as a result of the state change
570576 if ( write_version > wv && ( effect . f & USER_EFFECT ) !== 0 ) {
577+ // Work needs to happen in a separate batch, else prior sources would be mixed with
578+ // newly updated sources, which could lead to infinite loops when effects run over and over again.
579+ // We need to bring over the just written sources though to correctly mark the right reactions as dirty.
580+ var old_batch = /** @type {Batch } */ ( current_batch ) ;
581+ batches . delete ( old_batch ) ;
582+ current_batch = null ;
583+ var new_batch = Batch . ensure ( ! flushing_sync ) ;
584+ var current_idx = 0 ;
585+ // We're taking advantage of the spec here which says that entries in a Map are traversed by insertion order
586+ for ( const source of old_batch . current ) {
587+ if ( current_idx >= current_size ) {
588+ new_batch . capture ( source [ 0 ] , source [ 1 ] ) ;
589+ }
590+ current_idx ++ ;
591+ }
592+ i ++ ;
571593 break ;
572594 }
573595 }
0 commit comments