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