@solidjs/signals 2.0.0-rc.14 → 2.0.0-rc.16
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 +882 -405
- package/dist/dev.attribution.js +172 -66
- package/dist/dev.js +1529 -745
- package/dist/observe/affects-marks.js +86 -0
- package/dist/observe/affects.js +32 -80
- package/dist/observe/boundaries.js +202 -166
- package/dist/observe/core/async.js +192 -141
- package/dist/observe/core/attribution-queries.js +1 -1
- package/dist/observe/core/attribution.js +315 -214
- package/dist/observe/core/constants.js +23 -4
- package/dist/observe/core/context.js +3 -3
- package/dist/observe/core/core.js +532 -360
- package/dist/observe/core/dev.js +63 -35
- package/dist/observe/core/effect.js +47 -47
- package/dist/observe/core/error.js +5 -3
- package/dist/observe/core/external.js +5 -6
- package/dist/observe/core/graph.js +56 -42
- package/dist/observe/core/heap.js +33 -33
- package/dist/observe/core/identities.js +62 -0
- package/dist/observe/core/lanes.js +242 -229
- package/dist/observe/core/owner.js +62 -50
- package/dist/observe/core/scheduler.js +322 -168
- package/dist/observe/core/truth.js +45 -0
- package/dist/observe/core/verdict.js +114 -64
- package/dist/observe/deferred.js +184 -0
- package/dist/observe/index.js +7 -1
- package/dist/observe/map.js +339 -254
- package/dist/observe/reveal.js +77 -77
- package/dist/observe/signals.js +54 -33
- package/dist/observe/store/affects.js +76 -69
- package/dist/observe/store/edges.js +39 -0
- package/dist/observe/store/hooks.js +13 -0
- package/dist/observe/store/index.js +0 -2
- package/dist/observe/store/optimistic.js +82 -44
- package/dist/observe/store/projection.js +83 -64
- package/dist/observe/store/reconcile.js +32 -30
- package/dist/observe/store/store.js +219 -123
- package/dist/prod/affects-marks.js +86 -0
- package/dist/prod/affects.js +33 -79
- package/dist/prod/boundaries.js +185 -149
- package/dist/prod/core/async.js +176 -135
- package/dist/prod/core/constants.js +23 -4
- package/dist/prod/core/context.js +3 -3
- package/dist/prod/core/core.js +521 -353
- package/dist/prod/core/effect.js +66 -66
- package/dist/prod/core/error.js +5 -3
- package/dist/prod/core/external.js +5 -6
- package/dist/prod/core/graph.js +56 -42
- package/dist/prod/core/heap.js +38 -38
- package/dist/prod/core/identities.js +62 -0
- package/dist/prod/core/lanes.js +237 -224
- package/dist/prod/core/owner.js +62 -51
- package/dist/prod/core/scheduler.js +312 -158
- package/dist/prod/core/truth.js +45 -0
- package/dist/prod/core/verdict.js +113 -63
- package/dist/prod/deferred.js +182 -0
- package/dist/prod/index.js +7 -1
- package/dist/prod/map.js +366 -281
- package/dist/prod/reveal.js +77 -77
- package/dist/prod/signals.js +59 -38
- package/dist/prod/store/affects.js +76 -69
- package/dist/prod/store/edges.js +39 -0
- package/dist/prod/store/hooks.js +13 -0
- package/dist/prod/store/index.js +0 -2
- package/dist/prod/store/optimistic.js +121 -84
- package/dist/prod/store/projection.js +46 -34
- package/dist/prod/store/reconcile.js +32 -30
- package/dist/prod/store/store.js +205 -116
- package/dist/types/affects-marks.d.ts +14 -0
- package/dist/types/boundaries.d.ts +5 -1
- package/dist/types/core/async.d.ts +10 -0
- package/dist/types/core/attribution.d.ts +6 -2
- package/dist/types/core/constants.d.ts +18 -1
- package/dist/types/core/core.d.ts +77 -5
- package/dist/types/core/dev.d.ts +43 -13
- package/dist/types/core/graph.d.ts +11 -0
- package/dist/types/core/identities.d.ts +17 -0
- package/dist/types/core/index.d.ts +2 -1
- package/dist/types/core/lanes.d.ts +2 -4
- package/dist/types/core/owner.d.ts +5 -3
- package/dist/types/core/scheduler.d.ts +59 -34
- package/dist/types/core/truth.d.ts +16 -0
- package/dist/types/core/types.d.ts +21 -6
- package/dist/types/core/verdict.d.ts +32 -0
- package/dist/types/deferred.d.ts +28 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/map.d.ts +2 -2
- package/dist/types/signals.d.ts +28 -7
- package/dist/types/store/affects.d.ts +11 -0
- package/dist/types/store/edges.d.ts +9 -0
- package/dist/types/store/hooks.d.ts +48 -0
- package/dist/types/store/index.d.ts +10 -4
- package/dist/types/store/optimistic.d.ts +3 -2
- package/dist/types/store/projection.d.ts +4 -3
- package/dist/types/store/store.d.ts +4 -39
- package/dist/types/store/target.d.ts +16 -8
- package/dist/types/store/types.d.ts +14 -0
- package/package.json +1 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { releaseFlightTeardown, handleAsync, clearStatus, parkLoadingWindow,
|
|
1
|
+
import { notifyStatus, releaseFlightTeardown, handleAsync, clearStatus, parkLoadingWindow, stageError, settlePendingSource, releaseIfSettledUnobserved, settleErroredDependents } from "./async.js";
|
|
2
2
|
|
|
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,
|
|
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, STATUS_ERROR, REACTIVE_DIRTY, CONFIG_OVERRIDE, REACTIVE_DISPOSED, REACTIVE_CHECK, REACTIVE_IN_HEAP, REACTIVE_REASK, REACTIVE_SNAPSHOT_STALE, CONFIG_CHILDREN_FORBIDDEN, CONFIG_VERDICT, STATUS_PENDING, REACTIVE_LANE_DIRTY, CONFIG_GUESS, REACTIVE_RECOMPUTING_DEPS, CONFIG_LAGS, EFFECT_RENDER, CONFIG_HELD_FRAME, EFFECT_TRACKED, CONFIG_STAGED, CONFIG_STAGED_ERROR, REACTIVE_ZOMBIE, REACTIVE_MISSED_WAKE, REACTIVE_JOINED, REACTIVE_STAGED_READ, REACTIVE_PROBED, REACTIVE_FRAME_READ, REACTIVE_LANE_READ, REACTIVE_SCREEN_READ, REACTIVE_PROBE_UNANSWERED, CAUGHT_REARMED, CONFIG_PROMOTED, CONFIG_INPUTS_PUBLISHED, CONFIG_FRESH_READ, REACTIVE_IN_HEAP_HEIGHT, CONFIG_SLOT_NODE, EFFECT_USER, STATUS_NONE } from "./constants.js";
|
|
4
4
|
|
|
5
5
|
import { NotReadyError } from "./error.js";
|
|
6
6
|
|
|
7
|
-
import { clearDeps, trimStaleDeps,
|
|
7
|
+
import { link, dormantNodes, clearDeps, trimStaleDeps, deferReleases, deferredReleases } from "./graph.js";
|
|
8
8
|
|
|
9
9
|
import { insertIntoHeap, deleteFromHeap, insertIntoHeapHeight, enqueueSub, markNode, markHeap } from "./heap.js";
|
|
10
10
|
|
|
@@ -12,9 +12,9 @@ import { GRAPH_SIZE_WARN_AT, noteFanIn } from "./dev.js";
|
|
|
12
12
|
|
|
13
13
|
import { attrHooks } from "./attribution-hooks.js";
|
|
14
14
|
|
|
15
|
-
import { inheritId,
|
|
15
|
+
import { inheritId, linkChild, disposeChildren, markDisposal } from "./owner.js";
|
|
16
16
|
|
|
17
|
-
import { schedule,
|
|
17
|
+
import { schedule, externalUntrackHook, clock, txOf, flushTransaction, laneWriteHook, joinFuture, queuePendingNode, globalQueue, notifyEpoch, insertSubs, dirtyQueue, observeFlightHook, laneReadHook, laneStageHook, passLane, wireExternalSourceHook, bumpNotifyEpoch, setPassLane, joinPassTx, freshHook, GlobalQueue, laneOutcomeHook, passTx, holdNode, laneDirty, heldTrim, staleReader, laneSeamsHook, stagedReaders, deferZombie } from "./scheduler.js";
|
|
18
18
|
|
|
19
19
|
// The heap's per-node step. A tracked effect's heap visit is its compute
|
|
20
20
|
// phase — empty, like a user effect whose compute reads nothing — and hands
|
|
@@ -24,11 +24,11 @@ import { schedule, GlobalQueue, clock, txOf, flushTransaction, joinFuture, queue
|
|
|
24
24
|
// render-effect callback stages its value for the next pass, but a wake pushed
|
|
25
25
|
// directly into the user queue ran in the SAME pass, read the old value, and
|
|
26
26
|
// nothing re-notified it when the value landed (#3291).
|
|
27
|
-
GlobalQueue.
|
|
27
|
+
GlobalQueue.Ze = e => {
|
|
28
28
|
if (e.i === EFFECT_TRACKED) {
|
|
29
29
|
deleteFromHeap(e, dirtyQueue);
|
|
30
|
-
e.
|
|
31
|
-
globalQueue.enqueue(EFFECT_USER, e.
|
|
30
|
+
e.Qe = true;
|
|
31
|
+
globalQueue.enqueue(EFFECT_USER, e.M);
|
|
32
32
|
}
|
|
33
33
|
// L2: a zombie's fate is the seam's — its owner's commit disposes it, or
|
|
34
34
|
// the park cancels the pass (the write that dirtied it is held). It runs
|
|
@@ -36,10 +36,29 @@ GlobalQueue.et = e => {
|
|
|
36
36
|
// owner is held in the future). Its lane's own re-staging is a live write
|
|
37
37
|
// (REACTIVE_LANE_DIRTY — a lane shows ahead of any park): it runs now, as
|
|
38
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);
|
|
39
|
+
else if (e.R & REACTIVE_ZOMBIE && !(e.R & REACTIVE_LANE_DIRTY)) deferZombie(e);
|
|
40
|
+
// A lane frame's member (parked by a lane pass, #3698) follows mainline
|
|
41
|
+
// writes only: a lane's dirty on it is cancelled — the lane's apply retires
|
|
42
|
+
// it, and the pass that replaced it already decided what that screen shows
|
|
43
|
+
// (#4137: a `<Show>` branch withdrawn by its own lane re-ran on a falsy
|
|
44
|
+
// condition).
|
|
45
|
+
// (Lanes exist only with the lane engine installed: folded away without.)
|
|
46
|
+
else if (laneStageHook && e.R & REACTIVE_ZOMBIE && laneFrameMember(e)) {
|
|
47
|
+
deleteFromHeap(e, dirtyQueue);
|
|
48
|
+
e.R &= -65540;
|
|
49
|
+
} else recompute(e);
|
|
40
50
|
};
|
|
41
51
|
|
|
42
|
-
|
|
52
|
+
/** The zombie's frame was parked by lane work: the nearest non-zombie owner
|
|
53
|
+
* (the one whose pass parked it) carries a lane's derived value — or, an
|
|
54
|
+
* effect (no lane value: `laneStage`), is listed in a lane (an `insert()`
|
|
55
|
+
* hole's render effect). */ function laneFrameMember(e) {
|
|
56
|
+
let t = e._parent;
|
|
57
|
+
while (t.R & REACTIVE_ZOMBIE) t = t._parent;
|
|
58
|
+
return (t.l & (CONFIG_OVERRIDE | CONFIG_GUESS)) === CONFIG_OVERRIDE || t.i && t.T?.Je?.V;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
GlobalQueue.et = disposeChildren;
|
|
43
62
|
|
|
44
63
|
let tracking = false;
|
|
45
64
|
|
|
@@ -47,11 +66,15 @@ let context = null;
|
|
|
47
66
|
|
|
48
67
|
let snapshotCaptureActive = false;
|
|
49
68
|
|
|
69
|
+
/** The lane at the root of the running chain of first passes: the lane whose
|
|
70
|
+
* pass created the chain's first node (a mount), inherited by every first
|
|
71
|
+
* pass nested under it, whose seats are mainline (`recompute`, #4166). */ let laneRoot;
|
|
72
|
+
|
|
50
73
|
let snapshotSources = null;
|
|
51
74
|
|
|
52
75
|
function ownerInSnapshotScope(e) {
|
|
53
76
|
while (e) {
|
|
54
|
-
if (e.
|
|
77
|
+
if (e.ot) return true;
|
|
55
78
|
e = e._parent;
|
|
56
79
|
}
|
|
57
80
|
return false;
|
|
@@ -63,34 +86,34 @@ function setSnapshotCapture(e) {
|
|
|
63
86
|
}
|
|
64
87
|
|
|
65
88
|
function markSnapshotScope(e) {
|
|
66
|
-
e.
|
|
89
|
+
e.ot = true;
|
|
67
90
|
}
|
|
68
91
|
|
|
69
92
|
function releaseSnapshotScope(e) {
|
|
70
|
-
e.
|
|
93
|
+
e.ot = false;
|
|
71
94
|
releaseSubtree(e);
|
|
72
95
|
schedule();
|
|
73
96
|
}
|
|
74
97
|
|
|
75
98
|
function releaseSubtree(e) {
|
|
76
|
-
let t = e.
|
|
99
|
+
let t = e.ze;
|
|
77
100
|
while (t) {
|
|
78
|
-
if (t.
|
|
79
|
-
t = t.
|
|
101
|
+
if (t.ot) {
|
|
102
|
+
t = t.Xe;
|
|
80
103
|
continue;
|
|
81
104
|
}
|
|
82
|
-
if (t.
|
|
105
|
+
if (t.X) {
|
|
83
106
|
const e = t;
|
|
84
|
-
e.
|
|
107
|
+
e.l &= ~CONFIG_IN_SNAPSHOT_SCOPE;
|
|
85
108
|
if (e.R & REACTIVE_SNAPSHOT_STALE) {
|
|
86
109
|
e.R &= ~REACTIVE_SNAPSHOT_STALE;
|
|
87
110
|
e.R |= REACTIVE_DIRTY;
|
|
88
|
-
if (dirtyQueue.
|
|
111
|
+
if (dirtyQueue.st > e.Et) dirtyQueue.st = e.Et;
|
|
89
112
|
insertIntoHeap(e, dirtyQueue);
|
|
90
113
|
}
|
|
91
114
|
}
|
|
92
115
|
releaseSubtree(t);
|
|
93
|
-
t = t.
|
|
116
|
+
t = t.Xe;
|
|
94
117
|
}
|
|
95
118
|
}
|
|
96
119
|
|
|
@@ -101,8 +124,8 @@ function clearSnapshots() {
|
|
|
101
124
|
// pre-initialized to undefined (see ext()), and every reader tests
|
|
102
125
|
// `!== undefined` — assign, don't `delete`: deleting a field pushes the
|
|
103
126
|
// object to dictionary mode for every later read of every field.
|
|
104
|
-
const t = e.
|
|
105
|
-
if (t != null) t.
|
|
127
|
+
const t = e.T;
|
|
128
|
+
if (t != null) t.lt = undefined;
|
|
106
129
|
}
|
|
107
130
|
snapshotSources = null;
|
|
108
131
|
}
|
|
@@ -113,6 +136,7 @@ function recompute(e, t = false) {
|
|
|
113
136
|
// §12d: any recompute can clean a marked subscriber — invalidate skips.
|
|
114
137
|
bumpNotifyEpoch();
|
|
115
138
|
const n = e.i;
|
|
139
|
+
let o;
|
|
116
140
|
// Lanes (plan sec. 28): the seat of a pass is its node's. A node carrying a lane's
|
|
117
141
|
// derived value runs as the lane's whoever dirtied it — a sync write, a
|
|
118
142
|
// boundary reset, a frame rerun — so its children and its result are the
|
|
@@ -128,23 +152,30 @@ function recompute(e, t = false) {
|
|
|
128
152
|
// derivation's pass into the lane (`read`); a leaf's never moves.
|
|
129
153
|
// Restored at the end, after this pass's staging and runs have been
|
|
130
154
|
// routed.
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
155
|
+
// Lanes exist only with the lane engine installed (`laneStageHook`): a
|
|
156
|
+
// bundle without it seats every pass on mainline, and folds the seat away.
|
|
157
|
+
const s = passLane;
|
|
158
|
+
// The mount a first pass belongs to: its creator's lane, or the one its
|
|
159
|
+
// creator's own mount came from — a render effect a verdict lane's mount's
|
|
160
|
+
// content creates is that mount's too (#4166). Lanes only, like the seat.
|
|
161
|
+
const E = laneRoot;
|
|
162
|
+
const l = laneStageHook && (laneRoot = t && (s || E));
|
|
163
|
+
setPassLane(!laneStageHook ? null : (e.R & REACTIVE_LANE_DIRTY || (e.l & (CONFIG_OVERRIDE | CONFIG_GUESS)) === CONFIG_OVERRIDE) && e.T?.Je?.V ? e.T.Je : // Loose `!=`: false with no creator lane (undefined == null).
|
|
164
|
+
t && s?._parent.it != s && creatorPass(context)?.R & REACTIVE_RECOMPUTING_DEPS ? s : null);
|
|
134
165
|
// Attribution hook: fired before this run touches the dep list — `_deps`
|
|
135
166
|
// still holds the previous run's links (the subscriptions that could have
|
|
136
167
|
// triggered this run, and the baseline for the engine's subscription diff).
|
|
137
|
-
let
|
|
168
|
+
let i = false;
|
|
138
169
|
{
|
|
139
|
-
e.
|
|
170
|
+
e.Ve = 0;
|
|
140
171
|
if (attrHooks !== null) attrHooks.recomputeStart(e, t);
|
|
141
172
|
}
|
|
142
173
|
// CARVE 2: lane posture resolution (OPTIMISTIC_DIRTY, derived-override
|
|
143
174
|
// re-derivation, lane adoption through deps) went with the optimistic engine.
|
|
144
175
|
if (!t) {
|
|
145
176
|
deleteFromHeap(e, dirtyQueue);
|
|
146
|
-
if (e.
|
|
147
|
-
e.
|
|
177
|
+
if (e.T !== null) {
|
|
178
|
+
e.T.Se = null;
|
|
148
179
|
// Supersede is where an iterator flight dies (#3122): close it now.
|
|
149
180
|
// Idempotent with the cleanup-channel close.
|
|
150
181
|
releaseFlightTeardown(e);
|
|
@@ -156,23 +187,32 @@ function recompute(e, t = false) {
|
|
|
156
187
|
// joins all the same. Not a render effect (rule 3): it is the frame.
|
|
157
188
|
// Re-run outside its transaction's flush it publishes mainline — the
|
|
158
189
|
// value it staged there is superseded (`frameRead` remembers it for the
|
|
159
|
-
// landing). A frame born into the transaction (
|
|
160
|
-
// A29
|
|
161
|
-
// swap (ruling
|
|
162
|
-
|
|
190
|
+
// landing). A frame born into the transaction (`bornInto`: never run,
|
|
191
|
+
// A29, or mounted by a pass of it still awaiting its commit, #4010) keeps
|
|
192
|
+
// staging: live writes keep it current, it effects at the swap (ruling
|
|
193
|
+
// A).
|
|
194
|
+
// A pending flight is the transition's own work (#3884). A probe on an
|
|
195
|
+
// earlier pass left CONFIG_VERDICT, and a frame read here would serve the
|
|
196
|
+
// committed screen, settle the flight and drop the hold — a superseding
|
|
197
|
+
// write's verdict would depend on that probe. Cleared for this pass; a
|
|
198
|
+
// probe in the body re-stamps it after the plain reads.
|
|
199
|
+
if (e.m & STATUS_PENDING) e.l &= ~CONFIG_VERDICT;
|
|
200
|
+
if (e.l & CONFIG_HELD) {
|
|
163
201
|
const t = txOf(e);
|
|
164
202
|
// Held by a blocked lane: the frame joins nothing — a lane never holds
|
|
165
203
|
// a sync write (#3460). The pass is the lane's if it reads the lane's
|
|
166
204
|
// world (`read`), and has left it otherwise.
|
|
167
|
-
|
|
205
|
+
// An answered deferred node's re-pass is a lagging question (D-40):
|
|
206
|
+
// it joins nothing, so the write that reached it commits mainline.
|
|
207
|
+
if (t.V || e.l & CONFIG_LAGS) ; else if (n !== EFFECT_RENDER && !(e.l & CONFIG_VERDICT)) joinPassTx(t, freshHook?.(e, e.m & STATUS_UNINITIALIZED)); else if (t !== flushTransaction && !(e.m & STATUS_UNINITIALIZED || e.l & CONFIG_HELD_FRAME)) {
|
|
168
208
|
// Published mainline, it is not held: the frame this pass builds is
|
|
169
209
|
// replaced on the spot (#3404) — this flush's commit retires the
|
|
170
210
|
// committed frame the held pass had parked (`commitPendingNode`),
|
|
171
211
|
// after the new frame's first runs, and the held pass's own
|
|
172
212
|
// children die below (`disposeChildren`). The landing re-derives a
|
|
173
213
|
// fresh held frame for the reveal (`_reruns`).
|
|
174
|
-
e.
|
|
175
|
-
if (n === EFFECT_RENDER) e.
|
|
214
|
+
e.ue = NOT_PENDING;
|
|
215
|
+
if (n === EFFECT_RENDER) e.l &= ~CONFIG_HELD;
|
|
176
216
|
}
|
|
177
217
|
}
|
|
178
218
|
// The previous pass's frame. If that pass is still awaiting its commit
|
|
@@ -180,75 +220,91 @@ function recompute(e, t = false) {
|
|
|
180
220
|
// frame parked before it stays parked. Otherwise they ARE the frame:
|
|
181
221
|
// park them until this pass commits. Tracked effects run after the seam
|
|
182
222
|
// and own nothing to park.
|
|
183
|
-
if (n === EFFECT_TRACKED || e.
|
|
223
|
+
if (n === EFFECT_TRACKED || e.l & CONFIG_STAGED) {
|
|
224
|
+
// A lazy node only those children read is released when this pass
|
|
225
|
+
// ends, if the pass doesn't read it again (#4142).
|
|
226
|
+
const t = deferredReleases;
|
|
227
|
+
deferReleases(o = []);
|
|
228
|
+
disposeChildren(e);
|
|
229
|
+
deferReleases(t);
|
|
230
|
+
} else if (e.ze !== null || e.rt !== null) parkChildren(e);
|
|
231
|
+
// An error a pass staged (D-41) is replaced by this pass's answer: a
|
|
232
|
+
// value, a flight, or an error of its own (#4109, #4145).
|
|
233
|
+
if (e.l & CONFIG_STAGED_ERROR) {
|
|
234
|
+
e.ue = NOT_PENDING;
|
|
235
|
+
e.l ^= CONFIG_STAGED_ERROR;
|
|
236
|
+
}
|
|
184
237
|
}
|
|
185
|
-
const
|
|
238
|
+
const r = !!(e.m & STATUS_UNINITIALIZED);
|
|
186
239
|
// Capture both error and pending status before the compute clears them.
|
|
187
240
|
// A conditional can drop its pending source and recover to an unchanged
|
|
188
241
|
// value, leaving blocked dependents outside that source’s settle walk.
|
|
189
|
-
const u = e.
|
|
190
|
-
const
|
|
191
|
-
const
|
|
242
|
+
const u = e.m & STATUS_ERROR ? e.T?.F : undefined;
|
|
243
|
+
const _ = (e.m & STATUS_PENDING) !== 0;
|
|
244
|
+
const a = _ ? e.T?.fe : undefined;
|
|
192
245
|
// Pending SOURCE-hood, captured before the compute clears status: a node
|
|
193
246
|
// whose own flight parked dependents self-registers in _pendingSources
|
|
194
247
|
// (notifyStatus, isSource). If this recompute supersedes that flight and
|
|
195
248
|
// settles synchronously, those dependents settle HERE — asyncWrite's
|
|
196
249
|
// settlePendingSource walk never runs for a landing that was preempted
|
|
197
250
|
// (#3181).
|
|
198
|
-
const
|
|
251
|
+
const I = e.T?.fe?.has(e);
|
|
252
|
+
// D-41: this pass's error is staged, not published (`stageError`).
|
|
253
|
+
let N = false;
|
|
199
254
|
// A19 exc. 2: a `refresh()` re-asks the question already answered — quiet
|
|
200
255
|
// for the verdict — unless the question in flight is a new one, which the
|
|
201
256
|
// re-ask does not launder (a poll during a quiet confirm stays quiet).
|
|
202
|
-
const
|
|
203
|
-
const
|
|
257
|
+
const f = (e.R & REACTIVE_REASK) !== 0 && !(e.m & STATUS_PENDING && e.T !== null && !e.T.ut);
|
|
258
|
+
const T = context;
|
|
204
259
|
context = e;
|
|
205
|
-
e.
|
|
206
|
-
e.
|
|
260
|
+
e._ = null;
|
|
261
|
+
e.Ne++;
|
|
207
262
|
// REACTIVE_ZOMBIE is position: it says the node sits on its owner's parked
|
|
208
263
|
// frame, and the splice/reawaken paths key off it. A zombie reruns for
|
|
209
264
|
// live writes until the commit that disposes it, so every per-pass wipe
|
|
210
265
|
// — here, the finally below, updateIfNecessary — carries it (#3543).
|
|
211
|
-
e.R = REACTIVE_RECOMPUTING_DEPS | e.R & REACTIVE_ZOMBIE;
|
|
212
|
-
e.
|
|
213
|
-
// The pass's previous value: its staging, else
|
|
214
|
-
//
|
|
215
|
-
let
|
|
216
|
-
let
|
|
217
|
-
let
|
|
218
|
-
// L2: did this pass read the future (REACTIVE_JOINED, set by read)
|
|
219
|
-
|
|
220
|
-
|
|
266
|
+
e.R = REACTIVE_RECOMPUTING_DEPS | e.R & (REACTIVE_ZOMBIE | REACTIVE_REASK);
|
|
267
|
+
e.ie = clock;
|
|
268
|
+
// The pass's previous value: its staging, else a lane node's lane value
|
|
269
|
+
// when it has one (lanes.ts), else the committed one.
|
|
270
|
+
let S = e.ue !== NOT_PENDING ? e.ue : e.l & CONFIG_OVERRIDE && e.T.V !== NOT_PENDING ? e.T.V : e.Ie;
|
|
271
|
+
let c = e.Et;
|
|
272
|
+
let A = false;
|
|
273
|
+
// L2: did this pass read the future (REACTIVE_JOINED, set by read) — or,
|
|
274
|
+
// a lane's mount, a staging of this flush (#3851, #4166)?
|
|
275
|
+
let R = false;
|
|
276
|
+
let d = tracking;
|
|
221
277
|
tracking = true;
|
|
222
278
|
try {
|
|
223
|
-
if (!false && e.
|
|
224
|
-
|
|
225
|
-
if (e.
|
|
226
|
-
e.
|
|
279
|
+
if (!false && e.l & CONFIG_SYNC) {
|
|
280
|
+
S = e.X(S);
|
|
281
|
+
if (e.T !== null) e.T.Se = null;
|
|
282
|
+
e.ce = false;
|
|
227
283
|
} else {
|
|
228
284
|
// A projection's body self-registers its flight through handleAsync
|
|
229
285
|
// (with the commit as setter): its undefined return is not a sync
|
|
230
286
|
// answer, and the outer handleAsync must not clobber the registration.
|
|
231
|
-
const t = e.
|
|
232
|
-
const n = e.
|
|
233
|
-
const
|
|
234
|
-
const s = e.
|
|
235
|
-
|
|
236
|
-
if (!s && !
|
|
237
|
-
if (e.
|
|
287
|
+
const t = e.T?.Se;
|
|
288
|
+
const n = e.X(S);
|
|
289
|
+
const o = typeof n === "object" && n !== null;
|
|
290
|
+
const s = e.T?.Se !== t;
|
|
291
|
+
S = s || !o ? n : handleAsync(e, n);
|
|
292
|
+
if (!s && !o) {
|
|
293
|
+
if (e.T !== null) e.T.Se = null;
|
|
238
294
|
// A sync (non-object) return is the first real answer; async-shaped
|
|
239
295
|
// results clear inside handleAsync at their own landing points, and a
|
|
240
296
|
// self-registered flight clears when its own handleAsync lands.
|
|
241
|
-
e.
|
|
297
|
+
e.ce = false;
|
|
242
298
|
}
|
|
243
299
|
}
|
|
244
300
|
// On a status-free node clearStatus is a guaranteed no-op: every field
|
|
245
301
|
// its body gates on is either _statusFlags or lives in the cold
|
|
246
302
|
// extension — no extension, no status to clear. (_x from an unrelated
|
|
247
303
|
// installer just makes clearStatus a cheap re-verified no-op.)
|
|
248
|
-
if (e.
|
|
304
|
+
if (e.m !== 0 || e.T !== null) clearStatus(e, t);
|
|
249
305
|
} catch (t) {
|
|
250
306
|
const n = t instanceof NotReadyError;
|
|
251
|
-
if (n && e.
|
|
307
|
+
if (n && e.ce) {
|
|
252
308
|
// Loading window with an unready sync dependency: register for the
|
|
253
309
|
// source's settle (the settlePendingSource walk runs off
|
|
254
310
|
// _pendingSources + _blocked alone) but take NO read-visible pending
|
|
@@ -258,11 +314,15 @@ function recompute(e, t = false) {
|
|
|
258
314
|
// retry can actually run.
|
|
259
315
|
parkLoadingWindow(e, t);
|
|
260
316
|
} else {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
317
|
+
// D-41: a pass of a held node, or one that read a staging of this
|
|
318
|
+
// flush, stages its error (`stageError`).
|
|
319
|
+
if (n || !(N = stageError(e, t, true))) {
|
|
320
|
+
if (n) ext(e).se = true;
|
|
321
|
+
notifyStatus(e, n ? STATUS_PENDING : STATUS_ERROR, t);
|
|
322
|
+
// The replacement source is fully propagated now. If no new flight
|
|
323
|
+
// re-owned self, retire the superseded flight and its dependent copies.
|
|
324
|
+
if (n && I && !e.T?.Se) settlePendingSource(e);
|
|
325
|
+
}
|
|
266
326
|
// A re-park drops what the earlier pass carried (#3456): a source this
|
|
267
327
|
// pass no longer reaches — its branch switched, or a fresh flight
|
|
268
328
|
// replaced the inputs' pending with its own — stays copied onto
|
|
@@ -270,21 +330,24 @@ function recompute(e, t = false) {
|
|
|
270
330
|
// stops at this node (nothing left to retire) before it finds them. A
|
|
271
331
|
// dependent then waits forever on a flight it has no path to. The
|
|
272
332
|
// re-park twin of the unchanged-value recovery sweep below; dependents
|
|
273
|
-
// with another path keep the source (retryReaches).
|
|
274
|
-
|
|
333
|
+
// with another path keep the source (retryReaches). A staged error
|
|
334
|
+
// answered what the pass carried: the same.
|
|
335
|
+
if ((n || N) && a) for (const t of a) if (t !== e && !e.T?.fe?.has(t)) settlePendingSource(e, t);
|
|
275
336
|
}
|
|
276
337
|
} finally {
|
|
277
|
-
tracking =
|
|
338
|
+
tracking = d;
|
|
339
|
+
if (laneStageHook) laneRoot = E;
|
|
278
340
|
// Consume the missed-wake latch (#3037, set by insertSubs): a dep write
|
|
279
341
|
// landed beneath this pass on a link it had already validated. The wipe
|
|
280
342
|
// below must not key off DIRTY/CHECK — the read-time pull protocol
|
|
281
343
|
// (markNode(c) in read()) marks the running node as part of ordinary
|
|
282
344
|
// bookkeeping, and those marks are correctly discarded here.
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
// A verdict reader is one for as long as it probes
|
|
286
|
-
//
|
|
287
|
-
|
|
345
|
+
A = (e.R & REACTIVE_MISSED_WAKE) !== 0;
|
|
346
|
+
R = !!(e.R & (l ? REACTIVE_JOINED | REACTIVE_STAGED_READ : REACTIVE_JOINED));
|
|
347
|
+
// A verdict reader is one for as long as it probes, or reads a memo
|
|
348
|
+
// that does (`read`): a pass that did neither (REACTIVE_PROBED, the same
|
|
349
|
+
// bit) is an ordinary derivation again.
|
|
350
|
+
e.l = e.l & ~CONFIG_VERDICT | e.R & REACTIVE_PROBED;
|
|
288
351
|
// REACTIVE_DISPOSED survives too (#3621): the pass may have disposed its
|
|
289
352
|
// own owner (a memo calling its root's `dispose()`, a cleanup doing so
|
|
290
353
|
// #3601/#3606), and `disposeChildren` set the flag on this node
|
|
@@ -293,11 +356,12 @@ function recompute(e, t = false) {
|
|
|
293
356
|
// this pass's verdict for the landing; the next pass's wipe at the top
|
|
294
357
|
// clears it.
|
|
295
358
|
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 =
|
|
359
|
+
context = T;
|
|
360
|
+
if (o) for (const e of o) releaseIfSettledUnobserved(e);
|
|
297
361
|
// A19 exc. 2: a pass that went pending on a `refresh()` re-asks the
|
|
298
362
|
// question already answered — quiet for the verdict; any other pending
|
|
299
363
|
// pass is a new question. Settled, there is nothing to classify.
|
|
300
|
-
if (e.
|
|
364
|
+
if (e.T !== null) e.T.ut = f && (e.m & STATUS_PENDING) !== 0;
|
|
301
365
|
}
|
|
302
366
|
// A node that died during its own pass (#3621) is dead at the end of it,
|
|
303
367
|
// and the pass is void. Its owner's teardown already unlinked its deps,
|
|
@@ -312,9 +376,9 @@ function recompute(e, t = false) {
|
|
|
312
376
|
// attribution frame opened at the top is still closed.
|
|
313
377
|
if (e.R & REACTIVE_DISPOSED) {
|
|
314
378
|
clearDeps(e);
|
|
315
|
-
if (e.
|
|
379
|
+
if (e.T !== null) e.T.Se = null;
|
|
316
380
|
if (attrHooks !== null) attrHooks.recomputeEnd(e, t, false, false, false, false);
|
|
317
|
-
setPassLane(
|
|
381
|
+
setPassLane(s);
|
|
318
382
|
return;
|
|
319
383
|
}
|
|
320
384
|
// The lane this pass is work of, if any: its seat (the node's, its
|
|
@@ -322,14 +386,14 @@ function recompute(e, t = false) {
|
|
|
322
386
|
// seat that read none of the lane's world has left it: its result is the
|
|
323
387
|
// frame's (a derivation whose branch no longer reaches the guess). A
|
|
324
388
|
// guess is written, not derived — it never leaves this way.
|
|
325
|
-
let
|
|
389
|
+
let C = !laneStageHook ? null : passLane;
|
|
326
390
|
// Listed before its staging, a pending pass included (the lane's own
|
|
327
391
|
// flight is the lane's); false: the pass left the lane (lanes.ts).
|
|
328
|
-
const
|
|
329
|
-
if (
|
|
330
|
-
if (
|
|
392
|
+
const O = !!e.T?.F || N;
|
|
393
|
+
if (laneStageHook && C !== null) {
|
|
394
|
+
if (laneStageHook(e, C, t, O)) setPassLane(C); else C = null;
|
|
331
395
|
}
|
|
332
|
-
if (!
|
|
396
|
+
if (!O) {
|
|
333
397
|
// Observe-tier fan-in (HUGE_FAN_IN): the validated prefix [_deps.._depsTail]
|
|
334
398
|
// IS this pass's distinct sources — count it here rather than per link. A
|
|
335
399
|
// begin/end bracket around the pass plus a per-link increment measured
|
|
@@ -340,9 +404,9 @@ function recompute(e, t = false) {
|
|
|
340
404
|
// this pass, or at its commit — see recompute's tail.)
|
|
341
405
|
{
|
|
342
406
|
let t = 0;
|
|
343
|
-
for (let n = e.
|
|
407
|
+
for (let n = e.C; n !== null; n = n.G) {
|
|
344
408
|
t++;
|
|
345
|
-
if (n === e.
|
|
409
|
+
if (n === e._) break;
|
|
346
410
|
}
|
|
347
411
|
if (t >= GRAPH_SIZE_WARN_AT) noteFanIn(e, t);
|
|
348
412
|
}
|
|
@@ -350,41 +414,33 @@ function recompute(e, t = false) {
|
|
|
350
414
|
// publishes to — the staged `_pendingValue` when staged; a lane node's
|
|
351
415
|
// lane value (a node entering the lane this pass has none yet — its
|
|
352
416
|
// committed value, `laneStage` having voided a frame staging).
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
r = !n && E || !e.tn || !e.tn(i, I);
|
|
357
|
-
} catch (t) {
|
|
358
|
-
// A throwing user comparator is an error of this node's computation.
|
|
359
|
-
// Route it through the same status path as a compute-phase throw so
|
|
360
|
-
// error boundaries contain it; otherwise it unwinds the scheduler
|
|
361
|
-
// flush, bypassing every boundary and wedging the queue (#2837).
|
|
362
|
-
notifyStatus(e, STATUS_ERROR, t);
|
|
363
|
-
}
|
|
417
|
+
const o = e.ue !== NOT_PENDING ? e.ue : e.l & CONFIG_OVERRIDE && e.T.V !== NOT_PENDING ? e.T.V : e.Ie;
|
|
418
|
+
// A throwing comparator is contained inside `isSame` (A3, #2837; #4111).
|
|
419
|
+
const s = !n && r || !!e.T?.de || !isSame(e, o, S);
|
|
364
420
|
// A committed derived change becomes a cause for this node's subscribers,
|
|
365
421
|
// chaining their attribution through this node to the root write.
|
|
366
422
|
if (attrHooks !== null) {
|
|
367
|
-
|
|
368
|
-
if (
|
|
423
|
+
i = s && !e.T?.F;
|
|
424
|
+
if (i && !n && !t) attrHooks.derivedChanged(e);
|
|
369
425
|
}
|
|
370
426
|
// Effects use `_equals: false` (no per-effect closure). The side effects that
|
|
371
427
|
// the equals closure used to perform — flagging the effect dirty and enqueueing
|
|
372
428
|
// its runner — happen here instead. `!create` matches the previous `initialized`
|
|
373
429
|
// gate: the explicit recompute(node, true) inside effect() does not enqueue, so
|
|
374
430
|
// effect() can call its runner synchronously for the first run.
|
|
375
|
-
if (n &&
|
|
431
|
+
if (n && s &&
|
|
376
432
|
// A stale reader of a lane (REACTIVE_SCREEN_READ) that computed what it
|
|
377
433
|
// last applied owes no run (lanes.ts).
|
|
378
|
-
!(e.R & REACTIVE_SCREEN_READ && !e.
|
|
434
|
+
!(e.R & REACTIVE_SCREEN_READ && !e.Qe && Object.is(e._t, S)) &&
|
|
379
435
|
// A re-staging pass owes no run: the commit replays the effect (#3802).
|
|
380
|
-
e.
|
|
381
|
-
e.
|
|
436
|
+
e.ue === NOT_PENDING) {
|
|
437
|
+
e.Qe = !e.T?.F;
|
|
382
438
|
// Reuse one bound runner per effect — runEffect no-ops on a stale
|
|
383
439
|
// `_modified`, so re-enqueueing the same function is harmless.
|
|
384
440
|
// Lane work's run is the lane's (released at its reveal) — a first
|
|
385
441
|
// pass under a lane too: `effect()` skips the synchronous first run of
|
|
386
442
|
// a lane's node.
|
|
387
|
-
if (!t ||
|
|
443
|
+
if (!t || C !== null) globalQueue.enqueue(n, e.W ??= GlobalQueue.K.bind(null, e), C);
|
|
388
444
|
}
|
|
389
445
|
// Lanes. A written guess's own pass from the frame: its source
|
|
390
446
|
// recomputed it — the truth (A18: "the source is whatever recomputes the
|
|
@@ -392,7 +448,7 @@ function recompute(e, t = false) {
|
|
|
392
448
|
// confirm is a landing too — the lane's end commits it) and notifies
|
|
393
449
|
// only a correction: an equal truth re-runs nothing (lanes.ts). (As the
|
|
394
450
|
// lane's own work the lane judged it — `laneStage`.)
|
|
395
|
-
if (!t &&
|
|
451
|
+
if (!t && C === null && e.l & CONFIG_GUESS && laneOutcomeHook(e, S, O)) ; else if (O) ; else if (s) {
|
|
396
452
|
// L2: a first pass is born held — staged, committed with the
|
|
397
453
|
// transaction, its effect's first run the landing's (A29; `effect()`
|
|
398
454
|
// skips the synchronous first run on a staged value) — when the flush
|
|
@@ -409,15 +465,15 @@ function recompute(e, t = false) {
|
|
|
409
465
|
// pass that runs before anything joins: the pass's input, not a verdict
|
|
410
466
|
// after it. An effect still carrying an uncommitted staged value
|
|
411
467
|
// re-stages: the commit applies the latest pass, not the born-held one.
|
|
412
|
-
if (
|
|
468
|
+
if (C !== null) {
|
|
413
469
|
// Lane work: the lane's value — into the slot while the lane has not
|
|
414
470
|
// revealed (its reveal shows it), as its staging once it has (the
|
|
415
471
|
// next reveal promotes it; the screen keeps the revealed value). An
|
|
416
472
|
// effect's value slot is private: the run the lane holds is what
|
|
417
473
|
// shows it — unless the effect was born held, whose first run is the
|
|
418
474
|
// commit's (A29): it re-stages, as below.
|
|
419
|
-
if (n && e.
|
|
420
|
-
} else if (t ? !((flushTransaction !== null || passTx !== null) && (
|
|
475
|
+
if (n && e.ue === NOT_PENDING) e.Ie = S; else if (n || C.It) e.ue = S; else e.T.V = S;
|
|
476
|
+
} else if (t ? !((flushTransaction !== null || passTx !== null) && (R || creatorPass(T)?.R & REACTIVE_JOINED)) : n && e.ue === NOT_PENDING) {
|
|
421
477
|
// A first pass publishes directly. So does an effect: its value slot
|
|
422
478
|
// is private (only its own run reads it), and the staging round-trip
|
|
423
479
|
// (queuePendingNode + commitPendingNodes) paid per effect on the
|
|
@@ -428,9 +484,9 @@ function recompute(e, t = false) {
|
|
|
428
484
|
// sources commit) must see the fresh value while PLAIN reads stay
|
|
429
485
|
// committed until flush (#3009 purity). The pending round-trip is
|
|
430
486
|
// that separation; it cannot be skipped on any path a pull can reach.
|
|
431
|
-
e.
|
|
487
|
+
e.Ie = S;
|
|
432
488
|
} else {
|
|
433
|
-
e.
|
|
489
|
+
e.ue = S;
|
|
434
490
|
// Born held: in the transaction from birth, so a read of it this
|
|
435
491
|
// tick is a read of its world (the mount-during-a-hold case), not
|
|
436
492
|
// only after the seam parks it.
|
|
@@ -440,41 +496,55 @@ function recompute(e, t = false) {
|
|
|
440
496
|
// (`commitPendingNode` initializes it) — every reader of it
|
|
441
497
|
// derives from the future (`read`), an untracked one throws
|
|
442
498
|
// (A19 exc. 1).
|
|
443
|
-
e.
|
|
499
|
+
e.m |= STATUS_UNINITIALIZED;
|
|
500
|
+
// A boundary that has not shown content catches it: its fallback
|
|
501
|
+
// waits for the commit (#3540). (The pass's own status clear above
|
|
502
|
+
// dropped what the read recorded.) Not when the flush has joined
|
|
503
|
+
// the hold and the pass is its own (not a verdict lane's mount,
|
|
504
|
+
// #3851): the mount is part of the hold, and the boundary appears
|
|
505
|
+
// at its commit, its fallback never seen (D-13, U-3) — a `<Show>`
|
|
506
|
+
// mounting it over a landing included.
|
|
507
|
+
freshHook?.(e, !l && flushTransaction);
|
|
444
508
|
}
|
|
445
509
|
}
|
|
446
510
|
// insertSubs only walks _subs (no scheduling of its own), so a
|
|
447
511
|
// subscriber-less node has nothing to notify.
|
|
448
|
-
if (e.
|
|
512
|
+
if (e.o !== null) {
|
|
449
513
|
insertSubs(e);
|
|
450
514
|
// Lane work re-staged: the lane's members re-derive as its work.
|
|
451
|
-
if (
|
|
515
|
+
if (C !== null) laneDirty(e, C);
|
|
452
516
|
}
|
|
453
|
-
} else if (e.
|
|
454
|
-
for (let t = e.
|
|
517
|
+
} else if (e.Et != c) {
|
|
518
|
+
for (let t = e.o; t !== null; t = t.t) insertIntoHeapHeight(t.u, dirtyQueue);
|
|
455
519
|
}
|
|
456
520
|
// Silent recovery: errored → unchanged value fires no notification, but
|
|
457
521
|
// dependents still holding the propagated error consumed their dirty flag
|
|
458
522
|
// in an errored run and may sit on stale commits (#2949). Changed-value
|
|
459
523
|
// recoveries ride insertSubs above; a comparator throw re-errored the node
|
|
460
524
|
// (el._x?._error re-set), so this only runs on a genuinely clean recovery.
|
|
461
|
-
if (!
|
|
525
|
+
if (!s && !e.T?.F) {
|
|
462
526
|
if (u !== undefined) settleErroredDependents(e, u);
|
|
463
527
|
// Self-registration (this node's own superseded flight) is the #3181
|
|
464
528
|
// sweep's business below — retiring it here too would walk twice.
|
|
465
|
-
if (
|
|
529
|
+
if (a) for (const t of a) if (t !== e) settlePendingSource(e, t);
|
|
466
530
|
}
|
|
467
531
|
// #3181: a synchronous settle supersedes the old landing callback, so
|
|
468
532
|
// recompute owns its pending-source sweep. An uninitialized node without
|
|
469
533
|
// a replacement source still has no truth to reveal and must stay parked.
|
|
470
|
-
if (
|
|
534
|
+
if (I && !(e.m & (STATUS_PENDING | STATUS_UNINITIALIZED))) settlePendingSource(e);
|
|
471
535
|
}
|
|
472
536
|
// A first pass that went pending where the born-held arm would have held
|
|
473
537
|
// its value (it read a held node, or a verdict lane's mount read a staging,
|
|
474
|
-
// #3851) is
|
|
475
|
-
//
|
|
476
|
-
//
|
|
477
|
-
|
|
538
|
+
// #3851) is held like it: its answer is the hold's, and the hold waits
|
|
539
|
+
// for it (A29, #3800). A boundary that has not shown content catches it
|
|
540
|
+
// and it is held all the same — its answer reveals no earlier than the
|
|
541
|
+
// hold's commit (D-12) — but the hold does not wait for it: its only
|
|
542
|
+
// readers are behind the fallback (`_hidden`; F-4, #3540). A boundary
|
|
543
|
+
// re-armed by `on` owns what it collects outright (D-14): not held.
|
|
544
|
+
if (t && C === null && R && e.m & STATUS_PENDING) {
|
|
545
|
+
const t = flushTransaction ?? passTx;
|
|
546
|
+
if (t && !t.V && freshHook?.(e) !== CAUGHT_REARMED) holdNode(e, t);
|
|
547
|
+
}
|
|
478
548
|
// Dependencies are the committed frame's until it is replaced (A30, #3410):
|
|
479
549
|
// a pass that staged its value leaves the previous pass's tail linked for
|
|
480
550
|
// `commitPendingNode` to trim, so a write to a dependency the committed
|
|
@@ -492,29 +562,29 @@ function recompute(e, t = false) {
|
|
|
492
562
|
// kept at a park — one spurious recompute at most). A creation pass and a
|
|
493
563
|
// tracked effect trim now: their frames are replaceable like a direct
|
|
494
564
|
// commit, and a tracked effect's spurious run would be user-visible.
|
|
495
|
-
if (!e.
|
|
565
|
+
if (!e.T?.F && e.ue === NOT_PENDING && !(n && e.Qe)) {
|
|
496
566
|
if (t || n === EFFECT_TRACKED) trimStaleDeps(e); else heldTrim(e);
|
|
497
567
|
}
|
|
498
568
|
// Observe: the posture the run executed under — lane work (optimistic:
|
|
499
569
|
// overlay, never waste), a transaction's pass (held), or plain.
|
|
500
|
-
if (attrHooks !== null) attrHooks.recomputeEnd(e, t,
|
|
570
|
+
if (attrHooks !== null) attrHooks.recomputeEnd(e, t, i, C !== null, flushTransaction !== null || passTx !== null || (e.l & CONFIG_HELD) !== 0, e.ue !== NOT_PENDING);
|
|
501
571
|
// A staged value, a parked frame (L2: the commit retires it), or status the
|
|
502
572
|
// commit sweep must settle (a pending or uninitialized pass), queues the
|
|
503
573
|
// node for this flush's commit. A first pass queues only when pending or
|
|
504
574
|
// born held: otherwise its value published directly. A queued pass's
|
|
505
575
|
// children are uncommitted (CONFIG_STAGED) until that commit.
|
|
506
|
-
if (e.
|
|
507
|
-
e.
|
|
576
|
+
if (e.ue !== NOT_PENDING || e.T !== null && (e.T.Nt !== null || e.T.ft !== null) || (e.m & (STATUS_PENDING | STATUS_UNINITIALIZED)) !== 0 && (!t || (e.m & STATUS_PENDING) !== 0)) {
|
|
577
|
+
e.l |= CONFIG_STAGED;
|
|
508
578
|
// Lane work is the lane's to reveal (its seam), not this flush's commit.
|
|
509
|
-
if (
|
|
579
|
+
if (C === null) queuePendingNode(e);
|
|
510
580
|
}
|
|
511
|
-
setPassLane(
|
|
581
|
+
setPassLane(s);
|
|
512
582
|
// Missed-wake reschedule (see the finally above): values this pass read
|
|
513
583
|
// before the nested commit are stale, so run again now that the heap will
|
|
514
584
|
// accept the node. Equality gates stop same-value landings from cascading,
|
|
515
585
|
// and a re-run only latches again if another nested commit changes a dep
|
|
516
586
|
// beneath it — convergent unless deps genuinely keep changing.
|
|
517
|
-
if (
|
|
587
|
+
if (A) {
|
|
518
588
|
enqueueSub(e);
|
|
519
589
|
schedule();
|
|
520
590
|
}
|
|
@@ -529,9 +599,9 @@ function updateIfNecessary(e) {
|
|
|
529
599
|
// would resurrect it (#2983) — readers serve its last value.
|
|
530
600
|
if (e.R & (REACTIVE_RECOMPUTING_DEPS | REACTIVE_DISPOSED)) return;
|
|
531
601
|
if (e.R & REACTIVE_CHECK) {
|
|
532
|
-
for (let t = e.
|
|
533
|
-
const n = t.
|
|
534
|
-
if (n.
|
|
602
|
+
for (let t = e.C; t; t = t.G) {
|
|
603
|
+
const n = t.S;
|
|
604
|
+
if (n.X) {
|
|
535
605
|
updateIfNecessary(n);
|
|
536
606
|
}
|
|
537
607
|
if (e.R & REACTIVE_DIRTY) {
|
|
@@ -539,7 +609,7 @@ function updateIfNecessary(e) {
|
|
|
539
609
|
}
|
|
540
610
|
}
|
|
541
611
|
}
|
|
542
|
-
if (e.R & REACTIVE_DIRTY || e.
|
|
612
|
+
if (e.R & REACTIVE_DIRTY || e.T?.F && e.ie < clock && !e.T?.Se) {
|
|
543
613
|
recompute(e);
|
|
544
614
|
}
|
|
545
615
|
// The guard above refused an already-disposed node; the recompute it just
|
|
@@ -558,54 +628,55 @@ function computed(e, t) {
|
|
|
558
628
|
// `in` (not `!== undefined`): an explicit `loadingValue: undefined` on a
|
|
559
629
|
// `T | undefined` node is a real commit #0. The typeof guard tolerates
|
|
560
630
|
// non-object option values that older call shapes force through `as any`.
|
|
561
|
-
const
|
|
631
|
+
const o = t !== null && typeof t === "object" && "loadingValue" in t;
|
|
562
632
|
// Two literals, one per tier, selected at build time (the observe flag is
|
|
563
633
|
// a literal after replacement; the untaken branch is dead code). The observe
|
|
564
|
-
// literal is the prod literal plus its `_name`
|
|
565
|
-
// boilerplate, because a post-construction `self._name = …` forces a
|
|
634
|
+
// literal is the prod literal plus its `_name` and `_loc` slots — slots in
|
|
635
|
+
// the boilerplate, because a post-construction `self._name = …` forces a
|
|
566
636
|
// hidden-class transition and an out-of-object property store on EVERY node
|
|
567
637
|
// (measured: the whole of the observe tier's creation overhead). Keep the
|
|
568
638
|
// two in sync — the dist artifact test pins observe's key set to prod's
|
|
569
|
-
// plus
|
|
639
|
+
// plus the slots.
|
|
570
640
|
const s = {
|
|
571
641
|
id: inheritId(t, n, context),
|
|
572
|
-
|
|
642
|
+
l: (n ? CONFIG_TRANSPARENT : 0) | (t?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (!context || t?.lazy ? CONFIG_AUTO_DISPOSE : 0) | (t?.sync ? CONFIG_SYNC : 0) | (t?.le ? CONFIG_NO_SNAPSHOT : 0) | (
|
|
573
643
|
// Plumbing is an observe-tier notion (a name and records to
|
|
574
644
|
// withhold); the prod literal never carries the bit.
|
|
575
|
-
t?.
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
645
|
+
t?.Tt ? CONFIG_PLUMBING : 0) | (t?.St ?? 0) | (snapshotCaptureActive && ownerInSnapshotScope(context) ? CONFIG_IN_SNAPSHOT_SCOPE : 0),
|
|
646
|
+
Ye: t?.equals ?? isEqual,
|
|
647
|
+
rt: null,
|
|
648
|
+
A: context?.A ?? defaultContext,
|
|
649
|
+
ct: 0,
|
|
650
|
+
X: e,
|
|
651
|
+
Ie: o ? t.loadingValue : undefined,
|
|
652
|
+
Et: 0,
|
|
653
|
+
At: undefined,
|
|
584
654
|
Rt: null,
|
|
655
|
+
C: null,
|
|
585
656
|
_: null,
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
Dt: null,
|
|
657
|
+
Ne: 0,
|
|
658
|
+
o: null,
|
|
659
|
+
dt: null,
|
|
590
660
|
_parent: context,
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
661
|
+
Xe: null,
|
|
662
|
+
Ct: null,
|
|
663
|
+
ze: null,
|
|
594
664
|
R: t?.lazy ? REACTIVE_LAZY : REACTIVE_NONE,
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
665
|
+
m: o ? 0 : STATUS_UNINITIALIZED,
|
|
666
|
+
ie: clock,
|
|
667
|
+
ue: NOT_PENDING,
|
|
668
|
+
Ot: -1,
|
|
669
|
+
ce: o,
|
|
670
|
+
T: null,
|
|
601
671
|
// The slot is always present (hidden class); plumbing leaves it
|
|
602
672
|
// unset, which `ownerPath` skips.
|
|
603
|
-
_name: t?.
|
|
673
|
+
_name: t?.Tt ? undefined : t?.name ?? "computed",
|
|
674
|
+
_loc: t?.loc,
|
|
604
675
|
// Observe: the verdict windows this pass entered (verdict.ts) — a
|
|
605
676
|
// slot, so the stamp is never a write after construction.
|
|
606
|
-
|
|
677
|
+
Ve: 0
|
|
607
678
|
};
|
|
608
|
-
if (t?.unobserved) ext(s).
|
|
679
|
+
if (t?.unobserved) ext(s).Dt = t.unobserved;
|
|
609
680
|
setupComputedNode(s, t);
|
|
610
681
|
return s;
|
|
611
682
|
}
|
|
@@ -614,24 +685,24 @@ function computed(e, t) {
|
|
|
614
685
|
* computeds — `_x` access stays monomorphic). Installers write through
|
|
615
686
|
* this; hot paths read `el._x?._field` gated by the _config presence bits.
|
|
616
687
|
* Never call ext() just to store a field's default. */ function ext(e) {
|
|
617
|
-
return e.
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
688
|
+
return e.T ??= {
|
|
689
|
+
Se: null,
|
|
690
|
+
ae: null,
|
|
691
|
+
F: undefined,
|
|
692
|
+
se: undefined,
|
|
693
|
+
fe: undefined,
|
|
694
|
+
Dt: undefined,
|
|
695
|
+
lt: undefined,
|
|
696
|
+
Nt: null,
|
|
697
|
+
ft: null,
|
|
698
|
+
Je: null,
|
|
699
|
+
ut: false,
|
|
700
|
+
Gt: NOT_PENDING,
|
|
701
|
+
Ft: -1,
|
|
702
|
+
Ee: 0,
|
|
703
|
+
V: NOT_PENDING,
|
|
704
|
+
I: 0,
|
|
705
|
+
de: undefined
|
|
635
706
|
};
|
|
636
707
|
}
|
|
637
708
|
|
|
@@ -647,7 +718,12 @@ function computed(e, t) {
|
|
|
647
718
|
* the boundary collects it and shows its fallback, and the pass is the
|
|
648
719
|
* boundary's, not the tick's (A29's boundary exemption, #3540). */ function joinPass(e, t) {
|
|
649
720
|
e.R |= REACTIVE_JOINED;
|
|
650
|
-
|
|
721
|
+
// A new memo's first pass is the hold's work (born held) but not the
|
|
722
|
+
// tick's: a render effect that reads it decides — under a boundary that
|
|
723
|
+
// catches it, the boundary's; otherwise the tick joins the hold (#3540:
|
|
724
|
+
// the reader's position, not where the memo was created). Its catch, if
|
|
725
|
+
// any, is at its staging.
|
|
726
|
+
if (t.m & STATUS_UNINITIALIZED || e.i !== EFFECT_RENDER) joinPassTx(txOf(t), !e.i && e.m & STATUS_UNINITIALIZED || freshHook?.(e));
|
|
651
727
|
}
|
|
652
728
|
|
|
653
729
|
/** A15's stale reader (shared-hole and reveal corollaries): a render effect
|
|
@@ -656,11 +732,11 @@ function computed(e, t) {
|
|
|
656
732
|
* re-derived after that transaction's landing (`_reruns`,
|
|
657
733
|
* REACTIVE_FRAME_READ) so the frame catches up with it once it is the
|
|
658
734
|
* committed world. Not a frame born into that transaction (held and never
|
|
659
|
-
* committed — created by one of its held passes, A29): that
|
|
660
|
-
* transaction's values and keeps staging. Not of a flight
|
|
661
|
-
* already visible (CONFIG_INPUTS_PUBLISHED, #3305): its
|
|
662
|
-
* would tear against them, so the reveal observes the
|
|
663
|
-
* per pass.
|
|
735
|
+
* committed — created by one of its held passes, A29; `bornInto`): that
|
|
736
|
+
* one reads its transaction's values and keeps staging. Not of a flight
|
|
737
|
+
* whose inputs are already visible (CONFIG_INPUTS_PUBLISHED, #3305): its
|
|
738
|
+
* committed value would tear against them, so the reveal observes the
|
|
739
|
+
* flight instead. Once per pass.
|
|
664
740
|
*
|
|
665
741
|
* Lane work is a stale reader too (maintainer, 2026-10-01: a lane sees the
|
|
666
742
|
* screen plus its own guesses). A held write is held because showing it
|
|
@@ -684,20 +760,30 @@ function computed(e, t) {
|
|
|
684
760
|
// read the screen. Lane work with no committed value yet reads a flight
|
|
685
761
|
// as a mount's memo does: it enters, and the boundary it mounts catches
|
|
686
762
|
// the pending (#3540).
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
const
|
|
763
|
+
// (Verdict readers and lanes exist only with their engines installed.)
|
|
764
|
+
const n = observeFlightHook && e.l & CONFIG_VERDICT;
|
|
765
|
+
const o = laneStageHook && passLane !== null;
|
|
766
|
+
if ((!o || e.m & STATUS_UNINITIALIZED && t.m & STATUS_PENDING) && !n && (e.i !== EFFECT_RENDER || t.l & CONFIG_INPUTS_PUBLISHED)) return false;
|
|
767
|
+
const s = txOf(t);
|
|
690
768
|
// Lane work and verdict readers are never the transaction's pass, whichever
|
|
691
769
|
// flush runs it: a node T holds re-enters T when it re-runs (`recompute`'s
|
|
692
770
|
// head), and the pass may then read a guess and become the lane's.
|
|
693
|
-
if (
|
|
694
|
-
staleReader(e,
|
|
771
|
+
if (s === flushTransaction && !o && !n || bornInto(e, s)) return false;
|
|
772
|
+
staleReader(e, s);
|
|
695
773
|
return true;
|
|
696
774
|
}
|
|
697
775
|
|
|
776
|
+
/** A node born into transaction `t`: held by it and never committed (A29,
|
|
777
|
+
* ruling A) — uninitialized (born held), or a member of the frame one of its
|
|
778
|
+
* held passes built (CONFIG_HELD_FRAME). Its branch exists only in `t`: it
|
|
779
|
+
* has no screen to read, so it reads `t`'s values, whichever flush runs it,
|
|
780
|
+
* and keeps staging (#4010). */ function bornInto(e, t) {
|
|
781
|
+
return (e.m & STATUS_UNINITIALIZED || e.l & CONFIG_HELD_FRAME) && e.l & CONFIG_HELD && txOf(e) === t;
|
|
782
|
+
}
|
|
783
|
+
|
|
698
784
|
/** The computed whose pass a creation under `owner` belongs to (a root's is
|
|
699
785
|
* its nearest computed ancestor's), or null outside every pass. */ function creatorPass(e) {
|
|
700
|
-
return e !== null && e.
|
|
786
|
+
return e !== null && e.Pt ? e.Ht : e;
|
|
701
787
|
}
|
|
702
788
|
|
|
703
789
|
/** L2 — park the previous pass's frame for this node's commit: the children
|
|
@@ -706,11 +792,11 @@ function computed(e, t) {
|
|
|
706
792
|
* the frame that replaces them (`commitPendingNode`) or when the owner does. */ function parkChildren(e) {
|
|
707
793
|
markDisposal(e);
|
|
708
794
|
const t = ext(e);
|
|
709
|
-
t.
|
|
710
|
-
t.
|
|
711
|
-
e.
|
|
712
|
-
e.
|
|
713
|
-
e.
|
|
795
|
+
t.ft = e.rt;
|
|
796
|
+
t.Nt = e.ze;
|
|
797
|
+
e.rt = null;
|
|
798
|
+
e.ze = null;
|
|
799
|
+
e.ct = 0;
|
|
714
800
|
}
|
|
715
801
|
|
|
716
802
|
/**
|
|
@@ -718,58 +804,59 @@ function computed(e, t) {
|
|
|
718
804
|
* so V8 sees the full hidden class shape at construction time. Effects always run in lazy
|
|
719
805
|
* mode (recompute is called explicitly by `effect()`), so we hardcode the lazy bits and skip
|
|
720
806
|
* the auto-dispose CONFIG bit (effect() previously cleared it post-construction).
|
|
721
|
-
*/ function createEffectNode(e, t, n,
|
|
807
|
+
*/ function createEffectNode(e, t, n, o, s) {
|
|
722
808
|
const E = s?.transparent ?? false;
|
|
723
809
|
// Prod and observe boilerplates — see computed() for why the observe tier
|
|
724
810
|
// gets its `_name` as a literal slot rather than a write after the fact.
|
|
725
811
|
// The default label is the node kind (tracked effects relabel their computed
|
|
726
812
|
// in trackedEffect); the wrappers in signals.ts no longer spread a name into
|
|
727
813
|
// the options to get it.
|
|
728
|
-
const
|
|
814
|
+
const l = {
|
|
729
815
|
id: inheritId(s, E, context),
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
816
|
+
l: (E ? CONFIG_TRANSPARENT : 0) | (s?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (s?.sync ? CONFIG_SYNC : 0) | (s?.St ?? 0) | (snapshotCaptureActive && ownerInSnapshotScope(context) ? CONFIG_IN_SNAPSHOT_SCOPE : 0),
|
|
817
|
+
Ye: false,
|
|
818
|
+
rt: null,
|
|
819
|
+
A: context?.A ?? defaultContext,
|
|
820
|
+
ct: 0,
|
|
821
|
+
X: e,
|
|
822
|
+
Ie: undefined,
|
|
823
|
+
Et: 0,
|
|
824
|
+
At: undefined,
|
|
739
825
|
Rt: null,
|
|
826
|
+
C: null,
|
|
740
827
|
_: null,
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
Dt: null,
|
|
828
|
+
Ne: 0,
|
|
829
|
+
o: null,
|
|
830
|
+
dt: null,
|
|
745
831
|
_parent: context,
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
832
|
+
Xe: null,
|
|
833
|
+
Ct: null,
|
|
834
|
+
ze: null,
|
|
749
835
|
R: REACTIVE_LAZY,
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
836
|
+
m: STATUS_UNINITIALIZED,
|
|
837
|
+
ie: clock,
|
|
838
|
+
ue: NOT_PENDING,
|
|
839
|
+
Ot: -1,
|
|
840
|
+
ce: false,
|
|
841
|
+
Qe: false,
|
|
842
|
+
_t: undefined,
|
|
843
|
+
Ge: t,
|
|
844
|
+
ht: n,
|
|
759
845
|
kt: undefined,
|
|
760
|
-
i:
|
|
761
|
-
|
|
846
|
+
i: o,
|
|
847
|
+
T: null,
|
|
762
848
|
_name: s?.name ?? "effect",
|
|
763
|
-
|
|
849
|
+
_loc: s?.loc,
|
|
850
|
+
Ve: 0
|
|
764
851
|
};
|
|
765
852
|
// Effects dispatch status through the SHARED notifier (statusNotifierOf,
|
|
766
853
|
// keyed off _type) — storing it per node forced a full NodeExtension
|
|
767
854
|
// allocation on EVERY effect at creation (an alloc + 19 field stores,
|
|
768
855
|
// +23% effect creation, caught by the creation benches). Only genuinely
|
|
769
856
|
// per-node channels (boundaries) live on _x.
|
|
770
|
-
if (s?.unobserved) ext(
|
|
771
|
-
setupComputedNode(
|
|
772
|
-
return
|
|
857
|
+
if (s?.unobserved) ext(l).Dt = s.unobserved;
|
|
858
|
+
setupComputedNode(l, lazyOptions);
|
|
859
|
+
return l;
|
|
773
860
|
}
|
|
774
861
|
|
|
775
862
|
/**
|
|
@@ -797,15 +884,15 @@ const lazyOptions = {
|
|
|
797
884
|
|
|
798
885
|
function setupComputedNode(e, t) {
|
|
799
886
|
e.Rt = e;
|
|
800
|
-
const n = context?.
|
|
887
|
+
const n = context?.Pt ? context.Ht : context;
|
|
801
888
|
if (context) linkChild(context, e);
|
|
802
|
-
if (n) e.
|
|
803
|
-
|
|
889
|
+
if (n) e.Et = n.Et + 1;
|
|
890
|
+
wireExternalSourceHook?.(e);
|
|
804
891
|
!t?.lazy && recompute(e, true);
|
|
805
892
|
if (snapshotCaptureActive && !t?.lazy) {
|
|
806
|
-
if (!(e.
|
|
807
|
-
ext(e).
|
|
808
|
-
e.
|
|
893
|
+
if (!(e.m & STATUS_PENDING) && !(e.l & CONFIG_NO_SNAPSHOT)) {
|
|
894
|
+
ext(e).lt = e.Ie === undefined ? NO_SNAPSHOT : e.Ie;
|
|
895
|
+
e.l |= CONFIG_HAS_SNAPSHOT;
|
|
809
896
|
snapshotSources.add(e);
|
|
810
897
|
}
|
|
811
898
|
}
|
|
@@ -813,26 +900,27 @@ function setupComputedNode(e, t) {
|
|
|
813
900
|
|
|
814
901
|
function signal(e, t) {
|
|
815
902
|
// Prod and observe boilerplates — see computed(). The observe literal adds
|
|
816
|
-
// `_name` and `_owner` (the creating owner, stamped by registerGraph for
|
|
903
|
+
// `_name`, `_loc` and `_owner` (the creating owner, stamped by registerGraph for
|
|
817
904
|
// createSignal nodes so ownerPath can locate signal subjects; null here,
|
|
818
905
|
// and staying null on internal signals — one shape either way).
|
|
819
906
|
const n = {
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
907
|
+
Ye: t?.equals ?? isEqual,
|
|
908
|
+
l: (t?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (t?.le ? CONFIG_NO_SNAPSHOT : 0),
|
|
909
|
+
Ie: e,
|
|
910
|
+
o: null,
|
|
911
|
+
dt: null,
|
|
912
|
+
ie: clock,
|
|
913
|
+
ue: NOT_PENDING,
|
|
914
|
+
Ot: -1,
|
|
915
|
+
T: null,
|
|
829
916
|
_name: t?.name ?? "signal",
|
|
917
|
+
_loc: t?.loc,
|
|
830
918
|
H: null
|
|
831
919
|
};
|
|
832
|
-
if (t?.unobserved) ext(n).
|
|
833
|
-
if (snapshotCaptureActive && !(n.
|
|
834
|
-
ext(n).
|
|
835
|
-
n.
|
|
920
|
+
if (t?.unobserved) ext(n).Dt = t.unobserved;
|
|
921
|
+
if (snapshotCaptureActive && !(n.l & CONFIG_NO_SNAPSHOT)) {
|
|
922
|
+
ext(n).lt = e === undefined ? NO_SNAPSHOT : e;
|
|
923
|
+
n.l |= CONFIG_HAS_SNAPSHOT;
|
|
836
924
|
snapshotSources.add(n);
|
|
837
925
|
}
|
|
838
926
|
return n;
|
|
@@ -851,23 +939,25 @@ function isEqual(e, t) {
|
|
|
851
939
|
* object, no NodeExtension, no post-construction expandos. `ownedWrite` is
|
|
852
940
|
* baked in: the setter carries the owned-scope guard; node-level writes are
|
|
853
941
|
* the store's notification machinery. The observe literal adds the label
|
|
854
|
-
*
|
|
942
|
+
* and location slots (the store relabels `store.<key>` and stamps the
|
|
943
|
+
* store's `loc` when the engine is installed). */ function slotSignal(e, t, n, o, s) {
|
|
855
944
|
const E = {
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
945
|
+
Ye: t,
|
|
946
|
+
l: CONFIG_OWNED_WRITE | CONFIG_SLOT_NODE,
|
|
947
|
+
Ie: e,
|
|
948
|
+
o: null,
|
|
949
|
+
dt: null,
|
|
950
|
+
ie: clock,
|
|
951
|
+
ue: NOT_PENDING,
|
|
952
|
+
Ot: -1,
|
|
953
|
+
T: null,
|
|
954
|
+
O: n,
|
|
955
|
+
Vt: o,
|
|
867
956
|
acc: s,
|
|
868
957
|
px: undefined,
|
|
869
958
|
pxv: undefined,
|
|
870
959
|
_name: "signal",
|
|
960
|
+
_loc: undefined,
|
|
871
961
|
H: null
|
|
872
962
|
};
|
|
873
963
|
return E;
|
|
@@ -892,6 +982,16 @@ function setVerdict(e) {
|
|
|
892
982
|
* Use `untrack` inside a memo or effect when you need to read a signal once
|
|
893
983
|
* without making the surrounding computation depend on its future changes.
|
|
894
984
|
*
|
|
985
|
+
* `untrack` stops dependency tracking, not async settlement. Reading an
|
|
986
|
+
* uninitialized async source still suspends the owning computation; it runs
|
|
987
|
+
* again when that source first resolves. Afterward, the untracked read is a
|
|
988
|
+
* snapshot and does not subscribe the computation to later changes.
|
|
989
|
+
*
|
|
990
|
+
* If the first read must return immediately, give the async source a
|
|
991
|
+
* `loadingValue` before reading it with `untrack`. `isPending` and `latest`
|
|
992
|
+
* inspect updates to an existing value; neither supplies a missing initial
|
|
993
|
+
* value.
|
|
994
|
+
*
|
|
895
995
|
* Pass a `strictReadLabel` string to enable a dev-mode warning: any reactive
|
|
896
996
|
* read inside `fn` that isn't inside a nested tracking scope will log a
|
|
897
997
|
* warning naming the label.
|
|
@@ -905,13 +1005,15 @@ function setVerdict(e) {
|
|
|
905
1005
|
* log(snapshot);
|
|
906
1006
|
* }
|
|
907
1007
|
* );
|
|
1008
|
+
*
|
|
1009
|
+
* const color = createMemo<string>(() => fetchColor(), { loadingValue: "gray" });
|
|
1010
|
+
* const initialColor = untrack(color); // "gray" while the fetch is unresolved
|
|
908
1011
|
* ```
|
|
909
1012
|
*/ function untrack(e, t) {
|
|
910
|
-
if (GlobalQueue.Wt === null && !tracking && true) return e();
|
|
911
1013
|
const n = tracking;
|
|
912
1014
|
tracking = false;
|
|
913
1015
|
try {
|
|
914
|
-
if (
|
|
1016
|
+
if (externalUntrackHook) return externalUntrackHook(e);
|
|
915
1017
|
return e();
|
|
916
1018
|
} finally {
|
|
917
1019
|
tracking = n;
|
|
@@ -945,13 +1047,13 @@ function setVerdict(e) {
|
|
|
945
1047
|
// pay-for-use contract. Owner-lifecycle nodes are dead: recomputing would
|
|
946
1048
|
// re-run user code in a torn-down tree (and discard manual writes on
|
|
947
1049
|
// derived-writable signals), so reads return the last committed value.
|
|
948
|
-
if (e.
|
|
1050
|
+
if (e.l & CONFIG_AUTO_DISPOSE) {
|
|
949
1051
|
const t = e._parent;
|
|
950
1052
|
if (t !== null) {
|
|
951
1053
|
// A dormant node was off the chain when its owner died, so the strip
|
|
952
1054
|
// in disposeChildren missed it: freeze here instead (#3024).
|
|
953
1055
|
if (t.R & REACTIVE_DISPOSED) {
|
|
954
|
-
e.
|
|
1056
|
+
e.l &= ~CONFIG_AUTO_DISPOSE;
|
|
955
1057
|
return;
|
|
956
1058
|
}
|
|
957
1059
|
// A zombie never left its chain (the parked frame is drained whole,
|
|
@@ -979,7 +1081,7 @@ function setVerdict(e) {
|
|
|
979
1081
|
* never the graph — A32).
|
|
980
1082
|
* False means the reader derives from the staged value.
|
|
981
1083
|
*/ function readerSeesCommitted(e, t) {
|
|
982
|
-
return
|
|
1084
|
+
return !t || e.ue === NOT_PENDING || t.l & CONFIG_CHILDREN_FORBIDDEN;
|
|
983
1085
|
}
|
|
984
1086
|
|
|
985
1087
|
/** A28 — set when a node is staged (queuePendingNode) OUTSIDE a flush;
|
|
@@ -1000,21 +1102,21 @@ function markUnflushedStaged() {
|
|
|
1000
1102
|
* is unflushed. Exempt: promoted writes (A28 (4): a write issued inside a
|
|
1001
1103
|
* recompute is promoted at that recompute's end — boundary and loading
|
|
1002
1104
|
* machinery, signals declared for in-computation writes). */ function unflushedValue(e) {
|
|
1003
|
-
if (globalQueue.
|
|
1105
|
+
if (globalQueue.q || e.ue === NOT_PENDING || e.l & CONFIG_PROMOTED) return NOT_PENDING;
|
|
1004
1106
|
// A held node (L2) carries a staged value between flushes too, but it was
|
|
1005
1107
|
// flushed — and parked. Not an unflushed write — unless rewritten since:
|
|
1006
1108
|
// then the flushed staging is what the world sees until the next flush.
|
|
1007
|
-
if (e.
|
|
1008
|
-
return e.
|
|
1109
|
+
if (e.l & CONFIG_HELD) return e.T.Ft === clock ? e.T.Gt : NOT_PENDING;
|
|
1110
|
+
return e.Ie;
|
|
1009
1111
|
}
|
|
1010
1112
|
|
|
1011
1113
|
/** A28: a held node rewritten outside a flush keeps the staging the last
|
|
1012
1114
|
* flush left (`_flushed`) until the flush that carries the rewrite — the
|
|
1013
1115
|
* stash is stamped with the clock, which that flush advances. */ function stashFlushed(e) {
|
|
1014
|
-
const t = e.
|
|
1015
|
-
if (t.
|
|
1016
|
-
t.
|
|
1017
|
-
t.
|
|
1116
|
+
const t = e.T;
|
|
1117
|
+
if (t.Ft !== clock) {
|
|
1118
|
+
t.Gt = e.ue;
|
|
1119
|
+
t.Ft = clock;
|
|
1018
1120
|
unflushedStaged = true;
|
|
1019
1121
|
}
|
|
1020
1122
|
}
|
|
@@ -1034,7 +1136,7 @@ function resyncUnflushedCompanions() {
|
|
|
1034
1136
|
unflushedStaged = false;
|
|
1035
1137
|
// Length-guarded: the common flush has nothing here and allocates nothing.
|
|
1036
1138
|
if (promotedWrites.length !== 0) {
|
|
1037
|
-
for (const e of promotedWrites) e.
|
|
1139
|
+
for (const e of promotedWrites) e.l &= ~CONFIG_PROMOTED;
|
|
1038
1140
|
promotedWrites.length = 0;
|
|
1039
1141
|
}
|
|
1040
1142
|
}
|
|
@@ -1042,7 +1144,7 @@ function resyncUnflushedCompanions() {
|
|
|
1042
1144
|
/** A tracked read's pull of a computed: bring it current when the flush has
|
|
1043
1145
|
* reached its height (or the reader is a fresh-pull waiter), and sit the
|
|
1044
1146
|
* reader above it. */ function pullComputed(e, t) {
|
|
1045
|
-
if (e.
|
|
1147
|
+
if (e.Et >= dirtyQueue.st) {
|
|
1046
1148
|
markNode(t);
|
|
1047
1149
|
markHeap(dirtyQueue);
|
|
1048
1150
|
updateIfNecessary(e);
|
|
@@ -1053,27 +1155,27 @@ function resyncUnflushedCompanions() {
|
|
|
1053
1155
|
// never read the PRE-re-ask value as settled. Self-guarded: a clean
|
|
1054
1156
|
// node no-ops and updateIfNecessary refuses disposed nodes (#2983) —
|
|
1055
1157
|
// a dead target serves its last value, which is already quiescent.
|
|
1056
|
-
else if (t.
|
|
1057
|
-
const n = e.
|
|
1158
|
+
else if (t.l & CONFIG_FRESH_READ) updateIfNecessary(e);
|
|
1159
|
+
const n = e.Et;
|
|
1058
1160
|
// parent check is shallow, might need to be recursive
|
|
1059
|
-
if (n >= t.
|
|
1161
|
+
if (n >= t.Et && e._parent !== t) t.Et = n + 1;
|
|
1060
1162
|
}
|
|
1061
1163
|
|
|
1062
1164
|
function read(e) {
|
|
1063
1165
|
let t = context;
|
|
1064
|
-
if (t?.
|
|
1166
|
+
if (t?.Pt) t = t.Ht;
|
|
1065
1167
|
const n = e;
|
|
1066
1168
|
// CARVE 1: `owner` was `el._firewall || el` — a store leaf's status,
|
|
1067
1169
|
// height and error were read off its projection computed. (Typed loosely
|
|
1068
1170
|
// as the `||` expression was: a signal's `_statusFlags`/`_height` read as
|
|
1069
1171
|
// undefined and mask to 0 on every site below.)
|
|
1070
|
-
const
|
|
1071
|
-
if (typeof n.
|
|
1172
|
+
const o = e;
|
|
1173
|
+
if (typeof n.X === "function") prepareComputed(e);
|
|
1072
1174
|
// The verdict windows (`latest`, `isPending`; verdict.ts): one cold path,
|
|
1073
1175
|
// off this one, present only while a window is open.
|
|
1074
1176
|
if (verdict !== null) return verdict(e, t);
|
|
1075
|
-
if (!n.
|
|
1076
|
-
!(e.
|
|
1177
|
+
if (!n.X && e.T?.lt === undefined && !snapshotCaptureActive && (!unflushedStaged || e.ue === NOT_PENDING) && // A28, see readNodeFast
|
|
1178
|
+
!(e.l & CONFIG_OVERRIDE) && // lanes: the slow path's one arm
|
|
1077
1179
|
true) {
|
|
1078
1180
|
if (t) {
|
|
1079
1181
|
if (tracking) link(e, t);
|
|
@@ -1087,17 +1189,17 @@ function read(e) {
|
|
|
1087
1189
|
// stays out too (rule 3, `frameRead`). The pass remembers it read the
|
|
1088
1190
|
// future (REACTIVE_JOINED): a first pass that did is born held (A29),
|
|
1089
1191
|
// wherever it was created. Lane work sees the screen (`frameRead`).
|
|
1090
|
-
if (e.
|
|
1091
|
-
if (frameRead(t, e)) return e.
|
|
1192
|
+
if (e.l & CONFIG_HELD && !(t.l & CONFIG_CHILDREN_FORBIDDEN)) {
|
|
1193
|
+
if (frameRead(t, e)) return e.Ie;
|
|
1092
1194
|
joinPass(t, e);
|
|
1093
1195
|
}
|
|
1094
1196
|
}
|
|
1095
1197
|
// Committed visibility for untracked and children-forbidden readers.
|
|
1096
|
-
if (!t || e.
|
|
1198
|
+
if (!t || e.ue === NOT_PENDING || t.l & CONFIG_CHILDREN_FORBIDDEN) return e.Ie;
|
|
1097
1199
|
// (A plain signal is never uninitialized — the carve-out is `serve`'s.)
|
|
1098
|
-
if (t.
|
|
1200
|
+
if (observeFlightHook && t.l & CONFIG_VERDICT && stagedScreen(t)) return e.Ie;
|
|
1099
1201
|
stagedRead(t);
|
|
1100
|
-
return e.
|
|
1202
|
+
return e.ue;
|
|
1101
1203
|
}
|
|
1102
1204
|
// Rule 3: this read serves the committed value to a render effect outside a
|
|
1103
1205
|
// parking flush (decided after the pull below — a pull can join).
|
|
@@ -1105,20 +1207,29 @@ function read(e) {
|
|
|
1105
1207
|
if (t) {
|
|
1106
1208
|
if (tracking) {
|
|
1107
1209
|
link(e, t);
|
|
1108
|
-
if (
|
|
1210
|
+
if (o.X) {
|
|
1211
|
+
pullComputed(o, t);
|
|
1212
|
+
// F-6 (#4050): a memo over a verdict reader's value derives from a
|
|
1213
|
+
// display-ahead read — a verdict reader from this pass's end, as if
|
|
1214
|
+
// it probed inline: it reads held values as the screen and shows the
|
|
1215
|
+
// verdict now. Not an effect: a render effect is a frame reader
|
|
1216
|
+
// already, and a user effect's posture is its own reads'.
|
|
1217
|
+
if (!t.i) t.R |= o.l & CONFIG_VERDICT;
|
|
1218
|
+
}
|
|
1109
1219
|
}
|
|
1110
1220
|
// L2, as in the fast block above (tracked or not). A node born into the
|
|
1111
1221
|
// future (uninitialized) has no committed value: every reader derives
|
|
1112
1222
|
// from the future there, a render effect included (A29: a stale reader
|
|
1113
1223
|
// of it cannot fall back to the committed frame and enters instead).
|
|
1114
|
-
if (e.
|
|
1115
|
-
if (
|
|
1224
|
+
if (e.l & CONFIG_HELD && !(e.l & CONFIG_OVERRIDE) && !(t.l & CONFIG_CHILDREN_FORBIDDEN)) {
|
|
1225
|
+
if (o.m & STATUS_UNINITIALIZED || !frameRead(t, e)) joinPass(t, e); else s = true;
|
|
1116
1226
|
}
|
|
1117
1227
|
}
|
|
1118
1228
|
// Lanes: a lane's node (after the pull — the node is current). NOT_PENDING
|
|
1119
1229
|
// falls through: lane work's read of a pending member throws like any.
|
|
1120
|
-
|
|
1121
|
-
|
|
1230
|
+
// (CONFIG_OVERRIDE is the lane engine's: no engine, no lane's node.)
|
|
1231
|
+
if (laneReadHook && e.l & CONFIG_OVERRIDE) {
|
|
1232
|
+
const n = laneReadHook(t, e);
|
|
1122
1233
|
if (n !== NOT_PENDING) return n;
|
|
1123
1234
|
}
|
|
1124
1235
|
// A stale reader of a held flight (rule 3, A15's reveal corollary) is served
|
|
@@ -1133,53 +1244,58 @@ function read(e) {
|
|
|
1133
1244
|
// effect keeps its DOM by throwing, a memo has no DOM and is handed the
|
|
1134
1245
|
// value. `[isPending(x), x()]` reads `[true, stale]` in either order (A10)
|
|
1135
1246
|
// — unless the flight committed beneath inputs already on screen
|
|
1136
|
-
// (`observeFlight`)
|
|
1137
|
-
|
|
1247
|
+
// (`observeFlight`), or the reader was born into the flight's transaction
|
|
1248
|
+
// (#4010): it has no screen, and waits on the flight with its frame.
|
|
1249
|
+
if (o.m & STATUS_PENDING && !s && !(e.l & CONFIG_OVERRIDE) && !(o.m & STATUS_UNINITIALIZED)) {
|
|
1138
1250
|
// A reader with no committed value yet sees the flight pending: a
|
|
1139
1251
|
// boundary a lane mounts shows its fallback (A29's boundary exemption).
|
|
1140
|
-
|
|
1252
|
+
// (A lane, and a verdict reader, exist only with their engines installed.)
|
|
1253
|
+
if (laneStageHook && passLane !== null && !(t?.m & STATUS_UNINITIALIZED)) s = true; else if (observeFlightHook && t !== null && t.l & CONFIG_VERDICT) s = observeFlightHook(t, o);
|
|
1141
1254
|
}
|
|
1142
|
-
if (
|
|
1255
|
+
if (o.m & STATUS_PENDING && !s) {
|
|
1143
1256
|
// A reader landing on a pending node throws; an untracked read of an
|
|
1144
1257
|
// UNINITIALIZED node has no committed value to serve and throws too. A
|
|
1145
1258
|
// children-forbidden reader (createTrackedEffect / onSettled) sees the
|
|
1146
1259
|
// frame, not the graph (A32): the committed value, and the NotReady only
|
|
1147
1260
|
// where there is none (uninitialized).
|
|
1148
1261
|
if (t) {
|
|
1149
|
-
if (!(t.
|
|
1262
|
+
if (!(t.l & CONFIG_CHILDREN_FORBIDDEN) || o.m & STATUS_UNINITIALIZED) {
|
|
1150
1263
|
// An untracked read of a pending node still re-runs its reader when
|
|
1151
1264
|
// the node settles — unless nobody is reading (`spectating`, #3528)
|
|
1152
1265
|
// or the reader is the node itself.
|
|
1153
1266
|
if (!tracking && !spectating && e !== t) link(e, t);
|
|
1154
|
-
throw
|
|
1267
|
+
throw o.T?.F;
|
|
1155
1268
|
}
|
|
1156
|
-
} else if (
|
|
1157
|
-
throw
|
|
1269
|
+
} else if (o.m & STATUS_UNINITIALIZED) {
|
|
1270
|
+
throw o.T?.F;
|
|
1158
1271
|
}
|
|
1159
1272
|
}
|
|
1160
1273
|
// An errored derive throws for every late reader instead of silently
|
|
1161
1274
|
// serving node values (memo parity, #2897 ruling).
|
|
1162
|
-
if (
|
|
1275
|
+
if (o.X && o.m & STATUS_ERROR) {
|
|
1163
1276
|
// Only a genuine reactive re-read may retry an errored async source:
|
|
1164
1277
|
// - tracking: owned/tracked scope only (never events / `untrack` / effect side-effect phase)
|
|
1165
1278
|
// - owner._time < clock: only on a later cycle than the one the error was found
|
|
1166
|
-
if (tracking &&
|
|
1167
|
-
recompute(
|
|
1279
|
+
if (tracking && o.ie < clock) {
|
|
1280
|
+
recompute(o);
|
|
1168
1281
|
return read(e);
|
|
1169
|
-
} else throw
|
|
1282
|
+
} else throw o.T?.F;
|
|
1170
1283
|
}
|
|
1171
|
-
if (snapshotCaptureActive && t && t.
|
|
1172
|
-
const n = e.
|
|
1284
|
+
if (snapshotCaptureActive && t && t.l & CONFIG_IN_SNAPSHOT_SCOPE) {
|
|
1285
|
+
const n = e.T?.lt;
|
|
1173
1286
|
if (n !== undefined) {
|
|
1174
|
-
const
|
|
1175
|
-
const s = e.
|
|
1176
|
-
if (s !==
|
|
1177
|
-
return
|
|
1287
|
+
const o = n === NO_SNAPSHOT ? undefined : n;
|
|
1288
|
+
const s = e.ue !== NOT_PENDING ? e.ue : e.Ie;
|
|
1289
|
+
if (s !== o) t.R |= REACTIVE_SNAPSHOT_STALE;
|
|
1290
|
+
return o;
|
|
1178
1291
|
}
|
|
1179
1292
|
}
|
|
1180
|
-
if (s) return e.
|
|
1293
|
+
if (s) return e.Ie;
|
|
1294
|
+
// D-41: an untracked read reads the screen, and a shown error is on it
|
|
1295
|
+
// until the answer that replaces it commits.
|
|
1296
|
+
if (!t && e.T?.de) throw e.T.de;
|
|
1181
1297
|
const E = serve(e, t);
|
|
1182
|
-
if (!t && typeof n.
|
|
1298
|
+
if (!t && typeof n.X === "function" && e.l & CONFIG_AUTO_DISPOSE && !(o.m & STATUS_PENDING) && !e.o) {
|
|
1183
1299
|
// Deferred, not inline (#3078): an inline unobserved() here made untracked
|
|
1184
1300
|
// reads destructive — dispose on this read, full revival recompute on the
|
|
1185
1301
|
// next — so consecutive reads could answer differently with no write in
|
|
@@ -1206,16 +1322,18 @@ function read(e) {
|
|
|
1206
1322
|
// an untracked reader has nothing to serve and holds (A19 exception 1) — a
|
|
1207
1323
|
// bookkeeping read (`spectate`) likewise, and a children-forbidden reader
|
|
1208
1324
|
// (A32: the frame, which has nothing here).
|
|
1209
|
-
if (e.
|
|
1325
|
+
if (e.ue !== NOT_PENDING && e.m & STATUS_UNINITIALIZED && (!t || spectating || t.l & CONFIG_CHILDREN_FORBIDDEN)) throw new NotReadyError(null);
|
|
1210
1326
|
const n = t && unflushedStaged ? unflushedValue(e) : NOT_PENDING;
|
|
1211
1327
|
if (n !== NOT_PENDING) {
|
|
1212
1328
|
markLateLinker(t);
|
|
1213
1329
|
return n;
|
|
1214
1330
|
}
|
|
1215
|
-
if (readerSeesCommitted(e, t)) return e.
|
|
1216
|
-
if (t.
|
|
1331
|
+
if (readerSeesCommitted(e, t)) return e.Ie;
|
|
1332
|
+
if (observeFlightHook && t.l & CONFIG_VERDICT && !(e.m & STATUS_UNINITIALIZED) && stagedScreen(t)) return e.Ie;
|
|
1217
1333
|
stagedRead(t, e);
|
|
1218
|
-
|
|
1334
|
+
// D-41: the staged answer is an error (`stageError`).
|
|
1335
|
+
if (e.l & CONFIG_STAGED_ERROR) throw e.ue;
|
|
1336
|
+
return e.ue;
|
|
1219
1337
|
}
|
|
1220
1338
|
|
|
1221
1339
|
/** A pass read a staging of this flush: it derives from what the flush may
|
|
@@ -1227,10 +1345,13 @@ function read(e) {
|
|
|
1227
1345
|
* write never shows through the lane. A verdict lane's work likewise: the
|
|
1228
1346
|
* lane holds verdicts, not the frame's other stagings (#3851) — except a
|
|
1229
1347
|
* 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
|
|
1231
|
-
* and a re-run would read the same staging and re-queue every
|
|
1232
|
-
|
|
1233
|
-
if (
|
|
1348
|
+
* a read of a node born staged at all: it has no committed value to
|
|
1349
|
+
* re-derive on, and a re-run would read the same staging and re-queue every
|
|
1350
|
+
* round (#3969: nor does it make a later lane read a re-derive, `enterLane`). */ function stagedRead(e, t) {
|
|
1351
|
+
if (!(t?.m & STATUS_UNINITIALIZED)) {
|
|
1352
|
+
e.R |= REACTIVE_STAGED_READ;
|
|
1353
|
+
if (laneSeamsHook && passLane !== null) stagedReaders.push(e);
|
|
1354
|
+
}
|
|
1234
1355
|
}
|
|
1235
1356
|
|
|
1236
1357
|
/** A10 for a staged node: a verdict reader (the pass entered a window) that
|
|
@@ -1240,8 +1361,9 @@ function read(e) {
|
|
|
1240
1361
|
* #3322 — one run, on the committed world), or with the hold. Before any
|
|
1241
1362
|
* transaction the answer is the seam's and the reader watches it
|
|
1242
1363
|
* (verdict.ts). A node born into the future has no screen: its staging is
|
|
1243
|
-
* its only value (A29).
|
|
1244
|
-
|
|
1364
|
+
* its only value (A29). Nor has a reader born into the flush's transaction
|
|
1365
|
+
* (`bornInto`, #4010): its branch exists only there. */ function stagedScreen(e) {
|
|
1366
|
+
if (flushTransaction === null || bornInto(e, flushTransaction)) return false;
|
|
1245
1367
|
staleReader(e, flushTransaction);
|
|
1246
1368
|
return true;
|
|
1247
1369
|
}
|
|
@@ -1249,40 +1371,77 @@ function read(e) {
|
|
|
1249
1371
|
/** A28 (4) — written inside a recompute that runs OUTSIDE a flush (a
|
|
1250
1372
|
* creation-time compute): promoted at that pass's end, visible to the rest of
|
|
1251
1373
|
* the block. Cold: only contextual writes reach here. */ function notePromotedWrite(e) {
|
|
1252
|
-
if (globalQueue.
|
|
1253
|
-
e.
|
|
1374
|
+
if (globalQueue.q || e.l & CONFIG_PROMOTED) return;
|
|
1375
|
+
e.l |= CONFIG_PROMOTED;
|
|
1254
1376
|
promotedWrites.push(e);
|
|
1255
1377
|
}
|
|
1256
1378
|
|
|
1257
|
-
/** Cold half of setSignal's snapshot arm (see there)
|
|
1258
|
-
*
|
|
1259
|
-
*
|
|
1379
|
+
/** Cold half of setSignal's snapshot arm (see there), shared with a store
|
|
1380
|
+
* container's structural notify: first write during capture to a plain
|
|
1381
|
+
* signal without a live snapshot records the pre-write value. Only plain
|
|
1382
|
+
* user signals qualify. Computeds reach setSignal from an
|
|
1260
1383
|
* async landing (asyncWrite), and a value arriving from async during the pass
|
|
1261
1384
|
* REVEALS — the creation-time arm skips pending computeds for the same
|
|
1262
1385
|
* reason. Firewall leaves belong to a projection whose compute is the tree's
|
|
1263
1386
|
* own work, captured (or deliberately not) at creation. */ function captureWriteSnapshot(e, t) {
|
|
1264
|
-
if (e.
|
|
1265
|
-
ext(e).
|
|
1266
|
-
e.
|
|
1387
|
+
if (e.l & CONFIG_NO_SNAPSHOT || e.X !== undefined || e.T?.lt !== undefined) return;
|
|
1388
|
+
ext(e).lt = t === undefined ? NO_SNAPSHOT : t;
|
|
1389
|
+
e.l |= CONFIG_HAS_SNAPSHOT;
|
|
1267
1390
|
snapshotSources.add(e);
|
|
1268
1391
|
}
|
|
1269
1392
|
|
|
1393
|
+
/**
|
|
1394
|
+
* A callback run before every signal or store write lands, while set, with
|
|
1395
|
+
* the written signal or the store proxy whose setter runs. The
|
|
1396
|
+
* server-components client sets it while slot occurrences are left
|
|
1397
|
+
* unmounted after hydration (deferred hydration): a write may be one an
|
|
1398
|
+
* unmounted component would have observed, so the client mounts what is
|
|
1399
|
+
* left before the write applies, and clears the hook. `null` otherwise.
|
|
1400
|
+
*/ let writeHook = null;
|
|
1401
|
+
|
|
1402
|
+
/** @internal */ function setWriteHook(e) {
|
|
1403
|
+
writeHook = e;
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
/**
|
|
1407
|
+
* `el`'s comparator verdict (`_equals`), the one guarded call for recompute,
|
|
1408
|
+
* setSignal (user setters and async landings) and the seam's
|
|
1409
|
+
* staged-equals-committed check.
|
|
1410
|
+
* - A comparator that read an unresolved async source has not shown the
|
|
1411
|
+
* values equal (#4111): falsy, a change, whose readers suspend in their own
|
|
1412
|
+
* tracked context.
|
|
1413
|
+
* - Any other throw is an error of `el`'s computation (A3, #2837): routed
|
|
1414
|
+
* through the status path so error boundaries contain it, instead of
|
|
1415
|
+
* unwinding a flush or a setter; answered "same" so nothing commits.
|
|
1416
|
+
* @internal
|
|
1417
|
+
*/ function isSame(e, t, n) {
|
|
1418
|
+
if (e.Ye === isEqual) return t === n;
|
|
1419
|
+
try {
|
|
1420
|
+
return e.Ye && e.Ye(t, n);
|
|
1421
|
+
} catch (t) {
|
|
1422
|
+
if (!(t instanceof NotReadyError)) return notifyStatus(e, STATUS_ERROR, t), true;
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1270
1426
|
function setSignal(e, t) {
|
|
1271
|
-
|
|
1427
|
+
if (writeHook !== null) writeHook(e);
|
|
1428
|
+
const n = e.ue === NOT_PENDING ? e.Ie : e.ue;
|
|
1272
1429
|
if (typeof t === "function") t = t(n);
|
|
1273
1430
|
// Lanes: a write landing on a guess is its truth (A18 — an async guess's
|
|
1274
1431
|
// own landing), judged against the guess; one landing on a lane's
|
|
1275
1432
|
// derivation (its flight) is the lane's staging (lanes.ts).
|
|
1276
|
-
if (e.
|
|
1433
|
+
if (laneWriteHook && e.l & CONFIG_OVERRIDE) return laneWriteHook(e, t);
|
|
1277
1434
|
// L2 (T4) / A34 (1): a write to a node whose staged state is held is a
|
|
1278
1435
|
// second proposal for the slot — the same value again or another — and
|
|
1279
1436
|
// the two cannot finish at different times: the writer's tick joins the
|
|
1280
1437
|
// hold, before the equality gate.
|
|
1281
|
-
if (e.
|
|
1438
|
+
if (e.l & CONFIG_HELD) joinFuture(txOf(e));
|
|
1282
1439
|
// Uninitialized check first: the first commit has no previous value, so the
|
|
1283
1440
|
// user comparator must not run against `undefined` (matches recompute).
|
|
1284
|
-
|
|
1285
|
-
|
|
1441
|
+
// D-41: a write over a shown error is a change whatever the last good
|
|
1442
|
+
// value was — the error leaves at its commit (`commitPendingNode`).
|
|
1443
|
+
const o = !!(e.m & STATUS_UNINITIALIZED) || !!e.T?.de || !isSame(e, n, t);
|
|
1444
|
+
if (!o) return t;
|
|
1286
1445
|
// Attribution hook: this committed write is where a re-run chain begins.
|
|
1287
1446
|
if (attrHooks !== null) attrHooks.write(e, n, t);
|
|
1288
1447
|
// A write during hydration's snapshot capture to a source that has no
|
|
@@ -1295,21 +1454,21 @@ function setSignal(e, t) {
|
|
|
1295
1454
|
// pass reads a value the server never had. Store leaves written here are
|
|
1296
1455
|
// plain signals and qualify the same way.
|
|
1297
1456
|
if (snapshotCaptureActive) captureWriteSnapshot(e, n);
|
|
1298
|
-
const s = e.
|
|
1457
|
+
const s = e.ue !== NOT_PENDING;
|
|
1299
1458
|
if (!s) queuePendingNode(e);
|
|
1300
1459
|
// A28: a held node rewritten outside a flush keeps its flushed staging
|
|
1301
1460
|
// for every reader until the flush that carries the rewrite.
|
|
1302
|
-
else if (e.
|
|
1303
|
-
e.
|
|
1461
|
+
else if (e.l & CONFIG_HELD && !globalQueue.q) stashFlushed(e);
|
|
1462
|
+
e.ue = t;
|
|
1304
1463
|
if (context !== null) notePromotedWrite(e);
|
|
1305
1464
|
// _time is a computed-only slot (§12e): writing it on a signal would fork
|
|
1306
1465
|
// the lean shape. Every read site is computed-typed.
|
|
1307
|
-
if (e.
|
|
1466
|
+
if (e.X !== undefined) e.ie = clock;
|
|
1308
1467
|
// Staged-rewrite fast path (§12d): a re-write to a node whose subscribers
|
|
1309
1468
|
// were already walked — and where nothing has recomputed or linked since
|
|
1310
1469
|
// (epoch) — re-stages the value and stops. The walk is idempotent (subs
|
|
1311
1470
|
// marked, heap entries flag-guarded, effects queued once).
|
|
1312
|
-
if (s && e.
|
|
1471
|
+
if (s && e.Ot === notifyEpoch) return t;
|
|
1313
1472
|
insertSubs(e);
|
|
1314
1473
|
schedule();
|
|
1315
1474
|
return t;
|
|
@@ -1334,18 +1493,31 @@ function setSignal(e, t) {
|
|
|
1334
1493
|
// of replacing it. A write made under the hold carries the mark and keeps
|
|
1335
1494
|
// (1)'s last-write-wins. Only the user setter asks; an async landing
|
|
1336
1495
|
// writing a held node is the transaction's own work.
|
|
1337
|
-
const n = e.
|
|
1496
|
+
const n = e.l & CONFIG_HELD && !(e.R & REACTIVE_MANUAL_WRITE) && txOf(e) !== flushTransaction;
|
|
1338
1497
|
// The writer read the committed frame; its updater composes on it.
|
|
1339
|
-
if (n && typeof t === "function")
|
|
1340
|
-
|
|
1498
|
+
if (n && typeof t === "function") {
|
|
1499
|
+
const n = t(e.Ie);
|
|
1500
|
+
t = () => n;
|
|
1501
|
+
}
|
|
1502
|
+
// #4110: the write replaces a failed derivation's outcome. It lands without
|
|
1503
|
+
// re-running `_fn`, and nothing else clears STATUS_ERROR, so without this
|
|
1504
|
+
// every later read keeps throwing the error the derivation left behind.
|
|
1505
|
+
// The error stays the display until the write commits (D-41).
|
|
1506
|
+
if (e.m & STATUS_ERROR) {
|
|
1507
|
+
e.T.de = e.T.F;
|
|
1508
|
+
notifyStatus(e, STATUS_NONE, undefined);
|
|
1509
|
+
}
|
|
1510
|
+
t = setSignal(e, t);
|
|
1511
|
+
// A write replaces an error a pass staged (D-41): it is a value again.
|
|
1512
|
+
e.l &= ~CONFIG_STAGED_ERROR;
|
|
1341
1513
|
if (n) {
|
|
1342
1514
|
// The one write that re-runs: the staging it replaced was derived from
|
|
1343
1515
|
// inputs the writer never saw, so the hold re-derives over the write.
|
|
1344
1516
|
e.R = e.R & ~REACTIVE_CHECK | REACTIVE_DIRTY;
|
|
1345
1517
|
insertIntoHeap(e, dirtyQueue);
|
|
1346
1518
|
schedule();
|
|
1347
|
-
} else if (e.
|
|
1348
|
-
return
|
|
1519
|
+
} else if (e.ue !== NOT_PENDING) e.R |= REACTIVE_MANUAL_WRITE;
|
|
1520
|
+
return t;
|
|
1349
1521
|
}
|
|
1350
1522
|
|
|
1351
1523
|
/**
|
|
@@ -1368,14 +1540,14 @@ function setSignal(e, t) {
|
|
|
1368
1540
|
* ```
|
|
1369
1541
|
*/ function runWithOwner(e, t) {
|
|
1370
1542
|
const n = context;
|
|
1371
|
-
const
|
|
1543
|
+
const o = tracking;
|
|
1372
1544
|
context = e;
|
|
1373
1545
|
tracking = false;
|
|
1374
1546
|
try {
|
|
1375
1547
|
return t();
|
|
1376
1548
|
} finally {
|
|
1377
1549
|
context = n;
|
|
1378
|
-
tracking =
|
|
1550
|
+
tracking = o;
|
|
1379
1551
|
}
|
|
1380
1552
|
}
|
|
1381
1553
|
|
|
@@ -1392,7 +1564,7 @@ function staleValues(e, t = true) {
|
|
|
1392
1564
|
* recompute as a quiet re-ask and schedules it; no-ops for non-derived or
|
|
1393
1565
|
* disposed targets.
|
|
1394
1566
|
*/ function markRefresh(e) {
|
|
1395
|
-
if (typeof e.
|
|
1567
|
+
if (typeof e.X === "function" && !(e.R & REACTIVE_DISPOSED)) {
|
|
1396
1568
|
// A manual write never refuses the re-run (A34 rule B): a refresh in the
|
|
1397
1569
|
// write's own frame re-asks with the write as `prev`, like any source
|
|
1398
1570
|
// change (#3026 generalized).
|
|
@@ -1408,4 +1580,4 @@ function staleValues(e, t = true) {
|
|
|
1408
1580
|
}
|
|
1409
1581
|
}
|
|
1410
1582
|
|
|
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 };
|
|
1583
|
+
export { bornInto, captureWriteSnapshot, clearSnapshots, computed, context, createEffectNode, effectStatusNotify, ext, isEqual, isSame, joinPass, markLateLinker, markRefresh, markSnapshotScope, markUnflushedStaged, notePromotedWrite, prepareComputed, pullComputed, read, readerSeesCommitted, recompute, releaseSnapshotScope, resyncUnflushedCompanions, runWithOwner, serve, setEffectStatusNotify, setMemo, setSignal, setSnapshotCapture, setVerdict, setWriteHook, signal, slotSignal, snapshotCaptureActive, snapshotSources, spectating, stagedRead, stagedScreen, staleValues, stashFlushed, statusNotifierOf, tracking, unflushedStaged, unflushedValue, untrack, verdict, writeHook };
|