step one - template effects

aa-coordination
Rich Harris 7 months ago
parent 422e658cdb
commit 2b0812817c

@ -25,6 +25,7 @@ export const EFFECT_HAS_DERIVED = 1 << 21;
// Flags used for async
export const IS_ASYNC = 1 << 22;
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');

@ -1,6 +1,6 @@
/** @import { Effect, TemplateNode, } from '#client' */
import { BOUNDARY_EFFECT, EFFECT_TRANSPARENT } from '../../constants.js';
import { BOUNDARY_EFFECT, BOUNDARY_SUSPENDED, EFFECT_TRANSPARENT } from '../../constants.js';
import {
block,
branch,
@ -16,7 +16,8 @@ import {
set_active_effect,
set_active_reaction,
set_component_context,
reset_is_throwing_error
reset_is_throwing_error,
schedule_effect
} from '../../runtime.js';
import {
hydrate_next,
@ -117,18 +118,8 @@ export function boundary(node, props, children) {
pause_effect(
effect,
() => {
var node = effect.nodes_start;
var end = effect.nodes_end;
offscreen_fragment = document.createDocumentFragment();
while (node !== null) {
/** @type {TemplateNode | null} */
var next = node === end ? null : /** @type {TemplateNode} */ (get_next_sibling(node));
offscreen_fragment.append(node);
node = next;
}
move_effect(effect, offscreen_fragment);
},
false
);
@ -146,7 +137,9 @@ export function boundary(node, props, children) {
}
if (pending_effect !== null) {
pause_effect(pending_effect);
pause_effect(pending_effect, () => {
pending_effect = null;
});
}
anchor.before(/** @type {DocumentFragment} */ (offscreen_fragment));
@ -159,7 +152,9 @@ export function boundary(node, props, children) {
function reset() {
if (failed_effect !== null) {
pause_effect(failed_effect);
pause_effect(failed_effect, () => {
failed_effect = null;
});
}
main_effect = with_boundary(boundary, () => {
@ -176,16 +171,32 @@ export function boundary(node, props, children) {
// @ts-ignore We re-use the effect's fn property to avoid allocation of an additional field
boundary.fn = (/** @type {unknown} */ input) => {
if (input === ASYNC_INCREMENT) {
if (async_count++ === 0) {
queue_boundary_micro_task(suspend);
}
async_count++;
// TODO post-init, show the pending snippet after a timeout
return;
}
if (input === ASYNC_DECREMENT) {
if (--async_count === 0) {
queue_boundary_micro_task(unsuspend);
boundary.f ^= BOUNDARY_SUSPENDED;
if (pending_effect) {
pause_effect(pending_effect, () => {
pending_effect = null;
});
}
if (offscreen_fragment) {
anchor.before(offscreen_fragment);
offscreen_fragment = null;
}
if (main_effect !== null) {
// TODO do we also need to `resume_effect` here?
schedule_effect(main_effect);
}
}
return;
@ -260,6 +271,17 @@ export function boundary(node, props, children) {
});
} else {
main_effect = branch(() => children(anchor));
if (async_count > 0) {
if (pending) {
offscreen_fragment = document.createDocumentFragment();
move_effect(main_effect, offscreen_fragment);
pending_effect = branch(() => pending(anchor));
} else {
// TODO trigger pending boundary on parent
}
}
}
reset_is_throwing_error();
@ -270,6 +292,24 @@ export function boundary(node, props, children) {
}
}
/**
*
* @param {Effect} effect
* @param {DocumentFragment} fragment
*/
function move_effect(effect, fragment) {
var node = effect.nodes_start;
var end = effect.nodes_end;
while (node !== null) {
/** @type {TemplateNode | null} */
var next = node === end ? null : /** @type {TemplateNode} */ (get_next_sibling(node));
fragment.append(node);
node = next;
}
}
export function capture() {
var previous_effect = active_effect;
var previous_reaction = active_reaction;

@ -30,7 +30,10 @@ import {
UNOWNED,
MAYBE_DIRTY,
BLOCK_EFFECT,
ROOT_EFFECT
ROOT_EFFECT,
IS_ASYNC,
BOUNDARY_EFFECT,
BOUNDARY_SUSPENDED
} from '../constants.js';
import * as e from '../errors.js';
import { legacy_mode_flag, tracing_mode_flag } from '../../flags/index.js';
@ -254,6 +257,22 @@ function mark_reactions(signal, status) {
continue;
}
// if we're about to trip an async derived, mark the boundary as
// suspended _before_ we actually process effects
if ((flags & IS_ASYNC) !== 0) {
let boundary = /** @type {Derived} */ (reaction).parent;
while (boundary !== null && (boundary.f & BOUNDARY_EFFECT) === 0) {
boundary = boundary.parent;
}
if (boundary === null) {
// TODO this is presumably an error — throw here?
} else {
boundary.f |= BOUNDARY_SUSPENDED;
}
}
set_signal_status(reaction, status);
// If the signal a) was previously clean or b) is an unowned derived, then mark it

@ -28,7 +28,8 @@ import {
BOUNDARY_EFFECT,
REACTION_IS_UPDATING,
IS_ASYNC,
TEMPLATE_EFFECT
TEMPLATE_EFFECT,
BOUNDARY_SUSPENDED
} from './constants.js';
import {
flush_idle_tasks,
@ -843,15 +844,16 @@ function process_effects(effect, collected_effects) {
((flags & BLOCK_EFFECT) === 0 || (flags & TEMPLATE_EFFECT) !== 0);
if ((flags & RENDER_EFFECT) !== 0) {
if (is_branch) {
if ((flags & BOUNDARY_EFFECT) !== 0) {
suspended = (flags & BOUNDARY_SUSPENDED) !== 0;
} else if (is_branch) {
if (!suspended) {
current_effect.f ^= CLEAN;
}
} else if (!skip_suspended) {
try {
if (check_dirtiness(current_effect)) {
update_effect(current_effect);
if ((flags & IS_ASYNC) !== 0 && !suspended) {
suspended = true;
}
}
} catch (error) {
handle_error(error, current_effect, null, current_effect.ctx);
@ -876,9 +878,16 @@ function process_effects(effect, collected_effects) {
if (effect === parent) {
break main_loop;
}
if (suspended && (parent.f & BOUNDARY_EFFECT) !== 0 && is_pending_boundary(parent)) {
suspended = false;
if ((parent.f & BOUNDARY_EFFECT) !== 0) {
let boundary = parent.parent;
while (boundary !== null && (boundary.f & BOUNDARY_EFFECT) === 0) {
boundary = boundary.parent;
}
suspended = boundary === null ? false : (boundary.f & BOUNDARY_SUSPENDED) !== 0;
}
var parent_sibling = parent.next;
if (parent_sibling !== null) {
current_effect = parent_sibling;

Loading…
Cancel
Save