From eb7ba0d0a290688f2de31a77923a4eff74533b94 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 13 Mar 2024 17:06:36 +0000 Subject: [PATCH] expand on effect sequencing with levels --- .../src/internal/client/reactivity/effects.js | 42 ++++++++++++++----- .../svelte/src/internal/client/runtime.js | 29 ++++++++++++- packages/svelte/src/reactivity/set.test.ts | 2 +- .../binding-input-group-each-8/_config.js | 21 ++++++++-- .../_config.js | 21 ++++++++-- .../reactive-compound-operator/_config.js | 9 +++- .../reactive-import-statement/_config.js | 9 +++- .../reactive-update-expression/_config.js | 9 +++- 8 files changed, 114 insertions(+), 28 deletions(-) diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index 99c98013d4..82b0280a9d 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -5,7 +5,6 @@ import { current_effect, current_reaction, destroy_children, - flush_local_render_effects, get, remove_reactions, schedule_effect, @@ -15,15 +14,31 @@ import { import { DIRTY, MANAGED, RENDER_EFFECT, EFFECT, PRE_EFFECT, DESTROYED } from '../constants.js'; import { set } from './sources.js'; +export let current_pre_effect_sibling_count = 0; +export let current_render_effect_sibling_count = 0; +export let current_effect_sibling_count = 0; + +/** + * @param {number} pre + * @param {number} render + * @param {number} effect + */ +export function set_sibling_count(pre, render, effect) { + current_pre_effect_sibling_count = pre; + current_render_effect_sibling_count = render; + current_effect_sibling_count = effect; +} + /** * @param {import('./types.js').EffectType} type * @param {(() => void | (() => void)) | ((b: import('#client').Block) => void | (() => void))} fn * @param {boolean} sync + * @param {number} sibling_count * @param {null | import('#client').Block} block * @param {boolean} init * @returns {import('#client').Effect} */ -function create_effect(type, fn, sync, block = current_block, init = true) { +function create_effect(type, fn, sync, sibling_count, block = current_block, init = true) { /** @type {import('#client').Effect} */ const signal = { block, @@ -39,7 +54,11 @@ function create_effect(type, fn, sync, block = current_block, init = true) { }; if (current_effect !== null) { - signal.l = current_effect.l + 1; + // 256 per effect level + const level = current_effect.l; + const mod = level % 256; + const next_level = (level - mod) + 256 + sibling_count; + signal.l = next_level; } if ((type & MANAGED) === 0) { @@ -88,7 +107,7 @@ export function user_effect(fn) { current_component_context !== null && !current_component_context.m; - const effect = create_effect(EFFECT, fn, false, current_block, !defer); + const effect = create_effect(EFFECT, fn, false, current_effect_sibling_count++, current_block, !defer); if (defer) { const context = /** @type {import('#client').ComponentContext} */ (current_component_context); @@ -115,7 +134,7 @@ export function user_root_effect(fn) { * @returns {import('#client').Effect} */ export function effect(fn) { - return create_effect(EFFECT, fn, false); + return create_effect(EFFECT, fn, false, current_effect_sibling_count++); } /** @@ -123,7 +142,7 @@ export function effect(fn) { * @returns {import('#client').Effect} */ export function managed_effect(fn) { - return create_effect(EFFECT | MANAGED, fn, false); + return create_effect(EFFECT | MANAGED, fn, false, current_effect_sibling_count++); } /** @@ -131,7 +150,7 @@ export function managed_effect(fn) { * @returns {import('#client').Effect} */ export function managed_pre_effect(fn) { - return create_effect(PRE_EFFECT | MANAGED, fn, false); + return create_effect(PRE_EFFECT | MANAGED, fn, false, current_pre_effect_sibling_count++); } /** @@ -150,7 +169,7 @@ export function pre_effect(fn) { } const sync = current_effect !== null && (current_effect.f & RENDER_EFFECT) !== 0; - return create_effect(PRE_EFFECT, fn, sync); + return create_effect(PRE_EFFECT, fn, sync, current_pre_effect_sibling_count++); } /** @@ -175,7 +194,8 @@ export function legacy_pre_effect(deps, fn) { set(component_context.l2, true); return untrack(fn); }, - true + true, + current_pre_effect_sibling_count++ ); } @@ -200,7 +220,7 @@ export function legacy_pre_effect_reset() { * @returns {import('#client').Effect} */ export function invalidate_effect(fn) { - return create_effect(PRE_EFFECT, fn, true); + return create_effect(PRE_EFFECT, fn, true, current_pre_effect_sibling_count++); } /** @@ -216,7 +236,7 @@ export function render_effect(fn, block = current_block, managed = false, sync = if (managed) { flags |= MANAGED; } - return create_effect(flags, /** @type {any} */ (fn), sync, block); + return create_effect(flags, /** @type {any} */ (fn), sync, current_render_effect_sibling_count++, block); } /** diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 2620cddddd..4418f3def3 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -8,7 +8,14 @@ import { object_prototype } from './utils.js'; import { unstate } from './proxy.js'; -import { destroy_effect, pre_effect } from './reactivity/effects.js'; +import { + current_effect_sibling_count, + current_pre_effect_sibling_count, + current_render_effect_sibling_count, + destroy_effect, + pre_effect, + set_sibling_count +} from './reactivity/effects.js'; import { EACH_BLOCK, IF_BLOCK, @@ -384,12 +391,19 @@ export function execute_effect(signal) { const previous_effect = current_effect; const previous_component_context = current_component_context; const previous_block = current_block; + const previous_pre_effect_sibling_count = current_pre_effect_sibling_count; + const previous_render_effect_sibling_count = current_render_effect_sibling_count; + const previous_effect_sibling_count = current_effect_sibling_count; const component_context = signal.ctx; current_effect = signal; current_component_context = component_context; current_block = signal.block; + // 32 for pre + // 128 for render + // 96 for effect + set_sibling_count(0, 32, 160); try { destroy_children(signal); @@ -400,6 +414,11 @@ export function execute_effect(signal) { current_effect = previous_effect; current_component_context = previous_component_context; current_block = previous_block; + set_sibling_count( + previous_pre_effect_sibling_count, + previous_render_effect_sibling_count, + previous_effect_sibling_count + ); } if ((signal.f & PRE_EFFECT) !== 0 && current_queued_pre_and_render_effects.length > 0) { @@ -510,20 +529,26 @@ export function schedule_effect(signal, sync) { if (!should_append) { const target_level = signal.l; + const target_group_level = target_level - (target_level % 256); const target_block = signal.block; const is_pre_effect = (flags & PRE_EFFECT) !== 0; let target_signal; let target_signal_level; + let target_signal_group_level; let is_target_pre_effect; let i = length; while (true) { target_signal = current_queued_pre_and_render_effects[--i]; target_signal_level = target_signal.l; + target_signal_group_level = target_signal_level - (target_signal_level % 256); + if (target_group_level === target_signal_group_level && target_signal.block !== target_block) { + current_queued_pre_and_render_effects.splice(i + 1, 0, signal); + break; + } if (target_signal_level <= target_level) { if (i + 1 === length) { should_append = true; } else { - is_target_pre_effect = (target_signal.f & PRE_EFFECT) !== 0; if ( target_signal_level < target_level || target_signal.block !== target_block || diff --git a/packages/svelte/src/reactivity/set.test.ts b/packages/svelte/src/reactivity/set.test.ts index aff076fc39..e93ac4e565 100644 --- a/packages/svelte/src/reactivity/set.test.ts +++ b/packages/svelte/src/reactivity/set.test.ts @@ -30,7 +30,7 @@ test('set.values()', () => { set.clear(); }); - assert.deepEqual(log, [5, true, [1, 2, 3, 4, 5], 4, false, [1, 2, 4, 5], 0, [], false]); // TODO update when we fix effect ordering bug + assert.deepEqual(log, [5, true, [1, 2, 3, 4, 5], 4, false, [1, 2, 4, 5], 0, false, []]); cleanup(); }); diff --git a/packages/svelte/tests/runtime-legacy/samples/binding-input-group-each-8/_config.js b/packages/svelte/tests/runtime-legacy/samples/binding-input-group-each-8/_config.js index 865201f062..19a100f420 100644 --- a/packages/svelte/tests/runtime-legacy/samples/binding-input-group-each-8/_config.js +++ b/packages/svelte/tests/runtime-legacy/samples/binding-input-group-each-8/_config.js @@ -1,3 +1,4 @@ +import { flushSync } from 'svelte'; import { test } from '../../test'; // https://github.com/sveltejs/svelte/issues/7884 @@ -27,11 +28,22 @@ export default test({ const event = new window.Event('change'); inputs[0].checked = true; - await inputs[0].dispatchEvent(event); + + flushSync(() => { + inputs[0].dispatchEvent(event); + }); + inputs[2].checked = true; - await inputs[2].dispatchEvent(event); + + flushSync(() => { + inputs[2].dispatchEvent(event); + }); + inputs[3].checked = true; - await inputs[3].dispatchEvent(event); + + flushSync(() => { + inputs[3].dispatchEvent(event); + }); assert.htmlEqual( target.innerHTML, @@ -53,7 +65,8 @@ export default test({ ); await component.update(); - await Promise.resolve(); + + flushSync(); assert.htmlEqual( target.innerHTML, diff --git a/packages/svelte/tests/runtime-legacy/samples/component-binding-reactive-statement/_config.js b/packages/svelte/tests/runtime-legacy/samples/component-binding-reactive-statement/_config.js index fa9fe3f3f4..98991845c2 100644 --- a/packages/svelte/tests/runtime-legacy/samples/component-binding-reactive-statement/_config.js +++ b/packages/svelte/tests/runtime-legacy/samples/component-binding-reactive-statement/_config.js @@ -1,3 +1,4 @@ +import { flushSync } from 'svelte'; import { test } from '../../test'; export default test({ @@ -11,7 +12,10 @@ export default test({ const buttons = target.querySelectorAll('button'); - await buttons[0].dispatchEvent(event); + flushSync(() => { + buttons[0].dispatchEvent(event); + }); + assert.htmlEqual( target.innerHTML, ` @@ -20,7 +24,10 @@ export default test({ ` ); - await buttons[1].dispatchEvent(event); + flushSync(() => { + buttons[1].dispatchEvent(event); + }); + assert.htmlEqual( target.innerHTML, ` @@ -30,7 +37,10 @@ export default test({ ); // reactive update, reset to 2 - await buttons[0].dispatchEvent(event); + flushSync(() => { + buttons[0].dispatchEvent(event); + }); + assert.htmlEqual( target.innerHTML, ` @@ -40,7 +50,10 @@ export default test({ ); // bound to main, reset to 2 - await buttons[1].dispatchEvent(event); + flushSync(() => { + buttons[1].dispatchEvent(event); + }); + assert.htmlEqual( target.innerHTML, ` diff --git a/packages/svelte/tests/runtime-legacy/samples/reactive-compound-operator/_config.js b/packages/svelte/tests/runtime-legacy/samples/reactive-compound-operator/_config.js index 03e69eac3c..a5f3e52125 100644 --- a/packages/svelte/tests/runtime-legacy/samples/reactive-compound-operator/_config.js +++ b/packages/svelte/tests/runtime-legacy/samples/reactive-compound-operator/_config.js @@ -1,3 +1,4 @@ +import { flushSync } from 'svelte'; import { ok, test } from '../../test'; export default test({ @@ -11,7 +12,9 @@ export default test({ const button = target.querySelector('button'); ok(button); - await button.dispatchEvent(click); + flushSync(() => { + button.dispatchEvent(click); + }); assert.equal(component.x, 2); assert.htmlEqual( @@ -22,7 +25,9 @@ export default test({ ` ); - await button.dispatchEvent(click); + flushSync(() => { + button.dispatchEvent(click); + }); assert.equal(component.x, 6); assert.htmlEqual( diff --git a/packages/svelte/tests/runtime-legacy/samples/reactive-import-statement/_config.js b/packages/svelte/tests/runtime-legacy/samples/reactive-import-statement/_config.js index e05e8180b2..ea3ab4b356 100644 --- a/packages/svelte/tests/runtime-legacy/samples/reactive-import-statement/_config.js +++ b/packages/svelte/tests/runtime-legacy/samples/reactive-import-statement/_config.js @@ -1,3 +1,4 @@ +import { flushSync } from 'svelte'; import { ok, test } from '../../test'; import { reset_numbers } from './data'; @@ -18,7 +19,9 @@ export default test({ const clickEvent = new window.MouseEvent('click', { bubbles: true }); - await btn.dispatchEvent(clickEvent); + flushSync(() => { + btn.dispatchEvent(clickEvent); + }); assert.htmlEqual( target.innerHTML, @@ -31,7 +34,9 @@ export default test({ ` ); - await btn.dispatchEvent(clickEvent); + flushSync(() => { + btn.dispatchEvent(clickEvent); + }); assert.htmlEqual( target.innerHTML, diff --git a/packages/svelte/tests/runtime-legacy/samples/reactive-update-expression/_config.js b/packages/svelte/tests/runtime-legacy/samples/reactive-update-expression/_config.js index 8df0d32e9d..ac79c5dc5e 100644 --- a/packages/svelte/tests/runtime-legacy/samples/reactive-update-expression/_config.js +++ b/packages/svelte/tests/runtime-legacy/samples/reactive-update-expression/_config.js @@ -1,3 +1,4 @@ +import { flushSync } from 'svelte'; import { ok, test } from '../../test'; export default test({ @@ -13,7 +14,9 @@ export default test({ assert.equal(component.x, 1); - await button.dispatchEvent(click); + flushSync(() => { + button.dispatchEvent(click); + }); assert.equal(component.x, 3); assert.htmlEqual( @@ -24,7 +27,9 @@ export default test({ ` ); - await button.dispatchEvent(click); + flushSync(() => { + button.dispatchEvent(click); + }); assert.equal(component.x, 5); assert.htmlEqual(