consolidate (maybe_)dirty_effects and dirty_deriveds

async-another-try
Simon Holthausen 1 week 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 { BOUNDARY_EFFECT, EFFECT_PRESERVED, EFFECT_TRANSPARENT } from '#client/constants';
import { HYDRATION_START_ELSE, HYDRATION_START_FAILED } from '../../../../constants.js'; import { HYDRATION_START_ELSE, HYDRATION_START_FAILED } from '../../../../constants.js';
import { component_context, set_component_context } from '../../context.js'; import { component_context, set_component_context } from '../../context.js';
@ -101,14 +101,8 @@ export class Boundary {
#pending_count = 0; #pending_count = 0;
#pending_count_update_queued = false; #pending_count_update_queued = false;
/** @type {Set<Effect>} */ /** @type {Map<Reaction, number>} */
#dirty_effects = new Set(); #dirty_reactions = new Map();
/** @type {Set<Effect>} */
#maybe_dirty_effects = new Set();
/** @type {Set<Derived>} */
#dirty_deriveds = new Set();
/** /**
* A source containing the number of pending async deriveds/expressions. * 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 // any effects that were previously deferred should be transferred
// to the batch, which will flush in the next microtask // 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 * @param {Effect} effect
*/ */
defer_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 = []; #scheduled = [];
/** /**
* Deferred effects (which run after async work has completed) that are DIRTY * Deferred reactions and their status.
* @type {Set<Effect>} *
*/ * Leaf (i.e. not block/async) effects (dirty and maybe_dirty) are stored because we need
#dirty_effects = new Set(); * 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
* Deferred effects that are MAYBE_DIRTY * should execute might get executed in the meantime in another batch (they are lazy, so a DIRTY derived is
* @type {Set<Effect>} * 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
maybe_dirty_effects = new Set(); * incorrectly say "hey we don't need to rerun this" in the context of this batch.
* @type {Map<Reaction, number>}
/**
* 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}
*/ */
#dirty_deriveds = null; #dirty_reactions = new Map();
/** /**
* A map of branches that still exist, but will be destroyed when this batch * 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 // #is_deferred() is true, because traversing the tree could make
// an if block that contains the last blocking pending effect falsy, // an if block that contains the last blocking pending effect falsy,
// causing the block to no longer be deferred. // causing the block to no longer be deferred.
for (const e of this.#dirty_effects) { for (const [reaction, status] of this.#dirty_reactions) {
this.maybe_dirty_effects.delete(e); if ((reaction.f & DERIVED) !== 0) {
set_signal_status(e, DIRTY); set_signal_status(reaction, status);
this.schedule(e); } else if (status === DIRTY || (reaction.f & DIRTY) === 0) {
} set_signal_status(reaction, status);
this.schedule(/** @type {Effect} */ (reaction));
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);
} }
} }
@ -527,8 +509,7 @@ export class Batch {
} }
// clear effects. Those that are still needed will be rescheduled through unskipping the skipped branches. // clear effects. Those that are still needed will be rescheduled through unskipping the skipped branches.
this.#dirty_effects.clear(); this.#dirty_reactions.clear();
this.maybe_dirty_effects.clear();
this.apply(true); this.apply(true);
@ -694,12 +675,12 @@ export class Batch {
if ( if (
not_yet not_yet
? !this.seen_effects.has(effect) && ? !this.seen_effects.has(effect) && !this.#dirty_reactions.has(effect)
!this.#dirty_effects.has(effect) &&
!this.maybe_dirty_effects.has(effect)
: (flags & (ASYNC | BLOCK_EFFECT)) === 0 || this.seen_effects.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); set_signal_status(effect, status);
this.schedule(effect); this.schedule(effect);
marked = true; 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. // 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(); 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()); this.oncommit(() => batch.discard());
batch.#unlink(); batch.#unlink();
@ -789,12 +770,7 @@ export class Batch {
*/ */
#defer_effects(effects) { #defer_effects(effects) {
for (var i = 0; i < effects.length; i += 1) { for (var i = 0; i < effects.length; i += 1) {
defer_effect( defer_effect(effects[i], this.#dirty_reactions);
effects[i],
this.#dirty_effects,
this.maybe_dirty_effects,
(this.#dirty_deriveds ??= new Set())
);
} }
} }
@ -1053,32 +1029,29 @@ export class Batch {
} }
/** /**
* @param {Set<Effect>} dirty_effects * @param {Map<Reaction, number>} dirty_reactions
* @param {Set<Effect>} maybe_dirty_effects
* @param {Set<Derived> | null} dirty_deriveds
* @returns {void} * @returns {void}
*/ */
transfer_effects(dirty_effects, maybe_dirty_effects, dirty_deriveds) { transfer_reactions(dirty_reactions) {
if (this.merged_into) { 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) { for (const [reaction, status] of dirty_reactions) {
this.#dirty_effects.add(e); this.add_dirty_reaction(reaction, status);
} }
for (const e of maybe_dirty_effects) { dirty_reactions.clear();
this.maybe_dirty_effects.add(e); }
}
if (dirty_deriveds !== null) { /**
for (const d of dirty_deriveds) { * @param {Reaction} reaction
(this.#dirty_deriveds ??= new Set()).add(d); * @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 */ /** @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 { CLEAN, DERIVED, DIRTY, MAYBE_DIRTY } from '#client/constants';
import { set_signal_status } from './status.js'; import { set_signal_status } from './status.js';
/** /**
* @param {Effect} effect * @param {Effect} effect
* @param {Set<Effect>} dirty_effects * @param {Map<Reaction, number>} dirty_reactions
* @param {Set<Effect>} maybe_dirty_effects
* @param {Set<Derived>} dirty_deriveds
*/ */
export function defer_effect(effect, dirty_effects, maybe_dirty_effects, dirty_deriveds) { export function defer_effect(effect, dirty_reactions) {
if ((effect.f & DIRTY) !== 0) { if ((effect.f & DIRTY) !== 0) {
dirty_effects.add(effect); dirty_reactions.set(effect, DIRTY);
} else if ((effect.f & MAYBE_DIRTY) !== 0) { } else if ((effect.f & MAYBE_DIRTY) !== 0 && dirty_reactions.get(effect) !== DIRTY) {
maybe_dirty_effects.add(effect); dirty_reactions.set(effect, MAYBE_DIRTY);
} }
// mark as clean so they get scheduled if they depend on pending async state // 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; if (effect.deps === null) return;
for (const dep of effect.deps) { for (const dep of effect.deps) {
defer_derived(dep, dirty_deriveds); defer_derived(dep, dirty_reactions);
} }
} }
/** /**
* @param {Value} value * @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; if ((value.f & DERIVED) === 0 || (value.f & CLEAN) !== 0) return;
var derived = /** @type {Derived} */ (value); var derived = /** @type {Derived} */ (value);
if ((derived.f & DIRTY) !== 0) { if ((derived.f & DIRTY) !== 0) {
dirty_deriveds.add(derived); dirty_reactions.set(derived, DIRTY);
set_signal_status(derived, MAYBE_DIRTY); set_signal_status(derived, MAYBE_DIRTY);
} }
if (derived.deps === null) return; if (derived.deps === null) return;
for (const dep of derived.deps) { 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); var own = /** @type {Batch} */ (own_batch);
own.stale_effects.set(effect, write_version); own.stale_effects.set(effect, write_version);
for (var batch = own.next; batch !== null; batch = batch.next) { 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