@ -11,7 +11,8 @@ import {
RENDER _EFFECT ,
ROOT _EFFECT ,
MAYBE _DIRTY ,
DERIVED
DERIVED ,
BOUNDARY _EFFECT
} from '#client/constants' ;
import { async _mode _flag } from '../../flags/index.js' ;
import { deferred , define _property } from '../../shared/utils.js' ;
@ -31,6 +32,16 @@ import { invoke_error_boundary } from '../error-handling.js';
import { old _values , source , update } from './sources.js' ;
import { inspect _effect , unlink _effect } from './effects.js' ;
/ * *
* @ typedef { {
* parent : EffectTarget | null ;
* effect : Effect | null ;
* effects : Effect [ ] ;
* render _effects : Effect [ ] ;
* block _effects : Effect [ ] ;
* } } EffectTarget
* /
/** @type {Set<Batch>} */
const batches = new Set ( ) ;
@ -65,6 +76,8 @@ let is_flushing = false;
export let is _flushing _sync = false ;
export class Batch {
committed = false ;
/ * *
* The current values of any sources that are updated in this batch
* They keys of this map are identical to ` this.#previous `
@ -91,6 +104,11 @@ export class Batch {
* /
# pending = 0 ;
/ * *
* The number of async effects that are currently in flight , _not _ inside a pending boundary
* /
# blocking _pending = 0 ;
/ * *
* A deferred that resolves when the batch is committed , used with ` settled() `
* TODO replace with Promise . withResolvers once supported widely enough
@ -98,26 +116,6 @@ export class Batch {
* /
# deferred = null ;
/ * *
* Template effects and ` $ effect.pre ` effects , which run when
* a batch is committed
* @ type { Effect [ ] }
* /
# render _effects = [ ] ;
/ * *
* The same as ` #render_effects ` , but for ` $ effect ` ( which runs after )
* @ type { Effect [ ] }
* /
# effects = [ ] ;
/ * *
* Block effects , which may need to re - run on subsequent flushes
* in order to update internal sources ( e . g . each block items )
* @ type { Effect [ ] }
* /
# block _effects = [ ] ;
/ * *
* Deferred effects ( which run after async work has completed ) that are DIRTY
* @ type { Effect [ ] }
@ -148,41 +146,37 @@ export class Batch {
this . apply ( ) ;
/** @type {EffectTarget} */
var target = {
parent : null ,
effect : null ,
effects : [ ] ,
render _effects : [ ] ,
block _effects : [ ]
} ;
for ( const root of root _effects ) {
this . # traverse _effect _tree ( root ) ;
this . # traverse _effect _tree ( root , target );
}
// if there is no outstanding async work, commit
if ( this . # pending === 0 ) {
// TODO we need this because we commit _then_ flush effects...
// maybe there's a way we can reverse the order?
var previous _batch _sources = batch _values ;
this . # resolve ( ) ;
this . # commit ( ) ;
var render _effects = this . # render _effects ;
var effects = this . # effects ;
this . # render _effects = [ ] ;
this . # effects = [ ] ;
this . # block _effects = [ ] ;
if ( this . # blocking _pending > 0 ) {
this . # defer _effects ( target . effects ) ;
this . # defer _effects ( target . render _effects ) ;
this . # defer _effects ( target . block _effects ) ;
} else {
// TODO append/detach blocks here, not in #commit
// 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.
previous _batch = this ;
current _batch = null ;
batch _values = previous _batch _sources ;
flush _queued _effects ( render _effects ) ;
flush _queued _effects ( effects ) ;
flush _queued _effects ( target . render _effects ) ;
flush _queued _effects ( target . effects ) ;
previous _batch = null ;
this . # deferred ? . resolve ( ) ;
} else {
this . # defer _effects ( this . # render _effects ) ;
this . # defer _effects ( this . # effects ) ;
this . # defer _effects ( this . # block _effects ) ;
}
batch _values = null ;
@ -192,8 +186,9 @@ export class Batch {
* Traverse the effect tree , executing effects or stashing
* them for later execution as appropriate
* @ param { Effect } root
* @ param { EffectTarget } target
* /
# traverse _effect _tree ( root ) {
# traverse _effect _tree ( root , target ) {
root . f ^= CLEAN ;
var effect = root . first ;
@ -205,15 +200,25 @@ export class Batch {
var skip = is _skippable _branch || ( flags & INERT ) !== 0 || this . skipped _effects . has ( effect ) ;
if ( ( effect . f & BOUNDARY _EFFECT ) !== 0 && effect . b ? . is _pending ( ) ) {
target = {
parent : target ,
effect ,
effects : [ ] ,
render _effects : [ ] ,
block _effects : [ ]
} ;
}
if ( ! skip && effect . fn !== null ) {
if ( is _branch ) {
effect . f ^= CLEAN ;
} else if ( ( flags & EFFECT ) !== 0 ) {
this . # effects . push ( effect ) ;
target . effects . push ( effect ) ;
} else if ( async _mode _flag && ( flags & RENDER _EFFECT ) !== 0 ) {
this . # render _effects . push ( effect ) ;
target . render _effects . push ( effect ) ;
} else if ( is _dirty ( effect ) ) {
if ( ( effect . f & BLOCK _EFFECT ) !== 0 ) this . # block _effects . push ( effect ) ;
if ( ( effect . f & BLOCK _EFFECT ) !== 0 ) target . block _effects . push ( effect ) ;
update _effect ( effect ) ;
}
@ -229,6 +234,17 @@ export class Batch {
effect = effect . next ;
while ( effect === null && parent !== null ) {
if ( parent === target . effect ) {
// TODO rather than traversing into pending boundaries and deferring the effects,
// could we just attach the effects _to_ the pending boundary and schedule them
// once the boundary is ready?
this . # defer _effects ( target . effects ) ;
this . # defer _effects ( target . render _effects ) ;
this . # defer _effects ( target . block _effects ) ;
target = /** @type {EffectTarget} */ ( target . parent ) ;
}
effect = parent . next ;
parent = parent . parent ;
}
@ -246,8 +262,6 @@ export class Batch {
// mark as clean so they get scheduled if they depend on pending async state
set _signal _status ( e , CLEAN ) ;
}
effects . length = 0 ;
}
/ * *
@ -283,8 +297,8 @@ export class Batch {
// this can happen if a new batch was created during `flush_effects()`
return ;
}
} else if ( this . # pending === 0 ) {
this . # commit ( ) ;
} else {
this . # resolve ( ) ;
}
this . deactivate ( ) ;
@ -300,16 +314,19 @@ export class Batch {
}
}
/ * *
* Append and remove branches to / from the DOM
* /
# commit ( ) {
for ( const fn of this . # callbacks ) {
fn ( ) ;
# resolve ( ) {
if ( this . # blocking _pending === 0 ) {
// append/remove branches
for ( const fn of this . # callbacks ) fn ( ) ;
this . # callbacks . clear ( ) ;
}
this . # callbacks . clear ( ) ;
if ( this . # pending === 0 ) {
this . # commit ( ) ;
}
}
# commit ( ) {
// If there are other pending batches, they now need to be 'rebased' —
// in other words, we re-run block/async effects with the newly
// committed state, unless the batch in question has a more
@ -317,7 +334,17 @@ export class Batch {
if ( batches . size > 1 ) {
this . # previous . clear ( ) ;
let is _earlier = true ;
var previous _batch _values = batch _values ;
var is _earlier = true ;
/** @type {EffectTarget} */
var dummy _target = {
parent : null ,
effect : null ,
effects : [ ] ,
render _effects : [ ] ,
block _effects : [ ]
} ;
for ( const batch of batches ) {
if ( batch === this ) {
@ -359,9 +386,11 @@ export class Batch {
batch . apply ( ) ;
for ( const root of queued _root _effects ) {
batch . # traverse _effect _tree ( root );
batch . # traverse _effect _tree ( root , dummy _target );
}
// TODO do we need to do anything with `target`? defer block effects?
queued _root _effects = [ ] ;
batch . deactivate ( ) ;
}
@ -369,17 +398,31 @@ export class Batch {
}
current _batch = null ;
batch _values = previous _batch _values ;
}
this . committed = true ;
batches . delete ( this ) ;
this . # deferred ? . resolve ( ) ;
}
increment ( ) {
/ * *
*
* @ param { boolean } blocking
* /
increment ( blocking ) {
this . # pending += 1 ;
if ( blocking ) this . # blocking _pending += 1 ;
}
decrement ( ) {
/ * *
*
* @ param { boolean } blocking
* /
decrement ( blocking ) {
this . # pending -= 1 ;
if ( blocking ) this . # blocking _pending -= 1 ;
for ( const e of this . # dirty _effects ) {
set _signal _status ( e , DIRTY ) ;
@ -391,6 +434,9 @@ export class Batch {
schedule _effect ( e ) ;
}
this . # dirty _effects = [ ] ;
this . # maybe _dirty _effects = [ ] ;
this . flush ( ) ;
}