entangle-batches-2
Simon Holthausen 2 months ago
parent 75d1e0aef4
commit 59345a2f58
No known key found for this signature in database

@ -115,6 +115,25 @@ var source_stacks = new Set();
let uid = 1; let uid = 1;
/**
* @template T
* @param {Set<T> | null} target
* @param {Set<T> | null} source
* @returns {Set<T> | null}
*/
function transfer_set(target, source) {
if (source === null || source.size === 0) return target;
target ??= new Set();
for (const value of source) {
target.add(value);
}
source.clear();
return target;
}
export class Batch { export class Batch {
id = uid++; id = uid++;
@ -284,7 +303,7 @@ export class Batch {
} }
#is_deferred() { #is_deferred() {
if (this.waiting !== null && this.waiting.batches.size > 0) return true; if (this.waiting !== null) return true;
if (this.is_fork) return true; if (this.is_fork) return true;
if (this.#blocking_pending === null) return false; if (this.#blocking_pending === null) return false;
@ -391,34 +410,35 @@ export class Batch {
if (waiter === null || !(waiter = waiter.#resolved()).linked) return; if (waiter === null || !(waiter = waiter.#resolved()).linked) return;
waiter.waiting?.batches.delete(this); var waiting = /** @type {{ batches: Set<Batch>, reactions: Map<Reaction, Batch> }} */ (
waiter.waiting
);
waiting.batches.delete(this);
if (waiter.waiting !== null) { for (const [reaction, owner] of waiting.reactions) {
for (const [reaction, owner] of waiter.waiting.reactions) { if (owner !== this) continue;
if (owner !== this) continue;
waiter.waiting.reactions.delete(reaction); waiting.reactions.delete(reaction);
if ((reaction.f & DESTROYED) !== 0) continue; if ((reaction.f & DESTROYED) !== 0) continue;
reaction.batch = waiter; reaction.batch = waiter;
if ((reaction.f & DERIVED) !== 0 && (reaction.f & DIRTY) === 0) { if ((reaction.f & DERIVED) !== 0 && (reaction.f & DIRTY) === 0) {
set_signal_status(reaction, MAYBE_DIRTY); set_signal_status(reaction, MAYBE_DIRTY);
} else if ((reaction.f & DERIVED) === 0) { } else if ((reaction.f & DERIVED) === 0) {
var effect = /** @type {Effect} */ (reaction); var effect = /** @type {Effect} */ (reaction);
if (waiter.#dirty_effects?.delete(effect)) { if (waiter.#dirty_effects?.delete(effect)) {
set_signal_status(effect, DIRTY); set_signal_status(effect, DIRTY);
waiter.schedule(effect); waiter.schedule(effect);
} else if (waiter.#maybe_dirty_effects?.delete(effect)) { } else if (waiter.#maybe_dirty_effects?.delete(effect)) {
set_signal_status(effect, MAYBE_DIRTY); set_signal_status(effect, MAYBE_DIRTY);
waiter.schedule(effect); waiter.schedule(effect);
}
} }
} }
} }
if (waiter.waiting !== null && waiter.waiting.batches.size > 0) return; if (waiting.batches.size > 0) return;
waiter.waiting = null; waiter.waiting = null;
var released = /** @type {Batch} */ (waiter); var released = /** @type {Batch} */ (waiter);
@ -426,7 +446,7 @@ export class Batch {
queue_micro_task(() => { queue_micro_task(() => {
var batch = released.#resolved(); var batch = released.#resolved();
if (batch.linked && (batch.waiting === null || batch.waiting.batches.size === 0)) { if (batch.linked && batch.waiting === null) {
batch.flush(); batch.flush();
} }
}); });
@ -479,7 +499,7 @@ export class Batch {
} }
if (owner !== null && owner !== this && owner.linked && !owner.is_fork) { if (owner !== null && owner !== this && owner.linked && !owner.is_fork) {
if (owner.waiting !== null && owner.waiting.batches.size > 0) { if (owner.waiting !== null) {
this.#merge(owner); this.#merge(owner);
reaction.batch = this; reaction.batch = this;
return false; return false;
@ -595,14 +615,8 @@ export class Batch {
} }
other.#scheduled = []; other.#scheduled = [];
if (other.stale_readers !== null) { this.stale_readers = transfer_set(this.stale_readers, other.stale_readers);
this.stale_readers ??= new Set(); other.stale_readers = null;
for (const reader of other.stale_readers) {
this.stale_readers.add(reader);
}
other.stale_readers = null;
}
if (other.waiting !== null) { if (other.waiting !== null) {
this.waiting ??= { batches: new Set(), reactions: new Map() }; this.waiting ??= { batches: new Set(), reactions: new Map() };
@ -687,7 +701,7 @@ export class Batch {
#process() { #process() {
this.#started = true; this.#started = true;
if (this.waiting !== null && this.waiting.batches.size > 0) return; if (this.waiting !== null) return;
// this batch may be re-processed (e.g. when effects are scheduled during // this batch may be re-processed (e.g. when effects are scheduled during
// its effect-flush phase), in which case async work could still be // its effect-flush phase), in which case async work could still be
@ -830,11 +844,8 @@ export class Batch {
this.#commit(); this.#commit();
// #commit may have scheduled stale readers into a new batch // #commit may have scheduled stale readers into a new batch
if (next_batch === null) { next_batch ??= /** @type {Batch | null} */ (/** @type {unknown} */ (current_batch));
next_batch = /** @type {Batch | null} */ (/** @type {unknown} */ (current_batch)); current_batch = next_batch;
} else {
current_batch = next_batch;
}
} }
} }
@ -1050,18 +1061,15 @@ export class Batch {
* @param {Effect} effect * @param {Effect} effect
*/ */
increment(blocking, effect) { increment(blocking, effect) {
if (this.merged_into !== null) { var batch = this.merged_into === null ? this : this.#resolved();
this.#resolved().increment(blocking, effect);
return;
}
this.#pending += 1; batch.#pending += 1;
if (blocking) { if (blocking) {
this.#blocking_pending ??= new Map(); batch.#blocking_pending ??= new Map();
let blocking_pending_count = this.#blocking_pending.get(effect) ?? 0; let blocking_pending_count = batch.#blocking_pending.get(effect) ?? 0;
this.#blocking_pending.set(effect, blocking_pending_count + 1); batch.#blocking_pending.set(effect, blocking_pending_count + 1);
} }
} }
@ -1070,31 +1078,28 @@ export class Batch {
* @param {Effect} effect * @param {Effect} effect
*/ */
decrement(blocking, effect) { decrement(blocking, effect) {
if (this.merged_into !== null) { var batch = this.merged_into === null ? this : this.#resolved();
this.#resolved().decrement(blocking, effect);
return;
}
this.#pending -= 1; batch.#pending -= 1;
if (blocking && this.#blocking_pending !== null) { if (blocking && batch.#blocking_pending !== null) {
let blocking_pending_count = this.#blocking_pending.get(effect) ?? 0; let blocking_pending_count = batch.#blocking_pending.get(effect) ?? 0;
if (blocking_pending_count === 1) { if (blocking_pending_count === 1) {
this.#blocking_pending.delete(effect); batch.#blocking_pending.delete(effect);
} else { } else {
this.#blocking_pending.set(effect, blocking_pending_count - 1); batch.#blocking_pending.set(effect, blocking_pending_count - 1);
} }
} }
if (this.#decrement_queued) return; if (batch.#decrement_queued) return;
this.#decrement_queued = true; batch.#decrement_queued = true;
queue_micro_task(() => { queue_micro_task(() => {
this.#decrement_queued = false; batch.#decrement_queued = false;
if (this.linked) { if (batch.linked) {
this.flush(); batch.flush();
} }
}); });
} }
@ -1105,26 +1110,8 @@ export class Batch {
*/ */
transfer_effects(dirty_effects, 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);
if (dirty_effects !== null && dirty_effects.size > 0) { batch.#maybe_dirty_effects = transfer_set(batch.#maybe_dirty_effects, maybe_dirty_effects);
batch.#dirty_effects ??= new Set();
for (const e of dirty_effects) {
batch.#dirty_effects.add(e);
}
dirty_effects.clear();
}
if (maybe_dirty_effects !== null && maybe_dirty_effects.size > 0) {
batch.#maybe_dirty_effects ??= new Set();
for (const e of maybe_dirty_effects) {
batch.#maybe_dirty_effects.add(e);
}
maybe_dirty_effects.clear();
}
} }
/** @param {(batch: Batch) => void} fn */ /** @param {(batch: Batch) => void} fn */

@ -11,7 +11,6 @@ import {
ASYNC, ASYNC,
WAS_MARKED, WAS_MARKED,
DESTROYED, DESTROYED,
CLEAN,
REACTION_RAN, REACTION_RAN,
INERT INERT
} from '#client/constants'; } from '#client/constants';
@ -40,7 +39,7 @@ import { UNINITIALIZED } from '../../../constants.js';
import { batch_values, current_batch, previous_batch } from './batch.js'; import { batch_values, current_batch, previous_batch } from './batch.js';
import { increment_pending, unset_context } from './async.js'; import { increment_pending, unset_context } from './async.js';
import { deferred, includes, noop } from '../../shared/utils.js'; import { deferred, includes, noop } from '../../shared/utils.js';
import { set_signal_status, update_derived_status } from './status.js'; import { update_derived_status } from './status.js';
/** /**
* This allows us to track 'reactivity loss' that occurs when signals * This allows us to track 'reactivity loss' that occurs when signals
@ -421,21 +420,14 @@ export function update_derived(derived) {
} }
derived.v = value; derived.v = value;
// deriveds without dependencies should never be recomputed
if (derived.deps === null) {
set_signal_status(derived, CLEAN);
return;
}
} }
// don't mark derived clean if we're reading it inside a // don't mark derived clean if we're reading it inside a
// cleanup function, or it will cache a stale value // cleanup function, or it will cache a stale value. deriveds
if (is_destroying_effect) { // without dependencies can always be marked clean
return; if (!is_destroying_effect || derived.deps === null) {
update_derived_status(derived);
} }
update_derived_status(derived);
} }
/** /**

@ -369,10 +369,8 @@ export function mark_reactions(signal, status, updated_during_traversal) {
current_batch?.claim(derived); current_batch?.claim(derived);
// invalidate any world-local memoized values // invalidate any world-local memoized values
if (batch_values !== null) { batch_values?.delete(derived);
batch_values.delete(derived); current_batch?.fork_values?.delete(derived);
current_batch?.fork_values?.delete(derived);
}
if ((flags & WAS_MARKED) === 0) { if ((flags & WAS_MARKED) === 0) {
// Only connected deriveds being executed outside the update cycle can be reliably unmarked right away // Only connected deriveds being executed outside the update cycle can be reliably unmarked right away

Loading…
Cancel
Save