make unnecessary reruns less likely

eager-block-fix
Simon Holthausen 6 days ago
parent 62f09d8546
commit 54db9bf102

@ -53,7 +53,7 @@ export function CallExpression(node, context) {
case '$state.eager': {
// Keep a stable source for the eager state across reruns to properly notify dependencies
const id = context.state.scope.root.unique('eager');
context.state.hoisted.push(b.var(id, b.call('$.state', b.literal(0))));
context.state.hoisted.push(b.var(id, b.new(b.id('Map'))));
return b.call(
'$.eager',
id,
@ -88,7 +88,7 @@ export function CallExpression(node, context) {
case '$effect.pending': {
// Keep a stable source for the pending state across reruns to properly notify dependencies
const id = context.state.scope.root.unique('pending');
context.state.hoisted.push(b.var(id, b.call('$.state', b.literal(0))));
context.state.hoisted.push(b.var(id, b.new(b.id('Map'))));
return b.call('$.eager', id, b.thunk(b.call('$.pending')));
}

@ -686,6 +686,7 @@ export {
if_builder as if,
this_instance as this,
null_instance as null,
new_builder as new,
debugger_builder as debugger
};

@ -16,7 +16,8 @@ import {
EAGER_EFFECT,
ERROR_VALUE,
MANAGED_EFFECT,
REACTION_RAN
REACTION_RAN,
DESTROYING
} from '#client/constants';
import { async_mode_flag } from '../../flags/index.js';
import { deferred, define_property, includes } from '../../shared/utils.js';
@ -33,7 +34,7 @@ import { flush_tasks, queue_micro_task } from '../dom/task.js';
import { DEV } from 'esm-env';
import { invoke_error_boundary } from '../error-handling.js';
import { flush_eager_effects, old_values, set_eager_effects, source, update } from './sources.js';
import { eager_effect, unlink_effect } from './effects.js';
import { eager_effect, teardown, unlink_effect } from './effects.js';
import { defer_effect } from './utils.js';
import { UNINITIALIZED } from '../../../constants.js';
import { set_signal_status } from './status.js';
@ -1092,14 +1093,31 @@ function eager_flush() {
/**
* Implementation of `$state.eager(fn())`
* @template T
* @param {Source<number>} version
* @param {Map<Effect | null, Source<number>>} version_map
* @param {() => T} fn
* @returns {T}
*/
export function eager(version, fn) {
export function eager(version_map, fn) {
var initial = true;
var value = /** @type {T} */ (undefined);
// To prevent an each block or a reusable function with a $state.eager to rerun
// all the unrelated effects at once, we traverse up the tree until we find a branch,
// which will be right below a block effect we care about. To prevent memory leaks
// we also have to remove the entry from the map if the branch is removed.
let e = active_effect;
while (e !== null && (e.f & BRANCH_EFFECT) === 0) {
e = e.parent;
}
let version = version_map.get(e) ?? source(0);
version_map.set(e, version);
if (e) {
teardown(() => {
if (e.f & DESTROYING) version_map.delete(e);
});
}
get(version);
eager_effect(() => {

@ -0,0 +1,32 @@
import { tick } from 'svelte';
import { test } from '../../test';
export default test({
async test({ assert, target, logs }) {
await tick();
const [increment, resolve] = target.querySelectorAll('button');
logs.length = 0;
increment.click();
await tick();
assert.htmlEqual(
target.innerHTML,
`<button>increment</button> <button>resolve</button>
<ul><li>0 / 0</li><li>0 / loading...</li><li>0 / 0</li></ul>`
);
resolve.click();
await tick();
assert.htmlEqual(
target.innerHTML,
`<button>increment</button> <button>resolve</button>
<ul><li>0 / 0</li><li>1 / 1</li><li>0 / 0</li></ul>`
);
assert.equal(
logs.some((l) => l.toString().includes('0 ') || l.toString().includes('2')),
false,
'only the second $state.eager should have been evaluated'
);
}
});

@ -0,0 +1,24 @@
<script>
let counts = $state([0, 0, 0]);
const queued = [];
async function delay(v) {
if (!v) return v;
return new Promise(r => queued.push(() => r(v)));
}
</script>
<button onclick={() => counts[1]++}>increment</button>
<button onclick={() => queued.shift()?.()}>resolve</button>
<ul>
{#each counts as count, i}
<li>
{await delay(count)} /
{#if console.log(i) || $state.eager(count) !== count}
loading...
{:else}
{count}
{/if}
</li>
{/each}
</ul>
Loading…
Cancel
Save