Skip to content

Commit 302dff2

Browse files
committed
don't write values to deriveds when time travelling
1 parent 3cf0b9e commit 302dff2

File tree

4 files changed

+133
-20
lines changed

4 files changed

+133
-20
lines changed

packages/svelte/src/internal/client/reactivity/batch.js

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/** @import { Effect, Source } from '#client' */
1+
/** @import { Derived, Effect, Source } from '#client' */
22
import { CLEAN, DIRTY } from '#client/constants';
33
import {
44
flush_queued_effects,
@@ -23,7 +23,8 @@ function update_pending() {
2323
internal_set(pending, batches.size > 0);
2424
}
2525

26-
let uid = 1;
26+
/** @type {Map<Derived, any> | null} */
27+
export let batch_deriveds = null;
2728

2829
export class Batch {
2930
/** @type {Map<Source, any>} */
@@ -60,21 +61,34 @@ export class Batch {
6061
process(root_effects) {
6162
set_queued_root_effects([]);
6263

63-
/** @type {Map<Source, { v: unknown, wv: number }>} */
64-
var current_values = new Map();
64+
/** @type {Map<Source, { v: unknown, wv: number }> | null} */
65+
var current_values = null;
66+
var time_travelling = false;
6567

66-
for (const [source, current] of this.#current) {
67-
current_values.set(source, { v: source.v, wv: source.wv });
68-
source.v = current;
68+
for (const batch of batches) {
69+
if (batch !== this) {
70+
time_travelling = true;
71+
break;
72+
}
6973
}
7074

71-
for (const batch of batches) {
72-
if (batch === this) continue;
75+
if (time_travelling) {
76+
current_values = new Map();
77+
batch_deriveds = new Map();
78+
79+
for (const [source, current] of this.#current) {
80+
current_values.set(source, { v: source.v, wv: source.wv });
81+
source.v = current;
82+
}
7383

74-
for (const [source, previous] of batch.#previous) {
75-
if (!current_values.has(source)) {
76-
current_values.set(source, { v: source.v, wv: source.wv });
77-
source.v = previous;
84+
for (const batch of batches) {
85+
if (batch === this) continue;
86+
87+
for (const [source, previous] of batch.#previous) {
88+
if (!current_values.has(source)) {
89+
current_values.set(source, { v: source.v, wv: source.wv });
90+
source.v = previous;
91+
}
7892
}
7993
}
8094
}
@@ -144,12 +158,16 @@ export class Batch {
144158
for (const e of this.effects) set_signal_status(e, CLEAN);
145159
}
146160

147-
for (const [source, { v, wv }] of current_values) {
148-
// reset the source to the current value (unless
149-
// it got a newer value as a result of effects running)
150-
if (source.wv <= wv) {
151-
source.v = v;
161+
if (current_values) {
162+
for (const [source, { v, wv }] of current_values) {
163+
// reset the source to the current value (unless
164+
// it got a newer value as a result of effects running)
165+
if (source.wv <= wv) {
166+
source.v = v;
167+
}
152168
}
169+
170+
batch_deriveds = null;
153171
}
154172

155173
for (const effect of this.async_effects) {

packages/svelte/src/internal/client/runtime.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import { flush_tasks } from './dom/task.js';
3939
import { internal_set, old_values } from './reactivity/sources.js';
4040
import {
4141
destroy_derived_effects,
42+
execute_derived,
4243
from_async_derived,
4344
recent_async_deriveds,
4445
update_derived
@@ -57,7 +58,7 @@ import {
5758
import { Boundary } from './dom/blocks/boundary.js';
5859
import * as w from './warnings.js';
5960
import { is_firefox } from './dom/operations.js';
60-
import { current_batch, Batch } from './reactivity/batch.js';
61+
import { current_batch, Batch, batch_deriveds } from './reactivity/batch.js';
6162
import { log_effect_tree, root } from './dev/debug.js';
6263

6364
// Used for DEV time error handling
@@ -986,7 +987,10 @@ export function get(signal) {
986987
}
987988
}
988989

989-
if (is_derived) {
990+
// if this is a derived, we may need to update it, but
991+
// not if `batch_deriveds` is not null (meaning we're
992+
// currently time travelling))
993+
if (is_derived && batch_deriveds === null) {
990994
derived = /** @type {Derived} */ (signal);
991995

992996
if (check_dirtiness(derived)) {
@@ -1032,6 +1036,19 @@ export function get(signal) {
10321036
return old_values.get(signal);
10331037
}
10341038

1039+
// if we're time travelling, we don't want to update the
1040+
// intrinsic value of the derived — we want to compute it
1041+
// once and stash it for the duration of batch processing
1042+
if (is_derived && batch_deriveds !== null) {
1043+
derived = /** @type {Derived} */ (signal);
1044+
1045+
if (!batch_deriveds.has(derived)) {
1046+
batch_deriveds.set(derived, execute_derived(derived));
1047+
}
1048+
1049+
return batch_deriveds.get(derived);
1050+
}
1051+
10351052
return signal.v;
10361053
}
10371054

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { flushSync, settled, tick } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
html: `<p>loading...</p>`,
6+
7+
async test({ assert, target }) {
8+
await Promise.resolve();
9+
await Promise.resolve();
10+
await Promise.resolve();
11+
await Promise.resolve();
12+
13+
assert.htmlEqual(
14+
target.innerHTML,
15+
`
16+
<button>log</button>
17+
<button>x 1</button>
18+
<button>other 1</button>
19+
<p>1</p>
20+
<p>1</p>
21+
<p>1</p>
22+
`
23+
);
24+
25+
const [log, x, other] = target.querySelectorAll('button');
26+
27+
flushSync(() => x.click());
28+
flushSync(() => other.click());
29+
30+
assert.htmlEqual(
31+
target.innerHTML,
32+
`
33+
<button>log</button>
34+
<button>x 1</button>
35+
<button>other 2</button>
36+
<p>1</p>
37+
<p>1</p>
38+
<p>1</p>
39+
`
40+
);
41+
42+
await Promise.resolve();
43+
await Promise.resolve();
44+
await Promise.resolve();
45+
await Promise.resolve();
46+
47+
assert.htmlEqual(
48+
target.innerHTML,
49+
`
50+
<button>log</button>
51+
<button>x 2</button>
52+
<button>other 2</button>
53+
<p>2</p>
54+
<p>2</p>
55+
<p>2</p>
56+
`
57+
);
58+
}
59+
});
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<script>
2+
let x = $state(1);
3+
let y = $derived(x);
4+
let other = $state(1);
5+
</script>
6+
7+
<svelte:boundary>
8+
<button onclick={() => console.log({ x, y })}>log</button>
9+
<button onclick={() => x += 1}>x {x}</button>
10+
<button onclick={() => other += 1}>other {other}</button>
11+
12+
<p>{x}</p>
13+
<p>{await x}</p>
14+
<p>{y}</p>
15+
16+
{#snippet pending()}
17+
<p>loading...</p>
18+
{/snippet}
19+
</svelte:boundary>

0 commit comments

Comments
 (0)