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