simplify/comments

batch-views
Simon Holthausen 9 hours ago
parent 508b1908bd
commit 6d3317f972
No known key found for this signature in database

@ -461,27 +461,38 @@ function holds(derived, holder, value) {
}
/**
* The general case of `update_dependencies`, which
* 1. decides where the run belongs: to the batch whose view of the world is active (as a record, see
* `Batch#records`), or to the real world (as the reaction's global state)
* 2. records the versions of the values the run read (`dv`)
* 3. updates the edges of the graph
* 4. stores the run where it belongs
* @param {Reaction} reaction
* @param {number} flags
* @param {Value[] | null} deps
* @param {number} length
* @param {any} value
* @param {boolean} error
* @param {number} flags the flags of the reaction before the run
* @param {Value[] | null} deps `reaction.deps`, the first `skipped_deps` of which the run read again (in the
* same order) — what else it read is in `new_deps`
* @param {number} length the number of values the run read
* @param {any} value the value of the run (only relevant for deriveds)
* @param {boolean} error whether the value is an error
*/
function update_record(reaction, flags, deps, length, value, error) {
// Block and async effects that run while a batch is processed produce a result for that batch (the
// branch it selected, the value an async expression resolved to for it), which it keeps until it
// commits. The first run of a block effect is the exception — it creates (and commits) its initial branch
// commits. The first run of a block effect is the exception — it creates (and commits) its initial
// branch. (Whether a derived's run belongs to a batch depends on what it read, see below)
var batch =
((flags & ASYNC) !== 0 ||
(flags & (BLOCK_EFFECT | REACTION_RAN)) === (BLOCK_EFFECT | REACTION_RAN)) &&
view_batch?.committing === false
? view_batch
: null;
// A global run in the real world updates the versions in place. Anything else needs a new array:
// a record must not share it with the global state, and a derived's run can turn out to belong
// to the batch halfway through the loop
var dv = batch === null && batch_values === null && reaction.dv !== null ? reaction.dv : [];
var i, dep;
// record the versions of what was read
for (i = 0; i < length; i++) {
dep =
i < skipped_deps
@ -502,8 +513,10 @@ function update_record(reaction, flags, deps, length, value, error) {
if (dv.length !== length) dv.length = length;
// The edges of the graph are a superset of the dependencies of every live record (so that changes to
// any of them reach the reaction): they are only pruned by a global run if no batch holds a record
// The edges — `reaction.deps`, and `reaction` in the `reactions` of each of them — must cover what every
// live run of the reaction read (its global run, and the records batches hold of it), so that a change to
// any of those values reaches it. So unless this is a global run and no batch holds a record, edges are
// only added (and pruned by a later global run). Otherwise they are updated exactly like on `main`
var keep = batch !== null;
var tracking = effect_tracking() && (reaction.f & CONNECTED) !== 0;
@ -515,8 +528,8 @@ function update_record(reaction, flags, deps, length, value, error) {
}
if (keep) {
read = deps?.slice(0, skipped_deps) ?? [];
deps ??= [];
read = deps === null ? [] : deps.slice(0, skipped_deps);
reaction.deps = deps ??= [];
if (new_deps !== null) {
for (dep of new_deps) {
@ -528,13 +541,9 @@ function update_record(reaction, flags, deps, length, value, error) {
}
}
}
// the global state corresponds to the start of `deps` (a record has its own copy of what it read)
reaction.deps =
batch !== null
? deps
: read.concat(deps.filter((d) => !includes.call(/** @type {Value[]} */ (read), d)));
} else if (new_deps !== null || (deps !== null && skipped_deps < deps.length)) {
// remove the edges to values the run didn't read again, and add those to values it read
// for the first time
remove_reactions(reaction, skipped_deps);
if (deps === null || (skipped_deps === 0 && new_deps !== null)) {
@ -556,6 +565,7 @@ function update_record(reaction, flags, deps, length, value, error) {
}
if (batch !== null) {
// (the record has its own copy of what the run read, `deps` are the edges)
(batch.records ??= new Map()).set(
reaction,
(flags & DERIVED) !== 0
@ -570,11 +580,17 @@ function update_record(reaction, flags, deps, length, value, error) {
: { deps: /** @type {Value[]} */ (read), dv }
);
} else {
if (dv !== reaction.dv) reaction.dv = dv;
if ((flags & DERIVED) !== 0) set_derived_value(/** @type {Derived} */ (reaction), value, error);
// (if edges were kept, `deps` is reordered so that it starts with what the run read)
set_global_run(reaction, read, dv);
if ((flags & DERIVED) !== 0) {
set_derived_value(/** @type {Derived} */ (reaction), value, error);
// a record of the reaction in the active batch would now be outdated
if ((flags & (BLOCK_EFFECT | ASYNC | DERIVED)) !== 0) view_batch?.records?.delete(reaction);
// A record of the derived in the active batch is outdated now. (Effects never have one at this point:
// their runs only become global outside of processing, on their first run, or once the batch commits,
// whose records are promoted by then)
view_batch?.records?.delete(reaction);
}
}
return length;
@ -620,23 +636,24 @@ function derived_record(derived, batch, deps, dv, v, e) {
}
/**
* Make the record a batch holds of `reaction` its global state, e.g. because the batch
* committed, which makes its results the real-world ones (for a derived, that includes its value)
* Make the run that read the values `read` (in this order, `null` if they already are `reaction.deps`)
* with the versions `dv` the global run of `reaction`
* @param {Reaction} reaction
* @param {BatchRecord} record
* @param {Value[] | null} read
* @param {number[]} dv
*/
export function promote_record(reaction, record) {
function set_global_run(reaction, read, dv) {
var deps = /** @type {Value[]} */ (reaction.deps);
var read = record.deps;
var i = 0;
if (read !== null) {
// The record's reads become the global run, so they must be the start of `deps` (which is what `dv`
// lines up with). But `deps` can't simply be replaced by them: it's also the list of the graph's edges
// (the values that have `reaction` among their `reactions`), which covers the runs that other batches
// hold, too (see `update_record`). So the other edges are kept, after the reads. The record's array
// is reused for this (only its first `dv.length` entries are ever checked). If the reads already are
// the start of `deps` (the common case), nothing changes
// The reads must be the start of `deps` (which is what `dv` lines up with). But `deps` can't simply be
// replaced by them: it's also the list of the graph's edges (the values that have `reaction` among their
// `reactions`), which covers the runs that batches hold, too (see `update_record`). So the other edges
// are kept, after the reads — `read` itself is extended to become the new `deps` (a promoted record can
// still be looked at afterwards, e.g. the entry of a derived assigned in a fork, but only its first
// `dv.length` entries are checked). If the reads already are the start of `deps` (the common case),
// nothing changes
while (i < read.length && deps[i] === read[i]) i++;
if (i < read.length) {
@ -648,7 +665,17 @@ export function promote_record(reaction, record) {
}
}
reaction.dv = record.dv;
reaction.dv = dv;
}
/**
* Make the record a batch holds of `reaction` its global state, e.g. because the batch
* committed, which makes its results the real-world ones (for a derived, that includes its value)
* @param {Reaction} reaction
* @param {BatchRecord} record
*/
export function promote_record(reaction, record) {
set_global_run(reaction, record.deps, record.dv);
if ((reaction.f & DERIVED) !== 0) {
var derived = /** @type {Derived} */ (reaction);

Loading…
Cancel
Save