entangle-batches
Simon Holthausen 1 week ago
parent a8896c2f7f
commit c7d6fb9c4e

@ -2,6 +2,7 @@
export const DERIVED = 1 << 1;
export const EFFECT = 1 << 2;
export const RENDER_EFFECT = 1 << 3;
export const TEMPLATE_EFFECT = 1 << 26;
/**
* An effect that does not destroy its child effects when it reruns.
* Runs as part of render effects, i.e. not eagerly as part of tree traversal or effect flushing.

@ -100,6 +100,12 @@ export class Batch {
// for debugging. TODO remove once async is stable
id = uid++;
/**
* @type {Batch | null}
* If this batch is merged into another batch, successor points to the batch it was merged into.
*/
successor = null;
/**
* The current values of any sources that are updated in this batch
* They keys of this map are identical to `this.#previous`
@ -389,6 +395,11 @@ export class Batch {
}
flush() {
if (this.successor) {
this.successor.flush();
return;
}
var source_stacks = DEV ? new Set() : null;
try {
@ -554,6 +565,103 @@ export class Batch {
return (this.#deferred ??= deferred()).promise;
}
/**
* Ensure there is a current batch for scheduling work triggered by `reaction`.
* If both the current batch and the reaction batch are active, merge them.
* @param {Reaction} reaction
*/
static upsert(reaction) {
var reaction_batch = reaction.batch;
var has_reaction_batch = reaction_batch !== null && !reaction_batch.finished();
if (current_batch === null) {
if (has_reaction_batch) {
current_batch = reaction_batch;
return reaction_batch;
}
return Batch.ensure();
}
var batch = current_batch;
if (!has_reaction_batch || reaction_batch === batch) {
return batch;
}
// Fork batches are isolated speculative environments and should not entangle.
if (batch.is_fork || /** @type {Batch} */ (reaction_batch).is_fork) {
return batch;
}
Batch.merge(batch, /** @type {Batch} */ (reaction_batch));
current_batch = reaction_batch;
return reaction_batch;
}
/**
* Merge `from` into `to` and retire `from`.
* @param {Batch} from
* @param {Batch} to
*/
static merge(from, to) {
if (from === to) return;
for (const [source, value] of from.previous) {
if (!to.previous.has(source)) {
to.previous.set(source, value);
}
}
for (const [source, value] of from.current) {
to.current.set(source, value);
if (!to.is_fork) source.batch = to;
}
to.#pending += from.#pending;
to.#blocking_pending += from.#blocking_pending;
to.#roots.push(...from.#roots);
for (const e of from.#dirty_effects) to.#dirty_effects.add(e);
for (const e of from.#maybe_dirty_effects) to.#maybe_dirty_effects.add(e);
for (const fn of from.#commit_callbacks) to.#commit_callbacks.add(fn);
for (const fn of from.#discard_callbacks) to.#discard_callbacks.add(fn);
for (const [effect, tracked] of from.#skipped_branches) {
var existing = to.#skipped_branches.get(effect);
if (existing) {
existing.d.push(...tracked.d);
existing.m.push(...tracked.m);
} else {
to.#skipped_branches.set(effect, tracked);
}
}
if (from.#deferred !== null) {
var deferred = from.#deferred;
to.settled().then(deferred.resolve, deferred.reject);
}
from.current.clear();
from.previous.clear();
from.#roots = [];
from.#dirty_effects.clear();
from.#maybe_dirty_effects.clear();
from.#commit_callbacks.clear();
from.#discard_callbacks.clear();
from.#skipped_branches.clear();
from.#pending = 0;
from.#blocking_pending = 0;
from.#deferred = null;
from.#decrement_queued = false;
from.successor = to;
batches.delete(from);
batches.add(to);
}
static ensure() {
if (current_batch === null) {
const batch = (current_batch = new Batch());

@ -35,7 +35,8 @@ import {
ASYNC,
CONNECTED,
MANAGED_EFFECT,
DESTROYING
DESTROYING,
TEMPLATE_EFFECT
} from '#client/constants';
import * as e from '../errors.js';
import { DEV } from 'esm-env';
@ -383,7 +384,7 @@ export function render_effect(fn, flags = 0) {
*/
export function template_effect(fn, sync = [], async = [], blockers = []) {
flatten(blockers, sync, async, (values) => {
create_effect(RENDER_EFFECT, () => fn(...values.map(get)));
create_effect(RENDER_EFFECT | TEMPLATE_EFFECT, () => fn(...values.map(get)));
});
}

@ -27,7 +27,10 @@ import {
ROOT_EFFECT,
ASYNC,
WAS_MARKED,
CONNECTED
CONNECTED,
RENDER_EFFECT,
USER_EFFECT,
TEMPLATE_EFFECT
} from '#client/constants';
import * as e from '../errors.js';
import { legacy_mode_flag, tracing_mode_flag } from '../../flags/index.js';
@ -38,10 +41,8 @@ import { component_context, is_runes } from '../context.js';
import {
Batch,
batch_values,
current_batch,
eager_block_effects,
schedule_effect,
set_current_batch,
legacy_updates
} from './batch.js';
import { proxy } from '../proxy.js';
@ -342,11 +343,8 @@ function mark_reactions(signal, status, updated_during_traversal) {
for (var i = 0; i < length; i++) {
var reaction = reactions[i];
var flags = reaction.f;
var reaction_batch = reaction.batch;
if (current_batch === null && reaction_batch !== null && !reaction_batch.finished()) {
set_current_batch(reaction_batch);
}
reaction.batch = Batch.upsert(reaction);
// In legacy mode, skip the current effect to prevent infinite loops
if (!runes && reaction === active_effect) continue;

Loading…
Cancel
Save