Skip to content

fix: improve order of pre-effect execution #10942

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/hot-jobs-tap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"svelte": patch
---

fix: improve order of pre-effect execution
6 changes: 5 additions & 1 deletion packages/svelte/src/internal/client/dom/legacy/lifecycle.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { run } from '../../../common.js';
import { user_pre_effect, user_effect } from '../../reactivity/effects.js';
import {
current_component_context,
current_effect,
deep_read_state,
flush_local_render_effects,
get,
Expand All @@ -25,7 +26,10 @@ export function init() {
// beforeUpdate might change state that affects rendering, ensure the render effects following from it
// are batched up with the current run. Avoids for example child components rerunning when they're
// now hidden because beforeUpdate did set an if block to false.
flush_local_render_effects();
const parent = current_effect?.parent;
if (parent != null) {
flush_local_render_effects(parent);
}
});
}

Expand Down
100 changes: 75 additions & 25 deletions packages/svelte/src/internal/client/runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ import {
DESTROYED,
INERT,
MANAGED,
STATE_SYMBOL,
EFFECT_RAN
STATE_SYMBOL
} from './constants.js';
import { flush_tasks } from './dom/task.js';
import { add_owner } from './dev/ownership.js';
Expand Down Expand Up @@ -403,9 +402,10 @@ export function execute_effect(effect) {
current_effect = previous_effect;
current_component_context = previous_component_context;
}
const parent = effect.parent;

if ((effect.f & PRE_EFFECT) !== 0 && current_queued_pre_and_render_effects.length > 0) {
flush_local_pre_effects(component_context);
if ((effect.f & PRE_EFFECT) !== 0 && parent !== null) {
flush_local_pre_effects(parent);
}
}

Expand Down Expand Up @@ -540,36 +540,86 @@ export function schedule_effect(signal) {
}

/**
*
* This function recursively collects effects in topological order from the starting effect passed in.
* Effects will be collected when they match the filtered bitwise flag passed in only. The collected
* array will be populated with all the effects.
*
* @param {import('./types.js').Effect} effect
* @param {number} filter_flags
* @param {import('./types.js').Effect[]} collected
* @returns {void}
*/
export function flush_local_render_effects() {
const effects = [];
for (let i = 0; i < current_queued_pre_and_render_effects.length; i++) {
const effect = current_queued_pre_and_render_effects[i];
if ((effect.f & RENDER_EFFECT) !== 0 && effect.ctx === current_component_context) {
effects.push(effect);
current_queued_pre_and_render_effects.splice(i, 1);
i--;
function collect_effects(effect, filter_flags, collected) {
var effects = effect.effects;
if (effects === null) {
return;
}
var i, s, child, flags;
var render = [];
var user = [];

for (i = 0; i < effects.length; i++) {
child = effects[i];
flags = child.f;
if ((flags & CLEAN) !== 0) {
continue;
}

if ((flags & PRE_EFFECT) !== 0) {
if ((filter_flags & PRE_EFFECT) !== 0) {
collected.push(child);
}
collect_effects(child, filter_flags, collected);
} else if ((flags & RENDER_EFFECT) !== 0) {
render.push(child);
} else if ((flags & EFFECT) !== 0) {
user.push(child);
}
}

if (render.length > 0) {
if ((filter_flags & RENDER_EFFECT) !== 0) {
collected.push(...render);
}
for (s = 0; s < render.length; s++) {
collect_effects(render[s], filter_flags, collected);
}
}
if (user.length > 0) {
if ((filter_flags & EFFECT) !== 0) {
collected.push(...user);
}
for (s = 0; s < user.length; s++) {
collect_effects(user[s], filter_flags, collected);
}
}
flush_queued_effects(effects);
}

/**
* @param {null | import('./types.js').ComponentContext} context
* @param {import('./types.js').Effect} effect
* @returns {void}
*/
export function flush_local_pre_effects(context) {
const effects = [];
for (let i = 0; i < current_queued_pre_and_render_effects.length; i++) {
const effect = current_queued_pre_and_render_effects[i];
if ((effect.f & PRE_EFFECT) !== 0 && effect.ctx === context) {
effects.push(effect);
current_queued_pre_and_render_effects.splice(i, 1);
i--;
}
}
flush_queued_effects(effects);
export function flush_local_render_effects(effect) {
/**
* @type {import("./types.js").Effect[]}
*/
var render_effects = [];
collect_effects(effect, RENDER_EFFECT, render_effects);
flush_queued_effects(render_effects);
}

/**
* @param {import('./types.js').Effect} effect
* @returns {void}
*/
export function flush_local_pre_effects(effect) {
/**
* @type {import("./types.js").Effect[]}
*/
var pre_effects = [];
collect_effects(effect, PRE_EFFECT, pre_effects);
flush_queued_effects(pre_effects);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/svelte/src/reactivity/map.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ test('map.values()', () => {
map.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();
});
Expand Down
2 changes: 1 addition & 1 deletion packages/svelte/src/reactivity/set.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
Expand Down