From ed2a1e3d43e9a9675c3c23f246b7396d493fdcec Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Fri, 7 Feb 2025 18:45:30 +0000 Subject: [PATCH] async fork values --- .../client/visitors/shared/events.js | 2 +- .../svelte/src/internal/client/constants.js | 5 +- .../internal/client/dom/blocks/boundary.js | 18 +++++- .../internal/client/dom/elements/events.js | 18 +++++- packages/svelte/src/internal/client/index.js | 3 +- .../internal/client/reactivity/deriveds.js | 5 +- .../src/internal/client/reactivity/sources.js | 10 +-- .../svelte/src/internal/client/runtime.js | 64 +++++++++++++------ 8 files changed, 90 insertions(+), 35 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/events.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/events.js index f23f7548ec..28dae7a6a4 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/events.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/events.js @@ -46,7 +46,7 @@ export function visit_event_attribute(node, context) { // When we hoist a function we assign an array with the function and all // hoisted closure params. - const args = [handler, ...hoisted_params]; + const args = [handler, b.id('$.active_effect'), ...hoisted_params]; delegated_assignment = b.array(args); } else { delegated_assignment = handler; diff --git a/packages/svelte/src/internal/client/constants.js b/packages/svelte/src/internal/client/constants.js index cc04b66a4b..9b2690f331 100644 --- a/packages/svelte/src/internal/client/constants.js +++ b/packages/svelte/src/internal/client/constants.js @@ -20,10 +20,11 @@ export const LEGACY_DERIVED_PROP = 1 << 18; export const INSPECT_EFFECT = 1 << 19; export const HEAD_EFFECT = 1 << 20; export const EFFECT_PRESERVED = 1 << 21; // effects with this flag should not be pruned +export const ASYNC_DERIVED = 1 << 22; // Flags used for async -export const REACTION_IS_UPDATING = 1 << 22; -export const BOUNDARY_SUSPENDED = 1 << 23; +export const REACTION_IS_UPDATING = 1 << 23; +export const BOUNDARY_SUSPENDED = 1 << 24; export const STATE_SYMBOL = Symbol('$state'); export const STATE_SYMBOL_METADATA = Symbol('$state metadata'); diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js index 5c768be99b..2596494f37 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -173,6 +173,10 @@ export function boundary(node, props, children) { boundary.f ^= BOUNDARY_SUSPENDED; } + // @ts-ignore + var sources = boundary.fn.sources; + sources.clear(); + for (const e of render_effects) { try { if (check_dirtiness(e)) { @@ -349,6 +353,9 @@ export function boundary(node, props, children) { } }; + // @ts-ignore + boundary.fn.sources = new Map(); + // @ts-ignore boundary.fn.is_pending = () => props.pending; @@ -438,16 +445,21 @@ export function is_pending_boundary(boundary) { return boundary.fn.is_pending(); } -export function suspend() { - var boundary = active_effect; +export function get_boundary(effect) { + var boundary = effect; while (boundary !== null) { if ((boundary.f & BOUNDARY_EFFECT) !== 0 && is_pending_boundary(boundary)) { - break; + return boundary; } boundary = boundary.parent; } + return null; +} + +export function suspend() { + var boundary = get_boundary(active_effect); if (boundary === null) { e.await_outside_boundary(); diff --git a/packages/svelte/src/internal/client/dom/elements/events.js b/packages/svelte/src/internal/client/dom/elements/events.js index 363b8e1ed5..0d30d4bbce 100644 --- a/packages/svelte/src/internal/client/dom/elements/events.js +++ b/packages/svelte/src/internal/client/dom/elements/events.js @@ -8,10 +8,13 @@ import * as w from '../../warnings.js'; import { active_effect, active_reaction, + event_boundary_effect, set_active_effect, - set_active_reaction + set_active_reaction, + set_event_boundary_effect } from '../../runtime.js'; import { without_reactive_context } from './bindings/shared.js'; +import { get_boundary } from '../blocks/boundary.js'; /** @type {Set} */ export const all_registered_events = new Set(); @@ -239,8 +242,17 @@ export function handle_event_propagation(event) { if (delegated !== undefined && !(/** @type {any} */ (current_target).disabled)) { if (is_array(delegated)) { - var [fn, ...data] = delegated; - fn.apply(current_target, [event, ...data]); + var [fn, effect, ...data] = delegated; + var boundary_effect = (effect !== null && get_boundary(effect)) ?? null; + var previous_boundary_effect = event_boundary_effect; + try { + if (boundary_effect !== null) { + set_event_boundary_effect(boundary_effect); + } + fn.apply(current_target, [event, ...data]); + } finally { + set_event_boundary_effect(previous_boundary_effect); + } } else { delegated.call(current_target, event); } diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index 9035e50e4f..14ffcb5d59 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -143,7 +143,8 @@ export { untrack, exclude_from_object, deep_read, - deep_read_state + deep_read_state, + active_effect } from './runtime.js'; export { validate_binding, validate_each_keys } from './validate.js'; export { raf } from './timing.js'; diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index c2da6639b8..dd2dc107fa 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -1,6 +1,7 @@ /** @import { Derived, Effect, Source } from '#client' */ import { DEV } from 'esm-env'; import { + ASYNC_DERIVED, CLEAN, DERIVED, DESTROYED, @@ -29,7 +30,6 @@ import { get_stack } from '../dev/tracing.js'; import { tracing_mode_flag } from '../../flags/index.js'; import { capture, suspend } from '../dom/blocks/boundary.js'; import { component_context } from '../context.js'; -import { noop } from '../../shared/utils.js'; import { UNINITIALIZED } from '../../../constants.js'; /** @type {Effect | null} */ @@ -152,8 +152,9 @@ export function async_derived(fn, location) { } } ); - }, EFFECT_PRESERVED); + }, EFFECT_PRESERVED | ASYNC_DERIVED); + // eslint-disable-next-line no-async-promise-executor return new Promise(async (fulfil) => { // if the effect re-runs before the initial promise // resolves, delay resolution until we have a value diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index 0dc55f97ba..5ad2ca6dd7 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -169,6 +169,9 @@ export function set(source, value) { */ export function internal_set(source, value) { if (!source.equals(value)) { + + mark_reactions(source, DIRTY, source); + var old_value = source.v; source.v = value; source.wv = increment_write_version(); @@ -181,8 +184,6 @@ export function internal_set(source, value) { } } - mark_reactions(source, DIRTY); - // It's possible that the current reaction might not have up-to-date dependencies // whilst it's actively running. So in the case of ensuring it registers the reaction // properly for itself, we need to ensure the current effect actually gets @@ -257,9 +258,10 @@ export function update_pre(source, d = 1) { /** * @param {Value} signal * @param {number} status should be DIRTY or MAYBE_DIRTY + * @param {Source} [source] * @returns {void} */ -function mark_reactions(signal, status) { +function mark_reactions(signal, status, source) { var reactions = signal.reactions; if (reactions === null) return; @@ -289,7 +291,7 @@ function mark_reactions(signal, status) { if ((flags & DERIVED) !== 0) { mark_reactions(/** @type {Derived} */ (reaction), MAYBE_DIRTY); } else { - schedule_effect(/** @type {Effect} */ (reaction)); + schedule_effect(/** @type {Effect} */ (reaction), source); } } } diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 8016eeb9b2..d917eec3ed 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -25,14 +25,15 @@ import { DISCONNECTED, BOUNDARY_EFFECT, REACTION_IS_UPDATING, - BOUNDARY_SUSPENDED + BOUNDARY_SUSPENDED, + ASYNC_DERIVED } from './constants.js'; import { flush_idle_tasks, flush_boundary_micro_tasks, flush_post_micro_tasks } from './dom/task.js'; -import { internal_set } from './reactivity/sources.js'; +import { internal_set, set } from './reactivity/sources.js'; import { destroy_derived_effects, from_async_derived, @@ -50,7 +51,7 @@ import { set_component_context, set_dev_current_component_function } from './context.js'; -import { add_boundary_effect, commit_boundary } from './dom/blocks/boundary.js'; +import { add_boundary_effect, commit_boundary, get_boundary } from './dom/blocks/boundary.js'; import * as w from './warnings.js'; const FLUSH_MICROTASK = 0; @@ -109,6 +110,14 @@ export function set_active_effect(effect) { active_effect = effect; } +/** @type {null | Effect} */ +export let event_boundary_effect = null; + +/** @param {null | Effect} effect */ +export function set_event_boundary_effect(effect) { + event_boundary_effect = effect; +} + // TODO remove this, once we're satisfied that we're not leaking context /* @__PURE__ */ setInterval(() => { @@ -776,9 +785,10 @@ function flush_deferred() { /** * @param {Effect} signal + * @param {Source} [source] * @returns {void} */ -export function schedule_effect(signal) { +export function schedule_effect(signal, source) { if (scheduler_mode === FLUSH_MICROTASK) { if (!is_micro_task_queued) { is_micro_task_queued = true; @@ -786,6 +796,17 @@ export function schedule_effect(signal) { } } + if (source && (signal.f & ASYNC_DERIVED) !== 0) { + var boundary = get_boundary(signal); + // @ts-ignore + var sources = boundary.fn.sources; + var entry = sources.get(source); + if (entry === undefined) { + entry = { v: source.v }; + sources.set(source, entry); + } + } + last_scheduled_effect = signal; var effect = signal; @@ -812,10 +833,9 @@ export function schedule_effect(signal) { * * @param {Effect} effect * @param {Effect[]} collected_effects - * @param {Effect} [boundary] * @returns {void} */ -function process_effects(effect, collected_effects, boundary) { +function process_effects(effect, collected_effects) { var current_effect = effect.first; var effects = []; @@ -826,17 +846,7 @@ function process_effects(effect, collected_effects, boundary) { var sibling = current_effect.next; if (!is_skippable_branch && (flags & INERT) === 0) { - if (boundary !== undefined && (flags & (BLOCK_EFFECT | BRANCH_EFFECT)) === 0) { - // Inside a boundary, defer everything except block/branch effects - add_boundary_effect(/** @type {Effect} */ (boundary), current_effect); - } else if ((flags & BOUNDARY_EFFECT) !== 0) { - process_effects(current_effect, collected_effects, current_effect); - - if ((current_effect.f & BOUNDARY_SUSPENDED) === 0) { - // no more async work to happen - commit_boundary(current_effect); - } - } else if ((flags & RENDER_EFFECT) !== 0) { + if ((flags & RENDER_EFFECT) !== 0) { if (is_branch) { current_effect.f ^= CLEAN; } else { @@ -1024,12 +1034,28 @@ export function get(signal) { } } + var value = signal.v; + if (is_derived) { derived = /** @type {Derived} */ (signal); if (check_dirtiness(derived)) { update_derived(derived); } + value = signal.v; + } else { + var target_effect = event_boundary_effect ?? active_effect; + + if (target_effect !== null && (target_effect.f & ASYNC_DERIVED) === 0) { + var boundary = get_boundary(target_effect); + if (boundary !== null) { + var sources = boundary.fn.sources; + var entry = sources.get(signal); + if (entry !== undefined) { + value = entry.v; + } + } + } } if (DEV) { @@ -1052,7 +1078,7 @@ export function get(signal) { if (signal.debug) { signal.debug(); } else if (signal.created) { - var entry = tracing_expressions.entries.get(signal); + entry = tracing_expressions.entries.get(signal); if (entry === undefined) { entry = { read: [] }; @@ -1066,7 +1092,7 @@ export function get(signal) { recent_async_deriveds.delete(signal); } - return signal.v; + return value; } /**