Skip to content

Commit 6e6d378

Browse files
committed
feat: add $effect.yield rune
1 parent 2061ad6 commit 6e6d378

File tree

2 files changed

+70
-72
lines changed

2 files changed

+70
-72
lines changed

packages/svelte/src/internal/client/dom/task.js

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,11 @@ export const scheduler_yield =
2121

2222
let is_micro_task_queued = false;
2323
let is_idle_task_queued = false;
24-
let is_yield_task_queued = false;
2524

2625
/** @type {Array<() => void>} */
2726
let current_queued_micro_tasks = [];
2827
/** @type {Array<() => void>} */
2928
let current_queued_idle_tasks = [];
30-
/** @type {Array<() => void>} */
31-
let current_queued_yield_tasks = [];
3229

3330
function process_micro_tasks() {
3431
is_micro_task_queued = false;
@@ -44,13 +41,6 @@ function process_idle_tasks() {
4441
run_all(tasks);
4542
}
4643

47-
function process_yield_tasks() {
48-
is_yield_task_queued = false;
49-
const tasks = current_queued_yield_tasks.slice();
50-
current_queued_yield_tasks = [];
51-
run_all(tasks);
52-
}
53-
5444
/**
5545
* @param {() => void} fn
5646
*/
@@ -73,17 +63,6 @@ export function queue_idle_task(fn) {
7363
current_queued_idle_tasks.push(fn);
7464
}
7565

76-
/**
77-
* @param {() => void} fn
78-
*/
79-
export function queue_yield_task(fn) {
80-
if (!is_yield_task_queued) {
81-
is_yield_task_queued = true;
82-
scheduler_yield(process_yield_tasks);
83-
}
84-
current_queued_yield_tasks.push(fn);
85-
}
86-
8766
/**
8867
* Synchronously run any queued tasks.
8968
*/
@@ -94,7 +73,4 @@ export function flush_tasks() {
9473
if (is_idle_task_queued) {
9574
process_idle_tasks();
9675
}
97-
if (is_yield_task_queued) {
98-
process_yield_tasks();
99-
}
10076
}

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

Lines changed: 70 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/** @import { ComponentContext, Derived, Effect, Reaction, Signal, Source, Value } from '#client' */
22
import { DEV } from 'esm-env';
3-
import { define_property, get_descriptors, get_prototype_of } from '../shared/utils.js';
3+
import { define_property, get_descriptors, get_prototype_of, run_all } from '../shared/utils.js';
44
import {
55
destroy_block_effect_children,
66
destroy_effect_children,
@@ -28,7 +28,7 @@ import {
2828
BOUNDARY_EFFECT,
2929
YIELD_EFFECT
3030
} from './constants.js';
31-
import { flush_tasks, queue_yield_task } from './dom/task.js';
31+
import { flush_tasks, scheduler_yield } from './dom/task.js';
3232
import { add_owner } from './dev/ownership.js';
3333
import { internal_set, set, source } from './reactivity/sources.js';
3434
import { destroy_derived, execute_derived, update_derived } from './reactivity/deriveds.js';
@@ -40,6 +40,7 @@ import { tracing_expressions, get_stack } from './dev/tracing.js';
4040

4141
const FLUSH_MICROTASK = 0;
4242
const FLUSH_SYNC = 1;
43+
const FLUSH_YIELD = 2;
4344
// Used for DEV time error handling
4445
/** @param {WeakSet<Error>} value */
4546
const handled_errors = new WeakSet();
@@ -49,6 +50,9 @@ export let is_throwing_error = false;
4950
let scheduler_mode = FLUSH_MICROTASK;
5051
// Used for handling scheduling
5152
let is_micro_task_queued = false;
53+
let is_yield_queued = false;
54+
/** @type {Array<() => void>} */
55+
let queued_yield_tasks = [];
5256

5357
/** @type {Effect | null} */
5458
let last_scheduled_effect = null;
@@ -591,9 +595,10 @@ function infinite_loop_guard() {
591595

592596
/**
593597
* @param {Array<Effect>} root_effects
598+
* @param {boolean} yielded_effects
594599
* @returns {void}
595600
*/
596-
function flush_queued_root_effects(root_effects) {
601+
function flush_queued_root_effects(root_effects, yielded_effects) {
597602
var length = root_effects.length;
598603
if (length === 0) {
599604
return;
@@ -614,45 +619,14 @@ function flush_queued_root_effects(root_effects) {
614619
/** @type {Effect[]} */
615620
var collected_effects = [];
616621

617-
process_effects(effect, collected_effects);
622+
process_effects(effect, collected_effects, yielded_effects);
618623
flush_queued_effects(collected_effects);
619624
}
620625
} finally {
621626
is_flushing_effect = previously_flushing_effect;
622627
}
623628
}
624629

625-
/**
626-
* @param {Effect} effect
627-
* @returns {void}
628-
*/
629-
function flush_effect(effect) {
630-
if ((effect.f & (DESTROYED | INERT)) === 0) {
631-
try {
632-
if (check_dirtiness(effect)) {
633-
update_effect(effect);
634-
635-
// Effects with no dependencies or teardown do not get added to the effect tree.
636-
// Deferred effects (e.g. `$effect(...)`) _are_ added to the tree because we
637-
// don't know if we need to keep them until they are executed. Doing the check
638-
// here (rather than in `update_effect`) allows us to skip the work for
639-
// immediate effects.
640-
if (effect.deps === null && effect.first === null && effect.nodes_start === null) {
641-
if (effect.teardown === null) {
642-
// remove this effect from the graph
643-
unlink_effect(effect);
644-
} else {
645-
// keep the effect in the graph, but free up some memory
646-
effect.fn = null;
647-
}
648-
}
649-
}
650-
} catch (error) {
651-
handle_error(error, effect, null, effect.ctx);
652-
}
653-
}
654-
}
655-
656630
/**
657631
* @param {Array<Effect>} effects
658632
* @returns {void}
@@ -664,18 +638,41 @@ function flush_queued_effects(effects) {
664638
for (var i = 0; i < length; i++) {
665639
var effect = effects[i];
666640

667-
flush_effect(effect);
641+
if ((effect.f & (DESTROYED | INERT)) === 0) {
642+
try {
643+
if (check_dirtiness(effect)) {
644+
update_effect(effect);
645+
646+
// Effects with no dependencies or teardown do not get added to the effect tree.
647+
// Deferred effects (e.g. `$effect(...)`) _are_ added to the tree because we
648+
// don't know if we need to keep them until they are executed. Doing the check
649+
// here (rather than in `update_effect`) allows us to skip the work for
650+
// immediate effects.
651+
if (effect.deps === null && effect.first === null && effect.nodes_start === null) {
652+
if (effect.teardown === null) {
653+
// remove this effect from the graph
654+
unlink_effect(effect);
655+
} else {
656+
// keep the effect in the graph, but free up some memory
657+
effect.fn = null;
658+
}
659+
}
660+
}
661+
} catch (error) {
662+
handle_error(error, effect, null, effect.ctx);
663+
}
664+
}
668665
}
669666
}
670667

671-
function process_microtask_effects() {
668+
function flush_effects(yielded_effects = false) {
672669
is_micro_task_queued = false;
673670
if (flush_count > 1001) {
674671
return;
675672
}
676673
const previous_queued_root_effects = queued_root_effects;
677674
queued_root_effects = [];
678-
flush_queued_root_effects(previous_queued_root_effects);
675+
flush_queued_root_effects(previous_queued_root_effects, yielded_effects);
679676

680677
if (!is_micro_task_queued) {
681678
flush_count = 0;
@@ -686,20 +683,44 @@ function process_microtask_effects() {
686683
}
687684
}
688685

686+
function flush_yield_effects() {
687+
is_yield_queued = false;
688+
var tasks = queued_yield_tasks.slice();
689+
queued_yield_tasks = [];
690+
var previous_scheduler_mode = scheduler_mode;
691+
scheduler_mode = FLUSH_YIELD;
692+
try {
693+
run_all(tasks);
694+
} finally {
695+
scheduler_mode = previous_scheduler_mode;
696+
}
697+
flush_effects(true);
698+
}
699+
700+
/**
701+
* @param {() => void} fn
702+
*/
703+
export function queue_yield(fn) {
704+
if (!is_yield_queued) {
705+
is_yield_queued = true;
706+
scheduler_yield(flush_yield_effects);
707+
}
708+
queued_yield_tasks.push(fn);
709+
}
710+
689711
/**
690712
* @param {Effect} signal
691713
* @returns {void}
692714
*/
693715
export function schedule_effect(signal) {
694-
if ((signal.f & YIELD_EFFECT) !== 0) {
695-
queue_yield_task(() => {
696-
flush_effect(signal);
697-
process_microtask_effects();
716+
if (scheduler_mode !== FLUSH_YIELD && (signal.f & YIELD_EFFECT) !== 0) {
717+
queue_yield(() => {
718+
schedule_effect(signal);
698719
});
699-
} else if (scheduler_mode === FLUSH_MICROTASK) {
720+
} else if (scheduler_mode !== FLUSH_SYNC) {
700721
if (!is_micro_task_queued) {
701722
is_micro_task_queued = true;
702-
queueMicrotask(process_microtask_effects);
723+
queueMicrotask(flush_effects);
703724
}
704725
}
705726

@@ -729,9 +750,10 @@ export function schedule_effect(signal) {
729750
*
730751
* @param {Effect} effect
731752
* @param {Effect[]} collected_effects
753+
* @param {boolean} yielded_effects
732754
* @returns {void}
733755
*/
734-
function process_effects(effect, collected_effects) {
756+
function process_effects(effect, collected_effects, yielded_effects) {
735757
var current_effect = effect.first;
736758
var effects = [];
737759

@@ -761,7 +783,7 @@ function process_effects(effect, collected_effects) {
761783
current_effect = child;
762784
continue;
763785
}
764-
} else if ((flags & EFFECT) !== 0) {
786+
} else if ((flags & EFFECT) !== 0 || (yielded_effects && (flags & YIELD_EFFECT) !== 0)) {
765787
effects.push(current_effect);
766788
}
767789
}
@@ -790,7 +812,7 @@ function process_effects(effect, collected_effects) {
790812
for (var i = 0; i < effects.length; i++) {
791813
child = effects[i];
792814
collected_effects.push(child);
793-
process_effects(child, collected_effects);
815+
process_effects(child, collected_effects, yielded_effects);
794816
}
795817
}
796818

@@ -814,7 +836,7 @@ export function flush_sync(fn) {
814836
queued_root_effects = root_effects;
815837
is_micro_task_queued = false;
816838

817-
flush_queued_root_effects(previous_queued_root_effects);
839+
flush_queued_root_effects(previous_queued_root_effects, true);
818840

819841
var result = fn?.();
820842

0 commit comments

Comments
 (0)