1
- /** @import { Derived, Effect, Source } from '#client' */
1
+ /** @import { Derived, Effect, Source, Value } from '#client' */
2
2
import {
3
3
BLOCK_EFFECT ,
4
4
BRANCH_EFFECT ,
@@ -10,10 +10,11 @@ import {
10
10
INERT ,
11
11
RENDER_EFFECT ,
12
12
ROOT_EFFECT ,
13
- MAYBE_DIRTY
13
+ MAYBE_DIRTY ,
14
+ DERIVED
14
15
} from '#client/constants' ;
15
16
import { 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' ;
17
18
import {
18
19
active_effect ,
19
20
is_dirty ,
@@ -97,22 +98,8 @@ export class Batch {
97
98
#deferred = null ;
98
99
99
100
/**
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
116
103
* @type {Effect[] }
117
104
*/
118
105
#boundary_async_effects = [ ] ;
@@ -165,40 +152,15 @@ export class Batch {
165
152
166
153
previous_batch = null ;
167
154
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 ) ;
194
156
195
157
for ( const root of root_effects ) {
196
158
this . #traverse_effect_tree( root ) ;
197
159
}
198
160
199
161
// if we didn't start any new async work, and no async work
200
162
// is outstanding from a previous flush, commit
201
- if ( this . #async_effects . length === 0 && this . # pending === 0 ) {
163
+ if ( this . #pending === 0 ) {
202
164
this . #commit( ) ;
203
165
204
166
var render_effects = this . #render_effects;
@@ -210,7 +172,7 @@ export class Batch {
210
172
211
173
// If sources are written to, then work needs to happen in a separate batch, else prior sources would be mixed with
212
174
// 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 ;
214
176
current_batch = null ;
215
177
216
178
flush_queued_effects ( render_effects ) ;
@@ -223,27 +185,12 @@ export class Batch {
223
185
this . #defer_effects( this . #block_effects) ;
224
186
}
225
187
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 ( ) ;
241
189
242
190
for ( const effect of this . #boundary_async_effects) {
243
191
update_effect ( effect ) ;
244
192
}
245
193
246
- this . #async_effects = [ ] ;
247
194
this . #boundary_async_effects = [ ] ;
248
195
}
249
196
@@ -272,12 +219,8 @@ export class Batch {
272
219
} else if ( async_mode_flag && ( flags & RENDER_EFFECT ) !== 0 ) {
273
220
this . #render_effects. push ( effect ) ;
274
221
} 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 ) ;
281
224
} else if ( is_dirty ( effect ) ) {
282
225
if ( ( effect . f & BLOCK_EFFECT ) !== 0 ) this . #block_effects. push ( effect ) ;
283
226
update_effect ( effect ) ;
@@ -350,10 +293,6 @@ export class Batch {
350
293
}
351
294
}
352
295
353
- neuter ( ) {
354
- this . #neutered = true ;
355
- }
356
-
357
296
flush ( ) {
358
297
if ( queued_root_effects . length > 0 ) {
359
298
this . activate ( ) ;
@@ -374,13 +313,58 @@ export class Batch {
374
313
* Append and remove branches to/from the DOM
375
314
*/
376
315
#commit( ) {
377
- if ( ! this . #neutered) {
378
- for ( const fn of this . #callbacks) {
379
- fn ( ) ;
380
- }
316
+ for ( const fn of this . #callbacks) {
317
+ fn ( ) ;
381
318
}
382
319
383
320
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
+
384
368
batches . delete ( this ) ;
385
369
}
386
370
@@ -402,9 +386,6 @@ export class Batch {
402
386
schedule_effect ( e ) ;
403
387
}
404
388
405
- this . #render_effects = [ ] ;
406
- this . #effects = [ ] ;
407
-
408
389
this . flush ( ) ;
409
390
} else {
410
391
this . deactivate ( ) ;
@@ -444,6 +425,51 @@ export class Batch {
444
425
static enqueue ( task ) {
445
426
queue_micro_task ( task ) ;
446
427
}
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
+ }
447
473
}
448
474
449
475
/**
@@ -615,6 +641,26 @@ function flush_queued_effects(effects) {
615
641
eager_block_effects = null ;
616
642
}
617
643
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
+
618
664
/**
619
665
* @param {Effect } signal
620
666
* @returns {void }
0 commit comments