1- /** @import { Derived, Effect, Source } from '#client' */
1+ /** @import { Derived, Effect, Source, Value } from '#client' */
22import {
33 BLOCK_EFFECT ,
44 BRANCH_EFFECT ,
@@ -10,10 +10,11 @@ import {
1010 INERT ,
1111 RENDER_EFFECT ,
1212 ROOT_EFFECT ,
13- MAYBE_DIRTY
13+ MAYBE_DIRTY ,
14+ DERIVED
1415} from '#client/constants' ;
1516import { async_mode_flag } from '../../flags/index.js' ;
16- import { deferred , define_property } from '../../shared/utils.js' ;
17+ import { deferred , define_property , noop } from '../../shared/utils.js' ;
1718import {
1819 active_effect ,
1920 is_dirty ,
@@ -97,22 +98,8 @@ export class Batch {
9798 #deferred = null ;
9899
99100 /**
100- * True if an async effect inside this batch resolved and
101- * its parent branch was already deleted
102- */
103- #neutered = false ;
104-
105- /**
106- * Async effects (created inside `async_derived`) encountered during processing.
107- * These run after the rest of the batch has updated, since they should
108- * always have the latest values
109- * @type {Effect[] }
110- */
111- #async_effects = [ ] ;
112-
113- /**
114- * The same as `#async_effects`, but for effects inside a newly-created
115- * `<svelte:boundary>` — these do not prevent the batch from committing
101+ * Async effects inside a newly-created `<svelte:boundary>`
102+ * — these do not prevent the batch from committing
116103 * @type {Effect[] }
117104 */
118105 #boundary_async_effects = [ ] ;
@@ -165,40 +152,15 @@ export class Batch {
165152
166153 previous_batch = null ;
167154
168- /** @type {Map<Source, { v: unknown, wv: number }> | null } */
169- var current_values = null ;
170-
171- // if there are multiple batches, we are 'time travelling' —
172- // we need to undo the changes belonging to any batch
173- // other than the current one
174- if ( async_mode_flag && batches . size > 1 ) {
175- current_values = new Map ( ) ;
176- batch_deriveds = new Map ( ) ;
177-
178- for ( const [ source , current ] of this . current ) {
179- current_values . set ( source , { v : source . v , wv : source . wv } ) ;
180- source . v = current ;
181- }
182-
183- for ( const batch of batches ) {
184- if ( batch === this ) continue ;
185-
186- for ( const [ source , previous ] of batch . #previous) {
187- if ( ! current_values . has ( source ) ) {
188- current_values . set ( source , { v : source . v , wv : source . wv } ) ;
189- source . v = previous ;
190- }
191- }
192- }
193- }
155+ var revert = Batch . apply ( this ) ;
194156
195157 for ( const root of root_effects ) {
196158 this . #traverse_effect_tree( root ) ;
197159 }
198160
199161 // if we didn't start any new async work, and no async work
200162 // is outstanding from a previous flush, commit
201- if ( this . #async_effects . length === 0 && this . # pending === 0 ) {
163+ if ( this . #pending === 0 ) {
202164 this . #commit( ) ;
203165
204166 var render_effects = this . #render_effects;
@@ -210,7 +172,7 @@ export class Batch {
210172
211173 // If sources are written to, then work needs to happen in a separate batch, else prior sources would be mixed with
212174 // newly updated sources, which could lead to infinite loops when effects run over and over again.
213- previous_batch = current_batch ;
175+ previous_batch = this ;
214176 current_batch = null ;
215177
216178 flush_queued_effects ( render_effects ) ;
@@ -223,27 +185,12 @@ export class Batch {
223185 this . #defer_effects( this . #block_effects) ;
224186 }
225187
226- if ( current_values ) {
227- for ( const [ source , { v, wv } ] of current_values ) {
228- // reset the source to the current value (unless
229- // it got a newer value as a result of effects running)
230- if ( source . wv <= wv ) {
231- source . v = v ;
232- }
233- }
234-
235- batch_deriveds = null ;
236- }
237-
238- for ( const effect of this . #async_effects) {
239- update_effect ( effect ) ;
240- }
188+ revert ( ) ;
241189
242190 for ( const effect of this . #boundary_async_effects) {
243191 update_effect ( effect ) ;
244192 }
245193
246- this . #async_effects = [ ] ;
247194 this . #boundary_async_effects = [ ] ;
248195 }
249196
@@ -272,12 +219,8 @@ export class Batch {
272219 } else if ( async_mode_flag && ( flags & RENDER_EFFECT ) !== 0 ) {
273220 this . #render_effects. push ( effect ) ;
274221 } else if ( ( flags & CLEAN ) === 0 ) {
275- if ( ( flags & ASYNC ) !== 0 ) {
276- var effects = effect . b ?. is_pending ( )
277- ? this . #boundary_async_effects
278- : this . #async_effects;
279-
280- effects . push ( effect ) ;
222+ if ( ( flags & ASYNC ) !== 0 && effect . b ?. is_pending ( ) ) {
223+ this . #boundary_async_effects. push ( effect ) ;
281224 } else if ( is_dirty ( effect ) ) {
282225 if ( ( effect . f & BLOCK_EFFECT ) !== 0 ) this . #block_effects. push ( effect ) ;
283226 update_effect ( effect ) ;
@@ -350,10 +293,6 @@ export class Batch {
350293 }
351294 }
352295
353- neuter ( ) {
354- this . #neutered = true ;
355- }
356-
357296 flush ( ) {
358297 if ( queued_root_effects . length > 0 ) {
359298 this . activate ( ) ;
@@ -374,13 +313,58 @@ export class Batch {
374313 * Append and remove branches to/from the DOM
375314 */
376315 #commit( ) {
377- if ( ! this . #neutered) {
378- for ( const fn of this . #callbacks) {
379- fn ( ) ;
380- }
316+ for ( const fn of this . #callbacks) {
317+ fn ( ) ;
381318 }
382319
383320 this . #callbacks. clear ( ) ;
321+
322+ // If there are other pending batches, they now need to be 'rebased' —
323+ // in other words, we re-run block/async effects with the newly
324+ // committed state, unless the batch in question has a more
325+ // recent value for a given source
326+ if ( batches . size > 1 ) {
327+ this . #previous. clear ( ) ;
328+
329+ let is_earlier = true ;
330+
331+ for ( const batch of batches ) {
332+ if ( batch === this ) {
333+ is_earlier = false ;
334+ continue ;
335+ }
336+
337+ for ( const [ source , value ] of this . current ) {
338+ if ( batch . current . has ( source ) ) {
339+ if ( is_earlier ) {
340+ // bring the value up to date
341+ batch . current . set ( source , value ) ;
342+ } else {
343+ // later batch has more recent value,
344+ // no need to re-run these effects
345+ continue ;
346+ }
347+ }
348+
349+ mark_effects ( source ) ;
350+ }
351+
352+ if ( queued_root_effects . length > 0 ) {
353+ current_batch = batch ;
354+ const revert = Batch . apply ( batch ) ;
355+
356+ for ( const root of queued_root_effects ) {
357+ batch . #traverse_effect_tree( root ) ;
358+ }
359+
360+ queued_root_effects = [ ] ;
361+ revert ( ) ;
362+ }
363+ }
364+
365+ current_batch = null ;
366+ }
367+
384368 batches . delete ( this ) ;
385369 }
386370
@@ -402,9 +386,6 @@ export class Batch {
402386 schedule_effect ( e ) ;
403387 }
404388
405- this . #render_effects = [ ] ;
406- this . #effects = [ ] ;
407-
408389 this . flush ( ) ;
409390 } else {
410391 this . deactivate ( ) ;
@@ -444,6 +425,51 @@ export class Batch {
444425 static enqueue ( task ) {
445426 queue_micro_task ( task ) ;
446427 }
428+
429+ /**
430+ * @param {Batch } current_batch
431+ */
432+ static apply ( current_batch ) {
433+ if ( ! async_mode_flag || batches . size === 1 ) {
434+ return noop ;
435+ }
436+
437+ // if there are multiple batches, we are 'time travelling' —
438+ // we need to undo the changes belonging to any batch
439+ // other than the current one
440+
441+ /** @type {Map<Source, { v: unknown, wv: number }> } */
442+ var current_values = new Map ( ) ;
443+ batch_deriveds = new Map ( ) ;
444+
445+ for ( const [ source , current ] of current_batch . current ) {
446+ current_values . set ( source , { v : source . v , wv : source . wv } ) ;
447+ source . v = current ;
448+ }
449+
450+ for ( const batch of batches ) {
451+ if ( batch === current_batch ) continue ;
452+
453+ for ( const [ source , previous ] of batch . #previous) {
454+ if ( ! current_values . has ( source ) ) {
455+ current_values . set ( source , { v : source . v , wv : source . wv } ) ;
456+ source . v = previous ;
457+ }
458+ }
459+ }
460+
461+ return ( ) => {
462+ for ( const [ source , { v, wv } ] of current_values ) {
463+ // reset the source to the current value (unless
464+ // it got a newer value as a result of effects running)
465+ if ( source . wv <= wv ) {
466+ source . v = v ;
467+ }
468+ }
469+
470+ batch_deriveds = null ;
471+ } ;
472+ }
447473}
448474
449475/**
@@ -615,6 +641,26 @@ function flush_queued_effects(effects) {
615641 eager_block_effects = null ;
616642}
617643
644+ /**
645+ * This is similar to `mark_reactions`, but it only marks async/block effects
646+ * so that these can re-run after another batch has been committed
647+ * @param {Value } value
648+ */
649+ function mark_effects ( value ) {
650+ if ( value . reactions !== null ) {
651+ for ( const reaction of value . reactions ) {
652+ const flags = reaction . f ;
653+
654+ if ( ( flags & DERIVED ) !== 0 ) {
655+ mark_effects ( /** @type {Derived } */ ( reaction ) ) ;
656+ } else if ( ( flags & ( ASYNC | BLOCK_EFFECT ) ) !== 0 ) {
657+ set_signal_status ( reaction , DIRTY ) ;
658+ schedule_effect ( /** @type {Effect } */ ( reaction ) ) ;
659+ }
660+ }
661+ }
662+ }
663+
618664/**
619665 * @param {Effect } signal
620666 * @returns {void }
0 commit comments