@ -22,7 +22,6 @@ import {
import { async _mode _flag } from '../../flags/index.js' ;
import { deferred , define _property , includes } from '../../shared/utils.js' ;
import {
active _effect ,
active _reaction ,
get ,
increment _write _version ,
@ -38,7 +37,6 @@ import { eager_effect, teardown, unlink_effect } from './effects.js';
import { defer _effect } from './utils.js' ;
import { UNINITIALIZED } from '../../../constants.js' ;
import { set _signal _status } from './status.js' ;
import { legacy _is _updating _store } from './store.js' ;
import { invariant } from '../../shared/dev.js' ;
import { log _effect _tree } from '../dev/debug.js' ;
import { OBSOLETE } from './deriveds.js' ;
@ -160,10 +158,17 @@ export class Batch {
# deferred = null ;
/ * *
* The root effects that need to be flushed
* Effects that were scheduled in this batch but not yet 'resolved' into the
* root effects that need to be flushed . Resolving — the upwards traversal that
* marks the path to each effect on the shared effect tree ( see # resolve ) — is
* deferred until the batch is processed , so that the markers are created and
* consumed within a single traversal . Scheduling into other batches ( which can
* happen concurrently , e . g . while a batch is committed ) can therefore never
* observe ( and be confused by ) this batch ' s markers .
* May contain duplicates — deduplication happens during resolving
* @ type { Effect [ ] }
* /
# roots = [ ] ;
# scheduled = [ ] ;
/ * *
* Effects created while this batch was active .
@ -273,14 +278,57 @@ export class Batch {
this . # unskipped _branches . add ( effect ) ;
}
# process ( ) {
this . # started = true ;
/ * *
* Convert the effects that were scheduled in this batch into the root effects
* that need to be traversed , marking the path to each effect ( by clearing the
* ` CLEAN ` flag on ancestor branches ) so that the traversal can find them .
* This happens right before traversal rather than at scheduling time , so that
* the markers left on the ( shared ) effect tree are created and consumed within
* a single traversal — scheduling into other batches can never observe them
* @ returns { Effect [ ] }
* /
# resolve ( ) {
/** @type {Effect[]} */
var roots = [ ] ;
if ( flush _count ++ > 1000 ) {
this . # unlink ( ) ;
infinite _loop _guard ( ) ;
for ( const effect of this . # scheduled ) {
// skip effects that are destroyed, or that already ran (e.g. because
// they were reached by the traversal that preceded a drain iteration,
// or because they were scheduled twice)
if ( ( effect . f & DESTROYED ) !== 0 || ( effect . f & ( DIRTY | MAYBE _DIRTY ) ) === 0 ) continue ;
var e = effect ;
var covered = false ;
while ( e . parent !== null ) {
e = e . parent ;
var flags = e . f ;
if ( ( flags & ( ROOT _EFFECT | BRANCH _EFFECT ) ) !== 0 ) {
if ( ( flags & CLEAN ) === 0 ) {
// the path to the root was already marked, meaning the
// root was already collected — nothing left to do
covered = true ;
break ;
}
e . f ^= CLEAN ;
}
}
if ( ! covered ) {
roots . push ( e ) ;
}
}
this . # scheduled = [ ] ;
return roots ;
}
# process ( ) {
this . # started = true ;
if ( DEV ) {
// track all the values that were updated during this flush,
// so that they can be reset afterwards
@ -304,9 +352,6 @@ export class Batch {
this . schedule ( e ) ;
}
const roots = this . # roots ;
this . # roots = [ ] ;
this . apply ( ) ;
/** @type {Effect[]} */
@ -321,7 +366,16 @@ export class Batch {
* /
var updates = ( legacy _updates = [ ] ) ;
for ( const root of roots ) {
// Effects can be scheduled during traversal (e.g. because a parent each/await/etc
// block updated an internal source, or because an effect invalidated itself)
// hence we loop until there are no more scheduled effects.
while ( this . # scheduled . length > 0 ) {
if ( flush _count ++ > 1000 ) {
this . # unlink ( ) ;
infinite _loop _guard ( ) ; // TODO try to reset_all() here?
}
for ( const root of this . # resolve ( ) ) {
try {
this . # traverse ( root , effects , render _effects ) ;
} catch ( e ) {
@ -335,6 +389,7 @@ export class Batch {
throw e ;
}
}
}
// any writes should take effect in a subsequent batch
current _batch = null ;
@ -393,7 +448,7 @@ export class Batch {
var next _batch = /** @type {Batch | null} */ ( /** @type {unknown} */ ( current _batch ) ) ;
if ( this . # pending === 0 && ( this . # root s. length === 0 || next _batch !== null ) ) {
if ( this . # pending === 0 && ( this . # scheduled . length === 0 || next _batch !== null ) ) {
this . # unlink ( ) ;
// Order matters here - we need to commit and THEN continue flushing new batches, not the other way around,
@ -408,12 +463,15 @@ export class Batch {
}
// Edge case: During traversal new branches might create effects that run immediately and set state,
// causing an effect and therefore a root to be scheduled again. We need to traverse the current batch
// causing an effect to be scheduled again. We need to traverse the current batch
// once more in that case - most of the time this will just clean up dirty branches.
if ( this . # root s. length > 0 ) {
if ( this . # scheduled . length > 0 ) {
if ( next _batch !== null ) {
const batch = next _batch ;
batch . # roots . push ( ... this . # roots . filter ( ( r ) => ! batch . # roots . includes ( r ) ) ) ;
for ( const e of this . # scheduled ) {
next _batch . # scheduled . push ( e ) ;
}
this . # scheduled = [ ] ;
} else {
next _batch = this ;
}
@ -712,7 +770,7 @@ export class Batch {
// The microtask queue can contain the batch already scheduled to run right
// after this one is finished, so throwing the invariant would be wrong here.
if ( DEV && ! batch . # decrement _queued ) {
invariant ( batch . # root s. length === 0 , 'Batch has scheduled roo ts') ;
invariant ( batch . # scheduled . length === 0 , 'Batch has scheduled effec ts') ;
}
// A batch was unskipped in a later batch -> tell prior batches to unskip it, too
@ -768,14 +826,12 @@ export class Batch {
// Only apply and traverse when we know we triggered async work with marking the effects
// and know this won't run anyway right afterwards
if ( batch . # root s. length > 0 && ! batch . # decrement _queued ) {
if ( batch . # scheduled . length > 0 && ! batch . # decrement _queued ) {
batch . apply ( ) ;
for ( var root of batch . # r oots ) {
for ( var root of batch . # r esolve( ) ) {
batch . # traverse ( root , [ ] , [ ] ) ;
}
batch . # roots = [ ] ;
}
batch . deactivate ( ) ;
@ -939,43 +995,7 @@ export class Batch {
return ;
}
var e = effect ;
while ( e . parent !== null ) {
e = e . parent ;
var flags = e . f ;
// if the effect is being scheduled because a parent (each/await/etc) block
// updated an internal source, or because a branch is being unskipped,
// bail out or we'll cause a second flush
if ( collected _effects !== null && e === active _effect ) {
if ( async _mode _flag ) return ;
// in sync mode, render effects run during traversal. in an extreme edge case
// — namely that we're setting a value inside a derived read during traversal —
// they can be made dirty after they have already been visited, in which
// case we shouldn't bail out. we also shouldn't bail out if we're
// updating a store inside a `$:`, since this might invalidate
// effects that were already visited
if (
( active _reaction === null || ( active _reaction . f & DERIVED ) === 0 ) &&
! legacy _is _updating _store
) {
return ;
}
}
if ( ( flags & ( ROOT _EFFECT | BRANCH _EFFECT ) ) !== 0 ) {
if ( ( flags & CLEAN ) === 0 ) {
// branch is already dirty, bail
return ;
}
e . f ^= CLEAN ;
}
}
this . # roots . push ( e ) ;
this . # scheduled . push ( effect ) ;
}
# unlink ( ) {
@ -1012,15 +1032,15 @@ export class Batch {
* /
export function flushSync ( fn ) {
var was _flushing _sync = is _flushing _sync ;
var prev _previous _batch = previous _batch ;
previous _batch = null ;
is _flushing _sync = true ;
try {
var result ;
if ( fn ) {
if ( current _batch !== null && ! current _batch . is _fork ) {
current _batch . flush ( ) ;
}
flushSync ( ) ; // flush anything pending through the while loop below
result = fn ( ) ;
}
@ -1036,6 +1056,7 @@ export function flushSync(fn) {
}
} finally {
is _flushing _sync = was _flushing _sync ;
previous _batch = prev _previous _batch ;
}
}
@ -1275,6 +1296,10 @@ export function eager(fn) {
let version = version _map . get ( parent ) ? ? source ( 0 ) ;
version _map . set ( parent , version ) ;
if ( DEV ) {
version . label ? ? = '$state.eager version' ;
}
teardown ( ( ) => {
if ( parent . f & DESTROYING ) version _map . delete ( parent ) ;
} ) ;
@ -1382,6 +1407,8 @@ export function fork(fn) {
e . fork _timing ( ) ;
}
flushSync ( ) ;
var batch = Batch . ensure ( ) ;
batch . is _fork = true ;
batch _values = new Map ( ) ;
@ -1389,7 +1416,8 @@ export function fork(fn) {
var committed = false ;
var settled = batch . settled ( ) ;
flushSync ( fn ) ;
fn ( ) ;
flushSync ( ) ;
return {
commit : async ( ) => {