another fork fix

async-another-try
Simon Holthausen 2 days ago
parent df50aa8384
commit 08a17b9839
No known key found for this signature in database

@ -21,7 +21,7 @@ import {
FORK_ONLY_BRANCH
} from '#client/constants';
import { async_mode_flag } from '../../flags/index.js';
import { deferred, define_property, includes } from '../../shared/utils.js';
import { deferred, define_property } from '../../shared/utils.js';
import {
active_reaction,
get,
@ -911,11 +911,10 @@ export class Batch {
!is_derived &&
(!current || current.v !== value) &&
((source.f & ASYNC) === 0 ||
!depends_on(
/** @type {Effect} */ (/** @type {Source} */ (source).e),
[...batch.current.keys()].filter((s) => !this.current.has(s)),
new Map()
))
// If the fork ran an async effect, its pending/resolved result belongs to the
// fork. Revalidate it when its inputs change, not when another batch resolves
// the same expression with a different view of those inputs.
!batch.#stale_effects?.has(/** @type {Effect} */ (/** @type {Source} */ (source).e)))
) {
batch.current.delete(source);
batch.queue_revalidation(source);
@ -1387,33 +1386,6 @@ function mark_eager_effects(value, effects) {
}
}
/**
* @param {Reaction} reaction
* @param {Value[]} sources
* @param {Map<Reaction, boolean>} checked
*/
function depends_on(reaction, sources, checked) {
const depends = checked.get(reaction);
if (depends !== undefined) return depends;
if (reaction.deps !== null) {
for (const dep of reaction.deps) {
if (includes.call(sources, dep)) {
return true;
}
if ((dep.f & DERIVED) !== 0 && depends_on(/** @type {Derived} */ (dep), sources, checked)) {
checked.set(/** @type {Derived} */ (dep), true);
return true;
}
}
}
checked.set(reaction, false);
return false;
}
/**
* @param {Effect} effect
* @returns {void}
@ -1623,8 +1595,8 @@ export function fork(fn) {
// Apply changes and update write versions so deriveds see the change. Everything still
// in `batch.current` at this point is the latest value: sources that the real world has
// written to in the meantime were removed from the fork via `notify_fork` (an async
// source only survives if its effect depends on inputs that only the fork changed).
// written to in the meantime were removed from the fork via `notify_fork`, while
// async results are kept up to date by revalidating their producers when inputs change.
// We use fresh versions rather than the fork-time `content.wv`, because the real world
// may have run reactions since then whose versions would otherwise outrank them.
for (var [source, content] of batch.current) {

@ -0,0 +1,48 @@
import { tick } from 'svelte';
import { test } from '../../test';
export default test({
async test({ assert, target, logs }) {
await tick();
const [fork, update, pop, commit] = target.querySelectorAll('button');
const [sum, doubled] = target.querySelectorAll('p');
logs.length = 0;
fork.click();
await tick();
assert.deepEqual(logs, ['sum 1,0']);
logs.length = 0;
// Revalidate twice to also check that retaining async results does not
// prevent the fork from responding to genuine changes to its inputs.
for (const y of [1, 2]) {
update.click();
await tick();
assert.deepEqual(logs, [`sum 0,${y}`, `sum 1,${y}`]);
logs.length = 0;
pop.click(); // resolve the fork before the real world
await tick();
assert.deepEqual(logs, [`double ${y + 1}`]);
logs.length = 0;
pop.click();
await tick();
assert.deepEqual(logs, [`double ${y}`]);
assert.htmlEqual(sum.innerHTML, String(y));
assert.htmlEqual(doubled.innerHTML, String(y * 2));
logs.length = 0;
}
commit.click();
await tick();
assert.deepEqual(logs, []);
assert.htmlEqual(sum.innerHTML, '3');
assert.htmlEqual(doubled.innerHTML, '6');
pop.click(); // the superseded first fork run
await tick();
assert.htmlEqual(sum.innerHTML, '3');
assert.htmlEqual(doubled.innerHTML, '6');
}
});

@ -0,0 +1,29 @@
<script>
import { fork } from 'svelte';
let x = $state(0);
let y = $state(0);
let f;
const deferred = [];
function delay(x, y) {
console.log(`sum ${x},${y}`);
const value = x + y;
return value ? new Promise((resolve) => deferred.push(() => resolve(value))) : value;
}
async function double(value) {
console.log(`double ${value}`);
return value * 2;
}
let sum = $derived(await delay(x, y));
</script>
<button onclick={() => { f = fork(() => x++); }}>fork</button>
<button onclick={() => y++}>update</button>
<button onclick={() => deferred.pop()?.()}>pop</button>
<button onclick={() => f.commit()}>commit</button>
<p>{sum}</p>
<p>{await double(sum)}</p>
Loading…
Cancel
Save