pull/16197/head
Rich Harris 8 months ago
parent 7923b5a754
commit b18247be38

@ -18,19 +18,20 @@ import {
update_reaction,
increment_write_version,
set_active_effect,
handle_error
handle_error,
flush_sync
} from '../runtime.js';
import { equals, safe_equals } from './equality.js';
import * as e from '../errors.js';
import * as w from '../warnings.js';
import { block, destroy_effect, render_effect } from './effects.js';
import { destroy_effect, render_effect } from './effects.js';
import { inspect_effects, internal_set, set_inspect_effects, source } from './sources.js';
import { get_stack } from '../dev/tracing.js';
import { tracing_mode_flag } from '../../flags/index.js';
import { capture, suspend } from '../dom/blocks/boundary.js';
import { capture } from '../dom/blocks/boundary.js';
import { component_context } from '../context.js';
import { noop } from '../../shared/utils.js';
import { UNINITIALIZED } from '../../../constants.js';
import { active_fork } from './forks.js';
/** @type {Effect | null} */
export let from_async_derived = null;
@ -105,16 +106,19 @@ export function async_derived(fn, location) {
// only suspend in async deriveds created on initialisation
var should_suspend = !active_reaction;
/** @type {(() => void) | null} */
var unsuspend = null;
render_effect(() => {
if (DEV) from_async_derived = active_effect;
var current = (promise = fn());
promise = fn();
if (DEV) from_async_derived = null;
var restore = capture();
if (should_suspend) unsuspend ??= suspend();
var fork = active_fork;
if (should_suspend) {
// TODO if nearest pending boundary is not ready, attach to the boundary
fork?.increment();
}
promise.then(
(v) => {
@ -122,33 +126,36 @@ export function async_derived(fn, location) {
return;
}
if (promise === current) {
restore();
from_async_derived = null;
restore();
from_async_derived = null;
if (should_suspend) {
fork?.decrement();
}
if (fork !== null) {
fork?.enable();
flush_sync(() => {
internal_set(signal, v);
});
fork?.disable();
} else {
internal_set(signal, v);
}
if (DEV && location !== undefined) {
recent_async_deriveds.add(signal);
setTimeout(() => {
if (recent_async_deriveds.has(signal)) {
w.await_waterfall(location);
recent_async_deriveds.delete(signal);
}
});
}
// TODO we should probably null out active effect here,
// rather than inside `restore()`
unsuspend?.();
unsuspend = null;
if (DEV && location !== undefined) {
recent_async_deriveds.add(signal);
setTimeout(() => {
if (recent_async_deriveds.has(signal)) {
w.await_waterfall(location);
recent_async_deriveds.delete(signal);
}
});
}
},
(e) => {
if (promise === current) {
handle_error(e, parent, null, parent.ctx);
}
handle_error(e, parent, null, parent.ctx);
}
);
}, EFFECT_ASYNC | EFFECT_PRESERVED);

@ -0,0 +1,61 @@
/** @import { Effect, Source } from '#client' */
/** @type {Set<Fork>} */
const forks = new Set();
/** @type {Fork | null} */
export let active_fork = null;
let uid = 1;
export class Fork {
id = uid++;
/** @type {Map<Source, any>} */
previous = new Map();
/** @type {Set<Effect>} */
skipped_effects = new Set();
#pending = 0;
/**
* @param {Source} source
* @param {any} value
*/
capture(source, value) {
if (!this.previous.has(source)) {
this.previous.set(source, value);
}
}
enable() {
active_fork = this;
// TODO revert other forks
}
disable() {
active_fork = null;
// TODO restore state
}
increment() {
this.#pending += 1;
}
decrement() {
this.#pending -= 1;
}
settled() {
return this.#pending === 0;
}
static ensure() {
return (active_fork ??= new Fork());
}
static unset() {
active_fork = null;
}
}

@ -35,6 +35,7 @@ import * as e from '../errors.js';
import { legacy_mode_flag, tracing_mode_flag } from '../../flags/index.js';
import { get_stack } from '../dev/tracing.js';
import { component_context, is_runes } from '../context.js';
import { active_fork, Fork } from './forks.js';
export let inspect_effects = new Set();
@ -174,6 +175,9 @@ export function internal_set(source, value) {
source.v = value;
source.wv = increment_write_version();
const fork = Fork.ensure();
fork.capture(source, old_value);
if (DEV && tracing_mode_flag) {
source.updated = get_stack('UpdatedAt');
if (active_effect != null) {
@ -260,7 +264,7 @@ export function update_pre(source, d = 1) {
* @param {number} status should be DIRTY or MAYBE_DIRTY
* @returns {void}
*/
function mark_reactions(signal, status) {
export function mark_reactions(signal, status) {
var reactions = signal.reactions;
if (reactions === null) return;
@ -271,9 +275,6 @@ function mark_reactions(signal, status) {
var reaction = reactions[i];
var flags = reaction.f;
// Skip any effects that are already dirty
if ((flags & DIRTY) !== 0) continue;
// In legacy mode, skip the current effect to prevent infinite loops
if (!runes && reaction === active_effect) continue;
@ -285,13 +286,10 @@ function mark_reactions(signal, status) {
set_signal_status(reaction, status);
// If the signal a) was previously clean or b) is an unowned derived, then mark it
if ((flags & (CLEAN | UNOWNED)) !== 0) {
if ((flags & DERIVED) !== 0) {
mark_reactions(/** @type {Derived} */ (reaction), MAYBE_DIRTY);
} else {
schedule_effect(/** @type {Effect} */ (reaction));
}
if ((flags & DERIVED) !== 0) {
mark_reactions(/** @type {Derived} */ (reaction), MAYBE_DIRTY);
} else {
schedule_effect(/** @type {Effect} */ (reaction));
}
}
}

@ -53,6 +53,8 @@ import {
import { Boundary } from './dom/blocks/boundary.js';
import * as w from './warnings.js';
import { is_firefox } from './dom/operations.js';
import { active_fork, Fork } from './reactivity/forks.js';
import { log_effect_tree } from './dev/debug.js';
const FLUSH_MICROTASK = 0;
const FLUSH_SYNC = 1;
@ -702,10 +704,14 @@ function flush_queued_root_effects(root_effects) {
}
var collected_effects = process_effects(effect);
flush_queued_effects(collected_effects);
if (/** @type {Fork} */ (active_fork).settled()) {
flush_queued_effects(collected_effects);
}
}
} finally {
is_flushing_effect = previously_flushing_effect;
Fork.unset();
}
}
@ -805,14 +811,16 @@ export function schedule_effect(signal) {
* effects to be flushed.
*
* @param {Effect} effect
* @param {Effect[]} effects
* @param {Boundary} [boundary]
* @returns {Effect[]}
*/
function process_effects(effect, effects = [], boundary) {
function process_effects(effect) {
var current_effect = effect.first;
var current_effect = effect.first;
/** @type {Effect[]} */
var render_effects = [];
/** @type {Effect[]} */
var effects = [];
main_loop: while (current_effect !== null) {
var flags = current_effect.f;
@ -820,64 +828,24 @@ function process_effects(effect, effects = [], boundary) {
var is_skippable_branch = is_branch && (flags & CLEAN) !== 0;
var sibling = current_effect.next;
if (!is_skippable_branch && (flags & INERT) === 0) {
if (boundary !== undefined && (flags & (BLOCK_EFFECT | BRANCH_EFFECT | EFFECT_ASYNC)) === 0) {
// Inside a boundary, defer everything except block/branch effects
boundary.add_effect(current_effect);
} else if ((flags & BOUNDARY_EFFECT) !== 0) {
var b = /** @type {Boundary} */ (current_effect.b);
var skip =
is_skippable_branch ||
(flags & INERT) !== 0 ||
active_fork?.skipped_effects.has(current_effect);
process_effects(current_effect, effects, b);
if (!b.suspended) {
// no more async work to happen
b.commit();
if (!skip) {
if ((flags & (BLOCK_EFFECT | EFFECT_ASYNC)) !== 0) {
if (check_dirtiness(current_effect)) {
update_effect(current_effect);
}
} else if ((flags & RENDER_EFFECT) !== 0) {
if (is_branch) {
current_effect.f ^= CLEAN;
} else {
// Ensure we set the effect to be the active reaction
// to ensure that unowned deriveds are correctly tracked
// because we're flushing the current effect
var previous_active_reaction = active_reaction;
try {
active_reaction = current_effect;
if (check_dirtiness(current_effect)) {
update_effect(current_effect);
}
} catch (error) {
handle_error(error, current_effect, null, current_effect.ctx);
} finally {
active_reaction = previous_active_reaction;
}
}
var child = current_effect.first;
if (child !== null) {
current_effect = child;
continue;
render_effects.push(current_effect);
}
} else if ((flags & EFFECT) !== 0) {
effects.push(current_effect);
} else if (is_branch) {
current_effect.f ^= CLEAN;
} else {
// Ensure we set the effect to be the active reaction
// to ensure that unowned deriveds are correctly tracked
// because we're flushing the current effect
var previous_active_reaction = active_reaction;
try {
active_reaction = current_effect;
if (check_dirtiness(current_effect)) {
update_effect(current_effect);
}
} catch (error) {
handle_error(error, current_effect, null, current_effect.ctx);
} finally {
active_reaction = previous_active_reaction;
}
}
var child = current_effect.first;
@ -908,7 +876,7 @@ function process_effects(effect, effects = [], boundary) {
current_effect = sibling;
}
return effects;
return [...render_effects, ...effects];
}
/**

Loading…
Cancel
Save