pull/16971/head
Rich Harris 1 week ago
parent 7c1408d6df
commit c3db9ac137

@ -87,7 +87,7 @@ export class Boundary {
/** @type {DocumentFragment | null} */ /** @type {DocumentFragment | null} */
#offscreen_fragment = null; #offscreen_fragment = null;
#local_pending_count = 0; local_pending_count = 0;
#pending_count = 0; #pending_count = 0;
#is_creating_fallback = false; #is_creating_fallback = false;
@ -103,12 +103,12 @@ export class Boundary {
#effect_pending_update = () => { #effect_pending_update = () => {
if (this.#effect_pending) { if (this.#effect_pending) {
internal_set(this.#effect_pending, this.#local_pending_count); internal_set(this.#effect_pending, this.local_pending_count);
} }
}; };
#effect_pending_subscriber = createSubscriber(() => { #effect_pending_subscriber = createSubscriber(() => {
this.#effect_pending = source(this.#local_pending_count); this.#effect_pending = source(this.local_pending_count);
if (DEV) { if (DEV) {
tag(this.#effect_pending, '$effect.pending()'); tag(this.#effect_pending, '$effect.pending()');
@ -285,13 +285,6 @@ export class Boundary {
this.#anchor.before(this.#offscreen_fragment); this.#anchor.before(this.#offscreen_fragment);
this.#offscreen_fragment = null; this.#offscreen_fragment = null;
} }
// TODO this feels like a little bit of a kludge, but until we
// overhaul the boundary/batch relationship it's probably
// the most pragmatic solution available to us
queue_micro_task(() => {
Batch.ensure().flush();
});
} }
} }
@ -304,7 +297,7 @@ export class Boundary {
update_pending_count(d) { update_pending_count(d) {
this.#update_pending_count(d); this.#update_pending_count(d);
this.#local_pending_count += d; this.local_pending_count += d;
effect_pending_updates.add(this.#effect_pending_update); effect_pending_updates.add(this.#effect_pending_update);
} }
@ -363,7 +356,7 @@ export class Boundary {
// If the failure happened while flushing effects, current_batch can be null // If the failure happened while flushing effects, current_batch can be null
Batch.ensure(); Batch.ensure();
this.#local_pending_count = 0; this.local_pending_count = 0;
if (this.#failed_effect !== null) { if (this.#failed_effect !== null) {
pause_effect(this.#failed_effect, () => { pause_effect(this.#failed_effect, () => {

@ -202,10 +202,9 @@ export function unset_context() {
export async function async_body(fn) { export async function async_body(fn) {
var boundary = get_boundary(); var boundary = get_boundary();
var batch = /** @type {Batch} */ (current_batch); var batch = /** @type {Batch} */ (current_batch);
var pending = boundary.is_pending();
boundary.update_pending_count(1); boundary.update_pending_count(1);
if (!pending) batch.increment(); batch.increment();
var active = /** @type {Effect} */ (active_effect); var active = /** @type {Effect} */ (active_effect);
@ -238,12 +237,7 @@ export async function async_body(fn) {
} }
boundary.update_pending_count(-1); boundary.update_pending_count(-1);
if (pending) {
batch.flush();
} else {
batch.decrement(); batch.decrement();
}
unset_context(); unset_context();
} }

@ -11,7 +11,8 @@ import {
RENDER_EFFECT, RENDER_EFFECT,
ROOT_EFFECT, ROOT_EFFECT,
MAYBE_DIRTY, MAYBE_DIRTY,
DERIVED DERIVED,
BOUNDARY_EFFECT
} from '#client/constants'; } from '#client/constants';
import { async_mode_flag } from '../../flags/index.js'; import { async_mode_flag } from '../../flags/index.js';
import { deferred, define_property } from '../../shared/utils.js'; import { deferred, define_property } from '../../shared/utils.js';
@ -30,6 +31,16 @@ import { invoke_error_boundary } from '../error-handling.js';
import { old_values } from './sources.js'; import { old_values } from './sources.js';
import { unlink_effect } from './effects.js'; import { unlink_effect } from './effects.js';
/**
* @typedef {{
* parent: EffectTarget | null;
* effect: Effect | null;
* effects: Effect[];
* render_effects: Effect[];
* block_effects: Effect[];
* }} EffectTarget
*/
/** @type {Set<Batch>} */ /** @type {Set<Batch>} */
const batches = new Set(); const batches = new Set();
@ -97,26 +108,6 @@ export class Batch {
*/ */
#deferred = null; #deferred = null;
/**
* Template effects and `$effect.pre` effects, which run when
* a batch is committed
* @type {Effect[]}
*/
#render_effects = [];
/**
* The same as `#render_effects`, but for `$effect` (which runs after)
* @type {Effect[]}
*/
#effects = [];
/**
* Block effects, which may need to re-run on subsequent flushes
* in order to update internal sources (e.g. each block items)
* @type {Effect[]}
*/
#block_effects = [];
/** /**
* Deferred effects (which run after async work has completed) that are DIRTY * Deferred effects (which run after async work has completed) that are DIRTY
* @type {Effect[]} * @type {Effect[]}
@ -155,33 +146,26 @@ export class Batch {
if (this.#pending === 0) { if (this.#pending === 0) {
// TODO we need this because we commit _then_ flush effects... // TODO we need this because we commit _then_ flush effects...
// maybe there's a way we can reverse the order? // maybe there's a way we can reverse the order?
var previous_batch_sources = batch_values; // var previous_batch_sources = batch_values;
this.#commit(); this.#commit();
var render_effects = this.#render_effects;
var effects = this.#effects;
this.#render_effects = [];
this.#effects = [];
this.#block_effects = [];
// If sources are written to, then work needs to happen in a separate batch, else prior sources would be mixed with // If sources are written to, then work needs to happen in a separate batch, else prior sources would be mixed with
// newly updated sources, which could lead to infinite loops when effects run over and over again. // newly updated sources, which could lead to infinite loops when effects run over and over again.
previous_batch = this; previous_batch = this;
current_batch = null; current_batch = null;
batch_values = previous_batch_sources; // batch_values = previous_batch_sources;
flush_queued_effects(render_effects); // flush_queued_effects(target.render_effects);
flush_queued_effects(effects); // flush_queued_effects(target.effects);
previous_batch = null; previous_batch = null;
this.#deferred?.resolve(); this.#deferred?.resolve();
} else { } else {
this.#defer_effects(this.#render_effects); // this.#defer_effects(target.render_effects);
this.#defer_effects(this.#effects); // this.#defer_effects(target.effects);
this.#defer_effects(this.#block_effects); // this.#defer_effects(target.block_effects);
} }
batch_values = null; batch_values = null;
@ -195,6 +179,17 @@ export class Batch {
#traverse_effect_tree(root) { #traverse_effect_tree(root) {
root.f ^= CLEAN; root.f ^= CLEAN;
var should_defer = false;
/** @type {EffectTarget} */
var target = {
parent: null,
effect: null,
effects: [],
render_effects: [],
block_effects: []
};
var effect = root.first; var effect = root.first;
while (effect !== null) { while (effect !== null) {
@ -204,15 +199,25 @@ export class Batch {
var skip = is_skippable_branch || (flags & INERT) !== 0 || this.skipped_effects.has(effect); var skip = is_skippable_branch || (flags & INERT) !== 0 || this.skipped_effects.has(effect);
if ((effect.f & BOUNDARY_EFFECT) !== 0 && effect.b?.is_pending()) {
target = {
parent: target,
effect,
effects: [],
render_effects: [],
block_effects: []
};
}
if (!skip && effect.fn !== null) { if (!skip && effect.fn !== null) {
if (is_branch) { if (is_branch) {
effect.f ^= CLEAN; effect.f ^= CLEAN;
} else if ((flags & EFFECT) !== 0) { } else if ((flags & EFFECT) !== 0) {
this.#effects.push(effect); target.effects.push(effect);
} else if (async_mode_flag && (flags & RENDER_EFFECT) !== 0) { } else if (async_mode_flag && (flags & RENDER_EFFECT) !== 0) {
this.#render_effects.push(effect); target.render_effects.push(effect);
} else if (is_dirty(effect)) { } else if (is_dirty(effect)) {
if ((effect.f & BLOCK_EFFECT) !== 0) this.#block_effects.push(effect); if ((effect.f & BLOCK_EFFECT) !== 0) target.block_effects.push(effect);
update_effect(effect); update_effect(effect);
} }
@ -228,10 +233,41 @@ export class Batch {
effect = effect.next; effect = effect.next;
while (effect === null && parent !== null) { while (effect === null && parent !== null) {
if (parent.b !== null) {
var ready = parent.b.local_pending_count === 0;
if (target.parent === null) {
should_defer ||= !ready;
} else if (parent === target.effect) {
if (ready) {
// TODO can this happen?
target.parent.effects.push(...target.effects);
target.parent.render_effects.push(...target.render_effects);
target.parent.block_effects.push(...target.block_effects);
} else {
this.#defer_effects(target.effects);
this.#defer_effects(target.render_effects);
this.#defer_effects(target.block_effects);
}
target = /** @type {EffectTarget} */ (target.parent);
}
}
effect = parent.next; effect = parent.next;
parent = parent.parent; parent = parent.parent;
} }
} }
if (should_defer) {
this.#defer_effects(target.effects);
this.#defer_effects(target.render_effects);
this.#defer_effects(target.block_effects);
} else {
// TODO append/detach blocks here as well
flush_queued_effects(target.render_effects);
flush_queued_effects(target.effects);
}
} }
/** /**
@ -245,8 +281,6 @@ export class Batch {
// mark as clean so they get scheduled if they depend on pending async state // mark as clean so they get scheduled if they depend on pending async state
set_signal_status(e, CLEAN); set_signal_status(e, CLEAN);
} }
effects.length = 0;
} }
/** /**

@ -136,18 +136,15 @@ export function async_derived(fn, location) {
if (DEV) current_async_effect = null; if (DEV) current_async_effect = null;
var batch = /** @type {Batch} */ (current_batch); var batch = /** @type {Batch} */ (current_batch);
var pending = boundary.is_pending();
if (should_suspend) { if (should_suspend) {
boundary.update_pending_count(1); boundary.update_pending_count(1);
if (!pending) {
batch.increment(); batch.increment();
deferreds.get(batch)?.reject(STALE_REACTION); deferreds.get(batch)?.reject(STALE_REACTION);
deferreds.delete(batch); // delete to ensure correct order in Map iteration below deferreds.delete(batch); // delete to ensure correct order in Map iteration below
deferreds.set(batch, d); deferreds.set(batch, d);
} }
}
/** /**
* @param {any} value * @param {any} value
@ -156,7 +153,7 @@ export function async_derived(fn, location) {
const handler = (value, error = undefined) => { const handler = (value, error = undefined) => {
current_async_effect = null; current_async_effect = null;
if (!pending) batch.activate(); batch.activate();
if (error) { if (error) {
if (error !== STALE_REACTION) { if (error !== STALE_REACTION) {
@ -193,7 +190,7 @@ export function async_derived(fn, location) {
if (should_suspend) { if (should_suspend) {
boundary.update_pending_count(-1); boundary.update_pending_count(-1);
if (!pending) batch.decrement(); batch.decrement();
} }
}; };

Loading…
Cancel
Save