Skip to content

Commit 8971910

Browse files
trueadmRich-Harris
andauthored
fix: further improvements to effect scheduling and flushing (#10971)
* fix: improve effect scheduling * fix: further improvements to effect scheduling and flushin * add test * simplify * simplify * lint * fix e2e tests * fix e2e tests * simplify * Update packages/svelte/src/internal/client/runtime.js * Update packages/svelte/src/internal/client/runtime.js Co-authored-by: Rich Harris <[email protected]> * Update packages/svelte/src/internal/client/runtime.js Co-authored-by: Rich Harris <[email protected]> * Update packages/svelte/src/internal/client/runtime.js Co-authored-by: Rich Harris <[email protected]> * style tweak --------- Co-authored-by: Rich Harris <[email protected]>
1 parent 293f905 commit 8971910

File tree

7 files changed

+94
-71
lines changed

7 files changed

+94
-71
lines changed

.changeset/brown-houses-obey.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: further improvements to effect scheduling and flushing

packages/svelte/src/internal/client/dom/elements/attributes.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,11 @@ export function set_xlink_attribute(dom, attribute, value) {
6868
*/
6969
export function set_custom_element_data(node, prop, value) {
7070
if (prop in node) {
71-
node[prop] = typeof node[prop] === 'boolean' && value === '' ? true : value;
71+
var curr_val = node[prop];
72+
var next_val = typeof curr_val === 'boolean' && value === '' ? true : value;
73+
if (typeof curr_val !== 'object' || curr_val !== next_val) {
74+
node[prop] = next_val;
75+
}
7276
} else {
7377
set_attribute(node, prop, value);
7478
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { CLEAN } from '../../constants.js';
12
import { run, run_all } from '../../../shared/utils.js';
23
import { user_pre_effect, user_effect } from '../../reactivity/effects.js';
34
import {
@@ -27,7 +28,7 @@ export function init() {
2728
// are batched up with the current run. Avoids for example child components rerunning when they're
2829
// now hidden because beforeUpdate did set an if block to false.
2930
const parent = current_effect?.parent;
30-
if (parent != null) {
31+
if (parent != null && (parent.f & CLEAN) === 0) {
3132
flush_local_render_effects(parent);
3233
}
3334
});

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

Lines changed: 38 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -424,8 +424,7 @@ function infinite_loop_guard() {
424424
function flush_queued_root_effects(root_effects) {
425425
for (var i = 0; i < root_effects.length; i++) {
426426
var signal = root_effects[i];
427-
var effects = get_nested_effects(signal, RENDER_EFFECT | EFFECT);
428-
flush_queued_effects(effects);
427+
flush_nested_effects(signal, RENDER_EFFECT | EFFECT);
429428
}
430429
}
431430

@@ -438,24 +437,13 @@ function flush_queued_effects(effects) {
438437
if (length === 0) return;
439438

440439
infinite_loop_guard();
441-
var previously_flushing_effect = is_flushing_effect;
442-
is_flushing_effect = true;
443-
444-
try {
445-
for (var i = 0; i < length; i++) {
446-
var signal = effects[i];
440+
for (var i = 0; i < length; i++) {
441+
var effect = effects[i];
447442

448-
if ((signal.f & (DESTROYED | INERT)) === 0) {
449-
if (check_dirtiness(signal)) {
450-
execute_effect(signal);
451-
}
452-
}
443+
if ((effect.f & (DESTROYED | INERT)) === 0 && check_dirtiness(effect)) {
444+
execute_effect(effect);
453445
}
454-
} finally {
455-
is_flushing_effect = previously_flushing_effect;
456446
}
457-
458-
effects.length = 0;
459447
}
460448

461449
function process_microtask() {
@@ -512,77 +500,57 @@ export function schedule_effect(signal) {
512500
* @param {import('./types.js').Effect} effect
513501
* @param {number} filter_flags
514502
* @param {boolean} shallow
515-
* @param {import('./types.js').Effect[]} collected_render
516503
* @param {import('./types.js').Effect[]} collected_user
517504
* @returns {void}
518505
*/
519-
function recursively_collect_effects(
520-
effect,
521-
filter_flags,
522-
shallow,
523-
collected_render,
524-
collected_user
525-
) {
506+
function recursively_process_effects(effect, filter_flags, shallow, collected_user) {
526507
var effects = effect.effects;
527508
if (effects === null) return;
528509

529-
var render = [];
530510
var user = [];
531511

532512
for (var i = 0; i < effects.length; i++) {
533513
var child = effects[i];
534514
var flags = child.f;
515+
var is_inactive = (flags & (DESTROYED | INERT)) !== 0;
516+
if (is_inactive) continue;
535517
var is_branch = flags & BRANCH_EFFECT;
518+
var is_clean = (flags & CLEAN) !== 0;
536519

537520
if (is_branch) {
538521
// Skip this branch if it's clean
539-
if ((flags & CLEAN) !== 0) continue;
522+
if (is_clean) continue;
540523
set_signal_status(child, CLEAN);
541524
}
542525

543526
if ((flags & RENDER_EFFECT) !== 0) {
544527
if (is_branch) {
545528
if (shallow) continue;
546-
recursively_collect_effects(child, filter_flags, false, collected_render, collected_user);
529+
recursively_process_effects(child, filter_flags, false, collected_user);
547530
} else {
548-
render.push(child);
531+
if (check_dirtiness(child)) {
532+
execute_effect(child);
533+
}
534+
recursively_process_effects(child, filter_flags, false, collected_user);
549535
}
550536
} else if ((flags & EFFECT) !== 0) {
551-
if (is_branch) {
537+
if (is_branch || is_clean) {
552538
if (shallow) continue;
553-
recursively_collect_effects(child, filter_flags, false, collected_render, collected_user);
539+
recursively_process_effects(child, filter_flags, false, collected_user);
554540
} else {
555541
user.push(child);
556542
}
557543
}
558544
}
559545

560-
if (render.length > 0) {
561-
if ((filter_flags & RENDER_EFFECT) !== 0) {
562-
collected_render.push(...render);
563-
}
564-
565-
if (!shallow) {
566-
for (i = 0; i < render.length; i++) {
567-
recursively_collect_effects(
568-
render[i],
569-
filter_flags,
570-
false,
571-
collected_render,
572-
collected_user
573-
);
574-
}
575-
}
576-
}
577-
578546
if (user.length > 0) {
579547
if ((filter_flags & EFFECT) !== 0) {
580548
collected_user.push(...user);
581549
}
582550

583551
if (!shallow) {
584552
for (i = 0; i < user.length; i++) {
585-
recursively_collect_effects(user[i], filter_flags, false, collected_render, collected_user);
553+
recursively_process_effects(user[i], filter_flags, false, collected_user);
586554
}
587555
}
588556
}
@@ -597,35 +565,36 @@ function recursively_collect_effects(
597565
* @param {import('./types.js').Effect} effect
598566
* @param {number} filter_flags
599567
* @param {boolean} [shallow]
600-
* @returns {import('./types.js').Effect[]}
568+
* @returns {void}
601569
*/
602-
function get_nested_effects(effect, filter_flags, shallow = false) {
603-
/**
604-
* @type {import("./types.js").Effect[]}
605-
*/
606-
var render_effects = [];
607-
/**
608-
* @type {import("./types.js").Effect[]}
609-
*/
570+
function flush_nested_effects(effect, filter_flags, shallow = false) {
571+
/** @type {import('#client').Effect[]} */
610572
var user_effects = [];
611-
// When working with custom-elements, the root effects might not have a root
612-
if (effect.effects === null && (effect.f & BRANCH_EFFECT) === 0) {
613-
return [effect];
573+
574+
var previously_flushing_effect = is_flushing_effect;
575+
is_flushing_effect = true;
576+
577+
try {
578+
// When working with custom elements, the root effects might not have a root
579+
if (effect.effects === null && (effect.f & BRANCH_EFFECT) === 0) {
580+
flush_queued_effects([effect]);
581+
} else {
582+
recursively_process_effects(effect, filter_flags, shallow, user_effects);
583+
flush_queued_effects(user_effects);
584+
}
585+
} finally {
586+
is_flushing_effect = previously_flushing_effect;
614587
}
615-
recursively_collect_effects(effect, filter_flags, shallow, render_effects, user_effects);
616-
return [...render_effects, ...user_effects];
617588
}
618589

619590
/**
620591
* @param {import('./types.js').Effect} effect
621592
* @returns {void}
622593
*/
623594
export function flush_local_render_effects(effect) {
624-
/**
625-
* @type {import("./types.js").Effect[]}
626-
*/
627-
var render_effects = get_nested_effects(effect, RENDER_EFFECT, true);
628-
flush_queued_effects(render_effects);
595+
// We are entering a new flush sequence, so ensure counter is reset.
596+
flush_count = 0;
597+
flush_nested_effects(effect, RENDER_EFFECT, true);
629598
}
630599

631600
/**
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<script>
2+
import {beforeUpdate} from 'svelte';
3+
4+
let count1 = 0;
5+
let count2 = 0;
6+
7+
function increaseCount1() {
8+
count1++;
9+
}
10+
11+
$: if (count2 < 10) {
12+
increaseCount1();
13+
}
14+
15+
$: if (count1 < 10) {
16+
count2++;
17+
}
18+
19+
beforeUpdate(() => {
20+
// We don't need to do anything
21+
});
22+
</script>
23+
24+
<button on:click={() => count1++}>{count1} / {count2}</button>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
async test({ assert, component, target }) {
6+
const [btn1] = target.querySelectorAll('button');
7+
flushSync(() => {
8+
// This test would result in an infinite loop, so if this doesn't error, then the test is working.
9+
});
10+
}
11+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script>
2+
import Item from './Item.svelte';
3+
4+
var items = Array(1000);
5+
</script>
6+
7+
{#each items as item}
8+
<Item />
9+
{/each}

0 commit comments

Comments
 (0)