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