Skip to content

Commit afe589e

Browse files
authored
fix: improve order of pre-effect execution (#10942)
* chore: refactor local effect flushing to use new topological approach
1 parent 3ce74e4 commit afe589e

File tree

5 files changed

+87
-28
lines changed

5 files changed

+87
-28
lines changed

.changeset/hot-jobs-tap.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte": patch
3+
---
4+
5+
fix: improve order of pre-effect execution

packages/svelte/src/internal/client/dom/legacy/lifecycle.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { run } from '../../../common.js';
22
import { user_pre_effect, user_effect } from '../../reactivity/effects.js';
33
import {
44
current_component_context,
5+
current_effect,
56
deep_read_state,
67
flush_local_render_effects,
78
get,
@@ -25,7 +26,10 @@ export function init() {
2526
// beforeUpdate might change state that affects rendering, ensure the render effects following from it
2627
// are batched up with the current run. Avoids for example child components rerunning when they're
2728
// now hidden because beforeUpdate did set an if block to false.
28-
flush_local_render_effects();
29+
const parent = current_effect?.parent;
30+
if (parent != null) {
31+
flush_local_render_effects(parent);
32+
}
2933
});
3034
}
3135

packages/svelte/src/internal/client/runtime.js

Lines changed: 75 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@ import {
2121
DESTROYED,
2222
INERT,
2323
MANAGED,
24-
STATE_SYMBOL,
25-
EFFECT_RAN
24+
STATE_SYMBOL
2625
} from './constants.js';
2726
import { flush_tasks } from './dom/task.js';
2827
import { add_owner } from './dev/ownership.js';
@@ -403,9 +402,10 @@ export function execute_effect(effect) {
403402
current_effect = previous_effect;
404403
current_component_context = previous_component_context;
405404
}
405+
const parent = effect.parent;
406406

407-
if ((effect.f & PRE_EFFECT) !== 0 && current_queued_pre_and_render_effects.length > 0) {
408-
flush_local_pre_effects(component_context);
407+
if ((effect.f & PRE_EFFECT) !== 0 && parent !== null) {
408+
flush_local_pre_effects(parent);
409409
}
410410
}
411411

@@ -540,36 +540,86 @@ export function schedule_effect(signal) {
540540
}
541541

542542
/**
543+
*
544+
* This function recursively collects effects in topological order from the starting effect passed in.
545+
* Effects will be collected when they match the filtered bitwise flag passed in only. The collected
546+
* array will be populated with all the effects.
547+
*
548+
* @param {import('./types.js').Effect} effect
549+
* @param {number} filter_flags
550+
* @param {import('./types.js').Effect[]} collected
543551
* @returns {void}
544552
*/
545-
export function flush_local_render_effects() {
546-
const effects = [];
547-
for (let i = 0; i < current_queued_pre_and_render_effects.length; i++) {
548-
const effect = current_queued_pre_and_render_effects[i];
549-
if ((effect.f & RENDER_EFFECT) !== 0 && effect.ctx === current_component_context) {
550-
effects.push(effect);
551-
current_queued_pre_and_render_effects.splice(i, 1);
552-
i--;
553+
function collect_effects(effect, filter_flags, collected) {
554+
var effects = effect.effects;
555+
if (effects === null) {
556+
return;
557+
}
558+
var i, s, child, flags;
559+
var render = [];
560+
var user = [];
561+
562+
for (i = 0; i < effects.length; i++) {
563+
child = effects[i];
564+
flags = child.f;
565+
if ((flags & CLEAN) !== 0) {
566+
continue;
567+
}
568+
569+
if ((flags & PRE_EFFECT) !== 0) {
570+
if ((filter_flags & PRE_EFFECT) !== 0) {
571+
collected.push(child);
572+
}
573+
collect_effects(child, filter_flags, collected);
574+
} else if ((flags & RENDER_EFFECT) !== 0) {
575+
render.push(child);
576+
} else if ((flags & EFFECT) !== 0) {
577+
user.push(child);
578+
}
579+
}
580+
581+
if (render.length > 0) {
582+
if ((filter_flags & RENDER_EFFECT) !== 0) {
583+
collected.push(...render);
584+
}
585+
for (s = 0; s < render.length; s++) {
586+
collect_effects(render[s], filter_flags, collected);
587+
}
588+
}
589+
if (user.length > 0) {
590+
if ((filter_flags & EFFECT) !== 0) {
591+
collected.push(...user);
592+
}
593+
for (s = 0; s < user.length; s++) {
594+
collect_effects(user[s], filter_flags, collected);
553595
}
554596
}
555-
flush_queued_effects(effects);
556597
}
557598

558599
/**
559-
* @param {null | import('./types.js').ComponentContext} context
600+
* @param {import('./types.js').Effect} effect
560601
* @returns {void}
561602
*/
562-
export function flush_local_pre_effects(context) {
563-
const effects = [];
564-
for (let i = 0; i < current_queued_pre_and_render_effects.length; i++) {
565-
const effect = current_queued_pre_and_render_effects[i];
566-
if ((effect.f & PRE_EFFECT) !== 0 && effect.ctx === context) {
567-
effects.push(effect);
568-
current_queued_pre_and_render_effects.splice(i, 1);
569-
i--;
570-
}
571-
}
572-
flush_queued_effects(effects);
603+
export function flush_local_render_effects(effect) {
604+
/**
605+
* @type {import("./types.js").Effect[]}
606+
*/
607+
var render_effects = [];
608+
collect_effects(effect, RENDER_EFFECT, render_effects);
609+
flush_queued_effects(render_effects);
610+
}
611+
612+
/**
613+
* @param {import('./types.js').Effect} effect
614+
* @returns {void}
615+
*/
616+
export function flush_local_pre_effects(effect) {
617+
/**
618+
* @type {import("./types.js").Effect[]}
619+
*/
620+
var pre_effects = [];
621+
collect_effects(effect, PRE_EFFECT, pre_effects);
622+
flush_queued_effects(pre_effects);
573623
}
574624

575625
/**

packages/svelte/src/reactivity/map.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ test('map.values()', () => {
3636
map.clear();
3737
});
3838

39-
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
39+
assert.deepEqual(log, [5, true, [1, 2, 3, 4, 5], 4, false, [1, 2, 4, 5], 0, false, []]);
4040

4141
cleanup();
4242
});

packages/svelte/src/reactivity/set.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ test('set.values()', () => {
3030
set.clear();
3131
});
3232

33-
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
33+
assert.deepEqual(log, [5, true, [1, 2, 3, 4, 5], 4, false, [1, 2, 4, 5], 0, false, []]);
3434

3535
cleanup();
3636
});

0 commit comments

Comments
 (0)