batch-views
Simon Holthausen 14 hours ago
parent 0a1b1c264f
commit ab9863cebb
No known key found for this signature in database

@ -38,7 +38,7 @@ import { flush_eager_effects, old_values, set_eager_effects, source, update } fr
import { eager_effect, teardown, unlink_effect } from './effects.js';
import { defer_effect } from './utils.js';
import { UNINITIALIZED } from '../../../constants.js';
import { set_signal_status, update_derived_status } from './status.js';
import { set_signal_status } from './status.js';
import { invariant } from '../../shared/dev.js';
import { OBSOLETE } from './deriveds.js';
@ -1538,7 +1538,6 @@ export function fork(fn) {
if ((source.f & DERIVED) !== 0 && /** @type {BatchRecord} */ (entry).dv !== undefined) {
// an assigned derived, which holds its value until its dependencies change
promote_record(/** @type {Derived} */ (source), /** @type {BatchRecord} */ (entry));
update_derived_status(/** @type {Derived} */ (source));
}
}

@ -418,10 +418,6 @@ function adopt(derived, records) {
records.delete(derived);
promote_record(derived, record);
derived.v = record.v;
derived.wv = /** @type {number} */ (record.wv);
derived.f = record.e ? derived.f | ERROR_VALUE : derived.f & ~ERROR_VALUE;
update_derived_status(derived);
return true;
}
@ -437,11 +433,19 @@ function adopt(derived, records) {
*/
export function view_entry(derived) {
var entry = /** @type {Map<Value, any>} */ (batch_values).get(derived);
var batch = /** @type {Batch} */ (view_batch);
// either cached, or the derived was overridden via assignment in this batch
if (entry !== undefined) return entry;
// Cached — unless it is a value the derived was assigned in this batch, which holds as long as its
// dependencies keep the versions they had then (if the assignment is the latest value, the derived's
// global state is that value, see `internal_set`)
if (
entry !== undefined &&
(entry !== batch.current.get(derived) ||
(entry.dv !== undefined && is_current(entry.deps, entry.dv)))
) {
return entry;
}
var batch = /** @type {Batch} */ (view_batch);
var record = batch.record(derived);
if (record !== undefined && is_current(record.deps, record.dv)) {

@ -186,12 +186,22 @@ export function is_current(deps, dv) {
return true;
}
/**
* The run of `reaction` that applies in the active view of the world: the record of a block or async
* effect in the active batch, if it has one (see `update_record`), or else the reaction itself — whose
* `deps` and `dv` are its global state
* @param {Reaction} reaction
* @returns {Reaction | BatchRecord}
*/
function run_of(reaction) {
return ((reaction.f & (BLOCK_EFFECT | ASYNC)) !== 0 && view_batch?.record(reaction)) || reaction;
}
/**
* Determines whether a derived or effect is dirty.
* If it is MAYBE_DIRTY, will set the status to CLEAN.
* Deriveds are only checked like this in the real world — within the view of a batch,
* see `view_entry`. Effects are checked against their record in the active batch, if
* they have one (see `update_record`), or else their global state.
* see `view_entry`. Effects are checked against the run that applies (see `run_of`).
* @param {Reaction} reaction
* @returns {boolean}
*/
@ -203,17 +213,9 @@ export function is_dirty(reaction) {
}
if ((flags & MAYBE_DIRTY) !== 0) {
var deps = reaction.deps;
var dv = reaction.dv;
if ((flags & (BLOCK_EFFECT | ASYNC)) !== 0 && view_batch !== null) {
var record = view_batch.record(reaction);
if (record !== undefined) {
deps = record.deps;
dv = record.dv;
}
}
var run = run_of(reaction);
var deps = run.deps;
var dv = run.dv;
if (dv === null) return true;
@ -413,8 +415,8 @@ function update_dependencies(reaction, flags, value, error) {
length === deps?.length &&
(flags & (BLOCK_EFFECT | ASYNC)) === 0
) {
// A run saw everything up to its end — writes that happen during the run
// mark it dirty (unless it wrote them itself, which doesn't invalidate it)
// The run saw what its dependencies hold now — a write during the run after it read
// the value marks it dirty (see `Batch#schedule`), so it doesn't take effect here
for (var i = 0; i < length; i++) dv[i] = deps[i].wv;
if (dv.length !== length) dv.length = length;
@ -485,7 +487,7 @@ function update_record(reaction, flags, deps, length, value, error) {
i < skipped_deps
? /** @type {Value[]} */ (deps)[i]
: /** @type {Value[]} */ (new_deps)[i - skipped_deps];
dv[i] = version_of(dep);
dv[i] = seen_version(dep);
// Within a batch's view, a derived's run belongs to the batch unless everything it read is the latest
// value: the version it saw is the real-world one, and — for a derived — that one is up to date
@ -578,6 +580,20 @@ function update_record(reaction, flags, deps, length, value, error) {
return length;
}
/**
* The version of `signal` that the current run saw. Recording it never evaluates anything: a derived that
* isn't in the view's cache (anymore) was invalidated by a write during the run, or can't be cached —
* the run then doesn't count as having seen its current version, just like in the real world, where
* a derived that changes during the run gets a new version only once it is evaluated again
* @param {Value} signal
*/
function seen_version(signal) {
if (batch_values === null) return signal.wv;
var entry = batch_values.get(signal);
return entry !== undefined ? entry.wv : (signal.f & DERIVED) !== 0 ? -1 : signal.wv;
}
/**
* The record of a run of `derived` that belongs to `batch`. Its value keeps the version of the same value
* in the batch's view (if it held one before), or in the real world, so that reactions which saw it remain valid
@ -605,7 +621,7 @@ 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
* committed, which makes its results the real-world ones (for a derived, that includes its value)
* @param {Reaction} reaction
* @param {BatchRecord} record
*/
@ -628,6 +644,15 @@ export function promote_record(reaction, record) {
}
reaction.dv = record.dv;
if ((reaction.f & DERIVED) !== 0) {
var derived = /** @type {Derived} */ (reaction);
derived.v = record.v;
derived.wv = /** @type {number} */ (record.wv);
derived.f = record.e ? derived.f | ERROR_VALUE : derived.f & ~ERROR_VALUE;
update_derived_status(derived);
}
}
/**
@ -1012,10 +1037,9 @@ export function get(signal) {
* @param {number} version
*/
function record_late_read(reaction, signal, version) {
var record =
(reaction.f & (BLOCK_EFFECT | ASYNC)) !== 0 ? view_batch?.record(reaction) : undefined;
var deps = record === undefined ? /** @type {Value[]} */ (reaction.deps) : (record.deps ??= []);
var dv = record === undefined ? reaction.dv : record.dv;
var run = run_of(reaction);
var deps = /** @type {Value[]} */ (run.deps);
var dv = run.dv;
if (dv === null) return;

@ -0,0 +1,23 @@
import { tick } from 'svelte';
import { test } from '../../test';
// A value assigned to a derived holds until one of its dependencies changes — also in the
// view of a batch that is rendered while another batch is pending
export default test({
async test({ assert, target }) {
const [other, assign, resolve] = target.querySelectorAll('button');
resolve.click();
await tick();
// pending async work
other.click();
await tick();
assign.click();
await tick();
const [, p] = target.querySelectorAll('p');
assert.equal(p.textContent, '4');
}
});

@ -0,0 +1,26 @@
<script>
let count = $state(1);
let other = $state(0);
let d = $derived(count * 2);
let pending = [];
function wait(value) {
return new Promise((resolve) => pending.push(() => resolve(value)));
}
</script>
<button onclick={() => other++}>other</button>
<button
onclick={() => {
d = 100;
count++;
}}>assign then update</button
>
<button onclick={() => pending.shift()?.()}>resolve</button>
<svelte:boundary>
<p>{await wait(other)}</p>
{#snippet pending()}<p>loading</p>{/snippet}
</svelte:boundary>
<p>{d}</p>
Loading…
Cancel
Save