@solidjs/signals 2.0.0-rc.13 → 2.0.0-rc.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dev-shared.js +2000 -3768
- package/dist/dev.attribution.js +132 -79
- package/dist/dev.js +7226 -8488
- package/dist/observe/affects.js +125 -104
- package/dist/observe/boundaries.js +458 -665
- package/dist/observe/core/action.js +63 -66
- package/dist/observe/core/async.js +240 -383
- package/dist/observe/core/attribution-queries.js +1 -1
- package/dist/observe/core/attribution.js +240 -183
- package/dist/observe/core/constants.js +115 -187
- package/dist/observe/core/context.js +3 -3
- package/dist/observe/core/core.js +840 -1438
- package/dist/observe/core/dev.js +3 -3
- package/dist/observe/core/effect.js +75 -86
- package/dist/observe/core/external.js +5 -5
- package/dist/observe/core/graph.js +61 -70
- package/dist/observe/core/heap.js +75 -98
- package/dist/observe/core/invariants.js +1 -34
- package/dist/observe/core/lanes.js +704 -181
- package/dist/observe/core/owner.js +80 -101
- package/dist/observe/core/scheduler.js +708 -1351
- package/dist/observe/core/verdict.js +302 -621
- package/dist/observe/flatten.js +79 -0
- package/dist/observe/index.js +15 -13
- package/dist/observe/map.js +232 -235
- package/dist/observe/reveal.js +212 -0
- package/dist/observe/signals.js +133 -215
- package/dist/observe/store/affects.js +135 -0
- package/dist/observe/store/index.js +29 -24
- package/dist/observe/store/optimistic.js +538 -0
- package/dist/observe/store/projection.js +302 -0
- package/dist/observe/store/reconcile.js +257 -0
- package/dist/observe/store/store.js +1945 -277
- package/dist/observe/store/storePath.js +1 -1
- package/dist/{prod/store/next → observe/store}/target.js +17 -18
- package/dist/observe/store/types.js +73 -0
- package/dist/observe/store/utils.js +1 -13
- package/dist/prod/affects.js +127 -106
- package/dist/prod/boundaries.js +442 -654
- package/dist/prod/core/action.js +71 -74
- package/dist/prod/core/async.js +307 -442
- package/dist/prod/core/constants.js +112 -186
- package/dist/prod/core/context.js +3 -3
- package/dist/prod/core/core.js +852 -1458
- package/dist/prod/core/effect.js +88 -99
- package/dist/prod/core/external.js +5 -5
- package/dist/prod/core/graph.js +61 -70
- package/dist/prod/core/heap.js +75 -98
- package/dist/prod/core/invariants.js +1 -32
- package/dist/prod/core/lanes.js +666 -181
- package/dist/prod/core/owner.js +75 -96
- package/dist/prod/core/scheduler.js +677 -1332
- package/dist/prod/core/verdict.js +297 -621
- package/dist/prod/flatten.js +77 -0
- package/dist/prod/index.js +15 -13
- package/dist/prod/map.js +143 -146
- package/dist/prod/reveal.js +212 -0
- package/dist/prod/signals.js +144 -226
- package/dist/prod/store/affects.js +135 -0
- package/dist/prod/store/index.js +28 -19
- package/dist/prod/store/optimistic.js +533 -0
- package/dist/prod/store/projection.js +295 -0
- package/dist/prod/store/reconcile.js +257 -0
- package/dist/prod/store/store.js +1840 -272
- package/dist/prod/store/storePath.js +1 -1
- package/dist/{observe/store/next → prod/store}/target.js +13 -17
- package/dist/prod/store/types.js +73 -0
- package/dist/prod/store/utils.js +1 -11
- package/dist/types/affects.d.ts +11 -30
- package/dist/types/boundaries.d.ts +62 -176
- package/dist/types/carved.d.ts +1 -0
- package/dist/types/core/action.d.ts +5 -6
- package/dist/types/core/async.d.ts +10 -5
- package/dist/types/core/attribution-hooks.d.ts +20 -1
- package/dist/types/core/attribution.d.ts +20 -9
- package/dist/types/core/constants.d.ts +125 -180
- package/dist/types/core/core.d.ts +104 -264
- package/dist/types/core/dev.d.ts +2 -2
- package/dist/types/core/effect.d.ts +0 -7
- package/dist/types/core/graph.d.ts +7 -1
- package/dist/types/core/heap.d.ts +5 -7
- package/dist/types/core/index.d.ts +5 -5
- package/dist/types/core/invariants.d.ts +0 -29
- package/dist/types/core/lanes.d.ts +65 -87
- package/dist/types/core/owner.d.ts +3 -3
- package/dist/types/core/scheduler.d.ts +249 -331
- package/dist/types/core/types.d.ts +51 -85
- package/dist/types/core/verdict.d.ts +3 -0
- package/dist/types/flatten.d.ts +8 -0
- package/dist/types/index.d.ts +6 -4
- package/dist/types/reveal.d.ts +19 -0
- package/dist/types/signals.d.ts +35 -91
- package/dist/types/store/affects.d.ts +1 -0
- package/dist/types/store/index.d.ts +35 -15
- package/dist/types/store/optimistic.d.ts +23 -0
- package/dist/types/store/projection.d.ts +18 -0
- package/dist/types/store/reconcile.d.ts +3 -0
- package/dist/types/store/store.d.ts +165 -143
- package/dist/types/store/storePath.d.ts +1 -1
- package/dist/types/store/target.d.ts +151 -0
- package/dist/types/store/types.d.ts +84 -0
- package/package.json +1 -1
- package/dist/observe/core/optimistic.js +0 -669
- package/dist/observe/store/next/optimistic.js +0 -717
- package/dist/observe/store/next/projection.js +0 -266
- package/dist/observe/store/next/reconcile.js +0 -375
- package/dist/observe/store/next/store.js +0 -2608
- package/dist/prod/core/optimistic.js +0 -658
- package/dist/prod/store/next/optimistic.js +0 -710
- package/dist/prod/store/next/projection.js +0 -257
- package/dist/prod/store/next/reconcile.js +0 -373
- package/dist/prod/store/next/store.js +0 -2484
- package/dist/types/core/optimistic.d.ts +0 -6
- package/dist/types/store/next/optimistic.d.ts +0 -25
- package/dist/types/store/next/projection.d.ts +0 -27
- package/dist/types/store/next/reconcile.d.ts +0 -10
- package/dist/types/store/next/store.d.ts +0 -145
- package/dist/types/store/next/target.d.ts +0 -196
|
@@ -1,24 +1,20 @@
|
|
|
1
1
|
import { releaseFlightTeardown, handleAsync, clearStatus, parkLoadingWindow, notifyStatus, settlePendingSource, settleErroredDependents } from "./async.js";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { STATUS_UNINITIALIZED, REACTIVE_LAZY, REACTIVE_NONE, defaultContext, CONFIG_TRANSPARENT, CONFIG_OWNED_WRITE, CONFIG_AUTO_DISPOSE, CONFIG_SYNC, CONFIG_NO_SNAPSHOT, CONFIG_PLUMBING, CONFIG_IN_SNAPSHOT_SCOPE, NOT_PENDING, NO_SNAPSHOT, CONFIG_HAS_SNAPSHOT, CONFIG_HELD, REACTIVE_MANUAL_WRITE, REACTIVE_DIRTY, CONFIG_OVERRIDE, REACTIVE_DISPOSED, REACTIVE_CHECK, REACTIVE_IN_HEAP, REACTIVE_REASK, REACTIVE_SNAPSHOT_STALE, REACTIVE_LANE_DIRTY, CONFIG_GUESS, REACTIVE_RECOMPUTING_DEPS, EFFECT_RENDER, CONFIG_VERDICT, EFFECT_TRACKED, CONFIG_STAGED, STATUS_ERROR, STATUS_PENDING, REACTIVE_ZOMBIE, REACTIVE_MISSED_WAKE, REACTIVE_JOINED, REACTIVE_PROBED, REACTIVE_FRAME_READ, REACTIVE_STAGED_READ, REACTIVE_LANE_READ, REACTIVE_SCREEN_READ, REACTIVE_PROBE_UNANSWERED, CONFIG_CHILDREN_FORBIDDEN, CONFIG_PROMOTED, CONFIG_INPUTS_PUBLISHED, CONFIG_FRESH_READ, REACTIVE_IN_HEAP_HEIGHT, CONFIG_SLOT_NODE, EFFECT_USER } from "./constants.js";
|
|
4
4
|
|
|
5
5
|
import { NotReadyError } from "./error.js";
|
|
6
6
|
|
|
7
7
|
import { clearDeps, trimStaleDeps, link, dormantNodes } from "./graph.js";
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
import { findLane } from "./lanes.js";
|
|
9
|
+
import { insertIntoHeap, deleteFromHeap, insertIntoHeapHeight, enqueueSub, markNode, markHeap } from "./heap.js";
|
|
12
10
|
|
|
13
11
|
import { GRAPH_SIZE_WARN_AT, noteFanIn } from "./dev.js";
|
|
14
12
|
|
|
15
13
|
import { attrHooks } from "./attribution-hooks.js";
|
|
16
14
|
|
|
17
|
-
import "./
|
|
18
|
-
|
|
19
|
-
import { disposeChildren, markDisposal, linkChild, inheritId } from "./owner.js";
|
|
15
|
+
import { inheritId, disposeChildren, linkChild, markDisposal } from "./owner.js";
|
|
20
16
|
|
|
21
|
-
import { GlobalQueue,
|
|
17
|
+
import { schedule, GlobalQueue, clock, txOf, flushTransaction, joinFuture, queuePendingNode, globalQueue, notifyEpoch, insertSubs, dirtyQueue, bumpNotifyEpoch, setPassLane, passLane, joinPassTx, passTx, holdNode, laneDirty, heldTrim, staleReader, stagedReaders, deferZombie } from "./scheduler.js";
|
|
22
18
|
|
|
23
19
|
// The heap's per-node step. A tracked effect's heap visit is its compute
|
|
24
20
|
// phase — empty, like a user effect whose compute reads nothing — and hands
|
|
@@ -28,62 +24,34 @@ import { GlobalQueue, bumpNotifyEpoch, activeTransition, globalQueue, clock, cur
|
|
|
28
24
|
// render-effect callback stages its value for the next pass, but a wake pushed
|
|
29
25
|
// directly into the user queue ran in the SAME pass, read the old value, and
|
|
30
26
|
// nothing re-notified it when the value landed (#3291).
|
|
31
|
-
GlobalQueue.
|
|
32
|
-
if (e.
|
|
33
|
-
deleteFromHeap(e,
|
|
34
|
-
e.
|
|
35
|
-
|
|
36
|
-
}
|
|
27
|
+
GlobalQueue.et = e => {
|
|
28
|
+
if (e.i === EFFECT_TRACKED) {
|
|
29
|
+
deleteFromHeap(e, dirtyQueue);
|
|
30
|
+
e.lt = true;
|
|
31
|
+
globalQueue.enqueue(EFFECT_USER, e.q);
|
|
32
|
+
}
|
|
33
|
+
// L2: a zombie's fate is the seam's — its owner's commit disposes it, or
|
|
34
|
+
// the park cancels the pass (the write that dirtied it is held). It runs
|
|
35
|
+
// only past a seam that left it alive (a live write to a frame whose
|
|
36
|
+
// owner is held in the future). Its lane's own re-staging is a live write
|
|
37
|
+
// (REACTIVE_LANE_DIRTY — a lane shows ahead of any park): it runs now, as
|
|
38
|
+
// the lane's work, for the reveal this seam (#3463).
|
|
39
|
+
else if (e.R & REACTIVE_ZOMBIE && !(e.R & REACTIVE_LANE_DIRTY)) deferZombie(e); else recompute(e);
|
|
37
40
|
};
|
|
38
41
|
|
|
39
|
-
GlobalQueue.
|
|
42
|
+
GlobalQueue.st = disposeChildren;
|
|
40
43
|
|
|
41
44
|
let tracking = false;
|
|
42
45
|
|
|
43
|
-
/** @internal verdict-module glue */ function setPendingCheckActive(e) {
|
|
44
|
-
pendingCheckActive = e;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** @internal verdict-module glue */ function setLatestReadActive(e) {
|
|
48
|
-
latestReadActive = e;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** @internal verdict-module glue */ function setContextInternal(e) {
|
|
52
|
-
context = e;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
let stale = false;
|
|
56
|
-
|
|
57
|
-
let pendingCheckActive = false;
|
|
58
|
-
|
|
59
|
-
let latestReadActive = false;
|
|
60
|
-
|
|
61
46
|
let context = null;
|
|
62
47
|
|
|
63
|
-
let currentOptimisticLane = null;
|
|
64
|
-
|
|
65
|
-
/** Notify `node`'s subscribers on `lane`'s channel: they recompute as the
|
|
66
|
-
* lane's passes — a memo publishes a derived override (lanes stage, #3479),
|
|
67
|
-
* an effect runs from the lane's queue, at the park, ahead of the
|
|
68
|
-
* transaction — the display-ahead view. The write itself is already staged
|
|
69
|
-
* (setSignal) and commits with the frame; this walk shows it now. Used by a
|
|
70
|
-
* boundary re-armed from a lane pass (boundaries.ts `_swap`, #3540). */ function notifyOnLane(e, t) {
|
|
71
|
-
const n = currentOptimisticLane;
|
|
72
|
-
currentOptimisticLane = t;
|
|
73
|
-
try {
|
|
74
|
-
insertSubs(e, true);
|
|
75
|
-
} finally {
|
|
76
|
-
currentOptimisticLane = n;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
48
|
let snapshotCaptureActive = false;
|
|
81
49
|
|
|
82
50
|
let snapshotSources = null;
|
|
83
51
|
|
|
84
52
|
function ownerInSnapshotScope(e) {
|
|
85
53
|
while (e) {
|
|
86
|
-
if (e.
|
|
54
|
+
if (e.Et) return true;
|
|
87
55
|
e = e._parent;
|
|
88
56
|
}
|
|
89
57
|
return false;
|
|
@@ -95,34 +63,34 @@ function setSnapshotCapture(e) {
|
|
|
95
63
|
}
|
|
96
64
|
|
|
97
65
|
function markSnapshotScope(e) {
|
|
98
|
-
e.
|
|
66
|
+
e.Et = true;
|
|
99
67
|
}
|
|
100
68
|
|
|
101
69
|
function releaseSnapshotScope(e) {
|
|
102
|
-
e.
|
|
70
|
+
e.Et = false;
|
|
103
71
|
releaseSubtree(e);
|
|
104
72
|
schedule();
|
|
105
73
|
}
|
|
106
74
|
|
|
107
75
|
function releaseSubtree(e) {
|
|
108
|
-
let t = e.
|
|
76
|
+
let t = e.sn;
|
|
109
77
|
while (t) {
|
|
110
|
-
if (t.
|
|
111
|
-
t = t.
|
|
78
|
+
if (t.Et) {
|
|
79
|
+
t = t.rn;
|
|
112
80
|
continue;
|
|
113
81
|
}
|
|
114
|
-
if (t.
|
|
82
|
+
if (t.re) {
|
|
115
83
|
const e = t;
|
|
116
|
-
e.
|
|
117
|
-
if (e.
|
|
118
|
-
e.
|
|
119
|
-
e.
|
|
120
|
-
if (dirtyQueue.
|
|
84
|
+
e.u &= ~CONFIG_IN_SNAPSHOT_SCOPE;
|
|
85
|
+
if (e.R & REACTIVE_SNAPSHOT_STALE) {
|
|
86
|
+
e.R &= ~REACTIVE_SNAPSHOT_STALE;
|
|
87
|
+
e.R |= REACTIVE_DIRTY;
|
|
88
|
+
if (dirtyQueue.ut > e.it) dirtyQueue.ut = e.it;
|
|
121
89
|
insertIntoHeap(e, dirtyQueue);
|
|
122
90
|
}
|
|
123
91
|
}
|
|
124
92
|
releaseSubtree(t);
|
|
125
|
-
t = t.
|
|
93
|
+
t = t.rn;
|
|
126
94
|
}
|
|
127
95
|
}
|
|
128
96
|
|
|
@@ -133,12 +101,8 @@ function clearSnapshots() {
|
|
|
133
101
|
// pre-initialized to undefined (see ext()), and every reader tests
|
|
134
102
|
// `!== undefined` — assign, don't `delete`: deleting a field pushes the
|
|
135
103
|
// object to dictionary mode for every later read of every field.
|
|
136
|
-
const t = e.
|
|
137
|
-
if (t != null) t.
|
|
138
|
-
// StoreNode targets share one pre-initialized hidden class (see
|
|
139
|
-
// createStoreProxy) — same rule, and only when present so signal-node
|
|
140
|
-
// sources don't grow the field.
|
|
141
|
-
if (e[STORE_SNAPSHOT_PROPS] !== undefined) e[STORE_SNAPSHOT_PROPS] = undefined;
|
|
104
|
+
const t = e.m;
|
|
105
|
+
if (t != null) t.ot = undefined;
|
|
142
106
|
}
|
|
143
107
|
snapshotSources = null;
|
|
144
108
|
}
|
|
@@ -148,246 +112,157 @@ function clearSnapshots() {
|
|
|
148
112
|
function recompute(e, t = false) {
|
|
149
113
|
// §12d: any recompute can clean a marked subscriber — invalidate skips.
|
|
150
114
|
bumpNotifyEpoch();
|
|
151
|
-
const n = e.
|
|
115
|
+
const n = e.i;
|
|
116
|
+
// Lanes (plan sec. 28): the seat of a pass is its node's. A node carrying a lane's
|
|
117
|
+
// derived value runs as the lane's whoever dirtied it — a sync write, a
|
|
118
|
+
// boundary reset, a frame rerun — so its children and its result are the
|
|
119
|
+
// lane's; so does a member the lane's own re-staging dirtied
|
|
120
|
+
// (REACTIVE_LANE_DIRTY, lanes.ts — a leaf the lane owns). A written guess's
|
|
121
|
+
// own pass is its truth arriving (A18), the frame's. A first pass is its
|
|
122
|
+
// creator's (ruling A: a lane pass's children are the lane's frame), and
|
|
123
|
+
// under a guess's lane it reads as the lane's too — a binding the lane
|
|
124
|
+
// mounts sees the screen, like the pass that mounted it (#3835). Not
|
|
125
|
+
// under a verdict lane, for its reads or its result: a verdict lane holds
|
|
126
|
+
// verdicts, and a mount it makes is a mainline mount (A29's boundary
|
|
127
|
+
// exemption, #3851). A tracked read of a lane's value moves a
|
|
128
|
+
// derivation's pass into the lane (`read`); a leaf's never moves.
|
|
129
|
+
// Restored at the end, after this pass's staging and runs have been
|
|
130
|
+
// routed.
|
|
131
|
+
const l = passLane;
|
|
132
|
+
setPassLane((e.R & REACTIVE_LANE_DIRTY || (e.u & (CONFIG_OVERRIDE | CONFIG_GUESS)) === CONFIG_OVERRIDE) && e.m?._t?.Z ? e.m._t : // Loose `!=`: false with no creator lane (undefined == null).
|
|
133
|
+
t && l?._parent.rt != l && (creatorPass(context)?.R ?? 0) & REACTIVE_RECOMPUTING_DEPS ? l : null);
|
|
152
134
|
// Attribution hook: fired before this run touches the dep list — `_deps`
|
|
153
135
|
// still holds the previous run's links (the subscriptions that could have
|
|
154
136
|
// triggered this run, and the baseline for the engine's subscription diff).
|
|
155
|
-
let
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
// see the parking site), so the decision must be known there. `lane` is applied to
|
|
160
|
-
// `currentOptimisticLane` further down, once the previous posture is saved.
|
|
161
|
-
let l = !!(e.oe & REACTIVE_OPTIMISTIC_DIRTY);
|
|
162
|
-
let u = null;
|
|
163
|
-
if (l) {
|
|
164
|
-
u = GlobalQueue._t(e, true);
|
|
165
|
-
// `false` = wake-only lane demotion: recompute plain so a mid-tick
|
|
166
|
-
// latest()/isPending() pull stages instead of direct-committing (#3009).
|
|
167
|
-
// The predicate lives with the engine (recomputeLane).
|
|
168
|
-
if (u === false) l = false;
|
|
169
|
-
} else if (e.C & CONFIG_DERIVED_OVERRIDE) {
|
|
170
|
-
// Lanes stage (#3479): a pass over a live lane member carrying a derived
|
|
171
|
-
// override is the lane's pass whatever channel dirtied it (a boundary
|
|
172
|
-
// reset, an unrelated sync write) — its inputs serve the lane's view, so
|
|
173
|
-
// its result is the lane's and belongs in the override slot. Run plain,
|
|
174
|
-
// A18's sync twin below read that re-derived lane view as a differing
|
|
175
|
-
// truth (a fresh array), superseded the override and demoted the lane;
|
|
176
|
-
// the lane's next pass then dropped the staged "truth" and left the node
|
|
177
|
-
// flagged superseded with nothing to serve (fuzzer latest-1 #2481). A
|
|
178
|
-
// demoted node resolves no lane and stays plain: its pass IS the truth.
|
|
179
|
-
u = GlobalQueue._t(e, true);
|
|
180
|
-
if (u) l = true;
|
|
181
|
-
} else if (activeTransition && !t && activeTransition.bn.length) {
|
|
182
|
-
// Lane adoption: parent-deeper-than-owned-child can run before its OPT-dirty
|
|
183
|
-
// child propagates. Walk deps once and inherit the OPT lane so this node
|
|
184
|
-
// recomputes under the right posture and propagates correctly.
|
|
185
|
-
u = GlobalQueue._t(e, false);
|
|
186
|
-
if (u) l = true;
|
|
137
|
+
let s = false;
|
|
138
|
+
{
|
|
139
|
+
e.en = 0;
|
|
140
|
+
if (attrHooks !== null) attrHooks.recomputeStart(e, t);
|
|
187
141
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
deleteFromHeap(e, queueFor(e));
|
|
195
|
-
if (e.o !== null) {
|
|
196
|
-
e.o.pe = null;
|
|
142
|
+
// CARVE 2: lane posture resolution (OPTIMISTIC_DIRTY, derived-override
|
|
143
|
+
// re-derivation, lane adoption through deps) went with the optimistic engine.
|
|
144
|
+
if (!t) {
|
|
145
|
+
deleteFromHeap(e, dirtyQueue);
|
|
146
|
+
if (e.m !== null) {
|
|
147
|
+
e.m.De = null;
|
|
197
148
|
// Supersede is where an iterator flight dies (#3122): close it now.
|
|
198
|
-
//
|
|
199
|
-
// disposal list that a held transition only drains when the
|
|
200
|
-
// SUPERSEDING flight settles — cancellation must not wait for the
|
|
201
|
-
// work that replaced it. Idempotent with the cleanup-channel close.
|
|
149
|
+
// Idempotent with the cleanup-channel close.
|
|
202
150
|
releaseFlightTeardown(e);
|
|
203
151
|
}
|
|
204
|
-
//
|
|
205
|
-
//
|
|
206
|
-
//
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
//
|
|
210
|
-
//
|
|
211
|
-
//
|
|
212
|
-
//
|
|
213
|
-
//
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
// stamped-memo arm above — a `Show` whose `when` getter owns a
|
|
229
|
-
// compiler-emitted memo held an unrelated sync write for the action's
|
|
230
|
-
// lifetime, against the #3460 ruling that a lane never holds a sync write.
|
|
231
|
-
if (n === EFFECT_TRACKED || e.C & (CONFIG_HELD_CHILDREN | CONFIG_LANE_FRAME)) disposeChildren(e); else if (e.On !== null || e.Ve !== null) {
|
|
232
|
-
markDisposal(e);
|
|
233
|
-
const t = ext(e);
|
|
234
|
-
t.Et = e.Ve;
|
|
235
|
-
t.dt = e.On;
|
|
236
|
-
e.Ve = null;
|
|
237
|
-
e.On = null;
|
|
238
|
-
e.Nt = 0;
|
|
239
|
-
if (u) {
|
|
240
|
-
e.C |= CONFIG_LANE_FRAME;
|
|
241
|
-
findLane(u).It[0].push(() => {
|
|
242
|
-
e.C &= ~CONFIG_LANE_FRAME;
|
|
243
|
-
disposeChildren(e, false, true);
|
|
244
|
-
});
|
|
152
|
+
// L2: a pass over a held derivation joins its transaction — the
|
|
153
|
+
// propagation that reached this node reached the transaction, and this
|
|
154
|
+
// pass's answer replaces the held one there. A node held with nothing
|
|
155
|
+
// staged — pending when its flush parked, or a member of a held frame —
|
|
156
|
+
// joins all the same. Not a render effect (rule 3): it is the frame.
|
|
157
|
+
// Re-run outside its transaction's flush it publishes mainline — the
|
|
158
|
+
// value it staged there is superseded (`frameRead` remembers it for the
|
|
159
|
+
// landing). A frame born into the transaction (uninitialized: never run,
|
|
160
|
+
// A29) keeps staging: live writes keep it current, it effects at the
|
|
161
|
+
// swap (ruling A).
|
|
162
|
+
if (e.u & CONFIG_HELD) {
|
|
163
|
+
const t = txOf(e);
|
|
164
|
+
// Held by a blocked lane: the frame joins nothing — a lane never holds
|
|
165
|
+
// a sync write (#3460). The pass is the lane's if it reads the lane's
|
|
166
|
+
// world (`read`), and has left it otherwise.
|
|
167
|
+
if (t.Z) ; else if (n !== EFFECT_RENDER && !(e.u & CONFIG_VERDICT)) joinPassTx(t); else if (t !== flushTransaction && !(e.G & STATUS_UNINITIALIZED)) {
|
|
168
|
+
// Published mainline, it is not held: the frame this pass builds is
|
|
169
|
+
// replaced on the spot (#3404) — this flush's commit retires the
|
|
170
|
+
// committed frame the held pass had parked (`commitPendingNode`),
|
|
171
|
+
// after the new frame's first runs, and the held pass's own
|
|
172
|
+
// children die below (`disposeChildren`). The landing re-derives a
|
|
173
|
+
// fresh held frame for the reveal (`_reruns`).
|
|
174
|
+
e.Ae = NOT_PENDING;
|
|
175
|
+
if (n === EFFECT_RENDER) e.u &= ~CONFIG_HELD;
|
|
245
176
|
}
|
|
246
|
-
}
|
|
177
|
+
}
|
|
178
|
+
// The previous pass's frame. If that pass is still awaiting its commit
|
|
179
|
+
// (CONFIG_STAGED) its children were never shown and die on the spot — the
|
|
180
|
+
// frame parked before it stays parked. Otherwise they ARE the frame:
|
|
181
|
+
// park them until this pass commits. Tracked effects run after the seam
|
|
182
|
+
// and own nothing to park.
|
|
183
|
+
if (n === EFFECT_TRACKED || e.u & CONFIG_STAGED) disposeChildren(e); else if (e.sn !== null || e.Pe !== null) parkChildren(e);
|
|
247
184
|
}
|
|
248
|
-
|
|
249
|
-
// one: a plain pass over it — its source superseded (A18) — stages the truth
|
|
250
|
-
// and supersedes the override through the sync twin below.
|
|
251
|
-
const o = (e.C & (CONFIG_OPTIMISTIC | CONFIG_DERIVED_OVERRIDE)) !== 0 && e.o?.we !== NOT_PENDING && e.o?.we !== undefined;
|
|
252
|
-
const r = !!(e.S & STATUS_UNINITIALIZED);
|
|
185
|
+
const E = !!(e.G & STATUS_UNINITIALIZED);
|
|
253
186
|
// Capture both error and pending status before the compute clears them.
|
|
254
187
|
// A conditional can drop its pending source and recover to an unchanged
|
|
255
188
|
// value, leaving blocked dependents outside that source’s settle walk.
|
|
256
|
-
const
|
|
257
|
-
const
|
|
258
|
-
const
|
|
189
|
+
const u = e.G & STATUS_ERROR ? e.m?.v : undefined;
|
|
190
|
+
const i = (e.G & STATUS_PENDING) !== 0;
|
|
191
|
+
const o = i ? e.m?.Se : undefined;
|
|
259
192
|
// Pending SOURCE-hood, captured before the compute clears status: a node
|
|
260
193
|
// whose own flight parked dependents self-registers in _pendingSources
|
|
261
194
|
// (notifyStatus, isSource). If this recompute supersedes that flight and
|
|
262
195
|
// settles synchronously, those dependents settle HERE — asyncWrite's
|
|
263
196
|
// settlePendingSource walk never runs for a landing that was preempted
|
|
264
197
|
// (#3181).
|
|
265
|
-
const
|
|
266
|
-
//
|
|
267
|
-
// the
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
// commits (commitPendingNode) — a closed window plus a held value reads as
|
|
272
|
-
// a pending frame to live observers of the verdict (#2990).
|
|
273
|
-
const E = e.ye;
|
|
274
|
-
// Creation-time A29 (see enterStagedRead): a pass outside a flush that is
|
|
275
|
-
// served a live transaction's staged value records the transaction here
|
|
276
|
-
// and is staged INTO it below — "born held" — instead of committing.
|
|
277
|
-
const d = stagedEntry;
|
|
278
|
-
stagedEntry = null;
|
|
279
|
-
const N = context;
|
|
198
|
+
const _ = e.m?.Se?.has(e);
|
|
199
|
+
// A19 exc. 2: a `refresh()` re-asks the question already answered — quiet
|
|
200
|
+
// for the verdict — unless the question in flight is a new one, which the
|
|
201
|
+
// re-ask does not launder (a poll during a quiet confirm stays quiet).
|
|
202
|
+
const r = (e.R & REACTIVE_REASK) !== 0 && !(e.G & STATUS_PENDING && e.m !== null && !e.m.It);
|
|
203
|
+
const a = context;
|
|
280
204
|
context = e;
|
|
281
|
-
e.
|
|
282
|
-
e.
|
|
283
|
-
// REACTIVE_ZOMBIE is position
|
|
284
|
-
//
|
|
285
|
-
//
|
|
286
|
-
//
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
//
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
let
|
|
293
|
-
let
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
let
|
|
205
|
+
e.S = null;
|
|
206
|
+
e.he++;
|
|
207
|
+
// REACTIVE_ZOMBIE is position: it says the node sits on its owner's parked
|
|
208
|
+
// frame, and the splice/reawaken paths key off it. A zombie reruns for
|
|
209
|
+
// live writes until the commit that disposes it, so every per-pass wipe
|
|
210
|
+
// — here, the finally below, updateIfNecessary — carries it (#3543).
|
|
211
|
+
e.R = REACTIVE_RECOMPUTING_DEPS | e.R & REACTIVE_ZOMBIE;
|
|
212
|
+
e.Ne = clock;
|
|
213
|
+
// The pass's previous value: its staging, else the lane's value for a
|
|
214
|
+
// lane's node (lanes.ts), else the committed one.
|
|
215
|
+
let I = e.Ae !== NOT_PENDING ? e.Ae : e.u & CONFIG_OVERRIDE ? e.m.Z : e.pe;
|
|
216
|
+
let N = e.it;
|
|
217
|
+
let f = false;
|
|
218
|
+
// L2: did this pass read the future (REACTIVE_JOINED, set by read)?
|
|
219
|
+
let T = false;
|
|
220
|
+
let S = tracking;
|
|
297
221
|
tracking = true;
|
|
298
|
-
// A computed's fn establishes its OWN dependencies, so it must never run
|
|
299
|
-
// inside a latest() read window: read() short-circuits through the
|
|
300
|
-
// companion path before dependency linking, so a memo created (eagerly
|
|
301
|
-
// computed) inside latest(fn) came out permanently dependency-less (#2926).
|
|
302
|
-
// latestRead() already suspends the flag for its pull-recomputes; this
|
|
303
|
-
// covers creation-time computes and flushes that run inside the window.
|
|
304
|
-
const C = latestReadActive;
|
|
305
|
-
latestReadActive = false;
|
|
306
|
-
// A memo computes under its OWN lane posture, never the puller's (A31,
|
|
307
|
-
// #3442): its value is one shared slot every reader sees, so a pull from a
|
|
308
|
-
// lane-carrying reader (a probe effect on its companion lane pulling a sync
|
|
309
|
-
// memo) must not run it with that lane's read carve-outs — under a lane, a
|
|
310
|
-
// pending node on no lane serves its committed value instead of throwing,
|
|
311
|
-
// and the memo then published a stale "settled" value, dropped its
|
|
312
|
-
// pending status, and stopped holding its transaction. The branches below
|
|
313
|
-
// re-establish the posture the memo itself owns (OPT-dirty, or adopted
|
|
314
|
-
// through its deps). Effects keep the ambient lane: their runs are the
|
|
315
|
-
// lane's own view.
|
|
316
|
-
if (!n) currentOptimisticLane = null;
|
|
317
|
-
// Lane posture lives with the engine: OPTIMISTIC_DIRTY is only ever set by
|
|
318
|
-
// engine-driven paths, and _optimisticNodes is only pushed by
|
|
319
|
-
// _optimisticWrite, so the hook is installed whenever either gate holds.
|
|
320
|
-
// (Resolved at the top of this pass, ahead of the parking site.)
|
|
321
|
-
if (u) currentOptimisticLane = u;
|
|
322
|
-
const p = n && n !== EFFECT_USER;
|
|
323
|
-
const R = stale;
|
|
324
|
-
if (p) stale = true;
|
|
325
|
-
// An effect recorded for this transaction's commit replay (it once read a
|
|
326
|
-
// node the transaction held and showed the committed value) and now
|
|
327
|
-
// recomputing UNDER the transaction sees its staged view: the value this
|
|
328
|
-
// run produces is applied by the commit itself, and the stale recording
|
|
329
|
-
// would publish the frame a second time. Drop it; the reads below re-record
|
|
330
|
-
// if they are served the committed view again (a lane's committed read).
|
|
331
|
-
if (n && activeTransition !== null && activeTransition.Tt.size) activeTransition.Tt.delete(e);
|
|
332
222
|
try {
|
|
333
|
-
if (!false && e.
|
|
334
|
-
I = e.
|
|
335
|
-
if (e.
|
|
336
|
-
e.
|
|
223
|
+
if (!false && e.u & CONFIG_SYNC) {
|
|
224
|
+
I = e.re(I);
|
|
225
|
+
if (e.m !== null) e.m.De = null;
|
|
226
|
+
e._e = false;
|
|
337
227
|
} else {
|
|
338
|
-
//
|
|
339
|
-
//
|
|
340
|
-
//
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
const
|
|
344
|
-
const
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
if (!l && !i) {
|
|
349
|
-
if (e.o !== null) e.o.pe = null;
|
|
228
|
+
// A projection's body self-registers its flight through handleAsync
|
|
229
|
+
// (with the commit as setter): its undefined return is not a sync
|
|
230
|
+
// answer, and the outer handleAsync must not clobber the registration.
|
|
231
|
+
const t = e.m?.De;
|
|
232
|
+
const n = e.re(I);
|
|
233
|
+
const l = typeof n === "object" && n !== null;
|
|
234
|
+
const s = e.m?.De !== t;
|
|
235
|
+
I = s || !l ? n : handleAsync(e, n);
|
|
236
|
+
if (!s && !l) {
|
|
237
|
+
if (e.m !== null) e.m.De = null;
|
|
350
238
|
// A sync (non-object) return is the first real answer; async-shaped
|
|
351
239
|
// results clear inside handleAsync at their own landing points, and a
|
|
352
|
-
// self-registered flight
|
|
353
|
-
|
|
354
|
-
e.ye = false;
|
|
240
|
+
// self-registered flight clears when its own handleAsync lands.
|
|
241
|
+
e._e = false;
|
|
355
242
|
}
|
|
356
243
|
}
|
|
357
244
|
// On a status-free node clearStatus is a guaranteed no-op: every field
|
|
358
245
|
// its body gates on is either _statusFlags or lives in the cold
|
|
359
246
|
// extension — no extension, no status to clear. (_x from an unrelated
|
|
360
247
|
// installer just makes clearStatus a cheap re-verified no-op.)
|
|
361
|
-
|
|
362
|
-
// commits: it has no committed value, and read() holds its readers.
|
|
363
|
-
if (e.S !== 0 || e.o !== null) clearStatus(e, t && stagedEntry === null);
|
|
364
|
-
// _optimisticLane is only ever assigned by engine paths (CONFIG_HAS_LANE
|
|
365
|
-
// is their sticky presence mark).
|
|
366
|
-
if (e.C & CONFIG_HAS_LANE && e.o?.Ce) GlobalQueue.St(e);
|
|
248
|
+
if (e.G !== 0 || e.m !== null) clearStatus(e, t);
|
|
367
249
|
} catch (t) {
|
|
368
250
|
const n = t instanceof NotReadyError;
|
|
369
|
-
if (n && e.
|
|
251
|
+
if (n && e._e) {
|
|
370
252
|
// Loading window with an unready sync dependency: register for the
|
|
371
253
|
// source's settle (the settlePendingSource walk runs off
|
|
372
254
|
// _pendingSources + _blocked alone) but take NO read-visible pending
|
|
373
|
-
// status
|
|
374
|
-
//
|
|
255
|
+
// status and no downstream propagation — the committed loading value
|
|
256
|
+
// keeps serving. If the
|
|
375
257
|
// node is currently errored the error stays the answer until this
|
|
376
258
|
// retry can actually run.
|
|
377
259
|
parkLoadingWindow(e, t);
|
|
378
260
|
} else {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
if (n && currentOptimisticLane) GlobalQueue.Ot(e);
|
|
382
|
-
let i = false;
|
|
383
|
-
if (n) {
|
|
384
|
-
ext(e).Oe = true;
|
|
385
|
-
if (GlobalQueue.At !== null) i = GlobalQueue.At(e, _);
|
|
386
|
-
}
|
|
387
|
-
notifyStatus(e, n ? STATUS_PENDING : STATUS_ERROR, t, undefined, n ? e.o?.Ce : undefined);
|
|
261
|
+
if (n) ext(e).Ie = true;
|
|
262
|
+
notifyStatus(e, n ? STATUS_PENDING : STATUS_ERROR, t);
|
|
388
263
|
// The replacement source is fully propagated now. If no new flight
|
|
389
264
|
// re-owned self, retire the superseded flight and its dependent copies.
|
|
390
|
-
if (n &&
|
|
265
|
+
if (n && _ && !e.m?.De) settlePendingSource(e);
|
|
391
266
|
// A re-park drops what the earlier pass carried (#3456): a source this
|
|
392
267
|
// pass no longer reaches — its branch switched, or a fresh flight
|
|
393
268
|
// replaced the inputs' pending with its own — stays copied onto
|
|
@@ -396,32 +271,34 @@ function recompute(e, t = false) {
|
|
|
396
271
|
// dependent then waits forever on a flight it has no path to. The
|
|
397
272
|
// re-park twin of the unchanged-value recovery sweep below; dependents
|
|
398
273
|
// with another path keep the source (retryReaches).
|
|
399
|
-
if (n &&
|
|
400
|
-
if (i) GlobalQueue.k(e);
|
|
274
|
+
if (n && o) for (const t of o) if (t !== e && !e.m?.Se?.has(t)) settlePendingSource(e, t);
|
|
401
275
|
}
|
|
402
276
|
} finally {
|
|
403
|
-
tracking =
|
|
404
|
-
latestReadActive = C;
|
|
405
|
-
if (p) stale = R;
|
|
277
|
+
tracking = S;
|
|
406
278
|
// Consume the missed-wake latch (#3037, set by insertSubs): a dep write
|
|
407
279
|
// landed beneath this pass on a link it had already validated. The wipe
|
|
408
280
|
// below must not key off DIRTY/CHECK — the read-time pull protocol
|
|
409
281
|
// (markNode(c) in read()) marks the running node as part of ordinary
|
|
410
282
|
// bookkeeping, and those marks are correctly discarded here.
|
|
411
|
-
|
|
283
|
+
f = (e.R & REACTIVE_MISSED_WAKE) !== 0;
|
|
284
|
+
T = (e.R & REACTIVE_JOINED) !== 0;
|
|
285
|
+
// A verdict reader is one for as long as it probes: a pass that entered
|
|
286
|
+
// no window (REACTIVE_PROBED) is an ordinary derivation again.
|
|
287
|
+
if (!(e.R & REACTIVE_PROBED)) e.u &= ~CONFIG_VERDICT;
|
|
412
288
|
// REACTIVE_DISPOSED survives too (#3621): the pass may have disposed its
|
|
413
289
|
// own owner (a memo calling its root's `dispose()`, a cleanup doing so
|
|
414
290
|
// #3601/#3606), and `disposeChildren` set the flag on this node
|
|
415
291
|
// reentrantly. Dropped, the node read as live — `refresh()` re-ran it
|
|
416
|
-
// and `isDisposed()` lied.
|
|
417
|
-
|
|
418
|
-
|
|
292
|
+
// and `isDisposed()` lied. REACTIVE_FRAME_READ (A15 stale reader) is
|
|
293
|
+
// this pass's verdict for the landing; the next pass's wipe at the top
|
|
294
|
+
// clears it.
|
|
295
|
+
e.R = e.R & (REACTIVE_ZOMBIE | REACTIVE_DISPOSED | REACTIVE_FRAME_READ | REACTIVE_STAGED_READ | REACTIVE_LANE_READ | REACTIVE_SCREEN_READ | REACTIVE_PROBE_UNANSWERED) | (t ? e.R & REACTIVE_SNAPSHOT_STALE : 0);
|
|
296
|
+
context = a;
|
|
297
|
+
// A19 exc. 2: a pass that went pending on a `refresh()` re-asks the
|
|
298
|
+
// question already answered — quiet for the verdict; any other pending
|
|
299
|
+
// pass is a new question. Settled, there is nothing to classify.
|
|
300
|
+
if (e.m !== null) e.m.It = r && (e.G & STATUS_PENDING) !== 0;
|
|
419
301
|
}
|
|
420
|
-
// The cast re-widens: TS narrowed `stagedEntry` to `null` at the reset
|
|
421
|
-
// above and does not invalidate that across the compute call that
|
|
422
|
-
// `enterStagedRead` runs under. No emitted code.
|
|
423
|
-
const G = stagedEntry;
|
|
424
|
-
stagedEntry = d;
|
|
425
302
|
// A node that died during its own pass (#3621) is dead at the end of it,
|
|
426
303
|
// and the pass is void. Its owner's teardown already unlinked its deps,
|
|
427
304
|
// removed it from the heap and ran its cleanups; what remains is what the
|
|
@@ -433,14 +310,26 @@ function recompute(e, t = false) {
|
|
|
433
310
|
// so nothing is staged, committed, or propagated to subscribers, and an
|
|
434
311
|
// effect's run is not enqueued (runEffect would refuse it anyway). The
|
|
435
312
|
// attribution frame opened at the top is still closed.
|
|
436
|
-
if (e.
|
|
313
|
+
if (e.R & REACTIVE_DISPOSED) {
|
|
437
314
|
clearDeps(e);
|
|
438
|
-
if (e.
|
|
315
|
+
if (e.m !== null) e.m.De = null;
|
|
439
316
|
if (attrHooks !== null) attrHooks.recomputeEnd(e, t, false, false, false, false);
|
|
440
|
-
|
|
317
|
+
setPassLane(l);
|
|
441
318
|
return;
|
|
442
319
|
}
|
|
443
|
-
if (
|
|
320
|
+
// The lane this pass is work of, if any: its seat (the node's, its
|
|
321
|
+
// creator's, or the one a lane read moved it into). A pass in a lane's
|
|
322
|
+
// seat that read none of the lane's world has left it: its result is the
|
|
323
|
+
// frame's (a derivation whose branch no longer reaches the guess). A
|
|
324
|
+
// guess is written, not derived — it never leaves this way.
|
|
325
|
+
let c = passLane;
|
|
326
|
+
// Listed before its staging, a pending pass included (the lane's own
|
|
327
|
+
// flight is the lane's); false: the pass left the lane (lanes.ts).
|
|
328
|
+
const A = !!e.m?.v;
|
|
329
|
+
if (c !== null) {
|
|
330
|
+
if (GlobalQueue.M(e, c, t, A)) setPassLane(c); else c = null;
|
|
331
|
+
}
|
|
332
|
+
if (!e.m?.v) {
|
|
444
333
|
// Observe-tier fan-in (HUGE_FAN_IN): the validated prefix [_deps.._depsTail]
|
|
445
334
|
// IS this pass's distinct sources — count it here rather than per link. A
|
|
446
335
|
// begin/end bracket around the pass plus a per-link increment measured
|
|
@@ -451,22 +340,20 @@ function recompute(e, t = false) {
|
|
|
451
340
|
// this pass, or at its commit — see recompute's tail.)
|
|
452
341
|
{
|
|
453
342
|
let t = 0;
|
|
454
|
-
for (let n = e.
|
|
343
|
+
for (let n = e._; n !== null; n = n.p) {
|
|
455
344
|
t++;
|
|
456
|
-
if (n === e.
|
|
345
|
+
if (n === e.S) break;
|
|
457
346
|
}
|
|
458
347
|
if (t >= GRAPH_SIZE_WARN_AT) noteFanIn(e, t);
|
|
459
348
|
}
|
|
460
349
|
// INV-11 (#3330): the equality gate compares against the slot this run
|
|
461
|
-
// publishes to
|
|
462
|
-
//
|
|
463
|
-
//
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
const u = o ? unwrapOverride(e.o?.we) : l || e._e === NOT_PENDING ? e.ce : e._e;
|
|
467
|
-
let a = false;
|
|
350
|
+
// publishes to — the staged `_pendingValue` when staged; a lane node's
|
|
351
|
+
// lane value (a node entering the lane this pass has none yet — its
|
|
352
|
+
// committed value, `laneStage` having voided a frame staging).
|
|
353
|
+
const i = e.Ae !== NOT_PENDING ? e.Ae : e.u & CONFIG_OVERRIDE && e.m.Z !== NOT_PENDING ? e.m.Z : e.pe;
|
|
354
|
+
let r = false;
|
|
468
355
|
try {
|
|
469
|
-
|
|
356
|
+
r = !n && E || !e.tn || !e.tn(i, I);
|
|
470
357
|
} catch (t) {
|
|
471
358
|
// A throwing user comparator is an error of this node's computation.
|
|
472
359
|
// Route it through the same status path as a compute-phase throw so
|
|
@@ -477,263 +364,157 @@ function recompute(e, t = false) {
|
|
|
477
364
|
// A committed derived change becomes a cause for this node's subscribers,
|
|
478
365
|
// chaining their attribution through this node to the root write.
|
|
479
366
|
if (attrHooks !== null) {
|
|
480
|
-
|
|
481
|
-
if (
|
|
367
|
+
s = r && !e.m?.v;
|
|
368
|
+
if (s && !n && !t) attrHooks.derivedChanged(e);
|
|
482
369
|
}
|
|
483
370
|
// Effects use `_equals: false` (no per-effect closure). The side effects that
|
|
484
371
|
// the equals closure used to perform — flagging the effect dirty and enqueueing
|
|
485
372
|
// its runner — happen here instead. `!create` matches the previous `initialized`
|
|
486
373
|
// gate: the explicit recompute(node, true) inside effect() does not enqueue, so
|
|
487
374
|
// effect() can call its runner synchronously for the first run.
|
|
488
|
-
if (n &&
|
|
489
|
-
|
|
375
|
+
if (n && r &&
|
|
376
|
+
// A stale reader of a lane (REACTIVE_SCREEN_READ) that computed what it
|
|
377
|
+
// last applied owes no run (lanes.ts).
|
|
378
|
+
!(e.R & REACTIVE_SCREEN_READ && !e.lt && Object.is(e.Nt, I)) &&
|
|
379
|
+
// A re-staging pass owes no run: the commit replays the effect (#3802).
|
|
380
|
+
e.Ae === NOT_PENDING) {
|
|
381
|
+
e.lt = !e.m?.v;
|
|
490
382
|
// Reuse one bound runner per effect — runEffect no-ops on a stale
|
|
491
383
|
// `_modified`, so re-enqueueing the same function is harmless.
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
// when this write commits under a different transaction
|
|
497
|
-
// (`activeTransition`, null = mainline) than the one that produced the
|
|
498
|
-
// previous value (`_valueTransition`), that view is gone. Rather than
|
|
499
|
-
// merge the two, each commit re-derives the effect against its own
|
|
500
|
-
// committed world (Transition._contested, re-dirtied by
|
|
501
|
-
// finalizePureQueue ahead of the heap run). The previous owner always
|
|
502
|
-
// needs it if still live: its commit is silent (staging already
|
|
503
|
-
// notified) and the value it computed is gone. The new owner needs it
|
|
504
|
-
// too, for the same reason, unless it is mainline — mainline publishes
|
|
505
|
-
// what it computes. A previous owner that was mainline, or already
|
|
506
|
-
// committed, left nothing to protect.
|
|
507
|
-
let t = e.Gt;
|
|
508
|
-
if (t !== activeTransition) {
|
|
509
|
-
e.Gt = activeTransition;
|
|
510
|
-
if (t !== null && (t = currentTransition(t)) !== activeTransition && !t.Dt) {
|
|
511
|
-
(t.ht ??= []).push(e);
|
|
512
|
-
if (activeTransition !== null) (activeTransition.ht ??= []).push(e);
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
}
|
|
384
|
+
// Lane work's run is the lane's (released at its reveal) — a first
|
|
385
|
+
// pass under a lane too: `effect()` skips the synchronous first run of
|
|
386
|
+
// a lane's node.
|
|
387
|
+
if (!t || c !== null) globalQueue.enqueue(n, e.J ??= GlobalQueue.X.bind(null, e), c);
|
|
516
388
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
//
|
|
525
|
-
//
|
|
526
|
-
//
|
|
527
|
-
//
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
//
|
|
542
|
-
//
|
|
543
|
-
|
|
544
|
-
|
|
389
|
+
// Lanes. A written guess's own pass from the frame: its source
|
|
390
|
+
// recomputed it — the truth (A18: "the source is whatever recomputes the
|
|
391
|
+
// node"). It stages under the lane's parent whether or not it changed (a
|
|
392
|
+
// confirm is a landing too — the lane's end commits it) and notifies
|
|
393
|
+
// only a correction: an equal truth re-runs nothing (lanes.ts). (As the
|
|
394
|
+
// lane's own work the lane judged it — `laneStage`.)
|
|
395
|
+
if (!t && c === null && e.u & CONFIG_GUESS && GlobalQueue.ft(e, I, A)) ; else if (A) ; else if (r) {
|
|
396
|
+
// L2: a first pass is born held — staged, committed with the
|
|
397
|
+
// transaction, its effect's first run the landing's (A29; `effect()`
|
|
398
|
+
// skips the synchronous first run on a staged value) — when the flush
|
|
399
|
+
// has joined a transaction and either the pass read a held node (it
|
|
400
|
+
// derives from that world — the join merged the node's transaction
|
|
401
|
+
// into the flush's), or — a verdict lane's mount, mainline — read a
|
|
402
|
+
// staging of this flush (the same world before the seam parks it; the
|
|
403
|
+
// mount is not shown ahead of it: #3851), or a pass created it (ruling
|
|
404
|
+
// A: a held pass's children are the transaction's). Any other mount
|
|
405
|
+
// that read a staging does not join by being new (2026-10-07): it
|
|
406
|
+
// publishes and re-derives if the flush parks. A node created outside any pass
|
|
407
|
+
// that read only the committed world (root setup, a mount, an effect
|
|
408
|
+
// callback) is nobody's frame and publishes directly, as does a first
|
|
409
|
+
// pass that runs before anything joins: the pass's input, not a verdict
|
|
410
|
+
// after it. An effect still carrying an uncommitted staged value
|
|
411
|
+
// re-stages: the commit applies the latest pass, not the born-held one.
|
|
412
|
+
if (c !== null) {
|
|
413
|
+
// Lane work: the lane's value — into the slot while the lane has not
|
|
414
|
+
// revealed (its reveal shows it), as its staging once it has (the
|
|
415
|
+
// next reveal promotes it; the screen keeps the revealed value). An
|
|
416
|
+
// effect's value slot is private: the run the lane holds is what
|
|
417
|
+
// shows it — unless the effect was born held, whose first run is the
|
|
418
|
+
// commit's (A29): it re-stages, as below.
|
|
419
|
+
if (n && e.Ae === NOT_PENDING) e.pe = I; else if (n || c.Tt) e.Ae = I; else e.m.Z = I;
|
|
420
|
+
} else if (t ? !((flushTransaction !== null || passTx !== null) && (T || l && e.R & REACTIVE_STAGED_READ || (creatorPass(a)?.R ?? 0) & REACTIVE_JOINED)) : n && e.Ae === NOT_PENDING) {
|
|
421
|
+
// A first pass publishes directly. So does an effect: its value slot
|
|
422
|
+
// is private (only its own run reads it), and the staging round-trip
|
|
423
|
+
// (queuePendingNode + commitPendingNodes) paid per effect on the
|
|
424
|
+
// plain path is pure overhead.
|
|
425
|
+
// NOTE (stage-3, 2026-08-21): a quiet-world MEMO direct-commit was
|
|
426
|
+
// attempted here and REVERTED — memo staging is load-bearing beyond
|
|
427
|
+
// transitions: mid-batch pulls (read-triggered recomputes before
|
|
428
|
+
// sources commit) must see the fresh value while PLAIN reads stay
|
|
429
|
+
// committed until flush (#3009 purity). The pending round-trip is
|
|
430
|
+
// that separation; it cannot be skipped on any path a pull can reach.
|
|
431
|
+
e.pe = I;
|
|
545
432
|
} else {
|
|
546
|
-
e.
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
//
|
|
553
|
-
//
|
|
554
|
-
//
|
|
555
|
-
//
|
|
556
|
-
|
|
557
|
-
e.me = G;
|
|
558
|
-
G.In.push(e);
|
|
559
|
-
}
|
|
560
|
-
if (n) G.Tt.add(e);
|
|
561
|
-
// Under a boundary that has not revealed, a held first value is
|
|
562
|
-
// something not ready under it (#3540): the boundary collects this
|
|
563
|
-
// node as a source and shows its fallback; the source stays
|
|
564
|
-
// collected while born held (CollectionQueue._checkSources) and
|
|
565
|
-
// is released by the commit that initializes it. A boundary that
|
|
566
|
-
// already shows content is not told — it holds like any reader.
|
|
567
|
-
if (underFreshLoadingBoundary(e)) e.T.notify(e, STATUS_PENDING, STATUS_PENDING, new NotReadyError(e));
|
|
433
|
+
e.Ae = I;
|
|
434
|
+
// Born held: in the transaction from birth, so a read of it this
|
|
435
|
+
// tick is a read of its world (the mount-during-a-hold case), not
|
|
436
|
+
// only after the seam parks it.
|
|
437
|
+
if (t) {
|
|
438
|
+
holdNode(e, flushTransaction ?? passTx);
|
|
439
|
+
// Born into the future: no committed value until the landing
|
|
440
|
+
// (`commitPendingNode` initializes it) — every reader of it
|
|
441
|
+
// derives from the future (`read`), an untracked one throws
|
|
442
|
+
// (A19 exc. 1).
|
|
443
|
+
e.G |= STATUS_UNINITIALIZED;
|
|
568
444
|
}
|
|
569
|
-
// A window landing that gets held re-opens the window until the hold
|
|
570
|
-
// commits — the verdict's held-value branch is window-gated (#2990).
|
|
571
|
-
if (E) e.ye = true;
|
|
572
|
-
// A staged sync recompute is a write path like setSignal/asyncWrite,
|
|
573
|
-
// so sync derivations of held sources stay visible to isPending()/latest()
|
|
574
|
-
// (#2831). Both companion writes are transition-scoped (optimistic) and
|
|
575
|
-
// auto-revert/re-derive at commit. Not gated on an active transition:
|
|
576
|
-
// a plain flush can still become a hold after this recompute — an
|
|
577
|
-
// async memo downstream pends and the batch is adopted into a
|
|
578
|
-
// transaction (scheduler.enterTransition) — and nothing re-derives
|
|
579
|
-
// the companion at adoption, so a memo held that way read
|
|
580
|
-
// isPending() false while its held source read true (#3413).
|
|
581
|
-
if (e.C & CONFIG_HAS_COMPANIONS && GlobalQueue.Le !== null) GlobalQueue.Le(e, I);
|
|
582
445
|
}
|
|
583
446
|
// insertSubs only walks _subs (no scheduling of its own), so a
|
|
584
447
|
// subscriber-less node has nothing to notify.
|
|
585
|
-
if (e.
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
// source is this node's own async or an upstream node it derives from
|
|
590
|
-
// synchronously makes no difference — "the source recomputed". The
|
|
591
|
-
// override stays displayed until the commit; the graph moves to the
|
|
592
|
-
// staged truth now (plain channel, lane demoted). Ordering: "a new
|
|
593
|
-
// value from the source" postdates the override — an override written
|
|
594
|
-
// in this same tick (optimisticWrite stamps `_overrideTime`) is the
|
|
595
|
-
// newer intent over whatever this pass derives from the batch's staged
|
|
596
|
-
// inputs, and is not superseded by it.
|
|
597
|
-
else if (o && !l && e.o.Pt !== clock) GlobalQueue.Qe(e, I);
|
|
598
|
-
} else if (o) {
|
|
599
|
-
// Unchanged value (equals the override) recomputed while the override
|
|
600
|
-
// is active: _value may still be stale, so hold the authoritative value
|
|
601
|
-
// for commit on its own transition's schedule — invisibly (A17/A18).
|
|
602
|
-
if (e._e === NOT_PENDING) queuePendingNode(e);
|
|
603
|
-
e._e = I;
|
|
604
|
-
if (E) e.ye = true;
|
|
605
|
-
// see the held branch above (#2990)
|
|
606
|
-
// A confirmation after a supersession restores the override as the
|
|
607
|
-
// graph's value and notifies; a plain confirmation wakes only an
|
|
608
|
-
// authoritative-view reader (until()'s predicate, refresh()'s waiter)
|
|
609
|
-
// that observed this node past its override — "authoritative arrival
|
|
610
|
-
// equal to the override" is exactly the acknowledgment it waits for;
|
|
611
|
-
// A17 silence holds for every ordinary subscriber. Both live in the
|
|
612
|
-
// engine's supersedeOverride.
|
|
613
|
-
GlobalQueue.Qe(e, I);
|
|
614
|
-
} else if (e.ft != T) {
|
|
615
|
-
for (let t = e.u; t !== null; t = t.Pe) {
|
|
616
|
-
insertIntoHeapHeight(t.Ae, queueFor(t.Ae));
|
|
448
|
+
if (e.t !== null) {
|
|
449
|
+
insertSubs(e);
|
|
450
|
+
// Lane work re-staged: the lane's members re-derive as its work.
|
|
451
|
+
if (c !== null) laneDirty(e, c);
|
|
617
452
|
}
|
|
453
|
+
} else if (e.it != N) {
|
|
454
|
+
for (let t = e.t; t !== null; t = t.l) insertIntoHeapHeight(t.o, dirtyQueue);
|
|
618
455
|
}
|
|
619
456
|
// Silent recovery: errored → unchanged value fires no notification, but
|
|
620
457
|
// dependents still holding the propagated error consumed their dirty flag
|
|
621
458
|
// in an errored run and may sit on stale commits (#2949). Changed-value
|
|
622
459
|
// recoveries ride insertSubs above; a comparator throw re-errored the node
|
|
623
460
|
// (el._x?._error re-set), so this only runs on a genuinely clean recovery.
|
|
624
|
-
if (!
|
|
625
|
-
if (
|
|
461
|
+
if (!r && !e.m?.v) {
|
|
462
|
+
if (u !== undefined) settleErroredDependents(e, u);
|
|
626
463
|
// Self-registration (this node's own superseded flight) is the #3181
|
|
627
464
|
// sweep's business below — retiring it here too would walk twice.
|
|
628
|
-
if (
|
|
465
|
+
if (o) for (const t of o) if (t !== e) settlePendingSource(e, t);
|
|
629
466
|
}
|
|
630
467
|
// #3181: a synchronous settle supersedes the old landing callback, so
|
|
631
468
|
// recompute owns its pending-source sweep. An uninitialized node without
|
|
632
469
|
// a replacement source still has no truth to reveal and must stay parked.
|
|
633
|
-
if (
|
|
634
|
-
settlePendingSource(e);
|
|
635
|
-
// The superseded flight will not enter its waiting transactions at
|
|
636
|
-
// landing. Recheck them now so an unchanged value cannot leave their
|
|
637
|
-
// writes parked after the last pending source has settled.
|
|
638
|
-
wakeParked();
|
|
639
|
-
}
|
|
470
|
+
if (_ && !(e.G & (STATUS_PENDING | STATUS_UNINITIALIZED))) settlePendingSource(e);
|
|
640
471
|
}
|
|
641
|
-
// A
|
|
642
|
-
//
|
|
643
|
-
//
|
|
644
|
-
//
|
|
645
|
-
//
|
|
646
|
-
|
|
647
|
-
//
|
|
648
|
-
//
|
|
649
|
-
//
|
|
650
|
-
//
|
|
651
|
-
//
|
|
652
|
-
//
|
|
653
|
-
// not the reporter's stamp: the transaction waiting on it registered it
|
|
654
|
-
// without stamping it. One idle pass per parked transaction; done ones
|
|
655
|
-
// return at re-entry. Effects only — reporters register from render-effect
|
|
656
|
-
// notification (INV-3).
|
|
657
|
-
// (`_depsTail` was reset at the top of the pass; TS keeps that narrowing.)
|
|
658
|
-
const D = e.Je;
|
|
659
|
-
if (n && (a && !(e.S & STATUS_PENDING) || (D === null ? e.Ee !== null : D.Ie !== null))) wakeParked();
|
|
660
|
-
// Dependencies are the committed frame's until it is replaced (A30, #3410; the
|
|
661
|
-
// deps twin of the held children above): a pass that staged its value
|
|
662
|
-
// leaves the previous pass's tail linked for `commitPendingNode` to trim,
|
|
663
|
-
// so a write to a dependency the committed value still derives from
|
|
664
|
-
// reaches this node — and joins its hold if the stage is transaction-held
|
|
665
|
-
// by then (a plain flush decides nothing here: the transaction that holds
|
|
666
|
-
// the pass may open later in the same flush). A pass that published
|
|
667
|
-
// directly (a first pass, a lane's own reveal) trims now. An errored pass
|
|
668
|
-
// (a throw, NotReady included, or a comparator throw above) keeps its full
|
|
472
|
+
// A first pass that went pending where the born-held arm would have held
|
|
473
|
+
// its value (it read a held node, or a verdict lane's mount read a staging,
|
|
474
|
+
// #3851) is born into that transaction without holding it: the hold never
|
|
475
|
+
// waits for a first load (the direction rule), but the answer lands into it
|
|
476
|
+
// while it is live (A29, #3800).
|
|
477
|
+
if (t && c === null && (T || l && e.R & REACTIVE_STAGED_READ) && e.G & STATUS_PENDING) ext(e).Re = flushTransaction ?? passTx;
|
|
478
|
+
// Dependencies are the committed frame's until it is replaced (A30, #3410):
|
|
479
|
+
// a pass that staged its value leaves the previous pass's tail linked for
|
|
480
|
+
// `commitPendingNode` to trim, so a write to a dependency the committed
|
|
481
|
+
// value still derives from reaches this node until the commit. A pass that
|
|
482
|
+
// published directly, or changed nothing, trims now. An errored pass (a
|
|
483
|
+
// throw, NotReady included, or a comparator throw above) keeps its full
|
|
669
484
|
// list as before — `_depsTail` marks where it stopped — and the commit
|
|
670
485
|
// skips it by the same `_error`. An effect's frame is the run its value is
|
|
671
|
-
// applied by, not the value slot (#3438): a
|
|
672
|
-
//
|
|
673
|
-
//
|
|
674
|
-
//
|
|
675
|
-
//
|
|
676
|
-
//
|
|
677
|
-
//
|
|
678
|
-
//
|
|
679
|
-
//
|
|
680
|
-
if (!e.
|
|
681
|
-
if (t ||
|
|
486
|
+
// applied by, not the value slot (#3438): a pass that still owes a run
|
|
487
|
+
// (`_modified`) has not replaced what the last run published, so its tail
|
|
488
|
+
// waits for `runEffect` to trim once the run applies. A pass that changed
|
|
489
|
+
// nothing replaced nothing either (#3469): it cannot know at its tail
|
|
490
|
+
// whether the flush that ran it will park with its inputs held, so the
|
|
491
|
+
// trim waits on the flush's verdict (`heldTrim`: trimmed at the commit,
|
|
492
|
+
// kept at a park — one spurious recompute at most). A creation pass and a
|
|
493
|
+
// tracked effect trim now: their frames are replaceable like a direct
|
|
494
|
+
// commit, and a tracked effect's spurious run would be user-visible.
|
|
495
|
+
if (!e.m?.v && e.Ae === NOT_PENDING && !(n && e.lt)) {
|
|
496
|
+
if (t || n === EFFECT_TRACKED) trimStaleDeps(e); else heldTrim(e);
|
|
682
497
|
}
|
|
683
|
-
//
|
|
684
|
-
//
|
|
685
|
-
|
|
686
|
-
//
|
|
687
|
-
//
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
//
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
// Override-covered holds (hasOverride) always queue: their commit belongs
|
|
696
|
-
// to their own transition's schedule (A18 re-rule) and is unobservable
|
|
697
|
-
// under the override (A17). Revert no longer commits anything, so an
|
|
698
|
-
// unqueued covered hold would leak (INV-7) once the revert clears
|
|
699
|
-
// _transition.
|
|
700
|
-
|
|
701
|
-
// While a pass's result waits on a commit, its children (and `_disposal`)
|
|
702
|
-
// wait with it, and a re-run may tear them down immediately
|
|
703
|
-
// (CONFIG_HELD_CHILDREN, #3404). One exception: a transaction-owned effect
|
|
704
|
-
// recomputed mainline (contested, #3322) published its value directly — a
|
|
705
|
-
// `_pendingValue` left from an earlier held pass is that transaction's,
|
|
706
|
-
// not this one's — so this pass's children are the frame's, and any
|
|
707
|
-
// zombies deferred at the top are superseded on that same frame. Its
|
|
708
|
-
// commit rides the transaction, not this flush: release them here rather
|
|
709
|
-
// than let two generations render at once. A LANE pass is the same case
|
|
710
|
-
// (#3662): it direct-commits ahead of its transaction, so its children are
|
|
711
|
-
// the frame's too. Left flagged for an OLDER frame's zombies, the stamped
|
|
712
|
-
// transaction's same-flush re-run below disposed the lane's freshly built
|
|
713
|
-
// children (an insert's inner effect, its run still queued in the lane)
|
|
714
|
-
// and held their replacements for a commit that never came.
|
|
715
|
-
let F = P && (!t || G !== null || (e.S & STATUS_PENDING) !== 0);
|
|
716
|
-
if (F && (!e.me || o)) queuePendingNode(e); else if (F && (activeTransition === null || l) && !(e.S & (STATUS_PENDING | STATUS_UNINITIALIZED))) {
|
|
717
|
-
F = false;
|
|
718
|
-
if (!h) disposeChildren(e, false, true);
|
|
719
|
-
}
|
|
720
|
-
if (F) e.C |= CONFIG_HELD_CHILDREN; else e.C &= ~CONFIG_HELD_CHILDREN;
|
|
721
|
-
// (A born-held pass IS the transaction's staged view — nothing to refresh.)
|
|
722
|
-
if (e.me && n && activeTransition !== e.me && G === null) {
|
|
723
|
-
// The re-run refreshes the transaction's STAGED view (_pendingValue); the
|
|
724
|
-
// value this pass published in _value belongs to the run that just
|
|
725
|
-
// finished. Keep that ownership, or the effect phase parks a
|
|
726
|
-
// mainline-computed value with the transaction (#3412).
|
|
727
|
-
const t = e.Gt;
|
|
728
|
-
runInTransition(e.me, () => recompute(e));
|
|
729
|
-
e.Gt = t;
|
|
498
|
+
// Observe: the posture the run executed under — lane work (optimistic:
|
|
499
|
+
// overlay, never waste), a transaction's pass (held), or plain.
|
|
500
|
+
if (attrHooks !== null) attrHooks.recomputeEnd(e, t, s, c !== null, flushTransaction !== null || passTx !== null || (e.u & CONFIG_HELD) !== 0, e.Ae !== NOT_PENDING);
|
|
501
|
+
// A staged value, a parked frame (L2: the commit retires it), or status the
|
|
502
|
+
// commit sweep must settle (a pending or uninitialized pass), queues the
|
|
503
|
+
// node for this flush's commit. A first pass queues only when pending or
|
|
504
|
+
// born held: otherwise its value published directly. A queued pass's
|
|
505
|
+
// children are uncommitted (CONFIG_STAGED) until that commit.
|
|
506
|
+
if (e.Ae !== NOT_PENDING || e.m !== null && (e.m.St !== null || e.m.ct !== null) || (e.G & (STATUS_PENDING | STATUS_UNINITIALIZED)) !== 0 && (!t || (e.G & STATUS_PENDING) !== 0)) {
|
|
507
|
+
e.u |= CONFIG_STAGED;
|
|
508
|
+
// Lane work is the lane's to reveal (its seam), not this flush's commit.
|
|
509
|
+
if (c === null) queuePendingNode(e);
|
|
730
510
|
}
|
|
511
|
+
setPassLane(l);
|
|
731
512
|
// Missed-wake reschedule (see the finally above): values this pass read
|
|
732
513
|
// before the nested commit are stale, so run again now that the heap will
|
|
733
514
|
// accept the node. Equality gates stop same-value landings from cascading,
|
|
734
515
|
// and a re-run only latches again if another nested commit changes a dep
|
|
735
516
|
// beneath it — convergent unless deps genuinely keep changing.
|
|
736
|
-
if (
|
|
517
|
+
if (f) {
|
|
737
518
|
enqueueSub(e);
|
|
738
519
|
schedule();
|
|
739
520
|
}
|
|
@@ -746,30 +527,30 @@ function updateIfNecessary(e) {
|
|
|
746
527
|
// (#3037); readers meanwhile serve the values the pass has so far.
|
|
747
528
|
// Never recompute a DISPOSED node either: recompute rewrites _flags and
|
|
748
529
|
// would resurrect it (#2983) — readers serve its last value.
|
|
749
|
-
if (e.
|
|
750
|
-
if (e.
|
|
751
|
-
for (let t = e.
|
|
752
|
-
const n = t.
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
updateIfNecessary(i);
|
|
530
|
+
if (e.R & (REACTIVE_RECOMPUTING_DEPS | REACTIVE_DISPOSED)) return;
|
|
531
|
+
if (e.R & REACTIVE_CHECK) {
|
|
532
|
+
for (let t = e._; t; t = t.p) {
|
|
533
|
+
const n = t.I;
|
|
534
|
+
if (n.re) {
|
|
535
|
+
updateIfNecessary(n);
|
|
756
536
|
}
|
|
757
|
-
if (e.
|
|
537
|
+
if (e.R & REACTIVE_DIRTY) {
|
|
758
538
|
break;
|
|
759
539
|
}
|
|
760
540
|
}
|
|
761
541
|
}
|
|
762
|
-
if (e.
|
|
542
|
+
if (e.R & REACTIVE_DIRTY || e.m?.v && e.Ne < clock && !e.m?.De) {
|
|
763
543
|
recompute(e);
|
|
764
544
|
}
|
|
765
545
|
// The guard above refused an already-disposed node; the recompute it just
|
|
766
546
|
// ran may have disposed it (#3621) — carry the flag, or it comes back alive.
|
|
767
|
-
// The manual-write
|
|
768
|
-
// node's staging is a PROPOSAL,
|
|
769
|
-
//
|
|
770
|
-
//
|
|
771
|
-
//
|
|
772
|
-
|
|
547
|
+
// The manual-write mark is state, not scheduling (#3612, A34 rule B): it
|
|
548
|
+
// says the node's staging is a PROPOSAL, lifted by a pass that re-derives
|
|
549
|
+
// or by the commit — a pull that recomputed nothing must not. Nor may it
|
|
550
|
+
// erase the last pass's verdicts for the landing (a stale reader's
|
|
551
|
+
// REACTIVE_FRAME_READ — a verdict reader pulled by a sibling before the
|
|
552
|
+
// landing lost its re-derivation; the lane reads likewise).
|
|
553
|
+
e.R = e.R & (REACTIVE_SNAPSHOT_STALE | REACTIVE_IN_HEAP | REACTIVE_IN_HEAP_HEIGHT | REACTIVE_ZOMBIE | REACTIVE_DISPOSED | REACTIVE_MANUAL_WRITE | REACTIVE_FRAME_READ | REACTIVE_STAGED_READ | REACTIVE_LANE_READ | REACTIVE_SCREEN_READ | REACTIVE_PROBE_UNANSWERED);
|
|
773
554
|
}
|
|
774
555
|
|
|
775
556
|
function computed(e, t) {
|
|
@@ -777,7 +558,7 @@ function computed(e, t) {
|
|
|
777
558
|
// `in` (not `!== undefined`): an explicit `loadingValue: undefined` on a
|
|
778
559
|
// `T | undefined` node is a real commit #0. The typeof guard tolerates
|
|
779
560
|
// non-object option values that older call shapes force through `as any`.
|
|
780
|
-
const
|
|
561
|
+
const l = t !== null && typeof t === "object" && "loadingValue" in t;
|
|
781
562
|
// Two literals, one per tier, selected at build time (the observe flag is
|
|
782
563
|
// a literal after replacement; the untaken branch is dead code). The observe
|
|
783
564
|
// literal is the prod literal plus its `_name` slot — a slot in the
|
|
@@ -786,159 +567,228 @@ function computed(e, t) {
|
|
|
786
567
|
// (measured: the whole of the observe tier's creation overhead). Keep the
|
|
787
568
|
// two in sync — the dist artifact test pins observe's key set to prod's
|
|
788
569
|
// plus `_name`.
|
|
789
|
-
const
|
|
570
|
+
const s = {
|
|
790
571
|
id: inheritId(t, n, context),
|
|
791
|
-
|
|
572
|
+
u: (n ? CONFIG_TRANSPARENT : 0) | (t?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (!context || t?.lazy ? CONFIG_AUTO_DISPOSE : 0) | (t?.sync ? CONFIG_SYNC : 0) | (t?.ie ? CONFIG_NO_SNAPSHOT : 0) | (
|
|
792
573
|
// Plumbing is an observe-tier notion (a name and records to
|
|
793
574
|
// withhold); the prod literal never carries the bit.
|
|
794
|
-
t?.
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
Ht: null,
|
|
575
|
+
t?.At ? CONFIG_PLUMBING : 0) | (t?.dt ?? 0) | (snapshotCaptureActive && ownerInSnapshotScope(context) ? CONFIG_IN_SNAPSHOT_SCOPE : 0),
|
|
576
|
+
tn: t?.equals ?? isEqual,
|
|
577
|
+
Pe: null,
|
|
578
|
+
U: context?.U ?? defaultContext,
|
|
579
|
+
Ct: 0,
|
|
580
|
+
re: e,
|
|
581
|
+
pe: l ? t.loadingValue : undefined,
|
|
582
|
+
it: 0,
|
|
583
|
+
Ot: undefined,
|
|
584
|
+
Rt: null,
|
|
585
|
+
_: null,
|
|
586
|
+
S: null,
|
|
587
|
+
he: 0,
|
|
588
|
+
t: null,
|
|
589
|
+
Dt: null,
|
|
810
590
|
_parent: context,
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
o: null,
|
|
591
|
+
rn: null,
|
|
592
|
+
Gt: null,
|
|
593
|
+
sn: null,
|
|
594
|
+
R: t?.lazy ? REACTIVE_LAZY : REACTIVE_NONE,
|
|
595
|
+
G: l ? 0 : STATUS_UNINITIALIZED,
|
|
596
|
+
Ne: clock,
|
|
597
|
+
Ae: NOT_PENDING,
|
|
598
|
+
Pt: -1,
|
|
599
|
+
_e: l,
|
|
600
|
+
m: null,
|
|
822
601
|
// The slot is always present (hidden class); plumbing leaves it
|
|
823
602
|
// unset, which `ownerPath` skips.
|
|
824
|
-
_name: t?.
|
|
603
|
+
_name: t?.At ? undefined : t?.name ?? "computed",
|
|
604
|
+
// Observe: the verdict windows this pass entered (verdict.ts) — a
|
|
605
|
+
// slot, so the stamp is never a write after construction.
|
|
606
|
+
en: 0
|
|
825
607
|
};
|
|
826
|
-
if (t?.unobserved) ext(
|
|
827
|
-
setupComputedNode(
|
|
828
|
-
return
|
|
608
|
+
if (t?.unobserved) ext(s).Ft = t.unobserved;
|
|
609
|
+
setupComputedNode(s, t);
|
|
610
|
+
return s;
|
|
829
611
|
}
|
|
830
612
|
|
|
831
613
|
/** Lazily allocate a node's cold extension (ONE shape for signals and
|
|
832
614
|
* computeds — `_x` access stays monomorphic). Installers write through
|
|
833
615
|
* this; hot paths read `el._x?._field` gated by the _config presence bits.
|
|
834
616
|
* Never call ext() just to store a field's default. */ function ext(e) {
|
|
835
|
-
return e.
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
i: null,
|
|
854
|
-
bt: undefined,
|
|
855
|
-
ct: undefined,
|
|
856
|
-
Et: null,
|
|
857
|
-
dt: null,
|
|
858
|
-
xt: undefined
|
|
617
|
+
return e.m ??= {
|
|
618
|
+
De: null,
|
|
619
|
+
Te: null,
|
|
620
|
+
v: undefined,
|
|
621
|
+
Ie: undefined,
|
|
622
|
+
Se: undefined,
|
|
623
|
+
Ft: undefined,
|
|
624
|
+
ot: undefined,
|
|
625
|
+
St: null,
|
|
626
|
+
ct: null,
|
|
627
|
+
_t: null,
|
|
628
|
+
Re: null,
|
|
629
|
+
It: false,
|
|
630
|
+
ht: NOT_PENDING,
|
|
631
|
+
Vt: -1,
|
|
632
|
+
Oe: 0,
|
|
633
|
+
Z: NOT_PENDING,
|
|
634
|
+
T: 0
|
|
859
635
|
};
|
|
860
636
|
}
|
|
861
637
|
|
|
638
|
+
/** A tracked read of a held node (the reader derives from its transaction's
|
|
639
|
+
* world): the reading pass is marked as having read it (born-held decision
|
|
640
|
+
* in `recompute`), and the flush joins the node's transaction — unless the
|
|
641
|
+
* reader is a render effect. A render effect is the frame, not a derivation
|
|
642
|
+
* (rule 3): in that transaction's own flush, or born into it (uninitialized,
|
|
643
|
+
* A29), it reads the staged value and holds nothing of its own; otherwise it
|
|
644
|
+
* reads the committed value instead (`frameRead`). A node born into the
|
|
645
|
+
* future has no committed value: every reader joins it. A first pass is
|
|
646
|
+
* something not ready under a loading boundary that has not shown content:
|
|
647
|
+
* the boundary collects it and shows its fallback, and the pass is the
|
|
648
|
+
* boundary's, not the tick's (A29's boundary exemption, #3540). */ function joinPass(e, t) {
|
|
649
|
+
e.R |= REACTIVE_JOINED;
|
|
650
|
+
if (t.G & STATUS_UNINITIALIZED || e.i !== EFFECT_RENDER) joinPassTx(txOf(t), GlobalQueue.ce?.(e));
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/** A15's stale reader (shared-hole and reveal corollaries): a render effect
|
|
654
|
+
* reading a node held by a transaction that is not the flush's. It is served
|
|
655
|
+
* the committed value — a pending one's too — publishes mainline, and is
|
|
656
|
+
* re-derived after that transaction's landing (`_reruns`,
|
|
657
|
+
* REACTIVE_FRAME_READ) so the frame catches up with it once it is the
|
|
658
|
+
* committed world. Not a frame born into that transaction (held and never
|
|
659
|
+
* committed — created by one of its held passes, A29): that one reads its
|
|
660
|
+
* transaction's values and keeps staging. Not of a flight whose inputs are
|
|
661
|
+
* already visible (CONFIG_INPUTS_PUBLISHED, #3305): its committed value
|
|
662
|
+
* would tear against them, so the reveal observes the flight instead. Once
|
|
663
|
+
* per pass.
|
|
664
|
+
*
|
|
665
|
+
* Lane work is a stale reader too (maintainer, 2026-10-01: a lane sees the
|
|
666
|
+
* screen plus its own guesses). A held write is held because showing it
|
|
667
|
+
* would tear against the rest of its frame; a lane revealing a derivation
|
|
668
|
+
* of it would show it anyway, beside inputs still committed elsewhere on
|
|
669
|
+
* the page. The guess is the one value the user opted into predicting. So
|
|
670
|
+
* a lane pass reading a transaction's held node — its parent's or any
|
|
671
|
+
* other's — derives from the committed value and is re-derived at that
|
|
672
|
+
* landing. The case read-through was built for — a corrected guess whose
|
|
673
|
+
* downstream async must refetch with the truth at once, while another of
|
|
674
|
+
* the parent's flights is still up — needs none of this: the correction
|
|
675
|
+
* dissolves the lane, and the refetching pass is the parent's own, reading
|
|
676
|
+
* the truth as a member (`joinPass`). */ function frameRead(e, t) {
|
|
677
|
+
// Frame readers: a render effect, a verdict reader (CONFIG_VERDICT — it
|
|
678
|
+
// reads the screen like one: `[isPending(x), x()]` never pairs the fresh
|
|
679
|
+
// value with pending, A10, because the reader never sees the fresh value),
|
|
680
|
+
// and lane work (a lane sees the screen plus its own guesses). Not a
|
|
681
|
+
// mount's memo (2026-10-02, considered and reverted): a memo is not a
|
|
682
|
+
// leaf — the transaction's own later passes read it — so a mainline mount's
|
|
683
|
+
// derivations carry the future (A29, born held); only its direct bindings
|
|
684
|
+
// read the screen. Lane work with no committed value yet reads a flight
|
|
685
|
+
// as a mount's memo does: it enters, and the boundary it mounts catches
|
|
686
|
+
// the pending (#3540).
|
|
687
|
+
const n = e.u & CONFIG_VERDICT;
|
|
688
|
+
if ((passLane === null || e.G & STATUS_UNINITIALIZED && t.G & STATUS_PENDING) && !n && (e.i !== EFFECT_RENDER || t.u & CONFIG_INPUTS_PUBLISHED)) return false;
|
|
689
|
+
const l = txOf(t);
|
|
690
|
+
// Lane work and verdict readers are never the transaction's pass, whichever
|
|
691
|
+
// flush runs it: a node T holds re-enters T when it re-runs (`recompute`'s
|
|
692
|
+
// head), and the pass may then read a guess and become the lane's.
|
|
693
|
+
if (l === flushTransaction && passLane === null && !n || e.G & STATUS_UNINITIALIZED && e.u & CONFIG_HELD && txOf(e) === l) return false;
|
|
694
|
+
staleReader(e, l);
|
|
695
|
+
return true;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/** The computed whose pass a creation under `owner` belongs to (a root's is
|
|
699
|
+
* its nearest computed ancestor's), or null outside every pass. */ function creatorPass(e) {
|
|
700
|
+
return e !== null && e.Ht ? e.gt : e;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/** L2 — park the previous pass's frame for this node's commit: the children
|
|
704
|
+
* (already allocated — nothing new) and the `_disposal` list move to the
|
|
705
|
+
* extension, keep reacting from the heap, and die when the commit publishes
|
|
706
|
+
* the frame that replaces them (`commitPendingNode`) or when the owner does. */ function parkChildren(e) {
|
|
707
|
+
markDisposal(e);
|
|
708
|
+
const t = ext(e);
|
|
709
|
+
t.ct = e.Pe;
|
|
710
|
+
t.St = e.sn;
|
|
711
|
+
e.Pe = null;
|
|
712
|
+
e.sn = null;
|
|
713
|
+
e.Ct = 0;
|
|
714
|
+
}
|
|
715
|
+
|
|
862
716
|
/**
|
|
863
717
|
* Build an Effect node with all effect-specific fields baked into a single object literal,
|
|
864
718
|
* so V8 sees the full hidden class shape at construction time. Effects always run in lazy
|
|
865
719
|
* mode (recompute is called explicitly by `effect()`), so we hardcode the lazy bits and skip
|
|
866
720
|
* the auto-dispose CONFIG bit (effect() previously cleared it post-construction).
|
|
867
|
-
*/ function createEffectNode(e, t, n,
|
|
868
|
-
const
|
|
721
|
+
*/ function createEffectNode(e, t, n, l, s) {
|
|
722
|
+
const E = s?.transparent ?? false;
|
|
869
723
|
// Prod and observe boilerplates — see computed() for why the observe tier
|
|
870
724
|
// gets its `_name` as a literal slot rather than a write after the fact.
|
|
871
725
|
// The default label is the node kind (tracked effects relabel their computed
|
|
872
726
|
// in trackedEffect); the wrappers in signals.ts no longer spread a name into
|
|
873
727
|
// the options to get it.
|
|
874
|
-
const
|
|
875
|
-
id: inheritId(
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
Ht: null,
|
|
728
|
+
const u = {
|
|
729
|
+
id: inheritId(s, E, context),
|
|
730
|
+
u: (E ? CONFIG_TRANSPARENT : 0) | (s?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (s?.sync ? CONFIG_SYNC : 0) | (s?.dt ?? 0) | (snapshotCaptureActive && ownerInSnapshotScope(context) ? CONFIG_IN_SNAPSHOT_SCOPE : 0),
|
|
731
|
+
tn: false,
|
|
732
|
+
Pe: null,
|
|
733
|
+
U: context?.U ?? defaultContext,
|
|
734
|
+
Ct: 0,
|
|
735
|
+
re: e,
|
|
736
|
+
pe: undefined,
|
|
737
|
+
it: 0,
|
|
738
|
+
Ot: undefined,
|
|
739
|
+
Rt: null,
|
|
740
|
+
_: null,
|
|
741
|
+
S: null,
|
|
742
|
+
he: 0,
|
|
743
|
+
t: null,
|
|
744
|
+
Dt: null,
|
|
892
745
|
_parent: context,
|
|
893
|
-
|
|
894
|
-
kt: null,
|
|
895
|
-
On: null,
|
|
896
|
-
oe: REACTIVE_LAZY,
|
|
897
|
-
S: STATUS_UNINITIALIZED,
|
|
898
|
-
Ge: clock,
|
|
899
|
-
_e: NOT_PENDING,
|
|
900
|
-
me: null,
|
|
901
|
-
Lt: -1,
|
|
902
|
-
ye: false,
|
|
903
|
-
it: false,
|
|
904
|
-
Mt: undefined,
|
|
905
|
-
wt: t,
|
|
906
|
-
Wt: n,
|
|
907
|
-
qt: undefined,
|
|
908
|
-
He: i,
|
|
746
|
+
rn: null,
|
|
909
747
|
Gt: null,
|
|
910
|
-
|
|
911
|
-
|
|
748
|
+
sn: null,
|
|
749
|
+
R: REACTIVE_LAZY,
|
|
750
|
+
G: STATUS_UNINITIALIZED,
|
|
751
|
+
Ne: clock,
|
|
752
|
+
Ae: NOT_PENDING,
|
|
753
|
+
Pt: -1,
|
|
754
|
+
_e: false,
|
|
755
|
+
lt: false,
|
|
756
|
+
Nt: undefined,
|
|
757
|
+
Ut: t,
|
|
758
|
+
Lt: n,
|
|
759
|
+
kt: undefined,
|
|
760
|
+
i: l,
|
|
761
|
+
m: null,
|
|
762
|
+
_name: s?.name ?? "effect",
|
|
763
|
+
en: 0
|
|
912
764
|
};
|
|
913
765
|
// Effects dispatch status through the SHARED notifier (statusNotifierOf,
|
|
914
766
|
// keyed off _type) — storing it per node forced a full NodeExtension
|
|
915
767
|
// allocation on EVERY effect at creation (an alloc + 19 field stores,
|
|
916
768
|
// +23% effect creation, caught by the creation benches). Only genuinely
|
|
917
769
|
// per-node channels (boundaries) live on _x.
|
|
918
|
-
if (
|
|
919
|
-
setupComputedNode(
|
|
920
|
-
return
|
|
770
|
+
if (s?.unobserved) ext(u).Ft = s.unobserved;
|
|
771
|
+
setupComputedNode(u, lazyOptions);
|
|
772
|
+
return u;
|
|
921
773
|
}
|
|
922
774
|
|
|
923
775
|
/**
|
|
924
776
|
* The shared status notifier for effect nodes, installed once by effect.ts
|
|
925
777
|
* at module evaluation (`this`-dispatched — one function serves every
|
|
926
|
-
* effect, so nodes never store it).
|
|
927
|
-
*
|
|
778
|
+
* effect, so nodes never store it). CARVE 4: the boundaries' per-node
|
|
779
|
+
* channel (`_x._notifyStatus`) went with them; effects are the only
|
|
780
|
+
* display consumers.
|
|
928
781
|
*/ let effectStatusNotify = null;
|
|
929
782
|
|
|
930
783
|
function setEffectStatusNotify(e) {
|
|
931
784
|
effectStatusNotify = e;
|
|
932
785
|
}
|
|
933
786
|
|
|
934
|
-
/** Resolve a node's status notifier:
|
|
935
|
-
*
|
|
936
|
-
*
|
|
937
|
-
*
|
|
938
|
-
|
|
939
|
-
const t = e.o?.h;
|
|
940
|
-
if (t !== undefined) return t;
|
|
941
|
-
return e.He ? effectStatusNotify ?? undefined : undefined;
|
|
787
|
+
/** Resolve a node's status notifier: effect nodes (`_type` — EFFECT_PURE is
|
|
788
|
+
* 0, and only effect literals carry the field) get the shared notifier.
|
|
789
|
+
* Presence doubles as the "display consumer" membership test in the status
|
|
790
|
+
* walks, exactly as the per-node field did when every effect carried one. */ function statusNotifierOf(e) {
|
|
791
|
+
return e.i ? effectStatusNotify ?? undefined : undefined;
|
|
942
792
|
}
|
|
943
793
|
|
|
944
794
|
const lazyOptions = {
|
|
@@ -946,152 +796,93 @@ const lazyOptions = {
|
|
|
946
796
|
};
|
|
947
797
|
|
|
948
798
|
function setupComputedNode(e, t) {
|
|
949
|
-
e.
|
|
950
|
-
const n = context?.
|
|
799
|
+
e.Rt = e;
|
|
800
|
+
const n = context?.Ht ? context.gt : context;
|
|
951
801
|
if (context) linkChild(context, e);
|
|
952
|
-
if (n) e.
|
|
953
|
-
|
|
802
|
+
if (n) e.it = n.it + 1;
|
|
803
|
+
GlobalQueue.xt?.(e);
|
|
954
804
|
!t?.lazy && recompute(e, true);
|
|
955
805
|
if (snapshotCaptureActive && !t?.lazy) {
|
|
956
|
-
if (!(e.
|
|
957
|
-
ext(e).
|
|
958
|
-
e.
|
|
806
|
+
if (!(e.G & STATUS_PENDING) && !(e.u & CONFIG_NO_SNAPSHOT)) {
|
|
807
|
+
ext(e).ot = e.pe === undefined ? NO_SNAPSHOT : e.pe;
|
|
808
|
+
e.u |= CONFIG_HAS_SNAPSHOT;
|
|
959
809
|
snapshotSources.add(e);
|
|
960
810
|
}
|
|
961
811
|
}
|
|
962
812
|
}
|
|
963
813
|
|
|
964
|
-
function signal(e, t
|
|
814
|
+
function signal(e, t) {
|
|
965
815
|
// Prod and observe boilerplates — see computed(). The observe literal adds
|
|
966
816
|
// `_name` and `_owner` (the creating owner, stamped by registerGraph for
|
|
967
817
|
// createSignal nodes so ownerPath can locate signal subjects; null here,
|
|
968
818
|
// and staying null on internal signals — one shape either way).
|
|
969
|
-
const
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
_e: NOT_PENDING,
|
|
980
|
-
me: null,
|
|
981
|
-
Lt: -1,
|
|
982
|
-
o: null,
|
|
819
|
+
const n = {
|
|
820
|
+
tn: t?.equals ?? isEqual,
|
|
821
|
+
u: (t?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (t?.ie ? CONFIG_NO_SNAPSHOT : 0),
|
|
822
|
+
pe: e,
|
|
823
|
+
t: null,
|
|
824
|
+
Dt: null,
|
|
825
|
+
Ne: clock,
|
|
826
|
+
Ae: NOT_PENDING,
|
|
827
|
+
Pt: -1,
|
|
828
|
+
m: null,
|
|
983
829
|
_name: t?.name ?? "signal",
|
|
984
|
-
|
|
830
|
+
H: null
|
|
985
831
|
};
|
|
986
|
-
if (t?.unobserved) ext(
|
|
987
|
-
if (
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
snapshotSources.add(i);
|
|
832
|
+
if (t?.unobserved) ext(n).Ft = t.unobserved;
|
|
833
|
+
if (snapshotCaptureActive && !(n.u & CONFIG_NO_SNAPSHOT)) {
|
|
834
|
+
ext(n).ot = e === undefined ? NO_SNAPSHOT : e;
|
|
835
|
+
n.u |= CONFIG_HAS_SNAPSHOT;
|
|
836
|
+
snapshotSources.add(n);
|
|
992
837
|
}
|
|
993
|
-
return
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
// ---------------------------------------------------------------------------
|
|
997
|
-
// SLOT SIGNALS (store leaves) — the create-floor diet. Store mounts
|
|
998
|
-
// materialize one signal per touched leaf (~13 × rows on dbmon), so per-node
|
|
999
|
-
// allocations are mount bytes: the generic path costs an options object, an
|
|
1000
|
-
// equals closure, an unobserved closure, a NodeExtension to hold it, and
|
|
1001
|
-
// three post-construction expandos (acc/px/pxv → hidden-class transitions).
|
|
1002
|
-
// slotSignal bakes everything into ONE literal: `_host`/`_key` backrefs
|
|
1003
|
-
// replace the closures (equals is a method call — `this` is the node; the
|
|
1004
|
-
// unobserved sweep dispatches CONFIG_SLOT_NODE to one shared hook), and the
|
|
1005
|
-
// store's wrap-cache fields are pre-shaped.
|
|
1006
|
-
/** The shared slot-node unobserved handler — a live binding read directly by
|
|
1007
|
-
* the sweep sites (no wrapper frame, no null check: a CONFIG_SLOT_NODE node
|
|
1008
|
-
* existing implies the store module loaded and registered the hook). */ let slotUnobservedHook;
|
|
1009
|
-
|
|
1010
|
-
/** Install the shared slot-node unobserved handler (store module, once). */ function setSlotUnobserved(e) {
|
|
1011
|
-
slotUnobservedHook = e;
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
/** Push a new node onto its firewall's child chain (the literal already
|
|
1015
|
-
* points `_nextChild` at the old head). Doubly linked so a released leaf
|
|
1016
|
-
* unlinks in O(1) — the chain is walked per mark of the projection and
|
|
1017
|
-
* would otherwise grow by one node per leaf ever read (#3351). */ function linkFirewallChild(e, t) {
|
|
1018
|
-
const n = t.ge;
|
|
1019
|
-
if (n !== null) n.jt = t;
|
|
1020
|
-
ext(e).i = t;
|
|
1021
|
-
e.C |= CONFIG_FW_CHILDREN;
|
|
838
|
+
return n;
|
|
1022
839
|
}
|
|
1023
840
|
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
* projection stops retaining it and its last value. The node keeps its own
|
|
1027
|
-
* `_nextChild` so a walk that is mid-chain on it still terminates. It also
|
|
1028
|
-
* leaves `_companionChildren` (#3503): the companions themselves are
|
|
1029
|
-
* permanent on the node, but the node is unreachable through the store, so
|
|
1030
|
-
* the set would only retain it and its last value. */ function unlinkFirewallChild(e) {
|
|
1031
|
-
const t = e;
|
|
1032
|
-
const n = t.Ne;
|
|
1033
|
-
if (!n) return;
|
|
1034
|
-
const i = t.jt;
|
|
1035
|
-
const l = t.ge;
|
|
1036
|
-
if (i !== null) i.ge = l; else if (n.o.i === t) n.o.i = l;
|
|
1037
|
-
if (l !== null) l.jt = i;
|
|
1038
|
-
t.jt = null;
|
|
1039
|
-
// #3503: the companion set is the last projection-side reference to a
|
|
1040
|
-
// released leaf; leaving it there kept the leaf and its last value alive
|
|
1041
|
-
// for the projection's lifetime after every latest()/isPending() reader
|
|
1042
|
-
// was disposed.
|
|
1043
|
-
n.o.xt?.delete(t);
|
|
841
|
+
function isEqual(e, t) {
|
|
842
|
+
return e === t;
|
|
1044
843
|
}
|
|
1045
844
|
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
845
|
+
/** A store slot node (CONFIG_SLOT_NODE): the whole node in ONE literal — the
|
|
846
|
+
* target and key as back-refs in place of closures (`equals` is a method
|
|
847
|
+
* call, `this` the node; the unobserved sweep dispatches to one shared hook,
|
|
848
|
+
* graph.ts), the store's wrap cache (`px`/`pxv`: the proxy last served for
|
|
849
|
+
* this key and the raw it wrapped — one pointer compare replaces a WeakMap
|
|
850
|
+
* lookup per read) and the accessor verdict (`acc`) pre-shaped. No options
|
|
851
|
+
* object, no NodeExtension, no post-construction expandos. `ownedWrite` is
|
|
852
|
+
* baked in: the setter carries the owned-scope guard; node-level writes are
|
|
853
|
+
* the store's notification machinery. The observe literal adds the label
|
|
854
|
+
* slot (the store relabels it `store.<key>` when the engine is installed). */ function slotSignal(e, t, n, l, s) {
|
|
855
|
+
const E = {
|
|
856
|
+
tn: t,
|
|
857
|
+
u: CONFIG_OWNED_WRITE | CONFIG_SLOT_NODE,
|
|
858
|
+
pe: e,
|
|
859
|
+
t: null,
|
|
860
|
+
Dt: null,
|
|
861
|
+
Ne: clock,
|
|
862
|
+
Ae: NOT_PENDING,
|
|
863
|
+
Pt: -1,
|
|
864
|
+
m: null,
|
|
865
|
+
yt: n,
|
|
866
|
+
bt: l,
|
|
867
|
+
acc: s,
|
|
1066
868
|
px: undefined,
|
|
1067
869
|
pxv: undefined,
|
|
1068
|
-
_name: "signal"
|
|
870
|
+
_name: "signal",
|
|
871
|
+
H: null
|
|
1069
872
|
};
|
|
1070
|
-
|
|
1071
|
-
if (snapshotCaptureActive && !((u?.S ?? 0) & STATUS_PENDING)) {
|
|
1072
|
-
ext(o).ct = e === undefined ? NO_SNAPSHOT : e;
|
|
1073
|
-
o.C |= CONFIG_HAS_SNAPSHOT;
|
|
1074
|
-
snapshotSources.add(o);
|
|
1075
|
-
}
|
|
1076
|
-
return o;
|
|
873
|
+
return E;
|
|
1077
874
|
}
|
|
1078
875
|
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
function optimisticComputed(e, t) {
|
|
1087
|
-
const n = computed(e, t);
|
|
1088
|
-
ext(n).we = NOT_PENDING;
|
|
1089
|
-
n.C |= CONFIG_OPTIMISTIC;
|
|
1090
|
-
return n;
|
|
1091
|
-
}
|
|
876
|
+
/**
|
|
877
|
+
* When set to a component name string, any reactive read that is not inside a nested tracking
|
|
878
|
+
* scope will log a dev-mode warning. Managed automatically by `untrack(fn, strictReadLabel)`.
|
|
879
|
+
*/
|
|
880
|
+
/** The verdict windows' read (verdict.ts), installed while a `latest` or
|
|
881
|
+
* `isPending` window is open and null otherwise: `read` dispatches to it
|
|
882
|
+
* then, and a program that never imports them never has one. */ let verdict = null;
|
|
1092
883
|
|
|
1093
|
-
function
|
|
1094
|
-
|
|
884
|
+
function setVerdict(e) {
|
|
885
|
+
verdict = e;
|
|
1095
886
|
}
|
|
1096
887
|
|
|
1097
888
|
/**
|
|
@@ -1116,11 +907,11 @@ function isEqual(e, t) {
|
|
|
1116
907
|
* );
|
|
1117
908
|
* ```
|
|
1118
909
|
*/ function untrack(e, t) {
|
|
1119
|
-
if (GlobalQueue.
|
|
910
|
+
if (GlobalQueue.Wt === null && !tracking && true) return e();
|
|
1120
911
|
const n = tracking;
|
|
1121
912
|
tracking = false;
|
|
1122
913
|
try {
|
|
1123
|
-
if (GlobalQueue.
|
|
914
|
+
if (GlobalQueue.Wt) return GlobalQueue.Wt(e);
|
|
1124
915
|
return e();
|
|
1125
916
|
} finally {
|
|
1126
917
|
tracking = n;
|
|
@@ -1139,630 +930,327 @@ function isEqual(e, t) {
|
|
|
1139
930
|
* born-held tree would otherwise pull the mounting pass into the hold).
|
|
1140
931
|
*/ let spectating = false;
|
|
1141
932
|
|
|
1142
|
-
/**
|
|
1143
|
-
* Evaluates `fn` untracked, recording nothing on the current `context`: no
|
|
1144
|
-
* untracked-pending re-run link, no transaction entry. For bookkeeping reads
|
|
1145
|
-
* made on behalf of no node — see `spectating`.
|
|
1146
|
-
*/ function spectate(e) {
|
|
1147
|
-
const t = spectating;
|
|
1148
|
-
spectating = true;
|
|
1149
|
-
try {
|
|
1150
|
-
return untrack(e);
|
|
1151
|
-
} finally {
|
|
1152
|
-
spectating = t;
|
|
1153
|
-
}
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
933
|
/**
|
|
1157
934
|
* Bring a computed to a readable state: lazy/disposed nodes are (re)computed;
|
|
1158
|
-
*
|
|
1159
|
-
*
|
|
935
|
+
* `refresh` additionally pulls the node fully up to date so its status flags
|
|
936
|
+
* reflect the current graph.
|
|
1160
937
|
*/ function prepareComputed(e, t) {
|
|
1161
|
-
if (e.
|
|
1162
|
-
e.
|
|
938
|
+
if (e.R & REACTIVE_LAZY) {
|
|
939
|
+
e.R &= ~REACTIVE_LAZY;
|
|
1163
940
|
recompute(e, true);
|
|
1164
|
-
} else if (e.
|
|
941
|
+
} else if (e.R & REACTIVE_DISPOSED) {
|
|
1165
942
|
// Two disposal lifecycles share the flag (#3024). Observation-lifecycle
|
|
1166
943
|
// nodes (CONFIG_AUTO_DISPOSE) are dormant — torn down by unobserved()
|
|
1167
944
|
// when the last subscriber left — and reads reawaken them; that is the
|
|
1168
945
|
// pay-for-use contract. Owner-lifecycle nodes are dead: recomputing would
|
|
1169
946
|
// re-run user code in a torn-down tree (and discard manual writes on
|
|
1170
947
|
// derived-writable signals), so reads return the last committed value.
|
|
1171
|
-
if (e.
|
|
948
|
+
if (e.u & CONFIG_AUTO_DISPOSE) {
|
|
1172
949
|
const t = e._parent;
|
|
1173
950
|
if (t !== null) {
|
|
1174
951
|
// A dormant node was off the chain when its owner died, so the strip
|
|
1175
952
|
// in disposeChildren missed it: freeze here instead (#3024).
|
|
1176
|
-
if (t.
|
|
1177
|
-
e.
|
|
953
|
+
if (t.R & REACTIVE_DISPOSED) {
|
|
954
|
+
e.u &= ~CONFIG_AUTO_DISPOSE;
|
|
1178
955
|
return;
|
|
1179
956
|
}
|
|
1180
|
-
// A zombie never left its chain
|
|
1181
|
-
//
|
|
1182
|
-
|
|
957
|
+
// A zombie never left its chain (the parked frame is drained whole,
|
|
958
|
+
// and its members skip the splice): relinking would put it on the
|
|
959
|
+
// live chain too.
|
|
960
|
+
if (!(e.R & REACTIVE_ZOMBIE)) linkChild(t, e);
|
|
1183
961
|
}
|
|
1184
962
|
recompute(e, true);
|
|
1185
963
|
}
|
|
1186
|
-
} else
|
|
1187
|
-
updateIfNecessary(e);
|
|
1188
|
-
}
|
|
964
|
+
} else ;
|
|
1189
965
|
}
|
|
1190
966
|
|
|
967
|
+
// CARVE 3: until()'s authoritative-view wakeup (notifyAuthoritativeObservers /
|
|
968
|
+
// installAuthoritativeRead), the stale-of-foreign clause (heldFromStale,
|
|
969
|
+
// recordStaleReplay, ownsHold) and the transaction entry on a staged read
|
|
970
|
+
// (enterStagedRead, stagedEntry / born held, underFreshLoadingBoundary) went
|
|
971
|
+
// with the transactions.
|
|
1191
972
|
/**
|
|
1192
|
-
*
|
|
1193
|
-
*
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
/**
|
|
1197
|
-
* read()'s plain-signal fast path as a standalone entry for hot callers
|
|
1198
|
-
* (store traps). Safe to substitute for read() only because the bail
|
|
1199
|
-
* conditions mirror read()'s prelude and fast-path guard exactly: the
|
|
1200
|
-
* latestRead and pendingCheck windows run side-effectful hooks before the
|
|
1201
|
-
* fast path, `_fn` nodes need prepareComputed, and firewall / override /
|
|
1202
|
-
* snapshot / transition / lane / dev-strictRead state all take the full
|
|
1203
|
-
* resolution. Anything slow returns READ_SLOW; the caller then calls read().
|
|
1204
|
-
*/
|
|
1205
|
-
/**
|
|
1206
|
-
* Wake only authoritative-view readers (until() predicates) subscribed to `el`.
|
|
1207
|
-
* The A17-silent ack paths — an authoritative arrival equal to the active
|
|
1208
|
-
* override — use this so the predicate re-evaluates without re-firing
|
|
1209
|
-
* ordinary subscribers whose visible (override) value did not change.
|
|
1210
|
-
* Pay-for-use: reached through GlobalQueue._notifyAuthoritativeObservers,
|
|
1211
|
-
* installed at first until() call — apps that never use until() shake it.
|
|
1212
|
-
*/ function notifyAuthoritativeObservers(e) {
|
|
1213
|
-
for (let t = e.u; t !== null; t = t.Pe) {
|
|
1214
|
-
const e = t.Ae;
|
|
1215
|
-
if (!(e.C & CONFIG_AUTHORITATIVE_READ)) continue;
|
|
1216
|
-
// Missed-wake latch (#3037), same contract as insertSubs: the reader may
|
|
1217
|
-
// itself have pulled this recompute (updateIfNecessary from its own
|
|
1218
|
-
// read), and the heap refuses RECOMPUTING nodes — latch so recompute's
|
|
1219
|
-
// tail reschedules it with the staged value visible.
|
|
1220
|
-
if (e.oe & REACTIVE_RECOMPUTING_DEPS && t.Ze === e.We && t !== e.Je) e.oe |= REACTIVE_MISSED_WAKE;
|
|
1221
|
-
enqueueSub(e);
|
|
1222
|
-
}
|
|
1223
|
-
schedule();
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
/** Installs the authoritative-reader wakeup hook. Idempotent; called by every
|
|
1227
|
-
* creator of a CONFIG_AUTHORITATIVE_READ computation — until() and refresh() —
|
|
1228
|
-
* before its first read (same late-binding contract as the optimistic engine;
|
|
1229
|
-
* the gating bit is only ever set by such a read, so the `!` call sites are
|
|
1230
|
-
* safe once every setter installs, #3303). */ function installAuthoritativeRead() {
|
|
1231
|
-
if (GlobalQueue.Xt === null) GlobalQueue.Xt = notifyAuthoritativeObservers;
|
|
1232
|
-
}
|
|
1233
|
-
|
|
1234
|
-
/**
|
|
1235
|
-
* Stale-reader term of the value selections below: a render effect reading a
|
|
1236
|
-
* node some OTHER live transaction has staged sees the committed value. The
|
|
1237
|
-
* commit is silent — the staging walk was the notification — so a reader
|
|
1238
|
-
* that linked AFTER that walk (an effect created during the hold, a store
|
|
1239
|
-
* key first read under it) would show the old value past the reveal: record
|
|
1240
|
-
* it for the transaction's commit replay (the `_gatedSubs` contract lanes
|
|
1241
|
-
* already use). An effect the transaction itself computed re-derives at its
|
|
1242
|
-
* commit on its own (parked run, or the contested re-derive, #3322) and is
|
|
1243
|
-
* not recorded — replaying it too would publish the frame twice.
|
|
1244
|
-
*
|
|
1245
|
-
* Flight twin (the pending-branch carve-out): the reader is served the
|
|
1246
|
-
* node's committed, pre-flight value and now observes that flight — A15:
|
|
1247
|
-
* async work observed by a reader settles as one unit with the writes that
|
|
1248
|
-
* asked it — so it joins the transaction's reporters for the node. The
|
|
1249
|
-
* reporter the transaction recorded when the flight started may be gone (a
|
|
1250
|
-
* keyed remount disposed it, #3374); a completion check that found no live
|
|
1251
|
-
* reporter committed the writes ahead of the answer, tearing the new
|
|
1252
|
-
* reader's frame (`Count: 1` beside `Details: 0`). Joins an entry the
|
|
1253
|
-
* transaction already holds; a flight nobody had observed yet has none (the
|
|
1254
|
-
* reader is its first observer — a conditional that just revealed it, #3458)
|
|
1255
|
-
* and is notified up the reader's own queue chain under that transaction,
|
|
1256
|
-
* the one sanctioned registration site (INV-3): a collecting boundary above
|
|
1257
|
-
* the reader consumes it as it would any pending, an unboundaried reader
|
|
1258
|
-
* opens the entry — and the transaction, judged complete on its other
|
|
1259
|
-
* flights, revealed the inputs beside the reader's pre-flight value
|
|
1260
|
-
* otherwise (`Count: 1 | A: 1` beside `B: 0`). A staged signal or a settled
|
|
1261
|
-
* node registers nothing. Every reporter dies with its reader
|
|
1262
|
-
* (reporterBlocksSource: the read linked it as a dep). The node's own entry
|
|
1263
|
-
* is the only one that can matter: a chain's intermediate memo is re-pulled
|
|
1264
|
-
* by the read (updateIfNecessary's retry) and enters the transaction, so the
|
|
1265
|
-
* reader holds through the normal path; a node with its own flight that is
|
|
1266
|
-
* also pending on an upstream re-ask blocks through that flight until it
|
|
1267
|
-
* lands, and its landing re-runs the reader into the normal path.
|
|
1268
|
-
*/
|
|
1269
|
-
/** The replay half of the stale-of-foreign clause (A15 / A26): a stale reader
|
|
1270
|
-
* served the committed value because `txn` holds what it read re-runs at
|
|
1271
|
-
* txn's commit, when the value it was denied becomes the frame — unless its
|
|
1272
|
-
* own last value already came from that transaction. One registration for
|
|
1273
|
-
* the node path (heldFromStale) and the store's backing paths, which have
|
|
1274
|
-
* no node to carry the hold (heldFromReader, the adoption hold view). */ function recordStaleReplay(e, t) {
|
|
1275
|
-
const n = t.Gt;
|
|
1276
|
-
if (n == null || currentTransition(n) !== e) e.Tt.add(t);
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
/**
|
|
1280
|
-
* The ownership relation (DESIGN-CONSOLIDATION §6, ruled 2026-09-17): is
|
|
1281
|
-
* `hold` part of the running pass's world? A plain reader's world is the
|
|
1282
|
-
* transaction it runs under, through merges. A lane reader's world is its
|
|
1283
|
-
* lane AND the transition that owns the lane — the one asymmetry between a
|
|
1284
|
-
* lane and a separate transaction (a lane sees what lands from its parent as
|
|
1285
|
-
* its own; a separate transaction would wait for the parent to settle) —
|
|
1286
|
-
* see `ownsLane` in lanes.ts, built on this. One relation for the
|
|
1287
|
-
* stale-of-foreign clause (heldFromStale), the lane arm (readsHeldCommitted)
|
|
1288
|
-
* and the store's backing holds (foreignHold); `serve` has no lane arm of
|
|
1289
|
-
* its own, the lane's extra visibility lives here.
|
|
1290
|
-
*/ function ownsHold(e) {
|
|
1291
|
-
return activeTransition !== null && currentTransition(e) === currentTransition(activeTransition);
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
function heldFromStale(e, t) {
|
|
1295
|
-
const n = e.me;
|
|
1296
|
-
if (n === null || ownsHold(n)) return false;
|
|
1297
|
-
const i = currentTransition(n);
|
|
1298
|
-
recordStaleReplay(i, t);
|
|
1299
|
-
const l = i.ae.get(e);
|
|
1300
|
-
if (l) l.add(t); else if (e.S & STATUS_PENDING) runInTransition(i, () => t.T.notify(t, STATUS_PENDING, STATUS_PENDING, e.o._));
|
|
1301
|
-
return true;
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
/**
|
|
1305
|
-
* A tracked computation about to be served a live transaction's staged value
|
|
1306
|
-
* derives from that transaction's world, so it enters the transaction and
|
|
1307
|
-
* this pass's result is held with it (A29, #3408). The write side (`setSignal`)
|
|
1308
|
-
* and a stamped node's recompute already enter; a reader that only now
|
|
1309
|
-
* starts reading the held node — a conditional memo whose branch flipped —
|
|
1310
|
-
* was the gap: it published a value derived from the staged world into the
|
|
1311
|
-
* mainline frame. Not for a probe (`isPending(() => x())` observes, it does
|
|
1312
|
-
* not derive) and a no-op for the ambient batch (`_transition` null) or the
|
|
1313
|
-
* transaction already active.
|
|
1314
|
-
*/
|
|
1315
|
-
/** The transaction a pass running OUTSIDE a flush was served a staged value
|
|
1316
|
-
* from (A29, creation-time form). Inside a flush the pass enters through
|
|
1317
|
-
* initTransition; outside one — a memo or effect created from mainline code
|
|
1318
|
-
* while a hold is live — entering would leave `activeTransition` and the
|
|
1319
|
-
* batch pointed at the transaction for the rest of the synchronous block,
|
|
1320
|
-
* so an unrelated write made after the mount was held with someone else's
|
|
1321
|
-
* action. The entry is the pass's alone: recompute stages the node into the
|
|
1322
|
-
* transaction (born held) and mainline is never touched.
|
|
1323
|
-
*
|
|
1324
|
-
* A pass under a FRESH loading boundary takes this path inside a flush too
|
|
1325
|
-
* (#3540). Born held is right for a plain memo or effect: published, its
|
|
1326
|
-
* value would tear the frame. A boundary that has not revealed yet is the
|
|
1327
|
-
* exception by definition — its job is to catch what is not ready under it
|
|
1328
|
-
* rather than let it hold: the pass is staged into the transaction as
|
|
1329
|
-
* above, and the boundary is told (recompute's born-held arm notifies it
|
|
1330
|
-
* with a `NotReadyError` sourced at the node) so it shows its fallback now
|
|
1331
|
-
* and reveals the staged result at the commit.
|
|
1332
|
-
* Entering instead adopted the whole flush into the hold — a `Show` that
|
|
1333
|
-
* flipped mainline to mount a `Loading` over a held value waited for the
|
|
1334
|
-
* hold with it. A boundary that already shows content keeps the entering
|
|
1335
|
-
* path: it has content to keep, and holds like any reader. */ let stagedEntry = null;
|
|
1336
|
-
|
|
1337
|
-
/** Is `el` routed to a loading boundary that still shows its fallback — the
|
|
1338
|
-
* nearest pending-collecting queue up its chain is uninitialized? (Measured
|
|
1339
|
-
* 2026-09-18: relocating the walk behind a `GlobalQueue` slot installed by
|
|
1340
|
-
* boundaries.ts saved ~8 B on the core floor and cost boundary-using apps
|
|
1341
|
-
* 50–70 B — the indirection's tokens outweigh the walk. Kept inline.) */ function underFreshLoadingBoundary(e) {
|
|
1342
|
-
for (let t = e.T; t !== null; t = t._parent) if (t.te & STATUS_PENDING) return !t.H;
|
|
1343
|
-
return false;
|
|
1344
|
-
}
|
|
1345
|
-
|
|
1346
|
-
function enterStagedRead(e, t = e.me) {
|
|
1347
|
-
if (!t || t === activeTransition || pendingCheckActive) return;
|
|
1348
|
-
// A companion (the latest() shadow, the isPending() verdict signal) is the
|
|
1349
|
-
// engine's mirror of the flushed world — reading it, or being it, is an
|
|
1350
|
-
// observation, not a derivation from the hold: latest(x) never enters x's
|
|
1351
|
-
// transaction, and the shadow's own pass never enters either (it would
|
|
1352
|
-
// flip activeTransition under the reader that pulled it). (`el` is null for
|
|
1353
|
-
// a store backing served under a hold — no node, the transaction is the
|
|
1354
|
-
// fold's.)
|
|
1355
|
-
if (e?.o?.$n || context?.o?.$n) return;
|
|
1356
|
-
// A bookkeeping read (`spectate`) is nobody's derivation: it compares or
|
|
1357
|
-
// probes, and enters nothing — the boundary priming read of a born-held
|
|
1358
|
-
// tree must not enter the creator's pass into the hold (#3540).
|
|
1359
|
-
if (spectating) return;
|
|
1360
|
-
// Verdict machinery (GlobalQueue._verdictPull: companion creation and the
|
|
1361
|
-
// latest()/isPending() pulls — the latest() shadow is created before it is
|
|
1362
|
-
// marked optimistic, so the bit alone cannot tell) and optimistic nodes
|
|
1363
|
-
// (own lane posture, A31) read staged truth by design; they keep the
|
|
1364
|
-
// entering path.
|
|
1365
|
-
// (`context` is non-null here: every caller selected a value for a reader.)
|
|
1366
|
-
const n = context;
|
|
1367
|
-
const i = activeTransition === null && !globalQueue.$t;
|
|
1368
|
-
// Verdict pulls are observations, not derivations: a latest() /
|
|
1369
|
-
// isPending() call from mainline must never enter a transaction (it
|
|
1370
|
-
// would capture the rest of the caller's synchronous block).
|
|
1371
|
-
if (i && GlobalQueue.Tn) return;
|
|
1372
|
-
if (n.oe & REACTIVE_RECOMPUTING_DEPS && !(n.C & CONFIG_OPTIMISTIC) && (stagedEntry === null || stagedEntry === t) && (i || !GlobalQueue.Tn && underFreshLoadingBoundary(n))) {
|
|
1373
|
-
stagedEntry = t;
|
|
1374
|
-
return;
|
|
1375
|
-
}
|
|
1376
|
-
globalQueue.initTransition(t);
|
|
1377
|
-
}
|
|
1378
|
-
|
|
1379
|
-
/**
|
|
1380
|
-
* Rule 1 (value selection), the full arm: does this reader see a STAGED
|
|
1381
|
-
* node's COMMITTED value? One implementation of the rule the fast paths
|
|
1382
|
-
* (readNodeFast, read's fast block) carry as their trivial ternary and that
|
|
1383
|
-
* every slow site — read's tail, the store's backing selection, the lane and
|
|
1384
|
-
* verdict arms — used to restate by hand (docs/DESIGN-CONSOLIDATION.md, move 3b). In order:
|
|
973
|
+
* Rule 1 (value selection): does this reader see a STAGED node's COMMITTED
|
|
974
|
+
* value? One implementation of the rule the fast paths (read's fast block)
|
|
975
|
+
* carry as their trivial ternary. In order:
|
|
1385
976
|
* - no reader at all (an untracked read) — the committed frame;
|
|
1386
|
-
* - a reader under an optimistic lane the engine says reads committed
|
|
1387
|
-
* (laneReadsCommitted: another lane's hold, #3460);
|
|
1388
977
|
* - nothing staged;
|
|
1389
978
|
* - a children-forbidden reader (createTrackedEffect / onSettled: the frame,
|
|
1390
|
-
* never the graph — A32)
|
|
1391
|
-
*
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
* - HELD truth (#3164, CONFIG_HELD_TRUTH) read by a LANE pass: staged
|
|
1395
|
-
* confirming truth — fold-staged onto an armed family, or entangle-stolen
|
|
1396
|
-
* by an awaited until() — is masked from lane passes only, owning
|
|
1397
|
-
* transaction or not. A lane applies its frame display-ahead at the park,
|
|
1398
|
-
* so a lane pass served the truth would paint the confirmation beside the
|
|
1399
|
-
* optimism it confirms (`saving=true` beside the saved row — the #3164
|
|
1400
|
-
* tear); it keeps committed and is re-run by the reveal's post-revert
|
|
1401
|
-
* wake. Every other deriving reader falls through to A29 below: the truth
|
|
1402
|
-
* is a staged value like any other, and the pass that derives from it is
|
|
1403
|
-
* held with it — including the retaining transaction's own passes, which
|
|
1404
|
-
* a superseded override already hands the truth (#3568: masking them
|
|
1405
|
-
* composed the landed `length` with rows still masked to committed).
|
|
1406
|
-
* latest() and authoritative readers (until()'s predicate) tunnel
|
|
1407
|
-
* through — the tunnel that keeps the hold deadlock-free.
|
|
1408
|
-
* False means the reader derives from the staged value and enters its
|
|
1409
|
-
* transaction (enterStagedRead, A29).
|
|
1410
|
-
*/ function readerSeesCommitted(e, t, n, i) {
|
|
1411
|
-
return !!(!t || currentOptimisticLane !== null && GlobalQueue.An(e, n, t) || e._e === NOT_PENDING || t.C & CONFIG_CHILDREN_FORBIDDEN || stale && !i && heldFromStale(e, t) || e.C & CONFIG_HELD_TRUTH && currentOptimisticLane !== null && !latestReadActive && !(t.C & CONFIG_AUTHORITATIVE_READ));
|
|
979
|
+
* never the graph — A32).
|
|
980
|
+
* False means the reader derives from the staged value.
|
|
981
|
+
*/ function readerSeesCommitted(e, t) {
|
|
982
|
+
return !!(!t || e.Ae === NOT_PENDING || t.u & CONFIG_CHILDREN_FORBIDDEN);
|
|
1412
983
|
}
|
|
1413
984
|
|
|
1414
|
-
/** A28 — set when a node is staged (queuePendingNode)
|
|
1415
|
-
*
|
|
1416
|
-
*
|
|
1417
|
-
*
|
|
1418
|
-
*
|
|
985
|
+
/** A28 — set when a node is staged (queuePendingNode) OUTSIDE a flush;
|
|
986
|
+
* cleared when the next flush begins. The read sites test this one module
|
|
987
|
+
* boolean instead of `globalQueue._running`: inside a flush it is false and
|
|
988
|
+
* the A28 arm costs nothing; outside, only a tick with unflushed writes pays
|
|
989
|
+
* the staged-node check. */ let unflushedStaged = false;
|
|
1419
990
|
|
|
1420
991
|
function markUnflushedStaged() {
|
|
1421
992
|
unflushedStaged = true;
|
|
1422
993
|
}
|
|
1423
994
|
|
|
1424
|
-
/** A28 — a write becomes visible at flush. Outside a flush, a node holding
|
|
1425
|
-
*
|
|
1426
|
-
*
|
|
1427
|
-
*
|
|
1428
|
-
*
|
|
1429
|
-
*
|
|
1430
|
-
|
|
995
|
+
/** A28 — a write becomes visible at flush. Outside a flush, a node holding a
|
|
996
|
+
* staged value was written since the last flush: staging commits at flush
|
|
997
|
+
* end, so nothing else leaves a node in this state. Inside a flush the rule
|
|
998
|
+
* does not apply (A28 (4): promoted within the round). The value an
|
|
999
|
+
* unflushed node serves is its committed value, or NOT_PENDING when nothing
|
|
1000
|
+
* is unflushed. Exempt: promoted writes (A28 (4): a write issued inside a
|
|
1001
|
+
* recompute is promoted at that recompute's end — boundary and loading
|
|
1002
|
+
* machinery, signals declared for in-computation writes). */ function unflushedValue(e) {
|
|
1003
|
+
if (globalQueue.ee || e.Ae === NOT_PENDING || e.u & CONFIG_PROMOTED) return NOT_PENDING;
|
|
1004
|
+
// A held node (L2) carries a staged value between flushes too, but it was
|
|
1005
|
+
// flushed — and parked. Not an unflushed write — unless rewritten since:
|
|
1006
|
+
// then the flushed staging is what the world sees until the next flush.
|
|
1007
|
+
if (e.u & CONFIG_HELD) return e.m.Vt === clock ? e.m.ht : NOT_PENDING;
|
|
1008
|
+
return e.pe;
|
|
1431
1009
|
}
|
|
1432
1010
|
|
|
1433
|
-
/**
|
|
1434
|
-
*
|
|
1435
|
-
*
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
// Ambient, or adopted by a transaction before any flush carried the staging
|
|
1443
|
-
// (CONFIG_ADOPTED_UNFLUSHED): nothing flushed is staged — the committed
|
|
1444
|
-
// value answers (the caller's notion of committed: a store node's backing).
|
|
1445
|
-
// A held node: unflushed only if rewritten since the last flush (stash).
|
|
1446
|
-
if (e.me === null || e.C & CONFIG_ADOPTED_UNFLUSHED) return t;
|
|
1447
|
-
return e.o === null ? NOT_PENDING : e.o.Ut;
|
|
1011
|
+
/** A28: a held node rewritten outside a flush keeps the staging the last
|
|
1012
|
+
* flush left (`_flushed`) until the flush that carries the rewrite — the
|
|
1013
|
+
* stash is stamped with the clock, which that flush advances. */ function stashFlushed(e) {
|
|
1014
|
+
const t = e.m;
|
|
1015
|
+
if (t.Vt !== clock) {
|
|
1016
|
+
t.ht = e.Ae;
|
|
1017
|
+
t.Vt = clock;
|
|
1018
|
+
unflushedStaged = true;
|
|
1019
|
+
}
|
|
1448
1020
|
}
|
|
1449
1021
|
|
|
1450
|
-
/** Held nodes rewritten since the last flush (setSignal); the flush clears
|
|
1451
|
-
* their stash — from then on latest() answers with the rewrite. */ const unflushedRewrites = [];
|
|
1452
|
-
|
|
1453
1022
|
/** Nodes written inside a creation-time recompute (CONFIG_PROMOTED). */ const promotedWrites = [];
|
|
1454
1023
|
|
|
1455
|
-
/** A28 (5): an optimistic write becomes the ACTIVE override at the flush that
|
|
1456
|
-
* carries it. `_overrideTime` is stamped with `clock` at the write and `clock`
|
|
1457
|
-
* advances after every flush, so "this tick, outside a flush" is unflushed. */ function unflushedOverride(e) {
|
|
1458
|
-
// Companions are optimistic signals written by the engine (see unflushed).
|
|
1459
|
-
return !globalQueue.$t && e.o?.Pt === clock && !e.o?.$n;
|
|
1460
|
-
}
|
|
1461
|
-
|
|
1462
|
-
/** Active optimistic override on an armed node (an armed slot idles at
|
|
1463
|
-
* NOT_PENDING; undefined = unarmed plain node). The writer's own channels —
|
|
1464
|
-
* the draft, `in`/keys inside the setter — compose on this regardless of
|
|
1465
|
-
* flush state. */ function hasActiveOverride(e) {
|
|
1466
|
-
const t = e.o;
|
|
1467
|
-
return t !== null && t.we !== undefined && t.we !== NOT_PENDING;
|
|
1468
|
-
}
|
|
1469
|
-
|
|
1470
|
-
/** The override a READER sees: installed, and carried by a flush (A28 (5) —
|
|
1471
|
-
* an optimistic write is a write; until its flush no reader sees it). One
|
|
1472
|
-
* implementation for read()'s override arm, the verdict channels
|
|
1473
|
-
* (latestRead, computePendingState) and the store's selection
|
|
1474
|
-
* (docs/DESIGN-CONSOLIDATION.md, move 3b). */ function visibleOverride(e) {
|
|
1475
|
-
return hasActiveOverride(e) && !unflushedOverride(e);
|
|
1476
|
-
}
|
|
1477
|
-
|
|
1478
1024
|
/** A derivation served the committed value because of an unflushed write
|
|
1479
1025
|
* (A28) must run again in the flush that carries it — the late-linker case
|
|
1480
1026
|
* (#3337's reason to defer the walk): it linked after the write walked. */ function markLateLinker(e) {
|
|
1481
1027
|
// The pass's own tail re-enqueues on this latch (recompute's finally) —
|
|
1482
1028
|
// a direct enqueue here would be wiped by the pass's flag reset.
|
|
1483
|
-
e.
|
|
1029
|
+
e.R |= REACTIVE_MISSED_WAKE;
|
|
1484
1030
|
return true;
|
|
1485
1031
|
}
|
|
1486
1032
|
|
|
1487
|
-
/** Companion-bearing nodes written outside a flush (setSignal); the flush
|
|
1488
|
-
* that carries their writes re-syncs their companions (A28). */ const unflushedCompanions = [];
|
|
1489
|
-
|
|
1490
1033
|
function resyncUnflushedCompanions() {
|
|
1491
1034
|
unflushedStaged = false;
|
|
1492
|
-
// Length-guarded: the common flush has nothing here, and must allocate nothing.
|
|
1493
1035
|
// Length-guarded: the common flush has nothing here and allocates nothing.
|
|
1494
|
-
if (
|
|
1495
|
-
for (const e of
|
|
1496
|
-
unflushedRewrites.length = 0;
|
|
1497
|
-
}
|
|
1498
|
-
if (promotedWrites.length !== 0) {
|
|
1499
|
-
for (const e of promotedWrites) e.C &= ~CONFIG_PROMOTED;
|
|
1036
|
+
if (promotedWrites.length !== 0) {
|
|
1037
|
+
for (const e of promotedWrites) e.u &= ~CONFIG_PROMOTED;
|
|
1500
1038
|
promotedWrites.length = 0;
|
|
1501
1039
|
}
|
|
1502
|
-
if (unflushedCompanions.length !== 0) {
|
|
1503
|
-
for (const e of unflushedCompanions) GlobalQueue.Le(e, e._e !== NOT_PENDING ? e._e : e.ce);
|
|
1504
|
-
unflushedCompanions.length = 0;
|
|
1505
|
-
}
|
|
1506
1040
|
}
|
|
1507
1041
|
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
//
|
|
1517
|
-
//
|
|
1518
|
-
//
|
|
1519
|
-
//
|
|
1520
|
-
//
|
|
1521
|
-
//
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
//
|
|
1525
|
-
|
|
1526
|
-
return !t || e._e === NOT_PENDING || t.C & CONFIG_CHILDREN_FORBIDDEN || stale && heldFromStale(e, t) ? e.ce : (enterStagedRead(e),
|
|
1527
|
-
e._e);
|
|
1042
|
+
/** A tracked read's pull of a computed: bring it current when the flush has
|
|
1043
|
+
* reached its height (or the reader is a fresh-pull waiter), and sit the
|
|
1044
|
+
* reader above it. */ function pullComputed(e, t) {
|
|
1045
|
+
if (e.it >= dirtyQueue.ut) {
|
|
1046
|
+
markNode(t);
|
|
1047
|
+
markHeap(dirtyQueue);
|
|
1048
|
+
updateIfNecessary(e);
|
|
1049
|
+
}
|
|
1050
|
+
// Fresh-pull readers (awaitable refresh's waiter) recompute a dirty
|
|
1051
|
+
// source inline even when the height gate defers to the flush: the
|
|
1052
|
+
// waiter must park on the re-ask's window (or serve its sync answer),
|
|
1053
|
+
// never read the PRE-re-ask value as settled. Self-guarded: a clean
|
|
1054
|
+
// node no-ops and updateIfNecessary refuses disposed nodes (#2983) —
|
|
1055
|
+
// a dead target serves its last value, which is already quiescent.
|
|
1056
|
+
else if (t.u & CONFIG_FRESH_READ) updateIfNecessary(e);
|
|
1057
|
+
const n = e.it;
|
|
1058
|
+
// parent check is shallow, might need to be recursive
|
|
1059
|
+
if (n >= t.it && e._parent !== t) t.it = n + 1;
|
|
1528
1060
|
}
|
|
1529
1061
|
|
|
1530
1062
|
function read(e) {
|
|
1531
|
-
// Handle latest() mode: read from _latestValueComputed
|
|
1532
|
-
// Checked before isPending so that isPending(() => latest(x)) checks
|
|
1533
|
-
// the _pendingSignal of _latestValueComputed (async in flight) rather
|
|
1534
|
-
// than the original node (which stays "pending" while held in a transition).
|
|
1535
|
-
if (latestReadActive) return GlobalQueue.Cn(e);
|
|
1536
1063
|
let t = context;
|
|
1537
|
-
if (t?.
|
|
1064
|
+
if (t?.Ht) t = t.gt;
|
|
1538
1065
|
const n = e;
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
//
|
|
1542
|
-
//
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1066
|
+
// CARVE 1: `owner` was `el._firewall || el` — a store leaf's status,
|
|
1067
|
+
// height and error were read off its projection computed. (Typed loosely
|
|
1068
|
+
// as the `||` expression was: a signal's `_statusFlags`/`_height` read as
|
|
1069
|
+
// undefined and mask to 0 on every site below.)
|
|
1070
|
+
const l = e;
|
|
1071
|
+
if (typeof n.re === "function") prepareComputed(e);
|
|
1072
|
+
// The verdict windows (`latest`, `isPending`; verdict.ts): one cold path,
|
|
1073
|
+
// off this one, present only while a window is open.
|
|
1074
|
+
if (verdict !== null) return verdict(e, t);
|
|
1075
|
+
if (!n.re && e.m?.ot === undefined && !snapshotCaptureActive && (!unflushedStaged || e.Ae === NOT_PENDING) && // A28, see readNodeFast
|
|
1076
|
+
!(e.u & CONFIG_OVERRIDE) && // lanes: the slow path's one arm
|
|
1550
1077
|
true) {
|
|
1551
|
-
if (t
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
// Fresh-pull readers (awaitable refresh's waiter) recompute a dirty
|
|
1567
|
-
// source inline even when the height gate defers to the flush: the
|
|
1568
|
-
// waiter must park on the re-ask's window (or serve its sync answer),
|
|
1569
|
-
// never read the PRE-re-ask value as settled. Self-guarded: a clean
|
|
1570
|
-
// node no-ops and updateIfNecessary refuses disposed nodes (#2983) —
|
|
1571
|
-
// a dead target serves its last value, which is already quiescent.
|
|
1572
|
-
else if (t.C & CONFIG_FRESH_READ) updateIfNecessary(l);
|
|
1573
|
-
const i = l.ft;
|
|
1574
|
-
// parent check is shallow, might need to be recursive
|
|
1575
|
-
if (i >= t.ft && e._parent !== t) {
|
|
1576
|
-
t.ft = i + 1;
|
|
1078
|
+
if (t) {
|
|
1079
|
+
if (tracking) link(e, t);
|
|
1080
|
+
// L2: a pass that reads something already in the future joins it —
|
|
1081
|
+
// the one mechanism behind "a write that reaches the future joins
|
|
1082
|
+
// it". Tracked or not (`untrack` is about dependencies, not about
|
|
1083
|
+
// which world a pass derives from — posture-store-parity S4/S5): a
|
|
1084
|
+
// derivation of a held write published mainline would tear. Frame
|
|
1085
|
+
// readers (children-forbidden, A32) see committed below and stay
|
|
1086
|
+
// out; a render effect outside a parking flush sees committed and
|
|
1087
|
+
// stays out too (rule 3, `frameRead`). The pass remembers it read the
|
|
1088
|
+
// future (REACTIVE_JOINED): a first pass that did is born held (A29),
|
|
1089
|
+
// wherever it was created. Lane work sees the screen (`frameRead`).
|
|
1090
|
+
if (e.u & CONFIG_HELD && !(t.u & CONFIG_CHILDREN_FORBIDDEN)) {
|
|
1091
|
+
if (frameRead(t, e)) return e.pe;
|
|
1092
|
+
joinPass(t, e);
|
|
1577
1093
|
}
|
|
1578
1094
|
}
|
|
1095
|
+
// Committed visibility for untracked and children-forbidden readers.
|
|
1096
|
+
if (!t || e.Ae === NOT_PENDING || t.u & CONFIG_CHILDREN_FORBIDDEN) return e.pe;
|
|
1097
|
+
// (A plain signal is never uninitialized — the carve-out is `serve`'s.)
|
|
1098
|
+
if (t.u & CONFIG_VERDICT && stagedScreen(t)) return e.pe;
|
|
1099
|
+
stagedRead(t);
|
|
1100
|
+
return e.Ae;
|
|
1101
|
+
}
|
|
1102
|
+
// Rule 3: this read serves the committed value to a render effect outside a
|
|
1103
|
+
// parking flush (decided after the pull below — a pull can join).
|
|
1104
|
+
let s = false;
|
|
1105
|
+
if (t) {
|
|
1106
|
+
if (tracking) {
|
|
1107
|
+
link(e, t);
|
|
1108
|
+
if (l.re) pullComputed(l, t);
|
|
1109
|
+
}
|
|
1110
|
+
// L2, as in the fast block above (tracked or not). A node born into the
|
|
1111
|
+
// future (uninitialized) has no committed value: every reader derives
|
|
1112
|
+
// from the future there, a render effect included (A29: a stale reader
|
|
1113
|
+
// of it cannot fall back to the committed frame and enters instead).
|
|
1114
|
+
if (e.u & CONFIG_HELD && !(e.u & CONFIG_OVERRIDE) && !(t.u & CONFIG_CHILDREN_FORBIDDEN)) {
|
|
1115
|
+
if (l.G & STATUS_UNINITIALIZED || !frameRead(t, e)) joinPass(t, e); else s = true;
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
// Lanes: a lane's node (after the pull — the node is current). NOT_PENDING
|
|
1119
|
+
// falls through: lane work's read of a pending member throws like any.
|
|
1120
|
+
if (e.u & CONFIG_OVERRIDE) {
|
|
1121
|
+
const n = GlobalQueue.Zt(t, e);
|
|
1122
|
+
if (n !== NOT_PENDING) return n;
|
|
1123
|
+
}
|
|
1124
|
+
// A stale reader of a held flight (rule 3, A15's reveal corollary) is served
|
|
1125
|
+
// the committed value — coherent with the frame, whose inputs are the
|
|
1126
|
+
// committed ones too — and does not go pending on it. So is lane work
|
|
1127
|
+
// reading a flight that is not the lane's own (the parent's, or
|
|
1128
|
+
// mainline's): a lane breaks out of its parent's hold and waits only on
|
|
1129
|
+
// what derives from the guess (A31: "under a lane, a pending node on no
|
|
1130
|
+
// lane serves its committed value").
|
|
1131
|
+
// A verdict reader likewise (CONFIG_VERDICT): a frame reader sees the
|
|
1132
|
+
// screen, and for a flight the screen is the committed value — a render
|
|
1133
|
+
// effect keeps its DOM by throwing, a memo has no DOM and is handed the
|
|
1134
|
+
// value. `[isPending(x), x()]` reads `[true, stale]` in either order (A10)
|
|
1135
|
+
// — unless the flight committed beneath inputs already on screen
|
|
1136
|
+
// (`observeFlight`).
|
|
1137
|
+
if (l.G & STATUS_PENDING && !s && !(e.u & CONFIG_OVERRIDE) && !(l.G & STATUS_UNINITIALIZED)) {
|
|
1138
|
+
// A reader with no committed value yet sees the flight pending: a
|
|
1139
|
+
// boundary a lane mounts shows its fallback (A29's boundary exemption).
|
|
1140
|
+
if (passLane !== null && !(t?.G & STATUS_UNINITIALIZED)) s = true; else if (t !== null && t.u & CONFIG_VERDICT) s = GlobalQueue.vt(t, l);
|
|
1579
1141
|
}
|
|
1580
|
-
if (l.
|
|
1581
|
-
// A reader landing on a pending node throws
|
|
1582
|
-
//
|
|
1583
|
-
//
|
|
1584
|
-
//
|
|
1585
|
-
//
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
// flight's inputs are themselves unpublished: the stamp alone does not
|
|
1589
|
-
// say so (it is pending-node bookkeeping), so it is refused when the
|
|
1590
|
-
// inputs are on screen — committed by a batch that left the flight in
|
|
1591
|
-
// the air (CONFIG_INPUTS_PUBLISHED, #3305) or revealed through a lane
|
|
1592
|
-
// (optimistic / latest, #3334) — and the reader holds instead. An
|
|
1593
|
-
// UNINITIALIZED node has no committed value to show and holds too
|
|
1594
|
-
// (firewall-backed store reads always did; plain memos since the #3043
|
|
1595
|
-
// port): falling through served `undefined` as if settled and stranded
|
|
1596
|
-
// the reader outside both transactions, so it never re-ran.
|
|
1597
|
-
if (t && !(stale && !(l.S & STATUS_UNINITIALIZED) && !(l.C & CONFIG_INPUTS_PUBLISHED) && !(l.C & CONFIG_HAS_LANE && GlobalQueue.Gn(l)) && heldFromStale(l, t))) {
|
|
1598
|
-
// Per-lane suspension lives with the engine (a non-null lane implies it
|
|
1599
|
-
// is installed): under a lane, only same-lane pending async without an
|
|
1600
|
-
// active override throws — plus uninitialized sources regardless of
|
|
1601
|
-
// lane (#3276); that check rides laneSuspends so floor bundles don't
|
|
1602
|
-
// pay for it.
|
|
1603
|
-
if (currentOptimisticLane === null || GlobalQueue.Dn(l)) {
|
|
1142
|
+
if (l.G & STATUS_PENDING && !s) {
|
|
1143
|
+
// A reader landing on a pending node throws; an untracked read of an
|
|
1144
|
+
// UNINITIALIZED node has no committed value to serve and throws too. A
|
|
1145
|
+
// children-forbidden reader (createTrackedEffect / onSettled) sees the
|
|
1146
|
+
// frame, not the graph (A32): the committed value, and the NotReady only
|
|
1147
|
+
// where there is none (uninitialized).
|
|
1148
|
+
if (t) {
|
|
1149
|
+
if (!(t.u & CONFIG_CHILDREN_FORBIDDEN) || l.G & STATUS_UNINITIALIZED) {
|
|
1604
1150
|
// An untracked read of a pending node still re-runs its reader when
|
|
1605
1151
|
// the node settles — unless nobody is reading (`spectating`, #3528)
|
|
1606
1152
|
// or the reader is the node itself.
|
|
1607
1153
|
if (!tracking && !spectating && e !== t) link(e, t);
|
|
1608
|
-
throw l.
|
|
1154
|
+
throw l.m?.v;
|
|
1609
1155
|
}
|
|
1610
|
-
} else if (
|
|
1611
|
-
|
|
1612
|
-
// read with no reader identity (#3648 follow-up, A18 (d)): a memo
|
|
1613
|
-
// whose first landing rode its lane (asyncWrite's lane branch →
|
|
1614
|
-
// `laneOverride`) has no `_value` — the flag stays set until the lane's
|
|
1615
|
-
// commit promotes the override — yet the override IS the displayed
|
|
1616
|
-
// value of a pending node (A18 (c), the screen keeps it until the
|
|
1617
|
-
// commit), exactly as an initialized node re-deriving under its override
|
|
1618
|
-
// displays it. "Uninitialized must suspend" (#3276, A19 exception 1) was
|
|
1619
|
-
// written for a node with NOTHING to show; this node has the override.
|
|
1620
|
-
// The arm fires in the body-end window: the source override superseded
|
|
1621
|
-
// (A18 body-end, #3427), the memo re-deriving on the plain channel and
|
|
1622
|
-
// held, its own slot still armed. Fall through to serve()'s override
|
|
1623
|
-
// arm. Readers WITH identity are untouched: a tracked lane reader keeps
|
|
1624
|
-
// the override (A17), an off-lane one suspends (#3651, `overrideRead`).
|
|
1625
|
-
!(hasActiveOverride(e) && e.C & CONFIG_DERIVED_OVERRIDE)) {
|
|
1626
|
-
throw l.o?._;
|
|
1156
|
+
} else if (l.G & STATUS_UNINITIALIZED) {
|
|
1157
|
+
throw l.m?.v;
|
|
1627
1158
|
}
|
|
1628
1159
|
}
|
|
1629
|
-
//
|
|
1630
|
-
//
|
|
1631
|
-
|
|
1632
|
-
// serving node values (the seed, or last-good data after a failed refetch).
|
|
1633
|
-
if (l.Se && l.S & STATUS_ERROR) {
|
|
1160
|
+
// An errored derive throws for every late reader instead of silently
|
|
1161
|
+
// serving node values (memo parity, #2897 ruling).
|
|
1162
|
+
if (l.re && l.G & STATUS_ERROR) {
|
|
1634
1163
|
// Only a genuine reactive re-read may retry an errored async source:
|
|
1635
1164
|
// - tracking: owned/tracked scope only (never events / `untrack` / effect side-effect phase)
|
|
1636
|
-
// - !pendingCheckActive: an `isPending` probe observes the error, never refetches
|
|
1637
1165
|
// - owner._time < clock: only on a later cycle than the one the error was found
|
|
1638
|
-
if (tracking &&
|
|
1166
|
+
if (tracking && l.Ne < clock) {
|
|
1639
1167
|
recompute(l);
|
|
1640
1168
|
return read(e);
|
|
1641
|
-
} else throw l.
|
|
1169
|
+
} else throw l.m?.v;
|
|
1642
1170
|
}
|
|
1643
|
-
if (snapshotCaptureActive && t && t.
|
|
1644
|
-
const n = e.
|
|
1171
|
+
if (snapshotCaptureActive && t && t.u & CONFIG_IN_SNAPSHOT_SCOPE) {
|
|
1172
|
+
const n = e.m?.ot;
|
|
1645
1173
|
if (n !== undefined) {
|
|
1646
|
-
const
|
|
1647
|
-
const
|
|
1648
|
-
if (
|
|
1649
|
-
return
|
|
1174
|
+
const l = n === NO_SNAPSHOT ? undefined : n;
|
|
1175
|
+
const s = e.Ae !== NOT_PENDING ? e.Ae : e.pe;
|
|
1176
|
+
if (s !== l) t.R |= REACTIVE_SNAPSHOT_STALE;
|
|
1177
|
+
return l;
|
|
1650
1178
|
}
|
|
1651
1179
|
}
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
// sweep registration when the arm was inline; keep that.
|
|
1656
|
-
!visibleOverride(e)) {
|
|
1180
|
+
if (s) return e.pe;
|
|
1181
|
+
const E = serve(e, t);
|
|
1182
|
+
if (!t && typeof n.re === "function" && e.u & CONFIG_AUTO_DISPOSE && !(l.G & STATUS_PENDING) && !e.t) {
|
|
1657
1183
|
// Deferred, not inline (#3078): an inline unobserved() here made untracked
|
|
1658
1184
|
// reads destructive — dispose on this read, full revival recompute on the
|
|
1659
1185
|
// next — so consecutive reads could answer differently with no write in
|
|
1660
|
-
// between
|
|
1186
|
+
// between.
|
|
1661
1187
|
// The sweep at flush finalization re-validates and reclaims; schedule()
|
|
1662
1188
|
// guarantees that flush happens even if nothing else is queued.
|
|
1663
1189
|
dormantNodes.add(e);
|
|
1664
1190
|
schedule();
|
|
1665
1191
|
}
|
|
1666
|
-
return
|
|
1192
|
+
return E;
|
|
1667
1193
|
}
|
|
1668
1194
|
|
|
1669
1195
|
/**
|
|
1670
1196
|
* Rule 1, the one slow implementation (DESIGN-CONSOLIDATION move 3b, step
|
|
1671
1197
|
* 6c): the value a reader `c` (null = untracked, no pass) is served from
|
|
1672
|
-
* `el
|
|
1673
|
-
*
|
|
1674
|
-
*
|
|
1675
|
-
* served for one). Called by read()'s slow tail and by the store's untracked
|
|
1676
|
-
* node path (nodeValue); the fast paths (readNodeFast, read's fast block)
|
|
1677
|
-
* keep their trivial ternary by design (perf, see the doc). Arms, in order:
|
|
1678
|
-
* - the override (A17), routed through the engine for a tracked reader under
|
|
1679
|
-
* a lane or a supersession (A18), an authoritative reader marked instead;
|
|
1680
|
-
* - the lane entanglement gate (committed, recorded for replay);
|
|
1681
|
-
* - a node born held has nothing for an untracked reader (A19 exception 1);
|
|
1198
|
+
* `el`. Called by read()'s slow tail; the fast path (read's fast block) keeps
|
|
1199
|
+
* its trivial ternary by design (perf, see the doc). Arms, in order:
|
|
1200
|
+
* - a node born staged has nothing for an untracked reader (A19 exception 1);
|
|
1682
1201
|
* - an unflushed write serves committed and re-runs the reader in the
|
|
1683
1202
|
* carrying flush (A28);
|
|
1684
|
-
* - readerSeesCommitted, else the staged value
|
|
1685
|
-
*/ function serve(e, t
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
// view. Fall through to normal value selection (staged `_pendingValue` is
|
|
1694
|
-
// authoritative — optimism never lives there); the sticky mark makes the
|
|
1695
|
-
// A17-silent "landing equals override" paths notify this node's subs so
|
|
1696
|
-
// the reader re-runs when truth arrives.
|
|
1697
|
-
// A28 (5): an optimistic write written this tick, outside a flush, is not
|
|
1698
|
-
// yet the active override — fall through to the normal selection (the
|
|
1699
|
-
// authoritative mark below still applies: until() must wake on landing).
|
|
1700
|
-
if (!(t && t.C & CONFIG_AUTHORITATIVE_READ) && !unflushedOverride(e)) {
|
|
1701
|
-
// A tracked read of an override is the engine's selection (a lane or a
|
|
1702
|
-
// supersession implies the engine): a render effect OFF the override's
|
|
1703
|
-
// held lane sees the committed value (#3460, lanes mirror transitions);
|
|
1704
|
-
// a node whose own source answered with a DIFFERENT value hands a
|
|
1705
|
-
// tracked reader the staged truth (A18 supersession, #3331). Untracked
|
|
1706
|
-
// reads display the override.
|
|
1707
|
-
if (t && e.C & (CONFIG_HAS_LANE | CONFIG_OVERRIDE_SUPERSEDED)) return GlobalQueue.Pn(e, t);
|
|
1708
|
-
return unwrapOverride(e.o?.we);
|
|
1709
|
-
}
|
|
1710
|
-
e.C |= CONFIG_AUTHORITATIVE_OBSERVED;
|
|
1711
|
-
}
|
|
1712
|
-
// Entanglement gate: a reader recomputing under an optimistic lane that reads
|
|
1713
|
-
// a pending mid-transition write sees the committed value. Projection-store
|
|
1714
|
-
// manual writes use the firewall's manual-write flag to opt into this path.
|
|
1715
|
-
// Async drivers are not under an optimistic lane and so bypass this, reading
|
|
1716
|
-
// _pendingValue for correct fetching. The sub is recorded for replay at commit
|
|
1717
|
-
// so it re-runs with the new committed view. (Gate details live with the
|
|
1718
|
-
// engine — a non-null lane implies it is installed.)
|
|
1719
|
-
if (currentOptimisticLane !== null && activeTransition !== null && t !== null && GlobalQueue.Fn(e, n, t)) {
|
|
1720
|
-
return i;
|
|
1721
|
-
}
|
|
1722
|
-
// In optimistic lane context, return _value for optimistic/lane-assigned signals
|
|
1723
|
-
// and for regular signals in stale mode (render effects). Non-stale readers (user
|
|
1724
|
-
// effects) see _pendingValue so that latest() and direct reads stay consistent.
|
|
1725
|
-
// (The lane-context clause lives with the engine.) Children-forbidden readers
|
|
1726
|
-
// (createTrackedEffect / onSettled callbacks) get committed visibility — see
|
|
1727
|
-
// readNodeFast (#3006).
|
|
1728
|
-
// A node born held (recompute) has a staged value and no committed one: a
|
|
1729
|
-
// stale reader cannot fall back to the committed frame, so it takes the
|
|
1730
|
-
// staged value and enters like a tracked reader (A29); an untracked reader
|
|
1731
|
-
// has nothing to serve and holds (A19 exception 1) — a bookkeeping read
|
|
1732
|
-
// (`spectate`) likewise: it derives nothing, so it cannot enter, and a
|
|
1733
|
-
// held-only value is simply not ready to it.
|
|
1734
|
-
const l = e._e !== NOT_PENDING && (e.S & STATUS_UNINITIALIZED) !== 0;
|
|
1735
|
-
if (l && (!t || spectating)) throw new NotReadyError(null);
|
|
1736
|
-
const u = t && unflushedStaged ? unflushedValue(e, i) : NOT_PENDING;
|
|
1737
|
-
if (u !== NOT_PENDING) {
|
|
1203
|
+
* - readerSeesCommitted, else the staged value.
|
|
1204
|
+
*/ function serve(e, t) {
|
|
1205
|
+
// A node born staged (recompute) has a staged value and no committed one:
|
|
1206
|
+
// an untracked reader has nothing to serve and holds (A19 exception 1) — a
|
|
1207
|
+
// bookkeeping read (`spectate`) likewise, and a children-forbidden reader
|
|
1208
|
+
// (A32: the frame, which has nothing here).
|
|
1209
|
+
if (e.Ae !== NOT_PENDING && e.G & STATUS_UNINITIALIZED && (!t || spectating || t.u & CONFIG_CHILDREN_FORBIDDEN)) throw new NotReadyError(null);
|
|
1210
|
+
const n = t && unflushedStaged ? unflushedValue(e) : NOT_PENDING;
|
|
1211
|
+
if (n !== NOT_PENDING) {
|
|
1738
1212
|
markLateLinker(t);
|
|
1739
|
-
|
|
1740
|
-
return u;
|
|
1213
|
+
return n;
|
|
1741
1214
|
}
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
return o;
|
|
1215
|
+
if (readerSeesCommitted(e, t)) return e.pe;
|
|
1216
|
+
if (t.u & CONFIG_VERDICT && !(e.G & STATUS_UNINITIALIZED) && stagedScreen(t)) return e.pe;
|
|
1217
|
+
stagedRead(t, e);
|
|
1218
|
+
return e.Ae;
|
|
1747
1219
|
}
|
|
1748
1220
|
|
|
1749
|
-
/**
|
|
1750
|
-
*
|
|
1751
|
-
*
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1221
|
+
/** A pass read a staging of this flush: it derives from what the flush may
|
|
1222
|
+
* yet hold (REACTIVE_STAGED_READ — the seam decides). Lane work too (plan sec. 28, a
|
|
1223
|
+
* lane sees the screen plus its own guesses): the staging is the screen if
|
|
1224
|
+
* the frame commits — one pass, the common case — and a held write if it
|
|
1225
|
+
* parks, which the seam repairs (`stagedReaders`): the pass re-derives on
|
|
1226
|
+
* the committed world and its lane's runs wait that round, so the held
|
|
1227
|
+
* write never shows through the lane. A verdict lane's work likewise: the
|
|
1228
|
+
* lane holds verdicts, not the frame's other stagings (#3851) — except a
|
|
1229
|
+
* verdict reader, which answered for itself (the lane seam, lanes.ts). Not
|
|
1230
|
+
* a read of a node born staged: it has no committed value to re-derive on,
|
|
1231
|
+
* and a re-run would read the same staging and re-queue every round. */ function stagedRead(e, t) {
|
|
1232
|
+
e.R |= REACTIVE_STAGED_READ;
|
|
1233
|
+
if (passLane !== null && !(t?.G & STATUS_UNINITIALIZED)) stagedReaders.push(e);
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
/** A10 for a staged node: a verdict reader (the pass entered a window) that
|
|
1237
|
+
* also reads a frame's proposal plainly sees the screen — `[latest(q), q()]`
|
|
1238
|
+
* is `[b, a]` — once the frame has a transaction, and re-derives when it
|
|
1239
|
+
* lands: at this very seam if nothing holds it (the stale run is void,
|
|
1240
|
+
* #3322 — one run, on the committed world), or with the hold. Before any
|
|
1241
|
+
* transaction the answer is the seam's and the reader watches it
|
|
1242
|
+
* (verdict.ts). A node born into the future has no screen: its staging is
|
|
1243
|
+
* its only value (A29). */ function stagedScreen(e) {
|
|
1244
|
+
if (flushTransaction === null) return false;
|
|
1245
|
+
staleReader(e, flushTransaction);
|
|
1246
|
+
return true;
|
|
1759
1247
|
}
|
|
1760
1248
|
|
|
1761
1249
|
/** A28 (4) — written inside a recompute that runs OUTSIDE a flush (a
|
|
1762
1250
|
* creation-time compute): promoted at that pass's end, visible to the rest of
|
|
1763
1251
|
* the block. Cold: only contextual writes reach here. */ function notePromotedWrite(e) {
|
|
1764
|
-
if (globalQueue
|
|
1765
|
-
e.
|
|
1252
|
+
if (globalQueue.ee || e.u & CONFIG_PROMOTED) return;
|
|
1253
|
+
e.u |= CONFIG_PROMOTED;
|
|
1766
1254
|
promotedWrites.push(e);
|
|
1767
1255
|
}
|
|
1768
1256
|
|
|
@@ -1773,51 +1261,28 @@ function read(e) {
|
|
|
1773
1261
|
* REVEALS — the creation-time arm skips pending computeds for the same
|
|
1774
1262
|
* reason. Firewall leaves belong to a projection whose compute is the tree's
|
|
1775
1263
|
* own work, captured (or deliberately not) at creation. */ function captureWriteSnapshot(e, t) {
|
|
1776
|
-
if (e.
|
|
1777
|
-
ext(e).
|
|
1778
|
-
e.
|
|
1264
|
+
if (e.u & CONFIG_NO_SNAPSHOT || e.re !== undefined || e.m?.ot !== undefined) return;
|
|
1265
|
+
ext(e).ot = t === undefined ? NO_SNAPSHOT : t;
|
|
1266
|
+
e.u |= CONFIG_HAS_SNAPSHOT;
|
|
1779
1267
|
snapshotSources.add(e);
|
|
1780
1268
|
}
|
|
1781
1269
|
|
|
1782
1270
|
function setSignal(e, t) {
|
|
1783
|
-
|
|
1784
|
-
// the writer's tick reveals with the hold, the same value or another. Inside
|
|
1785
|
-
// a flush the round enters now; from mainline the entry waits for the next
|
|
1786
|
-
// flush's start (batchJoins) — entering here left activeTransition set for
|
|
1787
|
-
// the rest of the caller's block, so a memo created after the write became
|
|
1788
|
-
// the transaction's instead of mainline's (A28, A29). Before the equality
|
|
1789
|
-
// gate below: repeating the held value proposes it too — and that repeat
|
|
1790
|
-
// leaves through the gate, so the join schedules its own flush here; left
|
|
1791
|
-
// for the next flush to find, it adopted an unrelated tick (#3519 review).
|
|
1792
|
-
// A held DERIVATION is not a proposal (A34 amendment, #3612): the user
|
|
1793
|
-
// setters of derived nodes (setMemo, the derived store's setter) classify
|
|
1794
|
-
// the write before it reaches here — see heldDerivation — and re-derive
|
|
1795
|
-
// under the hold instead of masking; the join stands either way.
|
|
1796
|
-
if (e.me && activeTransition !== e.me) {
|
|
1797
|
-
if (globalQueue.$t) globalQueue.initTransition(e.me); else {
|
|
1798
|
-
batchJoins.push(e.me);
|
|
1799
|
-
// dupes: a bare return in initTransition
|
|
1800
|
-
schedule();
|
|
1801
|
-
}
|
|
1802
|
-
}
|
|
1803
|
-
// The optimistic write path lives with the engine: only optimisticSignal /
|
|
1804
|
-
// optimisticComputed callers and optimistic store nodes carry an
|
|
1805
|
-
// _overrideValue slot (flagged by CONFIG_OPTIMISTIC — a masked read of the
|
|
1806
|
-
// always-present config instead of a missing-property probe), and every
|
|
1807
|
-
// module that installs one installs the engine first.
|
|
1808
|
-
if (e.C & CONFIG_OPTIMISTIC) {
|
|
1809
|
-
if (!projectionWriteActive) return GlobalQueue.Ln(e, t);
|
|
1810
|
-
// An authoritative store landing on an override-covered node: the store
|
|
1811
|
-
// twin of asyncWrite's override branch, decided by the engine (#3331).
|
|
1812
|
-
const n = e.o?.we;
|
|
1813
|
-
if (n !== undefined && n !== NOT_PENDING) return GlobalQueue.Vn(e, t);
|
|
1814
|
-
}
|
|
1815
|
-
const n = e._e === NOT_PENDING ? e.ce : e._e;
|
|
1271
|
+
const n = e.Ae === NOT_PENDING ? e.pe : e.Ae;
|
|
1816
1272
|
if (typeof t === "function") t = t(n);
|
|
1273
|
+
// Lanes: a write landing on a guess is its truth (A18 — an async guess's
|
|
1274
|
+
// own landing), judged against the guess; one landing on a lane's
|
|
1275
|
+
// derivation (its flight) is the lane's staging (lanes.ts).
|
|
1276
|
+
if (e.u & CONFIG_OVERRIDE) return GlobalQueue.Qt(e, t);
|
|
1277
|
+
// L2 (T4) / A34 (1): a write to a node whose staged state is held is a
|
|
1278
|
+
// second proposal for the slot — the same value again or another — and
|
|
1279
|
+
// the two cannot finish at different times: the writer's tick joins the
|
|
1280
|
+
// hold, before the equality gate.
|
|
1281
|
+
if (e.u & CONFIG_HELD) joinFuture(txOf(e));
|
|
1817
1282
|
// Uninitialized check first: the first commit has no previous value, so the
|
|
1818
1283
|
// user comparator must not run against `undefined` (matches recompute).
|
|
1819
|
-
const
|
|
1820
|
-
if (!
|
|
1284
|
+
const l = !!(e.G & STATUS_UNINITIALIZED) || !e.tn || !e.tn(n, t);
|
|
1285
|
+
if (!l) return t;
|
|
1821
1286
|
// Attribution hook: this committed write is where a re-run chain begins.
|
|
1822
1287
|
if (attrHooks !== null) attrHooks.write(e, n, t);
|
|
1823
1288
|
// A write during hydration's snapshot capture to a source that has no
|
|
@@ -1830,93 +1295,57 @@ function setSignal(e, t) {
|
|
|
1830
1295
|
// pass reads a value the server never had. Store leaves written here are
|
|
1831
1296
|
// plain signals and qualify the same way.
|
|
1832
1297
|
if (snapshotCaptureActive) captureWriteSnapshot(e, n);
|
|
1833
|
-
const
|
|
1834
|
-
if (!
|
|
1835
|
-
// A28
|
|
1836
|
-
//
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
else if (e.me !== null) stashHeldRewrite(e);
|
|
1840
|
-
e._e = t;
|
|
1298
|
+
const s = e.Ae !== NOT_PENDING;
|
|
1299
|
+
if (!s) queuePendingNode(e);
|
|
1300
|
+
// A28: a held node rewritten outside a flush keeps its flushed staging
|
|
1301
|
+
// for every reader until the flush that carries the rewrite.
|
|
1302
|
+
else if (e.u & CONFIG_HELD && !globalQueue.ee) stashFlushed(e);
|
|
1303
|
+
e.Ae = t;
|
|
1841
1304
|
if (context !== null) notePromotedWrite(e);
|
|
1842
|
-
// syncCompanions only pokes _pendingSignal/_latestValueComputed — with
|
|
1843
|
-
// neither companion present the call is a guaranteed no-op (companions are
|
|
1844
|
-
// only ever created, never removed, and creating one installs the hook and
|
|
1845
|
-
// sets CONFIG_HAS_COMPANIONS — one masked read replaces two optional-field
|
|
1846
|
-
// probes on every write).
|
|
1847
|
-
if (e.C & CONFIG_HAS_COMPANIONS && GlobalQueue.Le !== null) {
|
|
1848
|
-
GlobalQueue.Le(e, t);
|
|
1849
|
-
// A28 (2): the verdict computed here is the PRE-flush one (an unflushed
|
|
1850
|
-
// write is not pending); the flush that carries the write re-syncs so
|
|
1851
|
-
// the companions mirror the flushed world. Only companion nodes pay.
|
|
1852
|
-
if (!globalQueue.$t) unflushedCompanions.push(e);
|
|
1853
|
-
}
|
|
1854
1305
|
// _time is a computed-only slot (§12e): writing it on a signal would fork
|
|
1855
1306
|
// the lean shape. Every read site is computed-typed.
|
|
1856
|
-
if (e.
|
|
1307
|
+
if (e.re !== undefined) e.Ne = clock;
|
|
1857
1308
|
// Staged-rewrite fast path (§12d): a re-write to a node whose subscribers
|
|
1858
1309
|
// were already walked — and where nothing has recomputed or linked since
|
|
1859
1310
|
// (epoch) — re-stages the value and stops. The walk is idempotent (subs
|
|
1860
|
-
// marked, heap entries flag-guarded, effects queued once)
|
|
1861
|
-
|
|
1862
|
-
if (l && e.Lt === notifyEpoch && currentOptimisticLane === null && !reaskArmed) return t;
|
|
1311
|
+
// marked, heap entries flag-guarded, effects queued once).
|
|
1312
|
+
if (s && e.Pt === notifyEpoch) return t;
|
|
1863
1313
|
insertSubs(e);
|
|
1864
1314
|
schedule();
|
|
1865
1315
|
return t;
|
|
1866
1316
|
}
|
|
1867
1317
|
|
|
1868
1318
|
/**
|
|
1869
|
-
*
|
|
1870
|
-
*
|
|
1871
|
-
*
|
|
1872
|
-
*
|
|
1873
|
-
*
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
schedule();
|
|
1879
|
-
}
|
|
1880
|
-
e.oe = e.oe & -4 | REACTIVE_MANUAL_WRITE;
|
|
1881
|
-
e.Un = clock;
|
|
1882
|
-
}
|
|
1883
|
-
|
|
1884
|
-
/** A34 amendment (#3612) — is `el`'s staging a DERIVATION another transaction
|
|
1885
|
-
* holds: stamped by a transaction that is not the writer's, and not a manual
|
|
1886
|
-
* proposal (the mask, on the node or — a store leaf — its firewall)? #2692's
|
|
1887
|
-
* "manual write wins" is a rule for one synchronous frame; across a hold the
|
|
1888
|
-
* held pass result is nobody's proposal, and a user setter reaching it
|
|
1889
|
-
* composes on the committed frame it was written against and becomes `prev`
|
|
1890
|
-
* for the transaction's re-derivation (rederiveHeld) instead of replacing it.
|
|
1891
|
-
* Writes made under the holding transaction masked the node and keep
|
|
1892
|
-
* A34(1)'s last-write-wins. Only the user setters ask; an async landing or a
|
|
1893
|
-
* companion writing a stamped node is the transaction's own work. */ function heldDerivation(e) {
|
|
1894
|
-
return e.me !== null && activeTransition !== e.me && !((e.Ne || e).oe & REACTIVE_MANUAL_WRITE);
|
|
1895
|
-
}
|
|
1896
|
-
|
|
1897
|
-
/** The held-derivation write's second half: the node re-derives under its
|
|
1898
|
-
* hold with the written staging as the pass's `prev`. Nothing is masked. The
|
|
1899
|
-
* write took the A34 join (setSignal), which scheduled the flush that drains
|
|
1900
|
-
* this; recompute re-enters the stamp. */ function rederiveHeld(e) {
|
|
1901
|
-
e.oe |= REACTIVE_DIRTY;
|
|
1902
|
-
// over CHECK: the heap visit recomputes, not re-checks
|
|
1903
|
-
enqueueSub(e);
|
|
1904
|
-
}
|
|
1905
|
-
|
|
1906
|
-
/**
|
|
1907
|
-
* User-facing setter for the memo form of `createSignal(fn)`. Behaves like
|
|
1908
|
-
* `setSignal`, but also cancels any pending recompute of the memo so the
|
|
1909
|
-
* manual value wins over a value that would otherwise be produced by an
|
|
1910
|
-
* upstream change in the same tick. Across a hold the write is not a
|
|
1911
|
-
* proposal: a memo another transaction holds as a pass result composes on the
|
|
1912
|
-
* committed value and re-derives under the hold with the write as `prev`
|
|
1913
|
-
* (A34 amendment, #3612; heldDerivation).
|
|
1319
|
+
* User-facing setter for the memo form of `createSignal(fn)`. A write applies
|
|
1320
|
+
* first, then derivations re-run (A34 as amended 2026-10-01, rule B; core
|
|
1321
|
+
* R31): the write stages like any other, and a source change — in this flush
|
|
1322
|
+
* or a later one, under a hold or on mainline — re-derives with the staged
|
|
1323
|
+
* write as `prev`. The write never refuses the re-run (the frame-scoped mask
|
|
1324
|
+
* of #3733/#3740 and #2692's "write wins the tick" before it are gone; call
|
|
1325
|
+
* order inside a flush was never observable) and on its own never causes one.
|
|
1326
|
+
* REACTIVE_MANUAL_WRITE only marks the staging as a user proposal — the A34
|
|
1327
|
+
* (1)/(3) discriminator — until a pass re-derives it or the commit lands it.
|
|
1914
1328
|
*/ function setMemo(e, t) {
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1329
|
+
// A34 (3), #3612: is `el`'s staging a derivation another transaction holds
|
|
1330
|
+
// — held, not a manual proposal, and the writer not that transaction
|
|
1331
|
+
// (mainline, or a flush joined elsewhere)? A held pass result is nobody's
|
|
1332
|
+
// proposal: a setter reaching it composes on the committed frame it was
|
|
1333
|
+
// written against and becomes `prev` for the hold's re-derivation, instead
|
|
1334
|
+
// of replacing it. A write made under the hold carries the mark and keeps
|
|
1335
|
+
// (1)'s last-write-wins. Only the user setter asks; an async landing
|
|
1336
|
+
// writing a held node is the transaction's own work.
|
|
1337
|
+
const n = e.u & CONFIG_HELD && !(e.R & REACTIVE_MANUAL_WRITE) && txOf(e) !== flushTransaction;
|
|
1338
|
+
// The writer read the committed frame; its updater composes on it.
|
|
1339
|
+
if (n && typeof t === "function") t = t(e.pe);
|
|
1340
|
+
const l = setSignal(e, t);
|
|
1341
|
+
if (n) {
|
|
1342
|
+
// The one write that re-runs: the staging it replaced was derived from
|
|
1343
|
+
// inputs the writer never saw, so the hold re-derives over the write.
|
|
1344
|
+
e.R = e.R & ~REACTIVE_CHECK | REACTIVE_DIRTY;
|
|
1345
|
+
insertIntoHeap(e, dirtyQueue);
|
|
1346
|
+
schedule();
|
|
1347
|
+
} else if (e.Ae !== NOT_PENDING) e.R |= REACTIVE_MANUAL_WRITE;
|
|
1348
|
+
return l;
|
|
1920
1349
|
}
|
|
1921
1350
|
|
|
1922
1351
|
/**
|
|
@@ -1939,25 +1368,21 @@ function setSignal(e, t) {
|
|
|
1939
1368
|
* ```
|
|
1940
1369
|
*/ function runWithOwner(e, t) {
|
|
1941
1370
|
const n = context;
|
|
1942
|
-
const
|
|
1371
|
+
const l = tracking;
|
|
1943
1372
|
context = e;
|
|
1944
1373
|
tracking = false;
|
|
1945
1374
|
try {
|
|
1946
1375
|
return t();
|
|
1947
1376
|
} finally {
|
|
1948
1377
|
context = n;
|
|
1949
|
-
tracking =
|
|
1378
|
+
tracking = l;
|
|
1950
1379
|
}
|
|
1951
1380
|
}
|
|
1952
1381
|
|
|
1953
1382
|
function staleValues(e, t = true) {
|
|
1954
|
-
const n = stale;
|
|
1955
|
-
stale = t;
|
|
1956
1383
|
try {
|
|
1957
1384
|
return e();
|
|
1958
|
-
} finally {
|
|
1959
|
-
stale = n;
|
|
1960
|
-
}
|
|
1385
|
+
} finally {}
|
|
1961
1386
|
}
|
|
1962
1387
|
|
|
1963
1388
|
/**
|
|
@@ -1965,45 +1390,22 @@ function staleValues(e, t = true) {
|
|
|
1965
1390
|
* it validates the target, marks through here, then builds the quiescence
|
|
1966
1391
|
* promise on the resolve()/until() effect machinery). Flags the node's next
|
|
1967
1392
|
* recompute as a quiet re-ask and schedules it; no-ops for non-derived or
|
|
1968
|
-
* disposed targets
|
|
1393
|
+
* disposed targets.
|
|
1969
1394
|
*/ function markRefresh(e) {
|
|
1970
|
-
if (typeof e.
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
if (e.Un === clock) return;
|
|
1979
|
-
e.oe &= ~REACTIVE_MANUAL_WRITE;
|
|
1980
|
-
// The lift falls through to the re-ask classification below. The held
|
|
1981
|
-
// write's value change already rides the transaction; the refetch it
|
|
1982
|
-
// asks for is the same question with unchanged inputs. Skipping the
|
|
1983
|
-
// mark here classified that refetch as a NEW question, which pends
|
|
1984
|
-
// every leaf (3.1) — an action doing setStore + yield + refresh(store)
|
|
1985
|
-
// lit up every sibling row, and affects() could not narrow it (a mark
|
|
1986
|
-
// only turns pending on). Same-question motion stays silent (3.4);
|
|
1987
|
-
// the written slot and any declared mark carry the pending instead.
|
|
1988
|
-
}
|
|
1989
|
-
// A refresh with no value-change dirt already queued is a re-ask of the
|
|
1990
|
-
// same question: mark it so the recompute classifies any resulting
|
|
1991
|
-
// pending window as quiet (not pending). If the node is already dirty
|
|
1992
|
-
// from a real input change, the question changed — don't mark.
|
|
1993
|
-
// REACTIVE_IN_HEAP counts as dirt: insertSubs schedules subscribers by
|
|
1994
|
-
// heap insertion alone (no DIRTY/CHECK flag), so a same-batch value
|
|
1995
|
-
// change followed by refresh() must not be laundered into a quiet re-ask.
|
|
1996
|
-
if (!(e.oe & (REACTIVE_DIRTY | REACTIVE_CHECK | REACTIVE_IN_HEAP))) {
|
|
1997
|
-
e.oe |= REACTIVE_REASK;
|
|
1998
|
-
armReaskClear();
|
|
1999
|
-
}
|
|
2000
|
-
e.oe = e.oe & ~REACTIVE_CHECK | REACTIVE_DIRTY;
|
|
1395
|
+
if (typeof e.re === "function" && !(e.R & REACTIVE_DISPOSED)) {
|
|
1396
|
+
// A manual write never refuses the re-run (A34 rule B): a refresh in the
|
|
1397
|
+
// write's own frame re-asks with the write as `prev`, like any source
|
|
1398
|
+
// change (#3026 generalized).
|
|
1399
|
+
// The re-ask of an answered question (A19 exc. 2): the next pass, going
|
|
1400
|
+
// pending, classifies its flight quiet for the verdict. Not when an
|
|
1401
|
+
// input already changed this tick — that pass asks a new question.
|
|
1402
|
+
e.R = e.R & ~REACTIVE_CHECK | REACTIVE_DIRTY | (e.R & (REACTIVE_DIRTY | REACTIVE_CHECK | REACTIVE_IN_HEAP) ? 0 : REACTIVE_REASK);
|
|
2001
1403
|
// A refresh() self-invalidation is a root cause too — the target's next
|
|
2002
1404
|
// run has no changed dep to point at, so it points here instead.
|
|
2003
1405
|
if (attrHooks !== null) attrHooks.refreshed(e);
|
|
2004
|
-
insertIntoHeap(e,
|
|
1406
|
+
insertIntoHeap(e, dirtyQueue);
|
|
2005
1407
|
schedule();
|
|
2006
1408
|
}
|
|
2007
1409
|
}
|
|
2008
1410
|
|
|
2009
|
-
export {
|
|
1411
|
+
export { clearSnapshots, computed, context, createEffectNode, effectStatusNotify, ext, isEqual, markLateLinker, markRefresh, markSnapshotScope, markUnflushedStaged, notePromotedWrite, prepareComputed, pullComputed, read, readerSeesCommitted, recompute, releaseSnapshotScope, resyncUnflushedCompanions, runWithOwner, serve, setEffectStatusNotify, setMemo, setSignal, setSnapshotCapture, setVerdict, signal, slotSignal, snapshotCaptureActive, snapshotSources, spectating, stagedRead, stagedScreen, staleValues, stashFlushed, statusNotifierOf, tracking, unflushedStaged, unflushedValue, untrack, verdict };
|