From b92bc88d96ec8f075d4e4743a56cc8ff7554cbbe Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 16 Oct 2026 20:52:33 -0400 Subject: [PATCH] note possible future simplification (simon, feel free to revert if this is actually nonsense) --- batch-views.md | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/batch-views.md b/batch-views.md index becce4883b..9399f1bee2 100644 --- a/batch-views.md +++ b/batch-views.md @@ -436,20 +436,21 @@ These are the places where two mechanisms cover the same ground, or one concept ## 16. What could be simplified, and at what cost -| Part | Could be removed or simplified if… | Cost / behaviour change | -| -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Exact versions (`dv`) | …views never went backwards, i.e. no time travel: process batches strictly in order, or always include earlier batches' writes. | Loses the core feature: a later batch can't render while an earlier one is pending without showing the earlier one's state. | -| Recording versions at end of run (relies on flags for writes during a run) | Could go back to recording at read time, in `get()`. | That would _remove_ the dependency on `schedule`'s `active_reaction` rule, but it costs on every read. Recording at the end is cheaper. | -| `#unprocessed` / status scoping | …statuses were stored per batch (e.g. a `Map` per batch), or every batch ran a full `#revalidate` on every process. | Per-batch statuses cost on the hot path. A full revalidation walks the graph for every process. | -| Alone → `MAYBE_DIRTY` in `schedule` | …async effects tolerated spurious restarts. | `async-stale-derived-2` fails; more restarted async work. | -| Block records when alone | …we knew before traversal that the batch will commit synchronously (no pending async work in it), or branch selection survived without records. | Today this is a small allocation per block re-run on the hot path. `async-state-new-branch-unrelated-update` shows why we can't just skip it. | -| `record_late_read` | …reads after `await` stopped being dependencies (dev already warns with `await_reactivity_loss`). | Breaking change for code that relies on it. | -| Latest rule ([§7.2](#72-latest-values)) | …deriveds' global state were updated eagerly, or every derived evaluation in a view were batch-local. | Eager updates cost on the hot path. Always-local means recomputing in the real world after each commit, and effects that saw the batch's versions re-run. | -| Derived records | …deriveds were simply re-evaluated in every view (cache per `activate` only). | Apps with pending async batches recompute deriveds on every process. With `adopt` gone too, forks redo work on commit. | -| `adopt` / fork commit keeping versions | …committing a fork were allowed to re-run its block/async effects. | Restarts async work that was preloaded in the fork, which defeats the point of forks. | -| Superset edges / `keep` | …we never pruned while any batch exists (simpler code). | More spurious `MAYBE_DIRTY` checks. Fewer branches in `update_record`, but it's off the hot path either way. | -| `own_result` capture when equal | — | Needed for the batch to own its async results. Otherwise other batches' undo, intersection, merge and commit logic would ignore them. | -| Derived records dropped on commit | Not a simplification but a possible optimisation: promote derived records that are still `is_current` in the real world on commit. | Would avoid re-evaluating those deriveds (and re-running effects that saw their versions) after a commit. | +| Part | Could be removed or simplified if… | Cost / behaviour change | +| -------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Exact versions (`dv`) | …views never went backwards, i.e. no time travel: process batches strictly in order, or always include earlier batches' writes. | Loses the core feature: a later batch can't render while an earlier one is pending without showing the earlier one's state. | +| Versions themselves (compare values instead) | …no signal used `safe_equals`, which treats an object as changed even when the same one is written again. Once non-runes mode is dropped (Svelte 6), stores are the only users. A store could instead drive a plain `===` value source plus a counter source that is incremented when it emits the same object again, and `$store` would read both. | Removes the version reuse rules ([§3.2](#32-the-same-value-has-the-same-version)) and the `-1` in `seen_version`, and gets 1 → 2 → 1 right (clean, where versions say dirty). But `dv` and records would keep the values they saw alive until the reaction re-runs (disconnected deriveds, paused branches and pending batches might not re-run for a long time). Errors would need an explicit "is an error" bit. Speed impact unknown (V8 stores small-integer arrays more compactly, and storing object references adds GC work). | +| Recording versions at end of run (relies on flags for writes during a run) | Could go back to recording at read time, in `get()`. | That would _remove_ the dependency on `schedule`'s `active_reaction` rule, but it costs on every read. Recording at the end is cheaper. | +| `#unprocessed` / status scoping | …statuses were stored per batch (e.g. a `Map` per batch), or every batch ran a full `#revalidate` on every process. | Per-batch statuses cost on the hot path. A full revalidation walks the graph for every process. | +| Alone → `MAYBE_DIRTY` in `schedule` | …async effects tolerated spurious restarts. | `async-stale-derived-2` fails; more restarted async work. | +| Block records when alone | …we knew before traversal that the batch will commit synchronously (no pending async work in it), or branch selection survived without records. | Today this is a small allocation per block re-run on the hot path. `async-state-new-branch-unrelated-update` shows why we can't just skip it. | +| `record_late_read` | …reads after `await` stopped being dependencies (dev already warns with `await_reactivity_loss`). | Breaking change for code that relies on it. | +| Latest rule ([§7.2](#72-latest-values)) | …deriveds' global state were updated eagerly, or every derived evaluation in a view were batch-local. | Eager updates cost on the hot path. Always-local means recomputing in the real world after each commit, and effects that saw the batch's versions re-run. | +| Derived records | …deriveds were simply re-evaluated in every view (cache per `activate` only). | Apps with pending async batches recompute deriveds on every process. With `adopt` gone too, forks redo work on commit. | +| `adopt` / fork commit keeping versions | …committing a fork were allowed to re-run its block/async effects. | Restarts async work that was preloaded in the fork, which defeats the point of forks. | +| Superset edges / `keep` | …we never pruned while any batch exists (simpler code). | More spurious `MAYBE_DIRTY` checks. Fewer branches in `update_record`, but it's off the hot path either way. | +| `own_result` capture when equal | — | Needed for the batch to own its async results. Otherwise other batches' undo, intersection, merge and commit logic would ignore them. | +| Derived records dropped on commit | Not a simplification but a possible optimisation: promote derived records that are still `is_current` in the real world on commit. | Would avoid re-evaluating those deriveds (and re-running effects that saw their versions) after a commit. | ## 17. Cost