entangle-batches-2
Simon Holthausen 2 months ago
parent 5c0e0dd47a
commit 546abea513
No known key found for this signature in database

@ -154,8 +154,7 @@ export class Batch {
#next = null;
/**
* Lazily initialised batches churn one-per-flush, so rarely-used
* collections (this and several below) are only allocated on first use
* Lazily initialised for perf reasons
* @type {Map<Effect, ReturnType<typeof deferred<any>> & { id: number }> | null}
*/
async_deriveds = null;
@ -177,13 +176,15 @@ export class Batch {
/**
* When the batch is committed (and the DOM is updated), we need to remove old branches
* and append new ones by calling the functions added inside (if/each/key/etc) blocks
* and append new ones by calling the functions added inside (if/each/key/etc) blocks.
* Lazily initialised for perf reasons.
* @type {Set<(batch: Batch) => void> | null}
*/
#commit_callbacks = null;
/**
* If a fork is discarded, we need to destroy any effects that are no longer needed
* If a fork is discarded, we need to destroy any effects that are no longer needed.
* Lazily initialised for perf reasons
* @type {Set<(batch: Batch) => void> | null}
*/
#discard_callbacks = null;
@ -194,7 +195,8 @@ export class Batch {
#pending = 0;
/**
* Async effects that are currently in flight, _not_ inside a pending boundary
* Async effects that are currently in flight, _not_ inside a pending boundary.
* Lazily initialised for perf reasons
* @type {Map<Effect, number> | null}
*/
#blocking_pending = null;
@ -220,13 +222,15 @@ export class Batch {
#scheduled = [];
/**
* Deferred effects (which run after async work has completed) that are DIRTY
* Deferred effects (which run after async work has completed) that are DIRTY.
* Lazily initialised for perf reasons
* @type {Set<Effect> | null}
*/
#dirty_effects = null;
/**
* Deferred effects that are MAYBE_DIRTY
* Deferred effects that are MAYBE_DIRTY.
* Lazily initialised for perf reasons
* @type {Set<Effect> | null}
*/
#maybe_dirty_effects = null;
@ -236,6 +240,7 @@ export class Batch {
* is committed we skip over these during `process`.
* The value contains child effects that were dirty/maybe_dirty before being reset,
* so they can be rescheduled if the branch survives.
* Lazily initialised for perf reasons.
* @type {Map<Effect, { d: Effect[], m: Effect[] }> | null}
*/
#skipped_branches = null;
@ -263,7 +268,8 @@ export class Batch {
* this map is the fork's own view of the affected part of the graph, and is
* discarded along with the fork. Committing writes the fork's sources
* through, after which deriveds recompute in the real world.
* `null` unless this batch is (or was) a fork
* `null` unless this batch is (or was) a fork.
* Lazily initialised for perf reasons
* @type {Map<Value, any> | null}
*/
fork_values = null;
@ -271,7 +277,8 @@ export class Batch {
/**
* Async and block effects that ran or were proven clean inside this fork.
* The version is that of the latest real-world execution when the effect was
* validated. Fork executions do not advance it, so forks remain independent
* validated. Fork executions do not advance it, so forks remain independent.
* Lazily initialised for perf reasons
* @type {Map<Effect, number> | null}
*/
fork_effects = null;
@ -279,7 +286,8 @@ export class Batch {
/**
* Reactions that observed the pre-write world of this batch (via
* `batch_values`) while it was pending. When this batch commits, they
* re-run with the real values
* re-run with the real values.
* Lazily initialised for perf reasons
* @type {Set<Reaction> | null}
*/
stale_readers = null;
@ -381,7 +389,7 @@ export class Batch {
* If this batch was merged into another one, get the surviving batch
* @returns {Batch}
*/
#resolved() {
resolved() {
/** @type {Batch} */
var batch = this;
while (batch.merged_into !== null) batch = batch.merged_into;
@ -398,7 +406,7 @@ export class Batch {
var waiter = owner.waiter;
if (waiter !== null) {
waiter = waiter.#resolved();
waiter = waiter.resolved();
if (waiter !== this && waiter.linked) {
this.#merge(waiter);
@ -421,7 +429,7 @@ export class Batch {
var waiter = this.waiter;
this.waiter = null;
if (waiter === null || !(waiter = waiter.#resolved()).linked) return;
if (waiter === null || !(waiter = waiter.resolved()).linked) return;
var waiting = /** @type {{ batches: Set<Batch>, reactions: Map<Reaction, Batch> }} */ (
waiter.waiting
@ -457,7 +465,7 @@ export class Batch {
var released = /** @type {Batch} */ (waiter);
queue_micro_task(() => {
var batch = released.#resolved();
var batch = released.resolved();
if (batch.linked && batch.waiting === null) {
batch.flush();
@ -488,11 +496,7 @@ export class Batch {
// template expression deriveds are leaves — they don't entangle
if ((reaction.f & TEMPLATE_EXPRESSION) !== 0) return false;
var owner = reaction.batch;
if (owner !== null) {
while (owner.merged_into !== null) owner = owner.merged_into;
}
var owner = reaction.batch && reaction.batch.resolved();
if (this.is_eager) {
// eager version bumps don't entangle — but an effect that belongs to
@ -560,17 +564,14 @@ export class Batch {
if ((effect.f & DESTROYED) !== 0) continue;
if (version !== (effect_versions.get(effect) ?? 0)) {
var owner = effect.batch;
while (owner !== null && owner.merged_into !== null) owner = owner.merged_into;
var owner = effect.batch && effect.batch.resolved();
if (owner !== null && owner !== this && owner.linked && !owner.is_fork) {
this.claim(effect);
}
continue;
} else {
this.claim(effect);
}
this.claim(effect);
}
this.fork_effects = null;
@ -1041,7 +1042,7 @@ export class Batch {
}
activate() {
current_batch = this.#resolved();
current_batch = this.resolved();
}
deactivate() {
@ -1117,9 +1118,7 @@ export class Batch {
}
if (fork.current.size === 0) {
const f = fork;
// In a microtask, because discard unlinks and so #next would always be null
queue_micro_task(() => f.discard());
fork.discard();
continue;
}
@ -1131,7 +1130,7 @@ export class Batch {
continue;
}
var owner = effect.batch && effect.batch.#resolved();
var owner = effect.batch && effect.batch.resolved();
if (owner !== this) continue;
fork.schedule(effect);
@ -1145,6 +1144,9 @@ export class Batch {
for (const [fork, effects] of forks) {
queue_micro_task(() => {
if (!fork.linked) return;
// TODO this can overfire. Ideally we could bump the version of the sources, then find the connection between the scheduled effects
// and those sources, only mark the path along them as (maybe)dirty, and then execute. That way they don't leave a trace behind
// and don't overfire. But it's probably a lot more tedious code for little gain in edge cases.
for (const effect of effects) set_signal_status(effect, DIRTY);
fork.flush();
});
@ -1179,7 +1181,7 @@ export class Batch {
* @param {Effect} effect
*/
increment(blocking, effect) {
var batch = this.merged_into === null ? this : this.#resolved();
var batch = this.resolved();
batch.#pending += 1;
@ -1196,7 +1198,7 @@ export class Batch {
* @param {Effect} effect
*/
decrement(blocking, effect) {
var batch = this.merged_into === null ? this : this.#resolved();
var batch = this.resolved();
batch.#pending -= 1;
@ -1227,7 +1229,7 @@ export class Batch {
* @param {Set<Effect> | null} maybe_dirty_effects
*/
transfer_effects(dirty_effects, maybe_dirty_effects) {
var batch = this.#resolved();
var batch = this.resolved();
batch.#dirty_effects = transfer_set(batch.#dirty_effects, dirty_effects);
batch.#maybe_dirty_effects = transfer_set(batch.#maybe_dirty_effects, maybe_dirty_effects);
}
@ -1243,7 +1245,7 @@ export class Batch {
}
settled() {
var batch = this.#resolved();
var batch = this.resolved();
return (batch.#deferred ??= deferred()).promise;
}
@ -1264,7 +1266,7 @@ export class Batch {
}
apply() {
var batch = this.#resolved();
var batch = this.resolved();
if (!async_mode_flag || (!batch.is_fork && batch.#prev === null && batch.#next === null)) {
batch_values = null;
@ -1530,7 +1532,7 @@ export function claimed_by_other(reaction) {
var owner = reaction.batch;
if (owner === null) return null;
while (owner.merged_into !== null) owner = owner.merged_into;
owner = owner.resolved();
reaction.batch = owner;
return owner.linked && owner !== batch_values_owner ? owner : null;
@ -1646,8 +1648,7 @@ function mark_committed_reactions(value, batch, marked, status) {
mark_committed_reactions(/** @type {Derived} */ (reaction), batch, marked, MAYBE_DIRTY);
} else if ((flags & (ASYNC | BLOCK_EFFECT)) !== 0) {
var effect = /** @type {Effect} */ (reaction);
var owner = effect.batch;
while (owner !== null && owner.merged_into !== null) owner = owner.merged_into;
var owner = effect.batch && effect.batch.resolved();
var fork_version = batch.fork_effects?.get(effect);
var validated =

Loading…
Cancel
Save