fix: improve order of pre-effect execution (#10942)

* chore: refactor local effect flushing to use new topological approach
pull/10947/head
Dominic Gannaway 9 months ago committed by GitHub
parent 3ce74e47a9
commit afe589e219
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: improve order of pre-effect execution

@ -2,6 +2,7 @@ import { run } from '../../../common.js';
import { user_pre_effect, user_effect } from '../../reactivity/effects.js'; import { user_pre_effect, user_effect } from '../../reactivity/effects.js';
import { import {
current_component_context, current_component_context,
current_effect,
deep_read_state, deep_read_state,
flush_local_render_effects, flush_local_render_effects,
get, get,
@ -25,7 +26,10 @@ export function init() {
// beforeUpdate might change state that affects rendering, ensure the render effects following from it // 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 // 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. // 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);
}
}); });
} }

@ -21,8 +21,7 @@ import {
DESTROYED, DESTROYED,
INERT, INERT,
MANAGED, MANAGED,
STATE_SYMBOL, STATE_SYMBOL
EFFECT_RAN
} from './constants.js'; } from './constants.js';
import { flush_tasks } from './dom/task.js'; import { flush_tasks } from './dom/task.js';
import { add_owner } from './dev/ownership.js'; import { add_owner } from './dev/ownership.js';
@ -403,9 +402,10 @@ export function execute_effect(effect) {
current_effect = previous_effect; current_effect = previous_effect;
current_component_context = previous_component_context; current_component_context = previous_component_context;
} }
const parent = effect.parent;
if ((effect.f & PRE_EFFECT) !== 0 && current_queued_pre_and_render_effects.length > 0) { if ((effect.f & PRE_EFFECT) !== 0 && parent !== null) {
flush_local_pre_effects(component_context); flush_local_pre_effects(parent);
} }
} }
@ -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} * @returns {void}
*/ */
export function flush_local_render_effects() { function collect_effects(effect, filter_flags, collected) {
const effects = []; var effects = effect.effects;
for (let i = 0; i < current_queued_pre_and_render_effects.length; i++) { if (effects === null) {
const effect = current_queued_pre_and_render_effects[i]; return;
if ((effect.f & RENDER_EFFECT) !== 0 && effect.ctx === current_component_context) { }
effects.push(effect); var i, s, child, flags;
current_queued_pre_and_render_effects.splice(i, 1); var render = [];
i--; 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} * @returns {void}
*/ */
export function flush_local_pre_effects(context) { export function flush_local_render_effects(effect) {
const effects = []; /**
for (let i = 0; i < current_queued_pre_and_render_effects.length; i++) { * @type {import("./types.js").Effect[]}
const effect = current_queued_pre_and_render_effects[i]; */
if ((effect.f & PRE_EFFECT) !== 0 && effect.ctx === context) { var render_effects = [];
effects.push(effect); collect_effects(effect, RENDER_EFFECT, render_effects);
current_queued_pre_and_render_effects.splice(i, 1); flush_queued_effects(render_effects);
i--; }
}
} /**
flush_queued_effects(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);
} }
/** /**

@ -36,7 +36,7 @@ test('map.values()', () => {
map.clear(); 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(); cleanup();
}); });

@ -30,7 +30,7 @@ test('set.values()', () => {
set.clear(); 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(); cleanup();
}); });

Loading…
Cancel
Save