async fork values

async-fork
Dominic Gannaway 7 months ago
parent 3289ac3ad1
commit ed2a1e3d43

@ -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 // When we hoist a function we assign an array with the function and all
// hoisted closure params. // hoisted closure params.
const args = [handler, ...hoisted_params]; const args = [handler, b.id('$.active_effect'), ...hoisted_params];
delegated_assignment = b.array(args); delegated_assignment = b.array(args);
} else { } else {
delegated_assignment = handler; delegated_assignment = handler;

@ -20,10 +20,11 @@ export const LEGACY_DERIVED_PROP = 1 << 18;
export const INSPECT_EFFECT = 1 << 19; export const INSPECT_EFFECT = 1 << 19;
export const HEAD_EFFECT = 1 << 20; export const HEAD_EFFECT = 1 << 20;
export const EFFECT_PRESERVED = 1 << 21; // effects with this flag should not be pruned export const EFFECT_PRESERVED = 1 << 21; // effects with this flag should not be pruned
export const ASYNC_DERIVED = 1 << 22;
// Flags used for async // Flags used for async
export const REACTION_IS_UPDATING = 1 << 22; export const REACTION_IS_UPDATING = 1 << 23;
export const BOUNDARY_SUSPENDED = 1 << 23; export const BOUNDARY_SUSPENDED = 1 << 24;
export const STATE_SYMBOL = Symbol('$state'); export const STATE_SYMBOL = Symbol('$state');
export const STATE_SYMBOL_METADATA = Symbol('$state metadata'); export const STATE_SYMBOL_METADATA = Symbol('$state metadata');

@ -173,6 +173,10 @@ export function boundary(node, props, children) {
boundary.f ^= BOUNDARY_SUSPENDED; boundary.f ^= BOUNDARY_SUSPENDED;
} }
// @ts-ignore
var sources = boundary.fn.sources;
sources.clear();
for (const e of render_effects) { for (const e of render_effects) {
try { try {
if (check_dirtiness(e)) { if (check_dirtiness(e)) {
@ -349,6 +353,9 @@ export function boundary(node, props, children) {
} }
}; };
// @ts-ignore
boundary.fn.sources = new Map();
// @ts-ignore // @ts-ignore
boundary.fn.is_pending = () => props.pending; boundary.fn.is_pending = () => props.pending;
@ -438,16 +445,21 @@ export function is_pending_boundary(boundary) {
return boundary.fn.is_pending(); return boundary.fn.is_pending();
} }
export function suspend() { export function get_boundary(effect) {
var boundary = active_effect; var boundary = effect;
while (boundary !== null) { while (boundary !== null) {
if ((boundary.f & BOUNDARY_EFFECT) !== 0 && is_pending_boundary(boundary)) { if ((boundary.f & BOUNDARY_EFFECT) !== 0 && is_pending_boundary(boundary)) {
break; return boundary;
} }
boundary = boundary.parent; boundary = boundary.parent;
} }
return null;
}
export function suspend() {
var boundary = get_boundary(active_effect);
if (boundary === null) { if (boundary === null) {
e.await_outside_boundary(); e.await_outside_boundary();

@ -8,10 +8,13 @@ import * as w from '../../warnings.js';
import { import {
active_effect, active_effect,
active_reaction, active_reaction,
event_boundary_effect,
set_active_effect, set_active_effect,
set_active_reaction set_active_reaction,
set_event_boundary_effect
} from '../../runtime.js'; } from '../../runtime.js';
import { without_reactive_context } from './bindings/shared.js'; import { without_reactive_context } from './bindings/shared.js';
import { get_boundary } from '../blocks/boundary.js';
/** @type {Set<string>} */ /** @type {Set<string>} */
export const all_registered_events = new 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 (delegated !== undefined && !(/** @type {any} */ (current_target).disabled)) {
if (is_array(delegated)) { if (is_array(delegated)) {
var [fn, ...data] = delegated; var [fn, effect, ...data] = delegated;
fn.apply(current_target, [event, ...data]); 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 { } else {
delegated.call(current_target, event); delegated.call(current_target, event);
} }

@ -143,7 +143,8 @@ export {
untrack, untrack,
exclude_from_object, exclude_from_object,
deep_read, deep_read,
deep_read_state deep_read_state,
active_effect
} from './runtime.js'; } from './runtime.js';
export { validate_binding, validate_each_keys } from './validate.js'; export { validate_binding, validate_each_keys } from './validate.js';
export { raf } from './timing.js'; export { raf } from './timing.js';

@ -1,6 +1,7 @@
/** @import { Derived, Effect, Source } from '#client' */ /** @import { Derived, Effect, Source } from '#client' */
import { DEV } from 'esm-env'; import { DEV } from 'esm-env';
import { import {
ASYNC_DERIVED,
CLEAN, CLEAN,
DERIVED, DERIVED,
DESTROYED, DESTROYED,
@ -29,7 +30,6 @@ import { get_stack } from '../dev/tracing.js';
import { tracing_mode_flag } from '../../flags/index.js'; import { tracing_mode_flag } from '../../flags/index.js';
import { capture, suspend } from '../dom/blocks/boundary.js'; import { capture, suspend } from '../dom/blocks/boundary.js';
import { component_context } from '../context.js'; import { component_context } from '../context.js';
import { noop } from '../../shared/utils.js';
import { UNINITIALIZED } from '../../../constants.js'; import { UNINITIALIZED } from '../../../constants.js';
/** @type {Effect | null} */ /** @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) => { return new Promise(async (fulfil) => {
// if the effect re-runs before the initial promise // if the effect re-runs before the initial promise
// resolves, delay resolution until we have a value // resolves, delay resolution until we have a value

@ -169,6 +169,9 @@ export function set(source, value) {
*/ */
export function internal_set(source, value) { export function internal_set(source, value) {
if (!source.equals(value)) { if (!source.equals(value)) {
mark_reactions(source, DIRTY, source);
var old_value = source.v; var old_value = source.v;
source.v = value; source.v = value;
source.wv = increment_write_version(); 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 // 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 // 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 // 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 {Value} signal
* @param {number} status should be DIRTY or MAYBE_DIRTY * @param {number} status should be DIRTY or MAYBE_DIRTY
* @param {Source} [source]
* @returns {void} * @returns {void}
*/ */
function mark_reactions(signal, status) { function mark_reactions(signal, status, source) {
var reactions = signal.reactions; var reactions = signal.reactions;
if (reactions === null) return; if (reactions === null) return;
@ -289,7 +291,7 @@ function mark_reactions(signal, status) {
if ((flags & DERIVED) !== 0) { if ((flags & DERIVED) !== 0) {
mark_reactions(/** @type {Derived} */ (reaction), MAYBE_DIRTY); mark_reactions(/** @type {Derived} */ (reaction), MAYBE_DIRTY);
} else { } else {
schedule_effect(/** @type {Effect} */ (reaction)); schedule_effect(/** @type {Effect} */ (reaction), source);
} }
} }
} }

@ -25,14 +25,15 @@ import {
DISCONNECTED, DISCONNECTED,
BOUNDARY_EFFECT, BOUNDARY_EFFECT,
REACTION_IS_UPDATING, REACTION_IS_UPDATING,
BOUNDARY_SUSPENDED BOUNDARY_SUSPENDED,
ASYNC_DERIVED
} from './constants.js'; } from './constants.js';
import { import {
flush_idle_tasks, flush_idle_tasks,
flush_boundary_micro_tasks, flush_boundary_micro_tasks,
flush_post_micro_tasks flush_post_micro_tasks
} from './dom/task.js'; } from './dom/task.js';
import { internal_set } from './reactivity/sources.js'; import { internal_set, set } from './reactivity/sources.js';
import { import {
destroy_derived_effects, destroy_derived_effects,
from_async_derived, from_async_derived,
@ -50,7 +51,7 @@ import {
set_component_context, set_component_context,
set_dev_current_component_function set_dev_current_component_function
} from './context.js'; } 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'; import * as w from './warnings.js';
const FLUSH_MICROTASK = 0; const FLUSH_MICROTASK = 0;
@ -109,6 +110,14 @@ export function set_active_effect(effect) {
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 // TODO remove this, once we're satisfied that we're not leaking context
/* @__PURE__ */ /* @__PURE__ */
setInterval(() => { setInterval(() => {
@ -776,9 +785,10 @@ function flush_deferred() {
/** /**
* @param {Effect} signal * @param {Effect} signal
* @param {Source} [source]
* @returns {void} * @returns {void}
*/ */
export function schedule_effect(signal) { export function schedule_effect(signal, source) {
if (scheduler_mode === FLUSH_MICROTASK) { if (scheduler_mode === FLUSH_MICROTASK) {
if (!is_micro_task_queued) { if (!is_micro_task_queued) {
is_micro_task_queued = true; 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; last_scheduled_effect = signal;
var effect = signal; var effect = signal;
@ -812,10 +833,9 @@ export function schedule_effect(signal) {
* *
* @param {Effect} effect * @param {Effect} effect
* @param {Effect[]} collected_effects * @param {Effect[]} collected_effects
* @param {Effect} [boundary]
* @returns {void} * @returns {void}
*/ */
function process_effects(effect, collected_effects, boundary) { function process_effects(effect, collected_effects) {
var current_effect = effect.first; var current_effect = effect.first;
var effects = []; var effects = [];
@ -826,17 +846,7 @@ function process_effects(effect, collected_effects, boundary) {
var sibling = current_effect.next; var sibling = current_effect.next;
if (!is_skippable_branch && (flags & INERT) === 0) { if (!is_skippable_branch && (flags & INERT) === 0) {
if (boundary !== undefined && (flags & (BLOCK_EFFECT | BRANCH_EFFECT)) === 0) { if ((flags & RENDER_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 (is_branch) { if (is_branch) {
current_effect.f ^= CLEAN; current_effect.f ^= CLEAN;
} else { } else {
@ -1024,12 +1034,28 @@ export function get(signal) {
} }
} }
var value = signal.v;
if (is_derived) { if (is_derived) {
derived = /** @type {Derived} */ (signal); derived = /** @type {Derived} */ (signal);
if (check_dirtiness(derived)) { if (check_dirtiness(derived)) {
update_derived(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) { if (DEV) {
@ -1052,7 +1078,7 @@ export function get(signal) {
if (signal.debug) { if (signal.debug) {
signal.debug(); signal.debug();
} else if (signal.created) { } else if (signal.created) {
var entry = tracing_expressions.entries.get(signal); entry = tracing_expressions.entries.get(signal);
if (entry === undefined) { if (entry === undefined) {
entry = { read: [] }; entry = { read: [] };
@ -1066,7 +1092,7 @@ export function get(signal) {
recent_async_deriveds.delete(signal); recent_async_deriveds.delete(signal);
} }
return signal.v; return value;
} }
/** /**

Loading…
Cancel
Save