run-batch-until-complete
Rich Harris 5 days ago
parent 7c1408d6df
commit c3db9ac137

@ -87,7 +87,7 @@ export class Boundary {
/** @type {DocumentFragment | null} */
#offscreen_fragment = null;
#local_pending_count = 0;
local_pending_count = 0;
#pending_count = 0;
#is_creating_fallback = false;
@ -103,12 +103,12 @@ export class Boundary {
#effect_pending_update = () => {
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(() => {
this.#effect_pending = source(this.#local_pending_count);
this.#effect_pending = source(this.local_pending_count);
if (DEV) {
tag(this.#effect_pending, '$effect.pending()');
@ -285,13 +285,6 @@ export class Boundary {
this.#anchor.before(this.#offscreen_fragment);
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) {
this.#update_pending_count(d);
this.#local_pending_count += d;
this.local_pending_count += d;
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
Batch.ensure();
this.#local_pending_count = 0;
this.local_pending_count = 0;
if (this.#failed_effect !== null) {
pause_effect(this.#failed_effect, () => {

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

@ -11,7 +11,8 @@ import {
RENDER_EFFECT,
ROOT_EFFECT,
MAYBE_DIRTY,
DERIVED
DERIVED,
BOUNDARY_EFFECT
} from '#client/constants';
import { async_mode_flag } from '../../flags/index.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 { unlink_effect } from './effects.js';
/**
* @typedef {{
* parent: EffectTarget | null;
* effect: Effect | null;
* effects: Effect[];
* render_effects: Effect[];
* block_effects: Effect[];
* }} EffectTarget
*/
/** @type {Set<Batch>} */
const batches = new Set();
@ -97,26 +108,6 @@ export class Batch {
*/
#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
* @type {Effect[]}
@ -155,33 +146,26 @@ export class Batch {
if (this.#pending === 0) {
// TODO we need this because we commit _then_ flush effects...
// maybe there's a way we can reverse the order?
var previous_batch_sources = batch_values;
// var previous_batch_sources = batch_values;
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
// newly updated sources, which could lead to infinite loops when effects run over and over again.
previous_batch = this;
current_batch = null;
batch_values = previous_batch_sources;
flush_queued_effects(render_effects);
flush_queued_effects(effects);
// batch_values = previous_batch_sources;
// flush_queued_effects(target.render_effects);
// flush_queued_effects(target.effects);
previous_batch = null;
this.#deferred?.resolve();
} else {
this.#defer_effects(this.#render_effects);
this.#defer_effects(this.#effects);
this.#defer_effects(this.#block_effects);
// this.#defer_effects(target.render_effects);
// this.#defer_effects(target.effects);
// this.#defer_effects(target.block_effects);
}
batch_values = null;
@ -195,6 +179,17 @@ export class Batch {
#traverse_effect_tree(root) {
root.f ^= CLEAN;
var should_defer = false;
/** @type {EffectTarget} */
var target = {
parent: null,
effect: null,
effects: [],
render_effects: [],
block_effects: []
};
var effect = root.first;
while (effect !== null) {
@ -204,15 +199,25 @@ export class Batch {
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 (is_branch) {
effect.f ^= CLEAN;
} else if ((flags & EFFECT) !== 0) {
this.#effects.push(effect);
target.effects.push(effect);
} else if (async_mode_flag && (flags & RENDER_EFFECT) !== 0) {
this.#render_effects.push(effect);
target.render_effects.push(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);
}
@ -228,10 +233,41 @@ export class Batch {
effect = effect.next;
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;
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
set_signal_status(e, CLEAN);
}
effects.length = 0;
}
/**

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

Loading…
Cancel
Save