consolidate (maybe_)dirty_effects and dirty_deriveds

async-another-try
Simon Holthausen 4 days ago
parent 4d8f4410f7
commit 87a061a73f
No known key found for this signature in database

@ -1,4 +1,4 @@
/** @import { Derived, Effect, Source, TemplateNode, } from '#client' */
/** @import { Effect, Reaction, Source, TemplateNode, } from '#client' */
import { BOUNDARY_EFFECT, EFFECT_PRESERVED, EFFECT_TRANSPARENT } from '#client/constants';
import { HYDRATION_START_ELSE, HYDRATION_START_FAILED } from '../../../../constants.js';
import { component_context, set_component_context } from '../../context.js';
@ -101,14 +101,8 @@ export class Boundary {
#pending_count = 0;
#pending_count_update_queued = false;
/** @type {Set<Effect>} */
#dirty_effects = new Set();
/** @type {Set<Effect>} */
#maybe_dirty_effects = new Set();
/** @type {Set<Derived>} */
#dirty_deriveds = new Set();
/** @type {Map<Reaction, number>} */
#dirty_reactions = new Map();
/**
* A source containing the number of pending async deriveds/expressions.
@ -338,7 +332,7 @@ export class Boundary {
// any effects that were previously deferred should be transferred
// to the batch, which will flush in the next microtask
batch.transfer_effects(this.#dirty_effects, this.#maybe_dirty_effects, this.#dirty_deriveds);
batch.transfer_reactions(this.#dirty_reactions);
}
/**
@ -346,7 +340,7 @@ export class Boundary {
* @param {Effect} effect
*/
defer_effect(effect) {
defer_effect(effect, this.#dirty_effects, this.#maybe_dirty_effects, this.#dirty_deriveds);
defer_effect(effect, this.#dirty_reactions);
}
/**

@ -242,27 +242,19 @@ export class Batch {
#scheduled = [];
/**
* Deferred effects (which run after async work has completed) that are DIRTY
* @type {Set<Effect>}
*/
#dirty_effects = new Set();
/**
* Deferred effects that are MAYBE_DIRTY
* @type {Set<Effect>}
*/
maybe_dirty_effects = new Set();
/**
* Deferred deriveds that are DIRTY. We need to store these because a derived that definitely should execute
* might get executed in the meantime in another batch (they are lazy, so a DIRTY derived is not guaranteed
* to run immediately). Relying on wv_values is insufficient because if this derived has stale dependencies
* in this batch but is executed with latest dependencies elsewhere, the wv is bumped and would incorrectly
* say "hey we don't need to rerun this" in the context of this batch.
* Lazily initialized for performance reasons.
* @type {Set<Derived> | null}
* Deferred reactions and their status.
*
* Leaf (i.e. not block/async) effects (dirty and maybe_dirty) are stored because we need
* to reset their status when a batch becomes pending, to not pollute other batches.
*
* Dirty deriveds (but not maybe_dirty deriveds) are stored because a derived that definitely
* should execute might get executed in the meantime in another batch (they are lazy, so a DIRTY derived is
* not guaranteed to run immediately). Relying on wv_values is insufficient because if this derived has stale
* dependencies in this batch but is executed with latest dependencies elsewhere, the wv is bumped and would
* incorrectly say "hey we don't need to rerun this" in the context of this batch.
* @type {Map<Reaction, number>}
*/
#dirty_deriveds = null;
#dirty_reactions = new Map();
/**
* A map of branches that still exist, but will be destroyed when this batch
@ -429,22 +421,12 @@ export class Batch {
// #is_deferred() is true, because traversing the tree could make
// an if block that contains the last blocking pending effect falsy,
// causing the block to no longer be deferred.
for (const e of this.#dirty_effects) {
this.maybe_dirty_effects.delete(e);
set_signal_status(e, DIRTY);
this.schedule(e);
}
for (const e of this.maybe_dirty_effects) {
if ((e.f & DIRTY) === 0) {
set_signal_status(e, MAYBE_DIRTY);
this.schedule(e);
}
}
if (this.#dirty_deriveds !== null) {
for (const d of this.#dirty_deriveds) {
set_signal_status(d, DIRTY);
for (const [reaction, status] of this.#dirty_reactions) {
if ((reaction.f & DERIVED) !== 0) {
set_signal_status(reaction, status);
} else if (status === DIRTY || (reaction.f & DIRTY) === 0) {
set_signal_status(reaction, status);
this.schedule(/** @type {Effect} */ (reaction));
}
}
@ -527,8 +509,7 @@ export class Batch {
}
// clear effects. Those that are still needed will be rescheduled through unskipping the skipped branches.
this.#dirty_effects.clear();
this.maybe_dirty_effects.clear();
this.#dirty_reactions.clear();
this.apply(true);
@ -694,12 +675,12 @@ export class Batch {
if (
not_yet
? !this.seen_effects.has(effect) &&
!this.#dirty_effects.has(effect) &&
!this.maybe_dirty_effects.has(effect)
? !this.seen_effects.has(effect) && !this.#dirty_reactions.has(effect)
: (flags & (ASYNC | BLOCK_EFFECT)) === 0 || this.seen_effects.has(effect)
) {
this.maybe_dirty_effects.delete(effect);
if (this.#dirty_reactions.get(effect) === MAYBE_DIRTY) {
this.#dirty_reactions.delete(effect);
}
set_signal_status(effect, status);
this.schedule(effect);
marked = true;
@ -774,7 +755,7 @@ export class Batch {
// This can happen when batch Y merged into X and Y has a pending boundary and therefore still-pending async deriveds inside.
batch.async_deriveds.clear();
this.transfer_effects(batch.#dirty_effects, batch.maybe_dirty_effects, batch.#dirty_deriveds);
this.transfer_reactions(batch.#dirty_reactions);
this.oncommit(() => batch.discard());
batch.#unlink();
@ -789,12 +770,7 @@ export class Batch {
*/
#defer_effects(effects) {
for (var i = 0; i < effects.length; i += 1) {
defer_effect(
effects[i],
this.#dirty_effects,
this.maybe_dirty_effects,
(this.#dirty_deriveds ??= new Set())
);
defer_effect(effects[i], this.#dirty_reactions);
}
}
@ -1053,32 +1029,29 @@ export class Batch {
}
/**
* @param {Set<Effect>} dirty_effects
* @param {Set<Effect>} maybe_dirty_effects
* @param {Set<Derived> | null} dirty_deriveds
* @param {Map<Reaction, number>} dirty_reactions
* @returns {void}
*/
transfer_effects(dirty_effects, maybe_dirty_effects, dirty_deriveds) {
transfer_reactions(dirty_reactions) {
if (this.merged_into) {
return this.merged_into.transfer_effects(dirty_effects, maybe_dirty_effects, dirty_deriveds);
return this.merged_into.transfer_reactions(dirty_reactions);
}
for (const e of dirty_effects) {
this.#dirty_effects.add(e);
for (const [reaction, status] of dirty_reactions) {
this.add_dirty_reaction(reaction, status);
}
for (const e of maybe_dirty_effects) {
this.maybe_dirty_effects.add(e);
}
dirty_reactions.clear();
}
if (dirty_deriveds !== null) {
for (const d of dirty_deriveds) {
(this.#dirty_deriveds ??= new Set()).add(d);
}
/**
* @param {Reaction} reaction
* @param {number} status
*/
add_dirty_reaction(reaction, status) {
if (status === DIRTY || this.#dirty_reactions.get(reaction) !== DIRTY) {
this.#dirty_reactions.set(reaction, status);
}
dirty_effects.clear();
maybe_dirty_effects.clear();
}
/** @param {(batch: Batch) => void} fn */

@ -1,18 +1,16 @@
/** @import { Derived, Effect, Value } from '#client' */
/** @import { Derived, Effect, Reaction, Value } from '#client' */
import { CLEAN, DERIVED, DIRTY, MAYBE_DIRTY } from '#client/constants';
import { set_signal_status } from './status.js';
/**
* @param {Effect} effect
* @param {Set<Effect>} dirty_effects
* @param {Set<Effect>} maybe_dirty_effects
* @param {Set<Derived>} dirty_deriveds
* @param {Map<Reaction, number>} dirty_reactions
*/
export function defer_effect(effect, dirty_effects, maybe_dirty_effects, dirty_deriveds) {
export function defer_effect(effect, dirty_reactions) {
if ((effect.f & DIRTY) !== 0) {
dirty_effects.add(effect);
} else if ((effect.f & MAYBE_DIRTY) !== 0) {
maybe_dirty_effects.add(effect);
dirty_reactions.set(effect, DIRTY);
} else if ((effect.f & MAYBE_DIRTY) !== 0 && dirty_reactions.get(effect) !== DIRTY) {
dirty_reactions.set(effect, MAYBE_DIRTY);
}
// mark as clean so they get scheduled if they depend on pending async state
@ -21,27 +19,27 @@ export function defer_effect(effect, dirty_effects, maybe_dirty_effects, dirty_d
if (effect.deps === null) return;
for (const dep of effect.deps) {
defer_derived(dep, dirty_deriveds);
defer_derived(dep, dirty_reactions);
}
}
/**
* @param {Value} value
* @param {Set<Derived>} dirty_deriveds
* @param {Map<Reaction, number>} dirty_reactions
*/
function defer_derived(value, dirty_deriveds) {
function defer_derived(value, dirty_reactions) {
if ((value.f & DERIVED) === 0 || (value.f & CLEAN) !== 0) return;
var derived = /** @type {Derived} */ (value);
if ((derived.f & DIRTY) !== 0) {
dirty_deriveds.add(derived);
dirty_reactions.set(derived, DIRTY);
set_signal_status(derived, MAYBE_DIRTY);
}
if (derived.deps === null) return;
for (const dep of derived.deps) {
defer_derived(dep, dirty_deriveds);
defer_derived(dep, dirty_reactions);
}
}

@ -516,7 +516,7 @@ export function update_effect(effect) {
var own = /** @type {Batch} */ (own_batch);
own.stale_effects.set(effect, write_version);
for (var batch = own.next; batch !== null; batch = batch.next) {
batch.maybe_dirty_effects.add(effect);
batch.add_dirty_reaction(effect, MAYBE_DIRTY);
}
}

Loading…
Cancel
Save