fix overlay equality logic

entangle-batches-2
Simon Holthausen 2 months ago
parent 1ce167a7cb
commit 072b97d426
No known key found for this signature in database

@ -5,7 +5,16 @@ import { active_batch, current_batch } from './batch.js';
function get_value(signal) { function get_value(signal) {
var batch = active_batch ?? (current_batch?.is_fork ? current_batch : null); var batch = active_batch ?? (current_batch?.is_fork ? current_batch : null);
var override = batch?.values?.get(signal); var override = batch?.values?.get(signal);
return override === undefined ? signal.v : override[0];
if (override === undefined) return signal.v;
// In a fork, or for a batch's own writes, the override _is_ the value being
// written over. An override owned by another live batch is just that batch's
// pre-write world being shown to us — writes must compare against the real
// (pending) value, otherwise a write that equals the visible-but-stale value
// would be swallowed, and one that equals the pending value would be
// treated as a change
return batch?.is_fork || override[1] === null ? override[0] : signal.v;
} }
/** @type {Equals} */ /** @type {Equals} */

@ -0,0 +1,36 @@
import { tick } from 'svelte';
import { test } from '../../test';
const buttons = '<button>b</button><button>z</button><button>shift</button>';
export default test({
async test({ assert, target, logs }) {
await tick();
const [b, z, shift] = target.querySelectorAll('button');
assert.htmlEqual(target.innerHTML, `<p>0</p><p>a</p>${buttons}`);
assert.deepEqual(logs, ['fetch a']);
// write x = 'b' — the batch is pending on its async expression
b.click();
await tick();
assert.deepEqual(logs, ['fetch a', 'fetch b']);
// an independent batch runs the effect, which writes x = 'b' — the
// real value is already 'b', so this is a no-op: the async expression
// must not re-run (no needless refetch), and the batch must not be
// entangled with the pending one (z commits immediately)
z.click();
await tick();
assert.deepEqual(logs, ['fetch a', 'fetch b']);
assert.htmlEqual(target.innerHTML, `<p>1</p><p>a</p>${buttons}`);
// the pending batch settles
shift.click();
await tick();
shift.click();
await tick();
assert.deepEqual(logs, ['fetch a', 'fetch b']);
assert.htmlEqual(target.innerHTML, `<p>1</p><p>b</p>${buttons}`);
}
});

@ -0,0 +1,25 @@
<script>
let x = $state('a');
let z = $state(0);
let pend = false;
const deferred = [];
function delay(value) {
console.log(`fetch ${value}`);
if (!pend) return value;
return new Promise((resolve) => deferred.push(() => resolve(value)));
}
$effect(() => {
if (z > 0) {
x = 'b';
}
});
</script>
<p>{z}</p>
<p>{await delay(x)}</p>
<button onclick={() => { pend = true; x = 'b'; }}>b</button>
<button onclick={() => z++}>z</button>
<button onclick={() => deferred.shift()?.()}>shift</button>

@ -0,0 +1,33 @@
import { tick } from 'svelte';
import { test } from '../../test';
const buttons = '<button>b</button><button>reset</button><button>shift</button>';
export default test({
async test({ assert, target, logs }) {
await tick();
const [b, reset, shift] = target.querySelectorAll('button');
assert.htmlEqual(target.innerHTML, `<p>0</p><p>a</p>${buttons}`);
assert.deepEqual(logs, ['fetch a']);
// write x = 'b' — the batch is pending on its async expression
b.click();
await tick();
assert.deepEqual(logs, ['fetch a', 'fetch b']);
// an independent batch runs the effect, which resets x = 'a'. The real
// (pending) value is 'b', so this is a genuine change and must not be
// swallowed just because the effect's world still shows 'a'
reset.click();
await tick();
assert.deepEqual(logs, ['fetch a', 'fetch b', 'fetch a']);
// resolve all in-flight runs — the reset must win
shift.click();
await tick();
shift.click();
await tick();
assert.htmlEqual(target.innerHTML, `<p>1</p><p>a</p>${buttons}`);
}
});

@ -0,0 +1,25 @@
<script>
let x = $state('a');
let z = $state(0);
let pend = false;
const deferred = [];
function delay(value) {
console.log(`fetch ${value}`);
if (!pend) return value;
return new Promise((resolve) => deferred.push(() => resolve(value)));
}
$effect(() => {
if (z > 0) {
x = 'a';
}
});
</script>
<p>{z}</p>
<p>{await delay(x)}</p>
<button onclick={() => { pend = true; x = 'b'; }}>b</button>
<button onclick={() => z++}>reset</button>
<button onclick={() => deferred.shift()?.()}>shift</button>
Loading…
Cancel
Save