@ -17,7 +17,8 @@ import {
ERROR _VALUE ,
MANAGED _EFFECT ,
REACTION _RAN ,
STALE _REACTION
STALE _REACTION ,
USER _EFFECT
} from '#client/constants' ;
import { async _mode _flag } from '../../flags/index.js' ;
import { deferred , define _property , includes } from '../../shared/utils.js' ;
@ -185,7 +186,13 @@ export class Batch {
return this . is _fork || this . # blocking _pending > 0 ;
}
/** @returns {boolean} */
finished ( ) {
// TODO instead of littering this class with if(this.successor) checks, it's probably less code/better to use Batch.find(batch).x() in the relevant places
if ( this . successor ) {
return this . successor . finished ( ) ;
}
return this . # pending === 0 && ! this . is _fork && this . # commit _callbacks . size === 0 ;
}
@ -195,6 +202,11 @@ export class Batch {
* @ param { ( batch : Batch ) => void } on _discard
* /
skip _effect ( effect , on _discard ) {
if ( this . successor ) {
this . successor . skip _effect ( effect , on _discard ) ;
return ;
}
if ( ! this . # skipped _branches . has ( effect ) ) {
this . # skipped _branches . set ( effect , { d : [ ] , m : [ ] , on _discard } ) ;
}
@ -206,6 +218,11 @@ export class Batch {
* @ param { Effect } effect
* /
unskip _effect ( effect ) {
if ( this . successor ) {
this . successor . unskip _effect ( effect ) ;
return ;
}
var tracked = this . # skipped _branches . get ( effect ) ;
if ( tracked ) {
this . # skipped _branches . delete ( effect ) ;
@ -244,6 +261,8 @@ export class Batch {
* /
var updates = ( legacy _updates = [ ] ) ;
if ( this . id === 5 ) debugger ;
for ( const root of roots ) {
this . # traverse ( root , effects , render _effects ) ;
}
@ -261,13 +280,16 @@ export class Batch {
collected _effects = null ;
legacy _updates = null ;
if ( this . # is _deferred ( ) ) {
if ( this . # is _deferred ( ) || this . successor ) {
this . # defer _effects ( render _effects ) ;
this . # defer _effects ( effects ) ;
for ( const [ e , t ] of this . # skipped _branches ) {
reset _branch ( e , t ) ;
}
// Once more for the potential new (render) effects
if ( this . successor ) Batch . merge ( this , this . successor ) ;
} else {
// clear effects. Those that are still needed will be rescheduled through unskipping the skipped branches.
this . # dirty _effects . clear ( ) ;
@ -372,7 +394,12 @@ export class Batch {
* @ param { any } value
* /
capture ( source , value ) {
if ( ! this . is _fork ) source . batch = this ;
if ( this . successor ) {
this . successor . capture ( source , value ) ;
return ;
}
if ( ! this . is _fork ) source . batch = Batch . upsert ( source ) ;
if ( value !== UNINITIALIZED && ! this . previous . has ( source ) ) {
this . previous . set ( source , value ) ;
@ -386,10 +413,20 @@ export class Batch {
}
activate ( ) {
if ( this . successor ) {
this . successor . activate ( ) ;
return ;
}
current _batch = this ;
}
deactivate ( ) {
if ( this . successor ) {
this . successor . deactivate ( ) ;
return ;
}
current _batch = null ;
batch _values = null ;
}
@ -443,6 +480,11 @@ export class Batch {
}
discard ( ) {
if ( this . successor ) {
this . successor . discard ( ) ;
return ;
}
for ( const fn of this . # discard _callbacks ) fn ( this ) ;
this . # discard _callbacks . clear ( ) ;
}
@ -530,6 +572,11 @@ export class Batch {
* @ param { boolean } blocking
* /
increment ( blocking ) {
if ( this . successor ) {
this . successor . increment ( blocking ) ;
return ;
}
this . # pending += 1 ;
if ( blocking ) this . # blocking _pending += 1 ;
}
@ -539,6 +586,11 @@ export class Batch {
* @ param { boolean } skip - whether to skip updates ( because this is triggered by a stale reaction )
* /
decrement ( blocking , skip ) {
if ( this . successor ) {
this . successor . decrement ( blocking , skip ) ;
return ;
}
this . # pending -= 1 ;
if ( blocking ) this . # blocking _pending -= 1 ;
@ -553,27 +605,61 @@ export class Batch {
/** @param {(batch: Batch) => void} fn */
oncommit ( fn ) {
if ( this . successor ) {
this . successor . oncommit ( fn ) ;
return ;
}
this . # commit _callbacks . add ( fn ) ;
}
/** @param {(batch: Batch) => void} fn */
ondiscard ( fn ) {
if ( this . successor ) {
this . successor . ondiscard ( fn ) ;
return ;
}
this . # discard _callbacks . add ( fn ) ;
}
/** @returns {Promise<void>} */
settled ( ) {
if ( this . successor ) {
return this . successor . settled ( ) ;
}
return ( this . # deferred ? ? = deferred ( ) ) . promise ;
}
/ * *
* Ensure there is a current batch for scheduling work triggered by ` reaction ` .
* If both the current batch and the reaction batch are active , merge them .
* @ param { Reaction} reaction
* @ param { Value | Reaction} reaction
* /
static upsert ( reaction ) {
if ( reaction . f & RENDER _EFFECT && ! ( reaction . f & USER _EFFECT ) ) {
return Batch . ensure ( ) ;
}
var existing _batch = [ ... batches ] . find (
( b ) => ! b . is _fork && b . current . has ( /** @type {any} */ ( reaction ) )
) ;
var reaction _batch = reaction . batch ;
var has _reaction _batch = reaction _batch !== null && ! reaction _batch . finished ( ) ;
if ( existing _batch ) {
if ( reaction _batch ) {
Batch . merge ( reaction _batch , existing _batch ) ;
}
if ( current _batch ) {
Batch . merge ( current _batch , existing _batch ) ;
}
current _batch = existing _batch ;
return existing _batch ;
}
if ( current _batch === null ) {
if ( has _reaction _batch ) {
current _batch = reaction _batch ;
@ -605,6 +691,8 @@ export class Batch {
* @ param { Batch } to
* /
static merge ( from , to ) {
// from = Batch.find(from);
to = Batch . find ( to ) ;
if ( from === to ) return ;
for ( const [ source , value ] of from . previous ) {
@ -614,14 +702,13 @@ export class Batch {
}
for ( const [ source , value ] of from . current ) {
to . current . set ( source , value ) ;
to . current . set ( source , value ) ; // TODO do we somehow need to know which one's more recent here?
if ( ! to . is _fork ) source . batch = to ;
}
to . # pending += from . # pending ;
to . # blocking _pending += from . # blocking _pending ;
to . # roots . push ( ... from . # roots );
to . # pending += from . # pending ;
to . # roots . push ( ... from . # roots .filter ( ( r ) => ! to . # roots . includes ( r ) ) );
for ( const e of from . # dirty _effects ) to . # dirty _effects . add ( e ) ;
for ( const e of from . # maybe _dirty _effects ) to . # maybe _dirty _effects . add ( e ) ;
@ -658,10 +745,21 @@ export class Batch {
from . # decrement _queued = false ;
from . successor = to ;
// Apply before modifying batches, else apply might erronously bail because there's only one batch left
if ( batch _values !== null ) to . apply ( ) ;
batches . delete ( from ) ;
batches . add ( to ) ;
}
/** @param {Batch} batch */
static find ( batch ) {
while ( batch . successor ) {
batch = batch . successor ;
}
return batch ;
}
static ensure ( ) {
if ( current _batch === null ) {
const batch = ( current _batch = new Batch ( ) ) ;
@ -682,16 +780,15 @@ export class Batch {
}
}
return current_batch ;
return Batch. find ( current_batch ) ;
}
# scheduling = false ;
// TODO do we need this or can we assume decrement does what we want?
reschedule ( ) {
if ( ! is _flushing _sync && ! this . # scheduling && this . # is _deferred ( ) ) {
this . # scheduling = true ;
if ( ! is _flushing _sync && ! this . # decrement_queued && this . # is _deferred ( ) ) {
this . # decrement_queued = true ;
queue _micro _task ( ( ) => {
this . # scheduling = false ;
this . # decrement_queued = false ;
this . flush ( ) ;
} ) ;
}
@ -721,6 +818,11 @@ export class Batch {
* @ param { Effect } effect
* /
schedule ( effect ) {
if ( this . successor ) {
this . successor . schedule ( effect ) ;
return ;
}
last _scheduled _effect = effect ;
// defer render effects inside a pending boundary
@ -1078,20 +1180,15 @@ export function eager(fn) {
* @ param { { d : Effect [ ] , m : Effect [ ] } } tracked
* /
function reset _branch ( effect , tracked ) {
// Clean branches normally need no traversal. But if a branch belongs to the
// current batch, we still need to walk it to cancel stale async effects.
if (
( effect . f & BRANCH _EFFECT ) !== 0 &&
( effect . f & CLEAN ) !== 0 &&
effect . batch !== current _batch
) {
// Clean branches normally need no traversal.
if ( ( effect . f & BRANCH _EFFECT ) !== 0 && ( effect . f & CLEAN ) !== 0 ) {
return ;
}
// This branch is stale for the current batch. Cancel async effects that
// belong to this batch so pending async work is rejected/discarded,
// which is important for getting the batch's pending count to 0.
if ( ( effect . f & ASYNC ) !== 0 && effect . batch === current _batch ) {
// This branch is stale for the current batch. Cancel async effects so
// pending async work is rejected/discarded, which is important for getting
// the batch's pending count to 0.
if ( ( effect . f & ASYNC ) !== 0 ) {
effect . ac ? . abort ( STALE _REACTION ) ;
effect . ac = null ;
}