@ -21,14 +21,13 @@ import {
is _updating _effect ,
set _is _updating _effect ,
set _signal _status ,
update _effect ,
write _version
update _effect
} from '../runtime.js' ;
import * as e from '../errors.js' ;
import { flush _tasks } from '../dom/task.js' ;
import { DEV } from 'esm-env' ;
import { invoke _error _boundary } from '../error-handling.js' ;
import { old_values } from './sources.js' ;
import { mark_reactions , old_values } from './sources.js' ;
import { unlink _effect } from './effects.js' ;
import { unset _context } from './async.js' ;
@ -70,13 +69,15 @@ let last_scheduled_effect = null;
let is _flushing = false ;
let is _flushing _sync = false ;
export class Batch {
/ * *
* The current values of any sources that are updated in this batch
* They keys of this map are identical to ` this.#previous `
* @ type { Map < Source , any > }
* /
# current = new Map ( ) ;
current = new Map ( ) ;
/ * *
* The values of any sources that are updated in this batch _before _ those updates took place .
@ -156,7 +157,7 @@ export class Batch {
*
* @ param { Effect [ ] } root _effects
* /
# process ( root _effects ) {
process ( root _effects ) {
queued _root _effects = [ ] ;
/** @type {Map<Source, { v: unknown, wv: number }> | null} */
@ -169,7 +170,7 @@ export class Batch {
current _values = new Map ( ) ;
batch _deriveds = new Map ( ) ;
for ( const [ source , current ] of this . # current ) {
for ( const [ source , current ] of this . current ) {
current _values . set ( source , { v : source . v , wv : source . wv } ) ;
source . v = current ;
}
@ -202,9 +203,22 @@ export class Batch {
this . # effects = [ ] ;
this . # block _effects = [ ] ;
// If sources are written to, then work needs to happen in a separate batch, else prior sources would be mixed with
// newly updated sources, which could lead to infinite loops when effects run over and over again.
current _batch = null ;
flush _queued _effects ( render _effects ) ;
flush _queued _effects ( effects ) ;
// Reinstate the current batch if there was no new one created, as `process()` runs in a loop in `flush_effects()`.
// That method expects `current_batch` to be set, and could run the loop again if effects result in new effects
// being scheduled but without writes happening in which case no new batch is created.
if ( current _batch === null ) {
current _batch = this ;
} else {
batches . delete ( this ) ;
}
this . # deferred ? . resolve ( ) ;
} else {
// otherwise mark effects clean so they get scheduled on the next run
@ -300,7 +314,7 @@ export class Batch {
this . # previous . set ( source , value ) ;
}
this . # current . set ( source , source . v ) ;
this . current . set ( source , source . v ) ;
}
activate ( ) {
@ -327,13 +341,13 @@ export class Batch {
flush ( ) {
if ( queued _root _effects . length > 0 ) {
this . flush _effects ( ) ;
flush _effects ( ) ;
} else {
this . # commit ( ) ;
}
if ( current _batch !== this ) {
// this can happen if a `flushSync` occurred during ` this. flush_effects()`,
// this can happen if a `flushSync` occurred during ` flush_effects()`,
// which is permitted in legacy mode despite being a terrible idea
return ;
}
@ -345,52 +359,6 @@ export class Batch {
this . deactivate ( ) ;
}
flush _effects ( ) {
var was _updating _effect = is _updating _effect ;
is _flushing = true ;
try {
var flush _count = 0 ;
set _is _updating _effect ( true ) ;
while ( queued _root _effects . length > 0 ) {
if ( flush _count ++ > 1000 ) {
if ( DEV ) {
var updates = new Map ( ) ;
for ( const source of this . # current . keys ( ) ) {
for ( const [ stack , update ] of source . updated ? ? [ ] ) {
var entry = updates . get ( stack ) ;
if ( ! entry ) {
entry = { error : update . error , count : 0 } ;
updates . set ( stack , entry ) ;
}
entry . count += update . count ;
}
}
for ( const update of updates . values ( ) ) {
// eslint-disable-next-line no-console
console . error ( update . error ) ;
}
}
infinite _loop _guard ( ) ;
}
this . # process ( queued _root _effects ) ;
old _values . clear ( ) ;
}
} finally {
is _flushing = false ;
set _is _updating _effect ( was _updating _effect ) ;
last _scheduled _effect = null ;
}
}
/ * *
* Append and remove branches to / from the DOM
* /
@ -412,19 +380,8 @@ export class Batch {
this . # pending -= 1 ;
if ( this . # pending === 0 ) {
for ( const e of this . # render _effects ) {
set _signal _status ( e , DIRTY ) ;
schedule _effect ( e ) ;
}
for ( const e of this . # effects ) {
set _signal _status ( e , DIRTY ) ;
schedule _effect ( e ) ;
}
for ( const e of this . # block _effects ) {
set _signal _status ( e , DIRTY ) ;
schedule _effect ( e ) ;
for ( const source of this . current . keys ( ) ) {
mark _reactions ( source , DIRTY , false ) ;
}
this . # render _effects = [ ] ;
@ -445,12 +402,12 @@ export class Batch {
return ( this . # deferred ? ? = deferred ( ) ) . promise ;
}
static ensure ( autoflush = true ) {
static ensure ( ) {
if ( current _batch === null ) {
const batch = ( current _batch = new Batch ( ) ) ;
batches . add ( current _batch ) ;
if ( autoflush ) {
if ( ! is _flushing _sync ) {
Batch . enqueue ( ( ) => {
if ( current _batch !== batch ) {
// a flushSync happened in the meantime
@ -487,32 +444,85 @@ export function flushSync(fn) {
e . flush _sync _in _effect ( ) ;
}
var result ;
var was _flushing _sync = is _flushing _sync ;
is _flushing _sync = true ;
const batch = Batch . ensure ( false ) ;
try {
var result ;
if ( fn ) {
batch . flush _effects ( ) ;
if ( fn ) {
flush _effects ( ) ;
result = fn ( ) ;
}
result = fn ( ) ;
}
while ( true ) {
flush _tasks ( ) ;
while ( true ) {
flush _tasks ( ) ;
if ( queued _root _effects . length === 0 ) {
current _batch ? . flush ( ) ;
if ( queued _root _effects . length === 0 ) {
if ( batch === current _batch ) {
batch . flush ( ) ;
// we need to check again, in case we just updated an `$effect.pending()`
if ( queued _root _effects . length === 0 ) {
// this would be reset in `flush_effects()` but since we are early returning here,
// we need to reset it here as well in case the first time there's 0 queued root effects
last _scheduled _effect = null ;
return /** @type {T} */ ( result ) ;
}
}
// this would be reset in `batch.flush_effects()` but since we are early returning here,
// we need to reset it here as well in case the first time there's 0 queued root effects
last _scheduled _effect = null ;
flush _effects ( ) ;
}
} finally {
is _flushing _sync = was _flushing _sync ;
}
}
function flush _effects ( ) {
var was _updating _effect = is _updating _effect ;
is _flushing = true ;
try {
var flush _count = 0 ;
set _is _updating _effect ( true ) ;
while ( queued _root _effects . length > 0 ) {
var batch = Batch . ensure ( ) ;
if ( flush _count ++ > 1000 ) {
if ( DEV ) {
var updates = new Map ( ) ;
for ( const source of batch . current . keys ( ) ) {
for ( const [ stack , update ] of source . updated ? ? [ ] ) {
var entry = updates . get ( stack ) ;
if ( ! entry ) {
entry = { error : update . error , count : 0 } ;
updates . set ( stack , entry ) ;
}
entry . count += update . count ;
}
}
for ( const update of updates . values ( ) ) {
// eslint-disable-next-line no-console
console . error ( update . error ) ;
}
}
infinite _loop _guard ( ) ;
}
return /** @type {T} */ ( result ) ;
batch . process ( queued _root _effects ) ;
old _values . clear ( ) ;
}
} finally {
is _flushing = false ;
set _is _updating _effect ( was _updating _effect ) ;
batch . flush _effects ( ) ;
last_scheduled _effect = null ;
}
}
@ -545,7 +555,7 @@ function flush_queued_effects(effects) {
var effect = effects [ i ++ ] ;
if ( ( effect . f & ( DESTROYED | INERT ) ) === 0 && is _dirty ( effect ) ) {
var wv = write _version ;
var n = current _batch ? current _batch . current . size : 0 ;
update _effect ( effect ) ;
@ -568,7 +578,11 @@ function flush_queued_effects(effects) {
// if state is written in a user effect, abort and re-schedule, lest we run
// effects that should be removed as a result of the state change
if ( write _version > wv && ( effect . f & USER _EFFECT ) !== 0 ) {
if (
current _batch !== null &&
current _batch . current . size > n &&
( effect . f & USER _EFFECT ) !== 0
) {
break ;
}
}