svelte 5.55.5 → 5.55.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/compiler/index.js +1 -1
- package/elements.d.ts +3 -3
- package/package.json +2 -2
- package/src/compiler/phases/1-parse/read/style.js +15 -0
- package/src/compiler/phases/3-transform/client/visitors/DebugTag.js +10 -4
- package/src/compiler/phases/3-transform/server/visitors/Component.js +5 -1
- package/src/compiler/phases/3-transform/server/visitors/DebugTag.js +21 -10
- package/src/compiler/phases/3-transform/server/visitors/shared/utils.js +2 -2
- package/src/internal/client/constants.js +5 -0
- package/src/internal/client/dev/inspect.js +2 -0
- package/src/internal/client/dom/blocks/async.js +1 -5
- package/src/internal/client/dom/blocks/boundary.js +4 -5
- package/src/internal/client/dom/blocks/svelte-head.js +6 -5
- package/src/internal/client/dom/elements/attributes.js +17 -8
- package/src/internal/client/dom/elements/bindings/shared.js +4 -6
- package/src/internal/client/dom/elements/bindings/this.js +1 -1
- package/src/internal/client/dom/elements/class.js +3 -4
- package/src/internal/client/dom/elements/events.js +2 -2
- package/src/internal/client/dom/elements/misc.js +2 -2
- package/src/internal/client/dom/elements/style.js +3 -4
- package/src/internal/client/dom/operations.js +12 -11
- package/src/internal/client/reactivity/async.js +34 -16
- package/src/internal/client/reactivity/batch.js +294 -127
- package/src/internal/client/reactivity/deriveds.js +30 -29
- package/src/internal/client/reactivity/effects.js +1 -9
- package/src/internal/client/reactivity/props.js +7 -1
- package/src/internal/client/reactivity/sources.js +19 -9
- package/src/internal/client/render.js +4 -5
- package/src/internal/server/hydratable.js +7 -2
- package/src/internal/server/index.js +1 -1
- package/src/internal/server/renderer.js +6 -1
- package/src/utils.js +1 -1
- package/src/version.js +1 -1
- package/types/index.d.ts +2 -0
- package/types/index.d.ts.map +1 -1
|
@@ -16,7 +16,8 @@ import {
|
|
|
16
16
|
EAGER_EFFECT,
|
|
17
17
|
ERROR_VALUE,
|
|
18
18
|
MANAGED_EFFECT,
|
|
19
|
-
REACTION_RAN
|
|
19
|
+
REACTION_RAN,
|
|
20
|
+
DESTROYING
|
|
20
21
|
} from '#client/constants';
|
|
21
22
|
import { async_mode_flag } from '../../flags/index.js';
|
|
22
23
|
import { deferred, define_property, includes } from '../../shared/utils.js';
|
|
@@ -33,7 +34,7 @@ import { flush_tasks, queue_micro_task } from '../dom/task.js';
|
|
|
33
34
|
import { DEV } from 'esm-env';
|
|
34
35
|
import { invoke_error_boundary } from '../error-handling.js';
|
|
35
36
|
import { flush_eager_effects, old_values, set_eager_effects, source, update } from './sources.js';
|
|
36
|
-
import { eager_effect, unlink_effect } from './effects.js';
|
|
37
|
+
import { eager_effect, teardown, unlink_effect } from './effects.js';
|
|
37
38
|
import { defer_effect } from './utils.js';
|
|
38
39
|
import { UNINITIALIZED } from '../../../constants.js';
|
|
39
40
|
import { set_signal_status } from './status.js';
|
|
@@ -41,8 +42,11 @@ import { legacy_is_updating_store } from './store.js';
|
|
|
41
42
|
import { invariant } from '../../shared/dev.js';
|
|
42
43
|
import { log_effect_tree } from '../dev/debug.js';
|
|
43
44
|
|
|
44
|
-
/** @type {
|
|
45
|
-
|
|
45
|
+
/** @type {Batch | null} */
|
|
46
|
+
let first_batch = null;
|
|
47
|
+
|
|
48
|
+
/** @type {Batch | null} */
|
|
49
|
+
let last_batch = null;
|
|
46
50
|
|
|
47
51
|
/** @type {Batch | null} */
|
|
48
52
|
export let current_batch = null;
|
|
@@ -85,13 +89,29 @@ export let collected_effects = null;
|
|
|
85
89
|
export let legacy_updates = null;
|
|
86
90
|
|
|
87
91
|
var flush_count = 0;
|
|
88
|
-
|
|
92
|
+
|
|
93
|
+
/** @type {Set<Value>} */
|
|
94
|
+
var source_stacks = new Set();
|
|
89
95
|
|
|
90
96
|
let uid = 1;
|
|
91
97
|
|
|
92
98
|
export class Batch {
|
|
93
99
|
id = uid++;
|
|
94
100
|
|
|
101
|
+
/** True as soon as `#process` was called */
|
|
102
|
+
#started = false;
|
|
103
|
+
|
|
104
|
+
linked = true;
|
|
105
|
+
|
|
106
|
+
/** @type {Batch | null} */
|
|
107
|
+
#prev = null;
|
|
108
|
+
|
|
109
|
+
/** @type {Batch | null} */
|
|
110
|
+
#next = null;
|
|
111
|
+
|
|
112
|
+
/** @type {Map<Effect, ReturnType<typeof deferred<any>>>} */
|
|
113
|
+
async_deriveds = new Map();
|
|
114
|
+
|
|
95
115
|
/**
|
|
96
116
|
* The current values of any signals that are updated in this batch.
|
|
97
117
|
* Tuple format: [value, is_derived] (note: is_derived is false for deriveds, too, if they were overridden via assignment)
|
|
@@ -107,6 +127,13 @@ export class Batch {
|
|
|
107
127
|
*/
|
|
108
128
|
previous = new Map();
|
|
109
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Async effects which this batch doesn't take into account anymore when calculating blockers,
|
|
132
|
+
* as it has a value for it already.
|
|
133
|
+
* @type {Set<Effect>}
|
|
134
|
+
*/
|
|
135
|
+
unblocked = new Set();
|
|
136
|
+
|
|
110
137
|
/**
|
|
111
138
|
* When the batch is committed (and the DOM is updated), we need to remove old branches
|
|
112
139
|
* and append new ones by calling the functions added inside (if/each/key/etc) blocks
|
|
@@ -127,10 +154,9 @@ export class Batch {
|
|
|
127
154
|
#fork_commit_callbacks = new Set();
|
|
128
155
|
|
|
129
156
|
/**
|
|
130
|
-
*
|
|
131
|
-
* @type {Map<Effect, number>}
|
|
157
|
+
* The number of async effects that are currently in flight
|
|
132
158
|
*/
|
|
133
|
-
#pending =
|
|
159
|
+
#pending = 0;
|
|
134
160
|
|
|
135
161
|
/**
|
|
136
162
|
* Async effects that are currently in flight, _not_ inside a pending boundary
|
|
@@ -188,31 +214,24 @@ export class Batch {
|
|
|
188
214
|
|
|
189
215
|
#decrement_queued = false;
|
|
190
216
|
|
|
191
|
-
/** @type {Set<Batch>} */
|
|
192
|
-
#blockers = new Set();
|
|
193
|
-
|
|
194
217
|
#is_deferred() {
|
|
195
|
-
|
|
196
|
-
}
|
|
218
|
+
if (this.is_fork) return true;
|
|
197
219
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
var skipped = false;
|
|
202
|
-
var e = effect;
|
|
220
|
+
for (const effect of this.#blocking_pending.keys()) {
|
|
221
|
+
var e = effect;
|
|
222
|
+
var skipped = false;
|
|
203
223
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
e = e.parent;
|
|
224
|
+
while (e.parent !== null) {
|
|
225
|
+
if (this.#skipped_branches.has(e)) {
|
|
226
|
+
skipped = true;
|
|
227
|
+
break;
|
|
211
228
|
}
|
|
212
229
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
230
|
+
e = e.parent;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (!skipped) {
|
|
234
|
+
return true;
|
|
216
235
|
}
|
|
217
236
|
}
|
|
218
237
|
|
|
@@ -255,11 +274,21 @@ export class Batch {
|
|
|
255
274
|
}
|
|
256
275
|
|
|
257
276
|
#process() {
|
|
277
|
+
this.#started = true;
|
|
278
|
+
|
|
258
279
|
if (flush_count++ > 1000) {
|
|
259
|
-
|
|
280
|
+
this.#unlink();
|
|
260
281
|
infinite_loop_guard();
|
|
261
282
|
}
|
|
262
283
|
|
|
284
|
+
if (DEV) {
|
|
285
|
+
// track all the values that were updated during this flush,
|
|
286
|
+
// so that they can be reset afterwards
|
|
287
|
+
for (const value of this.current.keys()) {
|
|
288
|
+
source_stacks.add(value);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
263
292
|
// we only reschedule previously-deferred effects if we expect
|
|
264
293
|
// to be able to run them after processing the batch
|
|
265
294
|
if (!this.#is_deferred()) {
|
|
@@ -314,61 +343,76 @@ export class Batch {
|
|
|
314
343
|
collected_effects = null;
|
|
315
344
|
legacy_updates = null;
|
|
316
345
|
|
|
317
|
-
if
|
|
346
|
+
// if the batch has outstanding pending work, stash effects and bail
|
|
347
|
+
if (this.#is_deferred()) {
|
|
318
348
|
this.#defer_effects(render_effects);
|
|
319
349
|
this.#defer_effects(effects);
|
|
320
350
|
|
|
321
351
|
for (const [e, t] of this.#skipped_branches) {
|
|
322
352
|
reset_branch(e, t);
|
|
323
353
|
}
|
|
324
|
-
} else {
|
|
325
|
-
if (this.#pending.size === 0) {
|
|
326
|
-
batches.delete(this);
|
|
327
|
-
}
|
|
328
354
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
355
|
+
if (updates.length > 0) {
|
|
356
|
+
/** @type {Batch} */ (/** @type {unknown} */ (current_batch)).#process();
|
|
357
|
+
}
|
|
332
358
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
this.#commit_callbacks.clear();
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
336
361
|
|
|
337
|
-
|
|
338
|
-
flush_queued_effects(render_effects);
|
|
339
|
-
flush_queued_effects(effects);
|
|
340
|
-
previous_batch = null;
|
|
362
|
+
const earlier_batch = this.#find_earlier_batch();
|
|
341
363
|
|
|
342
|
-
|
|
364
|
+
if (earlier_batch) {
|
|
365
|
+
earlier_batch.#merge(this);
|
|
366
|
+
return;
|
|
343
367
|
}
|
|
344
368
|
|
|
369
|
+
// clear effects. Those that are still needed will be rescheduled through unskipping the skipped branches.
|
|
370
|
+
this.#dirty_effects.clear();
|
|
371
|
+
this.#maybe_dirty_effects.clear();
|
|
372
|
+
|
|
373
|
+
// append/remove branches
|
|
374
|
+
for (const fn of this.#commit_callbacks) fn(this);
|
|
375
|
+
this.#commit_callbacks.clear();
|
|
376
|
+
|
|
377
|
+
previous_batch = this;
|
|
378
|
+
flush_queued_effects(render_effects);
|
|
379
|
+
flush_queued_effects(effects);
|
|
380
|
+
previous_batch = null;
|
|
381
|
+
|
|
382
|
+
this.#deferred?.resolve();
|
|
383
|
+
|
|
345
384
|
var next_batch = /** @type {Batch | null} */ (/** @type {unknown} */ (current_batch));
|
|
346
385
|
|
|
386
|
+
if (this.linked && this.#pending === 0) {
|
|
387
|
+
this.#unlink();
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Order matters here - we need to commit and THEN continue flushing new batches, not the other way around,
|
|
391
|
+
// else we could start flushing a new batch and then, if it has pending work, rebase it right afterwards, which is wrong.
|
|
392
|
+
// In sync mode flushSync can cause #commit to wrongfully think that there needs to be a rebase, so we only do it in async mode
|
|
393
|
+
// TODO fix the underlying cause, otherwise this will likely regress when non-async mode is removed
|
|
394
|
+
if (async_mode_flag && !this.linked) {
|
|
395
|
+
this.#commit();
|
|
396
|
+
// Rebases can activate other batches or null it out, therefore restore the new one here
|
|
397
|
+
current_batch = next_batch;
|
|
398
|
+
}
|
|
399
|
+
|
|
347
400
|
// Edge case: During traversal new branches might create effects that run immediately and set state,
|
|
348
401
|
// causing an effect and therefore a root to be scheduled again. We need to traverse the current batch
|
|
349
402
|
// once more in that case - most of the time this will just clean up dirty branches.
|
|
350
403
|
if (this.#roots.length > 0) {
|
|
351
|
-
|
|
404
|
+
if (next_batch === null) {
|
|
405
|
+
next_batch = this;
|
|
406
|
+
this.#link();
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const batch = next_batch;
|
|
352
410
|
batch.#roots.push(...this.#roots.filter((r) => !batch.#roots.includes(r)));
|
|
353
411
|
}
|
|
354
412
|
|
|
355
413
|
if (next_batch !== null) {
|
|
356
|
-
batches.add(next_batch);
|
|
357
|
-
|
|
358
|
-
if (DEV) {
|
|
359
|
-
for (const source of this.current.keys()) {
|
|
360
|
-
/** @type {Set<Source>} */ (source_stacks).add(source);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
414
|
next_batch.#process();
|
|
365
415
|
}
|
|
366
|
-
|
|
367
|
-
// In sync mode flushSync can cause #commit to wrongfully think that there needs to be a rebase, so we only do it in async mode
|
|
368
|
-
// TODO fix the underlying cause, otherwise this will likely regress when non-async mode is removed
|
|
369
|
-
if (async_mode_flag && !batches.has(this)) {
|
|
370
|
-
this.#commit();
|
|
371
|
-
}
|
|
372
416
|
}
|
|
373
417
|
|
|
374
418
|
/**
|
|
@@ -423,6 +467,82 @@ export class Batch {
|
|
|
423
467
|
}
|
|
424
468
|
}
|
|
425
469
|
|
|
470
|
+
#find_earlier_batch() {
|
|
471
|
+
var batch = this.#prev;
|
|
472
|
+
|
|
473
|
+
while (batch !== null) {
|
|
474
|
+
if (!batch.is_fork) {
|
|
475
|
+
// if the batches are connected, break
|
|
476
|
+
for (const [value, [, is_derived]] of this.current) {
|
|
477
|
+
if (batch.current.has(value) && !is_derived) {
|
|
478
|
+
return batch;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
batch = batch.#prev;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return null;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* @param {Batch} batch
|
|
491
|
+
*/
|
|
492
|
+
#merge(batch) {
|
|
493
|
+
for (const [source, value] of batch.current) {
|
|
494
|
+
if (!this.previous.has(source) && batch.previous.has(source)) {
|
|
495
|
+
this.previous.set(source, batch.previous.get(source));
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
this.current.set(source, value);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
for (const [effect, deferred] of batch.async_deriveds) {
|
|
502
|
+
const d = this.async_deriveds.get(effect);
|
|
503
|
+
if (d) deferred.promise.then(d.resolve);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* mark all effects that depend on `batch.current`, except the
|
|
508
|
+
* async effects that we just resolved (TODO unless they depend
|
|
509
|
+
* on values in this batch that are NOT in the later batch?).
|
|
510
|
+
* Through this we also will populate the correct #skipped_branches,
|
|
511
|
+
* oncommit callbacks etc, so we don't need to merge them separately.
|
|
512
|
+
* @param {Value} value
|
|
513
|
+
*/
|
|
514
|
+
const mark = (value) => {
|
|
515
|
+
var reactions = value.reactions;
|
|
516
|
+
if (reactions === null) return;
|
|
517
|
+
|
|
518
|
+
for (const reaction of reactions) {
|
|
519
|
+
var flags = reaction.f;
|
|
520
|
+
|
|
521
|
+
if ((flags & DERIVED) !== 0) {
|
|
522
|
+
mark(/** @type {Derived} */ (reaction));
|
|
523
|
+
} else {
|
|
524
|
+
var effect = /** @type {Effect} */ (reaction);
|
|
525
|
+
|
|
526
|
+
if (flags & (ASYNC | BLOCK_EFFECT) && !this.async_deriveds.has(effect)) {
|
|
527
|
+
this.#maybe_dirty_effects.delete(effect);
|
|
528
|
+
set_signal_status(effect, DIRTY);
|
|
529
|
+
this.schedule(effect);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
for (const source of this.current.keys()) {
|
|
536
|
+
mark(source);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
this.oncommit(() => batch.discard());
|
|
540
|
+
batch.#unlink();
|
|
541
|
+
|
|
542
|
+
current_batch = this;
|
|
543
|
+
this.#process();
|
|
544
|
+
}
|
|
545
|
+
|
|
426
546
|
/**
|
|
427
547
|
* @param {Effect[]} effects
|
|
428
548
|
*/
|
|
@@ -465,9 +585,11 @@ export class Batch {
|
|
|
465
585
|
}
|
|
466
586
|
|
|
467
587
|
flush() {
|
|
468
|
-
var source_stacks = DEV ? new Set() : null;
|
|
469
|
-
|
|
470
588
|
try {
|
|
589
|
+
if (DEV) {
|
|
590
|
+
source_stacks.clear();
|
|
591
|
+
}
|
|
592
|
+
|
|
471
593
|
is_processing = true;
|
|
472
594
|
current_batch = this;
|
|
473
595
|
|
|
@@ -485,7 +607,7 @@ export class Batch {
|
|
|
485
607
|
old_values.clear();
|
|
486
608
|
|
|
487
609
|
if (DEV) {
|
|
488
|
-
for (const source of
|
|
610
|
+
for (const source of source_stacks) {
|
|
489
611
|
source.updated = null;
|
|
490
612
|
}
|
|
491
613
|
}
|
|
@@ -497,7 +619,7 @@ export class Batch {
|
|
|
497
619
|
this.#discard_callbacks.clear();
|
|
498
620
|
this.#fork_commit_callbacks.clear();
|
|
499
621
|
|
|
500
|
-
|
|
622
|
+
this.#unlink();
|
|
501
623
|
}
|
|
502
624
|
|
|
503
625
|
/**
|
|
@@ -508,11 +630,13 @@ export class Batch {
|
|
|
508
630
|
}
|
|
509
631
|
|
|
510
632
|
#commit() {
|
|
633
|
+
this.#unlink();
|
|
634
|
+
|
|
511
635
|
// If there are other pending batches, they now need to be 'rebased' —
|
|
512
636
|
// in other words, we re-run block/async effects with the newly
|
|
513
637
|
// committed state, unless the batch in question has a more
|
|
514
638
|
// recent value for a given source
|
|
515
|
-
for (
|
|
639
|
+
for (let batch = first_batch; batch !== null; batch = batch.#next) {
|
|
516
640
|
var is_earlier = batch.id < this.id;
|
|
517
641
|
|
|
518
642
|
/** @type {Source[]} */
|
|
@@ -535,6 +659,17 @@ export class Batch {
|
|
|
535
659
|
sources.push(source);
|
|
536
660
|
}
|
|
537
661
|
|
|
662
|
+
if (is_earlier) {
|
|
663
|
+
// TODO do we need to restart these in some cases, instead of
|
|
664
|
+
// immediately resolving them? Likely not because of how this.apply() works.
|
|
665
|
+
for (const [effect, deferred] of this.async_deriveds) {
|
|
666
|
+
const d = batch.async_deriveds.get(effect);
|
|
667
|
+
if (d) deferred.promise.then(d.resolve);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (!batch.#started) continue;
|
|
672
|
+
|
|
538
673
|
// Re-run async/block effects that depend on distinct values changed in both batches
|
|
539
674
|
var others = [...batch.current.keys()].filter((s) => !this.current.has(s));
|
|
540
675
|
|
|
@@ -575,19 +710,23 @@ export class Batch {
|
|
|
575
710
|
|
|
576
711
|
checked = new Map();
|
|
577
712
|
var current_unequal = [...batch.current.keys()].filter((c) =>
|
|
578
|
-
this.current.has(c)
|
|
713
|
+
this.current.has(c)
|
|
714
|
+
? /** @type {[any, boolean]} */ (this.current.get(c))[0] !== c.v
|
|
715
|
+
: true
|
|
579
716
|
);
|
|
580
717
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
718
|
+
if (current_unequal.length > 0) {
|
|
719
|
+
for (const effect of this.#new_effects) {
|
|
720
|
+
if (
|
|
721
|
+
(effect.f & (DESTROYED | INERT | EAGER_EFFECT)) === 0 &&
|
|
722
|
+
depends_on(effect, current_unequal, checked)
|
|
723
|
+
) {
|
|
724
|
+
if ((effect.f & (ASYNC | BLOCK_EFFECT)) !== 0) {
|
|
725
|
+
set_signal_status(effect, DIRTY);
|
|
726
|
+
batch.schedule(effect);
|
|
727
|
+
} else {
|
|
728
|
+
batch.#dirty_effects.add(effect);
|
|
729
|
+
}
|
|
591
730
|
}
|
|
592
731
|
}
|
|
593
732
|
}
|
|
@@ -606,17 +745,6 @@ export class Batch {
|
|
|
606
745
|
batch.deactivate();
|
|
607
746
|
}
|
|
608
747
|
}
|
|
609
|
-
|
|
610
|
-
for (const batch of batches) {
|
|
611
|
-
if (batch.#blockers.has(this)) {
|
|
612
|
-
batch.#blockers.delete(this);
|
|
613
|
-
|
|
614
|
-
if (batch.#blockers.size === 0 && !batch.#is_deferred()) {
|
|
615
|
-
batch.activate();
|
|
616
|
-
batch.#process();
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
748
|
}
|
|
621
749
|
|
|
622
750
|
/**
|
|
@@ -624,8 +752,7 @@ export class Batch {
|
|
|
624
752
|
* @param {Effect} effect
|
|
625
753
|
*/
|
|
626
754
|
increment(blocking, effect) {
|
|
627
|
-
|
|
628
|
-
this.#pending.set(effect, pending_count + 1);
|
|
755
|
+
this.#pending += 1;
|
|
629
756
|
|
|
630
757
|
if (blocking) {
|
|
631
758
|
let blocking_pending_count = this.#blocking_pending.get(effect) ?? 0;
|
|
@@ -636,16 +763,9 @@ export class Batch {
|
|
|
636
763
|
/**
|
|
637
764
|
* @param {boolean} blocking
|
|
638
765
|
* @param {Effect} effect
|
|
639
|
-
* @param {boolean} skip - whether to skip updates (because this is triggered by a stale reaction)
|
|
640
766
|
*/
|
|
641
|
-
decrement(blocking, effect
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
if (pending_count === 1) {
|
|
645
|
-
this.#pending.delete(effect);
|
|
646
|
-
} else {
|
|
647
|
-
this.#pending.set(effect, pending_count - 1);
|
|
648
|
-
}
|
|
767
|
+
decrement(blocking, effect) {
|
|
768
|
+
this.#pending -= 1;
|
|
649
769
|
|
|
650
770
|
if (blocking) {
|
|
651
771
|
let blocking_pending_count = this.#blocking_pending.get(effect) ?? 0;
|
|
@@ -657,12 +777,15 @@ export class Batch {
|
|
|
657
777
|
}
|
|
658
778
|
}
|
|
659
779
|
|
|
660
|
-
if (this.#decrement_queued
|
|
780
|
+
if (this.#decrement_queued) return;
|
|
661
781
|
this.#decrement_queued = true;
|
|
662
782
|
|
|
663
783
|
queue_micro_task(() => {
|
|
664
784
|
this.#decrement_queued = false;
|
|
665
|
-
|
|
785
|
+
|
|
786
|
+
if (this.linked) {
|
|
787
|
+
this.flush();
|
|
788
|
+
}
|
|
666
789
|
});
|
|
667
790
|
}
|
|
668
791
|
|
|
@@ -710,20 +833,14 @@ export class Batch {
|
|
|
710
833
|
static ensure() {
|
|
711
834
|
if (current_batch === null) {
|
|
712
835
|
const batch = (current_batch = new Batch());
|
|
836
|
+
batch.#link();
|
|
713
837
|
|
|
714
|
-
if (!is_processing) {
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
if (!is_flushing_sync) {
|
|
718
|
-
queue_micro_task(() => {
|
|
719
|
-
if (current_batch !== batch) {
|
|
720
|
-
// a flushSync happened in the meantime
|
|
721
|
-
return;
|
|
722
|
-
}
|
|
723
|
-
|
|
838
|
+
if (!is_processing && !is_flushing_sync) {
|
|
839
|
+
queue_micro_task(() => {
|
|
840
|
+
if (!batch.#started) {
|
|
724
841
|
batch.flush();
|
|
725
|
-
}
|
|
726
|
-
}
|
|
842
|
+
}
|
|
843
|
+
});
|
|
727
844
|
}
|
|
728
845
|
}
|
|
729
846
|
|
|
@@ -731,7 +848,7 @@ export class Batch {
|
|
|
731
848
|
}
|
|
732
849
|
|
|
733
850
|
apply() {
|
|
734
|
-
if (!async_mode_flag || (!this.is_fork &&
|
|
851
|
+
if (!async_mode_flag || (!this.is_fork && this.#prev === null && this.#next === null)) {
|
|
735
852
|
batch_values = null;
|
|
736
853
|
return;
|
|
737
854
|
}
|
|
@@ -743,28 +860,33 @@ export class Batch {
|
|
|
743
860
|
batch_values.set(source, value);
|
|
744
861
|
}
|
|
745
862
|
|
|
746
|
-
// ...and undo changes belonging to other batches unless they
|
|
747
|
-
for (
|
|
863
|
+
// ...and undo changes belonging to other batches unless they intersect
|
|
864
|
+
for (let batch = first_batch; batch !== null; batch = batch.#next) {
|
|
748
865
|
if (batch === this || batch.is_fork) continue;
|
|
749
866
|
|
|
750
|
-
//
|
|
867
|
+
// If two batches intersect, the latter batch will be merged into the earlier batch,
|
|
868
|
+
// and we should treat them as a single set of changes
|
|
751
869
|
var intersects = false;
|
|
752
|
-
var differs = false;
|
|
753
870
|
|
|
754
871
|
if (batch.id < this.id) {
|
|
755
872
|
for (const [source, [, is_derived]] of batch.current) {
|
|
756
|
-
// Derived values don't partake in the
|
|
873
|
+
// Derived values don't partake in the intersection mechanism, because a derived could
|
|
757
874
|
// be triggered in one batch already but not the other one yet, causing a false-positive
|
|
758
875
|
if (is_derived) continue;
|
|
759
876
|
|
|
760
|
-
|
|
761
|
-
|
|
877
|
+
if (this.current.has(source)) {
|
|
878
|
+
intersects = true;
|
|
879
|
+
break;
|
|
880
|
+
}
|
|
762
881
|
}
|
|
763
882
|
}
|
|
764
883
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
884
|
+
// Since the latter batch merges into the earlier (if it resolves before the earlier one),
|
|
885
|
+
// we treat the earlier values as "already applied". This way we don't need to rerun async
|
|
886
|
+
// effects of the earlier batch in case they are merged.
|
|
887
|
+
// As a result you can think of batch_values as having the latest values of all intersecting
|
|
888
|
+
// batches up until this batch.
|
|
889
|
+
if (!intersects) {
|
|
768
890
|
for (const [source, previous] of batch.previous) {
|
|
769
891
|
if (!batch_values.has(source)) {
|
|
770
892
|
batch_values.set(source, previous);
|
|
@@ -830,6 +952,36 @@ export class Batch {
|
|
|
830
952
|
|
|
831
953
|
this.#roots.push(e);
|
|
832
954
|
}
|
|
955
|
+
|
|
956
|
+
#link() {
|
|
957
|
+
if (last_batch === null) {
|
|
958
|
+
first_batch = last_batch = this;
|
|
959
|
+
} else {
|
|
960
|
+
last_batch.#next = this;
|
|
961
|
+
this.#prev = last_batch;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
last_batch = this;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
#unlink() {
|
|
968
|
+
var prev = this.#prev;
|
|
969
|
+
var next = this.#next;
|
|
970
|
+
|
|
971
|
+
if (prev === null) {
|
|
972
|
+
first_batch = next;
|
|
973
|
+
} else {
|
|
974
|
+
prev.#next = next;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
if (next === null) {
|
|
978
|
+
last_batch = prev;
|
|
979
|
+
} else {
|
|
980
|
+
next.#prev = prev;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
this.linked = false;
|
|
984
|
+
}
|
|
833
985
|
}
|
|
834
986
|
|
|
835
987
|
// TODO Svelte@6 think about removing the callback argument.
|
|
@@ -1083,6 +1235,9 @@ function eager_flush() {
|
|
|
1083
1235
|
});
|
|
1084
1236
|
}
|
|
1085
1237
|
|
|
1238
|
+
/** @type {Map<Reaction, Source<number>>} */
|
|
1239
|
+
var version_map = new Map();
|
|
1240
|
+
|
|
1086
1241
|
/**
|
|
1087
1242
|
* Implementation of `$state.eager(fn())`
|
|
1088
1243
|
* @template T
|
|
@@ -1090,10 +1245,22 @@ function eager_flush() {
|
|
|
1090
1245
|
* @returns {T}
|
|
1091
1246
|
*/
|
|
1092
1247
|
export function eager(fn) {
|
|
1093
|
-
var version = source(0);
|
|
1094
1248
|
var initial = true;
|
|
1095
1249
|
var value = /** @type {T} */ (undefined);
|
|
1096
1250
|
|
|
1251
|
+
if (active_reaction === null) {
|
|
1252
|
+
return fn();
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
let parent = active_reaction;
|
|
1256
|
+
|
|
1257
|
+
let version = version_map.get(parent) ?? source(0);
|
|
1258
|
+
version_map.set(parent, version);
|
|
1259
|
+
|
|
1260
|
+
teardown(() => {
|
|
1261
|
+
if (parent.f & DESTROYING) version_map.delete(parent);
|
|
1262
|
+
});
|
|
1263
|
+
|
|
1097
1264
|
get(version);
|
|
1098
1265
|
|
|
1099
1266
|
eager_effect(() => {
|
|
@@ -1213,7 +1380,7 @@ export function fork(fn) {
|
|
|
1213
1380
|
return;
|
|
1214
1381
|
}
|
|
1215
1382
|
|
|
1216
|
-
if (!
|
|
1383
|
+
if (!batch.linked) {
|
|
1217
1384
|
e.fork_discarded();
|
|
1218
1385
|
}
|
|
1219
1386
|
|
|
@@ -1259,7 +1426,7 @@ export function fork(fn) {
|
|
|
1259
1426
|
source.wv = increment_write_version();
|
|
1260
1427
|
}
|
|
1261
1428
|
|
|
1262
|
-
if (!committed &&
|
|
1429
|
+
if (!committed && batch.linked) {
|
|
1263
1430
|
batch.discard();
|
|
1264
1431
|
}
|
|
1265
1432
|
}
|
|
@@ -1270,5 +1437,5 @@ export function fork(fn) {
|
|
|
1270
1437
|
* Forcibly remove all current batches, to prevent cross-talk between tests
|
|
1271
1438
|
*/
|
|
1272
1439
|
export function clear() {
|
|
1273
|
-
|
|
1440
|
+
first_batch = last_batch = null;
|
|
1274
1441
|
}
|