simplify/consolidate fork commit

async-another-try-pt-2
Simon Holthausen 5 days ago
parent 9d9647dd92
commit 5dfac3270b
No known key found for this signature in database

@ -35,10 +35,10 @@ import { flush_tasks, queue_micro_task } from '../dom/task.js';
import { DEV } from 'esm-env'; import { DEV } from 'esm-env';
import { invoke_error_boundary } from '../error-handling.js'; import { invoke_error_boundary } from '../error-handling.js';
import { import {
eager_effects,
flush_eager_effects, flush_eager_effects,
invalidate, invalidate,
old_values, old_values,
set_eager_effects,
source, source,
update update
} from './sources.js'; } from './sources.js';
@ -753,7 +753,11 @@ export class Batch {
} else { } else {
var effect = /** @type {Effect} */ (reaction); var effect = /** @type {Effect} */ (reaction);
if ( if ((flags & EAGER_EFFECT) !== 0) {
// Like in `mark_reactions`, these aren't scheduled but run right away (`flush_eager_effects`).
// For revalidations, they already did as part of the real world's write that caused it.
if (not_yet) eager_effects.add(effect);
} else if (
not_yet not_yet
? !this.seen_effects?.has(effect) && !this.#dirty_reactions.has(effect) ? !this.seen_effects?.has(effect) && !this.#dirty_reactions.has(effect)
: (flags & (ASYNC | BLOCK_EFFECT)) === 0 || this.seen_effects?.has(effect) : (flags & (ASYNC | BLOCK_EFFECT)) === 0 || this.seen_effects?.has(effect)
@ -1428,28 +1432,6 @@ function flush_queued_effects(effects) {
eager_block_effects = null; eager_block_effects = null;
} }
/**
* When committing a fork, we need to trigger eager effects so that
* any `$state.eager(...)` expressions update immediately. This
* function allows us to discover them
* @param {Value} value
* @param {Set<Effect>} effects
*/
function mark_eager_effects(value, effects) {
if (value.reactions === null) return;
for (const reaction of value.reactions) {
const flags = reaction.f;
if ((flags & DERIVED) !== 0) {
mark_eager_effects(/** @type {Derived} */ (reaction), effects);
} else if ((flags & EAGER_EFFECT) !== 0) {
set_signal_status(reaction, DIRTY);
effects.add(/** @type {Effect} */ (reaction));
}
}
}
/** /**
* @param {Effect} effect * @param {Effect} effect
* @returns {void} * @returns {void}
@ -1645,52 +1627,29 @@ export function fork(fn) {
batch.is_fork = false; batch.is_fork = false;
batch.enqueue(); batch.enqueue();
current_batch?.deactivate(); // write as the real world, not within some other batch's view
// Apply changes and update write versions so deriveds see the change. Everything still
// in `batch.current` at this point is the latest value: sources that the real world has // Committing means that the batch writes its values to the real world, just like a real
// written to in the meantime were removed from the fork via `overtake`, while // batch would (which also tells the other forks about it). Everything still in `current` at
// async results are kept up to date by revalidating their producers when inputs change. // this point is the latest value: sources that the real world has written to in the meantime
// We use fresh versions rather than the fork-time `content.wv`, because the real world // were removed from the fork via `overtake`, while async results are kept up to date by
// may have run reactions since then whose versions would otherwise outrank them. // revalidating their producers when inputs change. Capturing them anew gives them fresh write
// versions, because the real world may have run reactions since that would otherwise outrank them.
for (var [source, content] of batch.current) { for (var [source, content] of batch.current) {
var changed = source.v !== content.v; batch.capture(source, content.v, content.is_derived);
source.v = content.v;
if (!content.is_derived) {
content.wv = source.wv = increment_write_version();
// dirty those effects the fork did not see yet, e.g. because a later batch created new branches // dirty those effects the fork did not see yet, e.g. because a later batch created new branches
batch.mark(source, MAYBE_DIRTY, true); if (!content.is_derived) batch.mark(source, MAYBE_DIRTY, true);
} else if (changed) {
// A derived that was evaluated inside the fork: bump its version too, so that reactions
// which read the (then still old) real value _after_ the fork evaluated it — and which are
// therefore not in `stale_effects` — see a newer dependency version and re-run.
content.wv = source.wv = increment_write_version();
}
} }
// trigger any `$state.eager(...)` expressions with the new state
flush_eager_effects();
// All the block/async effects the fork executed are now guaranteed to be up to date // All the block/async effects the fork executed are now guaranteed to be up to date
for (const effect of batch.stale_effects.keys()) { for (const effect of batch.stale_effects.keys()) {
effect.wv = write_version; effect.wv = write_version;
} }
batch.stale_effects.clear(); batch.stale_effects.clear();
// trigger any `$state.eager(...)` expressions with the new state.
// eager effects don't get scheduled like other effects, so we
// can't just encounter them during traversal, we need to
// proactively flush them
// TODO maybe there's a better implementation?
flushSync(() => {
/** @type {Set<Effect>} */
var eager_effects = new Set();
for (var source of batch.current.keys()) {
mark_eager_effects(source, eager_effects);
}
set_eager_effects(eager_effects);
flush_eager_effects();
});
// Promote fork-only branches to the real world // Promote fork-only branches to the real world
for (const e of batch.unskipped_branches) { for (const e of batch.unskipped_branches) {
if (e.f & FORK_ONLY_BRANCH) { if (e.f & FORK_ONLY_BRANCH) {
@ -1699,18 +1658,6 @@ export function fork(fn) {
} }
batch.flush(); batch.flush();
// Committing means the real world writes the fork's values, so tell the other forks about
// it like `#capture` does. Batches that were created while flushing (e.g. by an effect
// writing to state) are real batches, and must not be treated like that.
for (let b = first_batch; b !== null; b = b.next) {
if (!b.is_fork) continue;
for (const [source, current] of batch.current) {
b.overtake(source, current.is_derived, current.v);
}
}
await settled; await settled;
}, },
discard: () => { discard: () => {

Loading…
Cancel
Save