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

@ -11,7 +11,6 @@ import {
ASYNC,
WAS_MARKED,
DESTROYED,
CLEAN,
REACTION_RAN,
INERT
} from '#client/constants';
@ -40,7 +39,7 @@ import { UNINITIALIZED } from '../../../constants.js';
import { batch_values, current_batch, previous_batch } from './batch.js';
import { increment_pending, unset_context } from './async.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
@ -421,21 +420,14 @@ export function update_derived(derived) {
}
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
// cleanup function, or it will cache a stale value
if (is_destroying_effect) {
return;
// cleanup function, or it will cache a stale value. deriveds
// without dependencies can always be marked clean
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);
// invalidate any world-local memoized values
if (batch_values !== null) {
batch_values.delete(derived);
current_batch?.fork_values?.delete(derived);
}
batch_values?.delete(derived);
current_batch?.fork_values?.delete(derived);
if ((flags & WAS_MARKED) === 0) {
// Only connected deriveds being executed outside the update cycle can be reliably unmarked right away

Loading…
Cancel
Save