From 2071160ce89dab1834c48042b739c725ff634597 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 31 May 2025 21:04:15 -0400 Subject: [PATCH] keep order --- .../src/internal/client/reactivity/batch.js | 95 ++++++++++++++----- .../_config.js | 40 ++++++++ .../main.svelte | 17 ++++ 3 files changed, 126 insertions(+), 26 deletions(-) create mode 100644 packages/svelte/tests/runtime-runes/samples/async-linear-order-different-deriveds/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-linear-order-different-deriveds/main.svelte diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index 7f5cdea1a1..8653bafb5c 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -86,18 +86,61 @@ export class Batch { } if (this.async_effects.length === 0 && this.settled()) { - var render_effects = this.render_effects; - var effects = this.effects; + var merged = false; + + // if there are older batches with overlapping + // state, we can't commit this batch. instead, + // we merge it into the older batches + for (const batch of batches) { + if (batch === this) break; + + for (const [source] of batch.#current) { + if (this.#current.has(source)) { + merged = true; + + for (const [source, value] of this.#current) { + batch.#current.set(source, value); + // TODO what about batch.#previous? + } + + for (const e of this.render_effects) { + set_signal_status(e, CLEAN); + // TODO use sets instead of arrays + if (!batch.render_effects.includes(e)) { + batch.render_effects.push(e); + } + } + + for (const e of this.effects) { + set_signal_status(e, CLEAN); + // TODO use sets instead of arrays + if (!batch.effects.includes(e)) { + batch.effects.push(e); + } + } + + this.remove(); + break; + } + } + } - this.render_effects = []; - this.effects = []; + if (merged) { + this.remove(); + } else { + var render_effects = this.render_effects; + var effects = this.effects; - this.commit(); + this.render_effects = []; + this.effects = []; + + this.commit(); - flush_queued_effects(render_effects); - flush_queued_effects(effects); + flush_queued_effects(render_effects); + flush_queued_effects(effects); - this.deferred?.resolve(); + this.deferred?.resolve(); + } } else { for (const e of this.render_effects) set_signal_status(e, CLEAN); for (const e of this.effects) set_signal_status(e, CLEAN); @@ -133,24 +176,24 @@ export class Batch { remove() { batches.delete(this); - for (var batch of batches) { - /** @type {Source} */ - var source; - - if (batch.#id < this.#id) { - // other batch is older than this - for (source of this.#previous.keys()) { - batch.#previous.delete(source); - } - } else { - // other batch is newer than this - for (source of batch.#previous.keys()) { - if (this.#previous.has(source)) { - batch.#previous.set(source, source.v); - } - } - } - } + // for (var batch of batches) { + // /** @type {Source} */ + // var source; + + // if (batch.#id < this.#id) { + // // other batch is older than this + // for (source of this.#previous.keys()) { + // batch.#previous.delete(source); + // } + // } else { + // // other batch is newer than this + // for (source of batch.#previous.keys()) { + // if (this.#previous.has(source)) { + // batch.#previous.set(source, source.v); + // } + // } + // } + // } } restore() { diff --git a/packages/svelte/tests/runtime-runes/samples/async-linear-order-different-deriveds/_config.js b/packages/svelte/tests/runtime-runes/samples/async-linear-order-different-deriveds/_config.js new file mode 100644 index 0000000000..e4d6979acf --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-linear-order-different-deriveds/_config.js @@ -0,0 +1,40 @@ +import { flushSync, settled, tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + html: `

loading...

`, + + async test({ assert, target }) { + const [both, a, b] = target.querySelectorAll('button'); + + await Promise.resolve(); + await Promise.resolve(); + await Promise.resolve(); + await Promise.resolve(); + + assert.htmlEqual( + target.innerHTML, + ` + +

1 * 2 = 2

+

2 * 2 = 4

+ ` + ); + + flushSync(() => both.click()); + flushSync(() => b.click()); + + await Promise.resolve(); + await Promise.resolve(); + await Promise.resolve(); + + assert.htmlEqual( + target.innerHTML, + ` + +

2 * 2 = 4

+

4 * 2 = 8

+ ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-linear-order-different-deriveds/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-linear-order-different-deriveds/main.svelte new file mode 100644 index 0000000000..432eed976c --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-linear-order-different-deriveds/main.svelte @@ -0,0 +1,17 @@ + + + + + + + +

{a} * 2 = {await (a * 2)}

+

{b} * 2 = {b * 2}

+ + {#snippet pending()} +

loading...

+ {/snippet} +