don't write values to deriveds when time travelling

pull/16197/head
Rich Harris 4 months ago
parent 3cf0b9e077
commit 302dff234b

@ -1,4 +1,4 @@
/** @import { Effect, Source } from '#client' */
/** @import { Derived, Effect, Source } from '#client' */
import { CLEAN, DIRTY } from '#client/constants';
import {
flush_queued_effects,
@ -23,7 +23,8 @@ function update_pending() {
internal_set(pending, batches.size > 0);
}
let uid = 1;
/** @type {Map<Derived, any> | null} */
export let batch_deriveds = null;
export class Batch {
/** @type {Map<Source, any>} */
@ -60,8 +61,20 @@ export class Batch {
process(root_effects) {
set_queued_root_effects([]);
/** @type {Map<Source, { v: unknown, wv: number }>} */
var current_values = new Map();
/** @type {Map<Source, { v: unknown, wv: number }> | null} */
var current_values = null;
var time_travelling = false;
for (const batch of batches) {
if (batch !== this) {
time_travelling = true;
break;
}
}
if (time_travelling) {
current_values = new Map();
batch_deriveds = new Map();
for (const [source, current] of this.#current) {
current_values.set(source, { v: source.v, wv: source.wv });
@ -78,6 +91,7 @@ export class Batch {
}
}
}
}
for (const root of root_effects) {
process_effects(this, root);
@ -144,6 +158,7 @@ export class Batch {
for (const e of this.effects) set_signal_status(e, CLEAN);
}
if (current_values) {
for (const [source, { v, wv }] of current_values) {
// reset the source to the current value (unless
// it got a newer value as a result of effects running)
@ -152,6 +167,9 @@ export class Batch {
}
}
batch_deriveds = null;
}
for (const effect of this.async_effects) {
update_effect(effect);
}

@ -39,6 +39,7 @@ import { flush_tasks } from './dom/task.js';
import { internal_set, old_values } from './reactivity/sources.js';
import {
destroy_derived_effects,
execute_derived,
from_async_derived,
recent_async_deriveds,
update_derived
@ -57,7 +58,7 @@ import {
import { Boundary } from './dom/blocks/boundary.js';
import * as w from './warnings.js';
import { is_firefox } from './dom/operations.js';
import { current_batch, Batch } from './reactivity/batch.js';
import { current_batch, Batch, batch_deriveds } from './reactivity/batch.js';
import { log_effect_tree, root } from './dev/debug.js';
// Used for DEV time error handling
@ -986,7 +987,10 @@ export function get(signal) {
}
}
if (is_derived) {
// if this is a derived, we may need to update it, but
// not if `batch_deriveds` is not null (meaning we're
// currently time travelling))
if (is_derived && batch_deriveds === null) {
derived = /** @type {Derived} */ (signal);
if (check_dirtiness(derived)) {
@ -1032,6 +1036,19 @@ export function get(signal) {
return old_values.get(signal);
}
// if we're time travelling, we don't want to update the
// intrinsic value of the derived — we want to compute it
// once and stash it for the duration of batch processing
if (is_derived && batch_deriveds !== null) {
derived = /** @type {Derived} */ (signal);
if (!batch_deriveds.has(derived)) {
batch_deriveds.set(derived, execute_derived(derived));
}
return batch_deriveds.get(derived);
}
return signal.v;
}

@ -0,0 +1,59 @@
import { flushSync, settled, tick } from 'svelte';
import { test } from '../../test';
export default test({
html: `<p>loading...</p>`,
async test({ assert, target }) {
await Promise.resolve();
await Promise.resolve();
await Promise.resolve();
await Promise.resolve();
assert.htmlEqual(
target.innerHTML,
`
<button>log</button>
<button>x 1</button>
<button>other 1</button>
<p>1</p>
<p>1</p>
<p>1</p>
`
);
const [log, x, other] = target.querySelectorAll('button');
flushSync(() => x.click());
flushSync(() => other.click());
assert.htmlEqual(
target.innerHTML,
`
<button>log</button>
<button>x 1</button>
<button>other 2</button>
<p>1</p>
<p>1</p>
<p>1</p>
`
);
await Promise.resolve();
await Promise.resolve();
await Promise.resolve();
await Promise.resolve();
assert.htmlEqual(
target.innerHTML,
`
<button>log</button>
<button>x 2</button>
<button>other 2</button>
<p>2</p>
<p>2</p>
<p>2</p>
`
);
}
});

@ -0,0 +1,19 @@
<script>
let x = $state(1);
let y = $derived(x);
let other = $state(1);
</script>
<svelte:boundary>
<button onclick={() => console.log({ x, y })}>log</button>
<button onclick={() => x += 1}>x {x}</button>
<button onclick={() => other += 1}>other {other}</button>
<p>{x}</p>
<p>{await x}</p>
<p>{y}</p>
{#snippet pending()}
<p>loading...</p>
{/snippet}
</svelte:boundary>
Loading…
Cancel
Save