@solidjs/signals 2.0.0-rc.8 → 2.0.0-rc.9
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 +1399 -285
- package/dist/dev.attribution.js +459 -307
- package/dist/dev.js +1864 -435
- package/dist/observe/affects.js +3 -1
- package/dist/observe/attribution.js +7 -1
- package/dist/observe/boundaries.js +209 -154
- package/dist/observe/core/action.js +18 -8
- package/dist/observe/core/async.js +220 -110
- package/dist/observe/core/attribution-costs.js +66 -0
- package/dist/observe/core/attribution-feedback.js +282 -0
- package/dist/observe/core/attribution-hooks.js +23 -1
- package/dist/observe/core/attribution-queries.js +28 -0
- package/dist/observe/core/attribution.js +262 -485
- package/dist/observe/core/constants.js +34 -1
- package/dist/observe/core/context.js +3 -3
- package/dist/observe/core/core.js +867 -367
- package/dist/observe/core/dev.js +78 -17
- package/dist/observe/core/effect.js +67 -51
- package/dist/observe/core/error-hooks.js +71 -0
- package/dist/observe/core/external.js +4 -4
- package/dist/observe/core/graph.js +37 -37
- package/dist/observe/core/heap.js +45 -45
- package/dist/observe/core/invariants.js +2 -0
- package/dist/observe/core/lanes.js +86 -49
- package/dist/observe/core/optimistic.js +242 -95
- package/dist/observe/core/owner.js +98 -84
- package/dist/observe/core/scheduler.js +543 -305
- package/dist/observe/core/verdict.js +247 -129
- package/dist/observe/index.js +7 -3
- package/dist/observe/map.js +126 -124
- package/dist/observe/signals.js +33 -17
- package/dist/observe/store/index.js +2 -0
- package/dist/observe/store/next/optimistic.js +141 -132
- package/dist/observe/store/next/projection.js +34 -21
- package/dist/observe/store/next/reconcile.js +58 -56
- package/dist/observe/store/next/store.js +260 -146
- package/dist/observe/store/store.js +5 -3
- package/dist/observe/store/utils.js +948 -135
- package/dist/prod/attribution.js +26 -17
- package/dist/prod/boundaries.js +128 -76
- package/dist/prod/core/action.js +16 -8
- package/dist/prod/core/async.js +251 -143
- package/dist/prod/core/constants.js +34 -1
- package/dist/prod/core/context.js +3 -3
- package/dist/prod/core/core.js +843 -347
- package/dist/prod/core/dev.js +17 -1
- package/dist/prod/core/effect.js +48 -34
- package/dist/prod/core/error-hooks.js +71 -0
- package/dist/prod/core/external.js +4 -4
- package/dist/prod/core/graph.js +37 -37
- package/dist/prod/core/heap.js +45 -45
- package/dist/prod/core/lanes.js +90 -53
- package/dist/prod/core/optimistic.js +247 -100
- package/dist/prod/core/owner.js +57 -45
- package/dist/prod/core/scheduler.js +543 -305
- package/dist/prod/core/verdict.js +245 -127
- package/dist/prod/index.js +7 -3
- package/dist/prod/map.js +112 -112
- package/dist/prod/signals.js +28 -12
- package/dist/prod/store/next/optimistic.js +135 -128
- package/dist/prod/store/next/projection.js +31 -20
- package/dist/prod/store/next/store.js +325 -252
- package/dist/prod/store/store.js +2 -2
- package/dist/prod/store/utils.js +946 -135
- package/dist/types/attribution.d.ts +7 -2
- package/dist/types/attribution.prod.d.ts +10 -1
- package/dist/types/boundaries.d.ts +10 -1
- package/dist/types/core/action.d.ts +12 -5
- package/dist/types/core/attribution-costs.d.ts +35 -0
- package/dist/types/core/attribution-feedback.d.ts +133 -0
- package/dist/types/core/attribution-hooks.d.ts +28 -3
- package/dist/types/core/attribution-queries.d.ts +10 -0
- package/dist/types/core/attribution.d.ts +51 -172
- package/dist/types/core/constants.d.ts +33 -0
- package/dist/types/core/core.d.ts +186 -5
- package/dist/types/core/dev.d.ts +129 -9
- package/dist/types/core/error-hooks.d.ts +71 -0
- package/dist/types/core/index.d.ts +3 -1
- package/dist/types/core/invariants.d.ts +4 -0
- package/dist/types/core/lanes.d.ts +31 -4
- package/dist/types/core/scheduler.d.ts +95 -2
- package/dist/types/core/types.d.ts +3 -0
- package/dist/types/index.d.ts +2 -2
- package/dist/types/signals.d.ts +8 -0
- package/dist/types/store/index.d.ts +2 -1
- package/dist/types/store/next/optimistic.d.ts +1 -1
- package/dist/types/store/next/store.d.ts +6 -5
- package/dist/types/store/next/target.d.ts +1 -1
- package/dist/types/store/utils.d.ts +177 -6
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { releaseFlightTeardown, handleAsync, clearStatus, parkLoadingWindow, notifyStatus, settlePendingSource, settleErroredDependents } from "./async.js";
|
|
2
2
|
|
|
3
|
-
import { EFFECT_TRACKED, EFFECT_USER, REACTIVE_OPTIMISTIC_DIRTY, CONFIG_OPTIMISTIC,
|
|
3
|
+
import { NOT_PENDING, EFFECT_TRACKED, EFFECT_USER, CONFIG_HELD_CHILDREN, REACTIVE_OPTIMISTIC_DIRTY, CONFIG_OPTIMISTIC, CONFIG_DERIVED_OVERRIDE, STATUS_UNINITIALIZED, STATUS_ERROR, STATUS_PENDING, REACTIVE_REASK, REACTIVE_RECOMPUTING_DEPS, CONFIG_SYNC, CONFIG_HAS_LANE, REACTIVE_MISSED_WAKE, REACTIVE_NONE, REACTIVE_SNAPSHOT_STALE, unwrapOverride, CONFIG_DIRECT_COMMIT, CONFIG_HAS_COMPANIONS, CONFIG_PROMOTED, CONFIG_ADOPTED_UNFLUSHED, CONFIG_CHILDREN_FORBIDDEN, CONFIG_FRESH_READ, CONFIG_INPUTS_PUBLISHED, CONFIG_AUTO_DISPOSE, REACTIVE_LAZY, REACTIVE_DISPOSED, REACTIVE_CHECK, REACTIVE_DIRTY, REACTIVE_IN_HEAP, REACTIVE_IN_HEAP_HEIGHT, CONFIG_AUTHORITATIVE_READ, CONFIG_OVERRIDE_SUPERSEDED, CONFIG_AUTHORITATIVE_OBSERVED, defaultContext, CONFIG_HELD_TRUTH, CONFIG_TRANSPARENT, CONFIG_OWNED_WRITE, CONFIG_NO_SNAPSHOT, CONFIG_IN_SNAPSHOT_SCOPE, NO_SNAPSHOT, CONFIG_HAS_SNAPSHOT, REACTIVE_MANUAL_WRITE, CONFIG_FW_CHILDREN, CONFIG_SLOT_NODE, STORE_SNAPSHOT_PROPS } from "./constants.js";
|
|
4
4
|
|
|
5
5
|
import { NotReadyError } from "./error.js";
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ import { trimStaleDeps, link, dormantNodes } from "./graph.js";
|
|
|
8
8
|
|
|
9
9
|
import { deleteFromHeap, queueFor, insertIntoHeapHeight, enqueueSub, markNode, markHeap, insertIntoHeap } from "./heap.js";
|
|
10
10
|
|
|
11
|
-
import { GlobalQueue, bumpNotifyEpoch, activeTransition, globalQueue, clock, currentTransition, insertSubs, queuePendingNode, runInTransition, schedule, notifyEpoch, dirtyQueue, projectionWriteActive, reaskArmed, armReaskClear } from "./scheduler.js";
|
|
11
|
+
import { GlobalQueue, bumpNotifyEpoch, activeTransition, globalQueue, clock, currentTransition, insertSubs, queuePendingNode, wakeParked, heldTrims, runInTransition, schedule, batchJoins, notifyEpoch, dirtyQueue, projectionWriteActive, reaskArmed, armReaskClear } from "./scheduler.js";
|
|
12
12
|
|
|
13
13
|
import { GRAPH_SIZE_WARN_AT, noteFanIn } from "./dev.js";
|
|
14
14
|
|
|
@@ -26,15 +26,15 @@ import { disposeChildren, markDisposal, inheritId } from "./owner.js";
|
|
|
26
26
|
// render-effect callback stages its value for the next pass, but a wake pushed
|
|
27
27
|
// directly into the user queue ran in the SAME pass, read the old value, and
|
|
28
28
|
// nothing re-notified it when the value landed (#3291).
|
|
29
|
-
GlobalQueue.
|
|
30
|
-
if (e.
|
|
29
|
+
GlobalQueue._t = e => {
|
|
30
|
+
if (e.Pe === EFFECT_TRACKED) {
|
|
31
31
|
deleteFromHeap(e, queueFor(e));
|
|
32
|
-
e.
|
|
33
|
-
e.
|
|
32
|
+
e.Nt = true;
|
|
33
|
+
e.T.enqueue(EFFECT_USER, e.Et);
|
|
34
34
|
} else recompute(e);
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
-
GlobalQueue.
|
|
37
|
+
GlobalQueue.It = disposeChildren;
|
|
38
38
|
|
|
39
39
|
let tracking = false;
|
|
40
40
|
|
|
@@ -66,8 +66,8 @@ let snapshotSources = null;
|
|
|
66
66
|
|
|
67
67
|
function ownerInSnapshotScope(e) {
|
|
68
68
|
while (e) {
|
|
69
|
-
if (e.
|
|
70
|
-
e = e.
|
|
69
|
+
if (e.St) return true;
|
|
70
|
+
e = e._parent;
|
|
71
71
|
}
|
|
72
72
|
return false;
|
|
73
73
|
}
|
|
@@ -78,44 +78,49 @@ function setSnapshotCapture(e) {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
function markSnapshotScope(e) {
|
|
81
|
-
e.
|
|
81
|
+
e.St = true;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
function releaseSnapshotScope(e) {
|
|
85
|
-
e.
|
|
85
|
+
e.St = false;
|
|
86
86
|
releaseSubtree(e);
|
|
87
87
|
schedule();
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
function releaseSubtree(e) {
|
|
91
|
-
let t = e.
|
|
91
|
+
let t = e.Ot;
|
|
92
92
|
while (t) {
|
|
93
|
-
if (t.
|
|
94
|
-
t = t.
|
|
93
|
+
if (t.St) {
|
|
94
|
+
t = t.At;
|
|
95
95
|
continue;
|
|
96
96
|
}
|
|
97
|
-
if (t.
|
|
97
|
+
if (t.ht) {
|
|
98
98
|
const e = t;
|
|
99
|
-
e.
|
|
100
|
-
if (e.
|
|
101
|
-
e.
|
|
102
|
-
e.
|
|
103
|
-
if (dirtyQueue.
|
|
99
|
+
e.C &= ~CONFIG_IN_SNAPSHOT_SCOPE;
|
|
100
|
+
if (e.ft & REACTIVE_SNAPSHOT_STALE) {
|
|
101
|
+
e.ft &= ~REACTIVE_SNAPSHOT_STALE;
|
|
102
|
+
e.ft |= REACTIVE_DIRTY;
|
|
103
|
+
if (dirtyQueue.Ct > e.Rt) dirtyQueue.Ct = e.Rt;
|
|
104
104
|
insertIntoHeap(e, dirtyQueue);
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
releaseSubtree(t);
|
|
108
|
-
t = t.
|
|
108
|
+
t = t.At;
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
function clearSnapshots() {
|
|
113
113
|
if (snapshotSources) {
|
|
114
114
|
for (const e of snapshotSources) {
|
|
115
|
-
|
|
115
|
+
// The extension is a fixed-shape object with `_snapshotValue`
|
|
116
|
+
// pre-initialized to undefined (see ext()), and every reader tests
|
|
117
|
+
// `!== undefined` — assign, don't `delete`: deleting a field pushes the
|
|
118
|
+
// object to dictionary mode for every later read of every field.
|
|
119
|
+
const t = e.o;
|
|
120
|
+
if (t != null) t.Gt = undefined;
|
|
116
121
|
// StoreNode targets share one pre-initialized hidden class (see
|
|
117
|
-
// createStoreProxy) —
|
|
118
|
-
//
|
|
122
|
+
// createStoreProxy) — same rule, and only when present so signal-node
|
|
123
|
+
// sources don't grow the field.
|
|
119
124
|
if (e[STORE_SNAPSHOT_PROPS] !== undefined) e[STORE_SNAPSHOT_PROPS] = undefined;
|
|
120
125
|
}
|
|
121
126
|
snapshotSources = null;
|
|
@@ -126,17 +131,21 @@ function clearSnapshots() {
|
|
|
126
131
|
function recompute(e, t = false) {
|
|
127
132
|
// §12d: any recompute can clean a marked subscriber — invalidate skips.
|
|
128
133
|
bumpNotifyEpoch();
|
|
129
|
-
const n = e.
|
|
134
|
+
const n = e.Pe;
|
|
130
135
|
// Attribution hook: fired before this run touches the dep list — `_deps`
|
|
131
136
|
// still holds the previous run's links (the subscriptions that could have
|
|
132
137
|
// triggered this run, and the baseline for the engine's subscription diff).
|
|
133
138
|
let i = false;
|
|
134
139
|
if (attrHooks !== null) attrHooks.recomputeStart(e, t);
|
|
135
140
|
if (!t) {
|
|
136
|
-
|
|
141
|
+
// A stamped memo re-enters its hold: its value is that transaction's work.
|
|
142
|
+
// An effect's pass belongs to whatever dirtied it (A15 corollary: effects
|
|
143
|
+
// don't entangle parallel transactions); it joins its stamp only when the
|
|
144
|
+
// pass observes the held flight — queue notification (#3407).
|
|
145
|
+
if (e.Te && !n && activeTransition !== e.Te) globalQueue.initTransition(e.Te);
|
|
137
146
|
deleteFromHeap(e, queueFor(e));
|
|
138
147
|
if (e.o !== null) {
|
|
139
|
-
e.o.
|
|
148
|
+
e.o.ce = null;
|
|
140
149
|
// Supersede is where an iterator flight dies (#3122): close it now.
|
|
141
150
|
// Its cleanup(close) registration may sit in a zombie-deferred
|
|
142
151
|
// disposal list that a held transition only drains when the
|
|
@@ -144,51 +153,66 @@ function recompute(e, t = false) {
|
|
|
144
153
|
// work that replaced it. Idempotent with the cleanup-channel close.
|
|
145
154
|
releaseFlightTeardown(e);
|
|
146
155
|
}
|
|
147
|
-
// Tracked effects run after finalizePureQueue, so dispose immediately
|
|
148
|
-
|
|
156
|
+
// Tracked effects run after finalizePureQueue, so dispose immediately
|
|
157
|
+
// instead of deferring. Children built by an uncommitted recompute
|
|
158
|
+
// (CONFIG_HELD_CHILDREN) die immediately too: no frame ever showed them.
|
|
159
|
+
// Everything else is the committed frame's and is deferred as zombies
|
|
160
|
+
// until this node's commit — a transaction-owned node included (#3404):
|
|
161
|
+
// a parked node's children predate the hold, and tearing them down when
|
|
162
|
+
// the source lands ran cleanups before the transaction's atomic reveal.
|
|
163
|
+
if (n === EFFECT_TRACKED || e.C & CONFIG_HELD_CHILDREN) disposeChildren(e); else if (e.Ot !== null || e.Ue !== null) {
|
|
149
164
|
markDisposal(e);
|
|
150
165
|
const t = ext(e);
|
|
151
|
-
t.
|
|
152
|
-
t.
|
|
153
|
-
e.
|
|
154
|
-
e.
|
|
155
|
-
e.
|
|
166
|
+
t.Dt = e.Ue;
|
|
167
|
+
t.Pt = e.Ot;
|
|
168
|
+
e.Ue = null;
|
|
169
|
+
e.Ot = null;
|
|
170
|
+
e.Ft = 0;
|
|
156
171
|
} else ;
|
|
157
172
|
}
|
|
158
|
-
let l = !!(e.
|
|
159
|
-
|
|
173
|
+
let l = !!(e.ft & REACTIVE_OPTIMISTIC_DIRTY);
|
|
174
|
+
// A derived override (lanes stage, #3479) is override-covered like a written
|
|
175
|
+
// one: a plain pass over it — its source superseded (A18) — stages the truth
|
|
176
|
+
// and supersedes the override through the sync twin below.
|
|
177
|
+
const u = (e.C & (CONFIG_OPTIMISTIC | CONFIG_DERIVED_OVERRIDE)) !== 0 && e.o?.Ne !== NOT_PENDING && e.o?.Ne !== undefined;
|
|
160
178
|
const o = !!(e.S & STATUS_UNINITIALIZED);
|
|
161
179
|
// Capture both error and pending status before the compute clears them.
|
|
162
180
|
// A conditional can drop its pending source and recover to an unchanged
|
|
163
181
|
// value, leaving blocked dependents outside that source’s settle walk.
|
|
164
|
-
const
|
|
165
|
-
const
|
|
182
|
+
const r = e.S & STATUS_ERROR ? e.o?._ : undefined;
|
|
183
|
+
const s = (e.S & STATUS_PENDING) !== 0;
|
|
184
|
+
const a = s ? e.o?.ut : undefined;
|
|
166
185
|
// Pending SOURCE-hood, captured before the compute clears status: a node
|
|
167
186
|
// whose own flight parked dependents self-registers in _pendingSources
|
|
168
187
|
// (notifyStatus, isSource). If this recompute supersedes that flight and
|
|
169
188
|
// settles synchronously, those dependents settle HERE — asyncWrite's
|
|
170
189
|
// settlePendingSource walk never runs for a landing that was preempted
|
|
171
190
|
// (#3181).
|
|
172
|
-
const
|
|
191
|
+
const c = e.o?.ut?.has(e);
|
|
173
192
|
// Re-ask classification lives in the verdict module; capture the flag before
|
|
174
193
|
// the recompute wipes _flags below.
|
|
175
|
-
const
|
|
194
|
+
const f = (e.ft & REACTIVE_REASK) !== 0;
|
|
176
195
|
// Captured before the compute clears it on a sync landing: if that landing
|
|
177
196
|
// is transition-held below, the window must stay open until the hold
|
|
178
197
|
// commits (commitPendingNode) — a closed window plus a held value reads as
|
|
179
198
|
// a pending frame to live observers of the verdict (#2990).
|
|
180
|
-
const
|
|
181
|
-
|
|
199
|
+
const _ = e.Se;
|
|
200
|
+
// Creation-time A29 (see enterStagedRead): a pass outside a flush that is
|
|
201
|
+
// served a live transaction's staged value records the transaction here
|
|
202
|
+
// and is staged INTO it below — "born held" — instead of committing.
|
|
203
|
+
const N = stagedEntry;
|
|
204
|
+
stagedEntry = null;
|
|
205
|
+
const d = context;
|
|
182
206
|
context = e;
|
|
183
|
-
e.
|
|
184
|
-
e.
|
|
185
|
-
e.
|
|
186
|
-
e.
|
|
187
|
-
let E = e.
|
|
188
|
-
let I = e.
|
|
189
|
-
let
|
|
190
|
-
let
|
|
191
|
-
let
|
|
207
|
+
e.Fe = null;
|
|
208
|
+
e.ye++;
|
|
209
|
+
e.ft = REACTIVE_RECOMPUTING_DEPS;
|
|
210
|
+
e.fe = clock;
|
|
211
|
+
let E = e._e === NOT_PENDING ? e.Ae : e._e;
|
|
212
|
+
let I = e.Rt;
|
|
213
|
+
let T = false;
|
|
214
|
+
let S = tracking;
|
|
215
|
+
let O = currentOptimisticLane;
|
|
192
216
|
tracking = true;
|
|
193
217
|
// A computed's fn establishes its OWN dependencies, so it must never run
|
|
194
218
|
// inside a latest() read window: read() short-circuits through the
|
|
@@ -196,74 +220,102 @@ function recompute(e, t = false) {
|
|
|
196
220
|
// computed) inside latest(fn) came out permanently dependency-less (#2926).
|
|
197
221
|
// latestRead() already suspends the flag for its pull-recomputes; this
|
|
198
222
|
// covers creation-time computes and flushes that run inside the window.
|
|
199
|
-
const
|
|
223
|
+
const A = latestReadActive;
|
|
200
224
|
latestReadActive = false;
|
|
225
|
+
// A memo computes under its OWN lane posture, never the puller's (A31,
|
|
226
|
+
// #3442): its value is one shared slot every reader sees, so a pull from a
|
|
227
|
+
// lane-carrying reader (a probe effect on its companion lane pulling a sync
|
|
228
|
+
// memo) must not run it with that lane's read carve-outs — under a lane, a
|
|
229
|
+
// pending node on no lane serves its committed value instead of throwing,
|
|
230
|
+
// and the memo then published a stale "settled" value, dropped its
|
|
231
|
+
// pending status, and stopped holding its transaction. The branches below
|
|
232
|
+
// re-establish the posture the memo itself owns (OPT-dirty, or adopted
|
|
233
|
+
// through its deps). Effects keep the ambient lane: their runs are the
|
|
234
|
+
// lane's own view.
|
|
235
|
+
if (!n) currentOptimisticLane = null;
|
|
201
236
|
// Lane posture lives with the engine: OPTIMISTIC_DIRTY is only ever set by
|
|
202
237
|
// engine-driven paths, and _optimisticNodes is only pushed by
|
|
203
238
|
// _optimisticWrite, so the hook is installed whenever either gate holds.
|
|
204
239
|
if (l) {
|
|
205
|
-
const t = GlobalQueue.
|
|
240
|
+
const t = GlobalQueue.gt(e, true);
|
|
206
241
|
if (t) currentOptimisticLane = t;
|
|
207
242
|
// `false` = wake-only lane demotion: recompute plain so a mid-tick
|
|
208
243
|
// latest()/isPending() pull stages instead of direct-committing (#3009).
|
|
209
244
|
// The predicate lives with the engine (recomputeLane).
|
|
210
245
|
else if (t === false) l = false;
|
|
211
|
-
} else if (
|
|
246
|
+
} else if (e.C & CONFIG_DERIVED_OVERRIDE) {
|
|
247
|
+
// Lanes stage (#3479): a pass over a live lane member carrying a derived
|
|
248
|
+
// override is the lane's pass whatever channel dirtied it (a boundary
|
|
249
|
+
// reset, an unrelated sync write) — its inputs serve the lane's view, so
|
|
250
|
+
// its result is the lane's and belongs in the override slot. Run plain,
|
|
251
|
+
// A18's sync twin below read that re-derived lane view as a differing
|
|
252
|
+
// truth (a fresh array), superseded the override and demoted the lane;
|
|
253
|
+
// the lane's next pass then dropped the staged "truth" and left the node
|
|
254
|
+
// flagged superseded with nothing to serve (fuzzer latest-1 #2481). A
|
|
255
|
+
// demoted node resolves no lane and stays plain: its pass IS the truth.
|
|
256
|
+
const t = GlobalQueue.gt(e, true);
|
|
257
|
+
if (t) {
|
|
258
|
+
l = true;
|
|
259
|
+
currentOptimisticLane = t;
|
|
260
|
+
}
|
|
261
|
+
} else if (activeTransition && !t && activeTransition.tn.length) {
|
|
212
262
|
// Lane adoption: parent-deeper-than-owned-child can run before its OPT-dirty
|
|
213
263
|
// child propagates. Walk deps once and inherit the OPT lane so this node
|
|
214
264
|
// recomputes under the right posture and propagates correctly.
|
|
215
|
-
const t = GlobalQueue.
|
|
265
|
+
const t = GlobalQueue.gt(e, false);
|
|
216
266
|
if (t) {
|
|
217
267
|
l = true;
|
|
218
268
|
currentOptimisticLane = t;
|
|
219
269
|
}
|
|
220
270
|
}
|
|
221
|
-
const
|
|
222
|
-
const
|
|
223
|
-
if (
|
|
271
|
+
const C = n && n !== EFFECT_USER;
|
|
272
|
+
const p = stale;
|
|
273
|
+
if (C) stale = true;
|
|
224
274
|
// An effect recorded for this transaction's commit replay (it once read a
|
|
225
275
|
// node the transaction held and showed the committed value) and now
|
|
226
276
|
// recomputing UNDER the transaction sees its staged view: the value this
|
|
227
277
|
// run produces is applied by the commit itself, and the stale recording
|
|
228
278
|
// would publish the frame a second time. Drop it; the reads below re-record
|
|
229
279
|
// if they are served the committed view again (a lane's committed read).
|
|
230
|
-
if (n && activeTransition !== null && activeTransition.
|
|
280
|
+
if (n && activeTransition !== null && activeTransition.Ht.size) activeTransition.Ht.delete(e);
|
|
231
281
|
try {
|
|
232
|
-
if (!false && e.
|
|
233
|
-
E = e.
|
|
234
|
-
if (e.o !== null) e.o.
|
|
235
|
-
e.
|
|
282
|
+
if (!false && e.C & CONFIG_SYNC) {
|
|
283
|
+
E = e.ht(E);
|
|
284
|
+
if (e.o !== null) e.o.ce = null;
|
|
285
|
+
e.Se = false;
|
|
236
286
|
} else {
|
|
237
287
|
// Snapshot `_inFlight` so we can detect whether `_fn` self-registered an async
|
|
238
288
|
// subscription (e.g. `createProjection` calls `handleAsync` from inside its body
|
|
239
289
|
// with a setter callback). In that case, the outer `handleAsync` call below would
|
|
240
290
|
// clobber the fresh subscription, so we skip it and let the internally-registered
|
|
241
291
|
// iteration drive updates.
|
|
242
|
-
const t = e.o?.
|
|
243
|
-
const n = e.
|
|
292
|
+
const t = e.o?.ce;
|
|
293
|
+
const n = e.ht(E);
|
|
244
294
|
const i = typeof n === "object" && n !== null;
|
|
245
|
-
const l = e.o?.
|
|
295
|
+
const l = e.o?.ce !== t;
|
|
246
296
|
E = l || !i ? n : handleAsync(e, n);
|
|
247
297
|
if (!l && !i) {
|
|
248
|
-
if (e.o !== null) e.o.
|
|
298
|
+
if (e.o !== null) e.o.ce = null;
|
|
249
299
|
// A sync (non-object) return is the first real answer; async-shaped
|
|
250
300
|
// results clear inside handleAsync at their own landing points, and a
|
|
251
301
|
// self-registered flight (inFlightChanged — projections) clears when
|
|
252
302
|
// its internal handleAsync lands.
|
|
253
|
-
e.
|
|
303
|
+
e.Se = false;
|
|
254
304
|
}
|
|
255
305
|
}
|
|
256
306
|
// On a status-free node clearStatus is a guaranteed no-op: every field
|
|
257
307
|
// its body gates on is either _statusFlags or lives in the cold
|
|
258
308
|
// extension — no extension, no status to clear. (_x from an unrelated
|
|
259
309
|
// installer just makes clearStatus a cheap re-verified no-op.)
|
|
260
|
-
|
|
310
|
+
// A node born held keeps STATUS_UNINITIALIZED until its transaction
|
|
311
|
+
// commits: it has no committed value, and read() holds its readers.
|
|
312
|
+
if (e.S !== 0 || e.o !== null) clearStatus(e, t && stagedEntry === null);
|
|
261
313
|
// _optimisticLane is only ever assigned by engine paths (CONFIG_HAS_LANE
|
|
262
314
|
// is their sticky presence mark).
|
|
263
|
-
if (e.
|
|
315
|
+
if (e.C & CONFIG_HAS_LANE && e.o?.de) GlobalQueue.vt(e);
|
|
264
316
|
} catch (t) {
|
|
265
317
|
const n = t instanceof NotReadyError;
|
|
266
|
-
if (n && e.
|
|
318
|
+
if (n && e.Se) {
|
|
267
319
|
// Loading window with an unready sync dependency: register for the
|
|
268
320
|
// source's settle (the settlePendingSource walk runs off
|
|
269
321
|
// _pendingSources + _blocked alone) but take NO read-visible pending
|
|
@@ -275,43 +327,60 @@ function recompute(e, t = false) {
|
|
|
275
327
|
} else {
|
|
276
328
|
// Track pending async in the lane (not the lane's source — it creates the lane
|
|
277
329
|
// but doesn't belong to it). Set lane BEFORE notifyStatus for downstream propagation.
|
|
278
|
-
if (n && currentOptimisticLane) GlobalQueue.
|
|
330
|
+
if (n && currentOptimisticLane) GlobalQueue.kt(e);
|
|
279
331
|
let i = false;
|
|
280
332
|
if (n) {
|
|
281
|
-
ext(e).
|
|
282
|
-
if (GlobalQueue.
|
|
333
|
+
ext(e).oe = true;
|
|
334
|
+
if (GlobalQueue.bt !== null) i = GlobalQueue.bt(e, f);
|
|
283
335
|
}
|
|
284
|
-
notifyStatus(e, n ? STATUS_PENDING : STATUS_ERROR, t, undefined, n ? e.o?.
|
|
336
|
+
notifyStatus(e, n ? STATUS_PENDING : STATUS_ERROR, t, undefined, n ? e.o?.de : undefined);
|
|
285
337
|
// The replacement source is fully propagated now. If no new flight
|
|
286
338
|
// re-owned self, retire the superseded flight and its dependent copies.
|
|
287
|
-
if (n &&
|
|
339
|
+
if (n && c && !e.o?.ce) settlePendingSource(e);
|
|
340
|
+
// A re-park drops what the earlier pass carried (#3456): a source this
|
|
341
|
+
// pass no longer reaches — its branch switched, or a fresh flight
|
|
342
|
+
// replaced the inputs' pending with its own — stays copied onto
|
|
343
|
+
// dependents that reached it only through here, and its landing walk
|
|
344
|
+
// stops at this node (nothing left to retire) before it finds them. A
|
|
345
|
+
// dependent then waits forever on a flight it has no path to. The
|
|
346
|
+
// re-park twin of the unchanged-value recovery sweep below; dependents
|
|
347
|
+
// with another path keep the source (retryReaches).
|
|
348
|
+
if (n && a) for (const t of a) if (t !== e && !e.o?.ut?.has(t)) settlePendingSource(e, t);
|
|
288
349
|
if (i) GlobalQueue.k(e);
|
|
289
350
|
}
|
|
290
351
|
} finally {
|
|
291
|
-
tracking =
|
|
292
|
-
latestReadActive =
|
|
293
|
-
if (
|
|
352
|
+
tracking = S;
|
|
353
|
+
latestReadActive = A;
|
|
354
|
+
if (C) stale = p;
|
|
294
355
|
// Consume the missed-wake latch (#3037, set by insertSubs): a dep write
|
|
295
356
|
// landed beneath this pass on a link it had already validated. The wipe
|
|
296
357
|
// below must not key off DIRTY/CHECK — the read-time pull protocol
|
|
297
358
|
// (markNode(c) in read()) marks the running node as part of ordinary
|
|
298
359
|
// bookkeeping, and those marks are correctly discarded here.
|
|
299
|
-
|
|
300
|
-
e.
|
|
301
|
-
context =
|
|
360
|
+
T = (e.ft & REACTIVE_MISSED_WAKE) !== 0;
|
|
361
|
+
e.ft = REACTIVE_NONE | (t ? e.ft & REACTIVE_SNAPSHOT_STALE : 0);
|
|
362
|
+
context = d;
|
|
302
363
|
}
|
|
364
|
+
// The cast re-widens: TS narrowed `stagedEntry` to `null` at the reset
|
|
365
|
+
// above and does not invalidate that across the compute call that
|
|
366
|
+
// `enterStagedRead` runs under. No emitted code.
|
|
367
|
+
const R = stagedEntry;
|
|
368
|
+
stagedEntry = N;
|
|
303
369
|
if (!e.o?._) {
|
|
304
|
-
|
|
305
|
-
//
|
|
306
|
-
//
|
|
307
|
-
//
|
|
308
|
-
//
|
|
309
|
-
//
|
|
310
|
-
//
|
|
311
|
-
//
|
|
312
|
-
|
|
370
|
+
// Observe-tier fan-in (HUGE_FAN_IN): the validated prefix [_deps.._depsTail]
|
|
371
|
+
// IS this pass's distinct sources — count it here rather than per link. A
|
|
372
|
+
// begin/end bracket around the pass plus a per-link increment measured
|
|
373
|
+
// -5.8% on createRenderEffects:create1to1 (CodSpeed, dev tier) and cost
|
|
374
|
+
// several points of the shape wins elsewhere; this walk is a fraction of
|
|
375
|
+
// the reads that built the list and keeps no module state, so nested
|
|
376
|
+
// pulls need no save/restore. (The stale tail is trimmed at the end of
|
|
377
|
+
// this pass, or at its commit — see recompute's tail.)
|
|
378
|
+
{
|
|
313
379
|
let t = 0;
|
|
314
|
-
for (let n = e.
|
|
380
|
+
for (let n = e.ee; n !== null; n = n.te) {
|
|
381
|
+
t++;
|
|
382
|
+
if (n === e.Fe) break;
|
|
383
|
+
}
|
|
315
384
|
if (t >= GRAPH_SIZE_WARN_AT) noteFanIn(e, t);
|
|
316
385
|
}
|
|
317
386
|
// INV-11 (#3330): the equality gate compares against the slot this run
|
|
@@ -320,10 +389,10 @@ function recompute(e, t = false) {
|
|
|
320
389
|
// schedule — so a transaction-held `_pendingValue` that already equals
|
|
321
390
|
// the new result is not "unchanged": the screen still shows `_value`.
|
|
322
391
|
// Only a transaction-staged run compares against `_pendingValue`.
|
|
323
|
-
const
|
|
392
|
+
const s = u ? unwrapOverride(e.o?.Ne) : l || e._e === NOT_PENDING ? e.Ae : e._e;
|
|
324
393
|
let f = false;
|
|
325
394
|
try {
|
|
326
|
-
f = !n && o || !e.
|
|
395
|
+
f = !n && o || !e.Re || !e.Re(s, E);
|
|
327
396
|
} catch (t) {
|
|
328
397
|
// A throwing user comparator is an error of this node's computation.
|
|
329
398
|
// Route it through the same status path as a compute-phase throw so
|
|
@@ -343,11 +412,11 @@ function recompute(e, t = false) {
|
|
|
343
412
|
// gate: the explicit recompute(node, true) inside effect() does not enqueue, so
|
|
344
413
|
// effect() can call its runner synchronously for the first run.
|
|
345
414
|
if (n && f) {
|
|
346
|
-
e.
|
|
415
|
+
e.Nt = !e.o?._;
|
|
347
416
|
// Reuse one bound runner per effect — runEffect no-ops on a stale
|
|
348
417
|
// `_modified`, so re-enqueueing the same function is harmless.
|
|
349
418
|
if (!t) {
|
|
350
|
-
e.
|
|
419
|
+
e.T.enqueue(n, e.Ut ??= GlobalQueue.Lt.bind(null, e));
|
|
351
420
|
// Contested effect (#3322). Effects don't entangle transactions (a
|
|
352
421
|
// shared effect is not shared state), yet they have one value slot:
|
|
353
422
|
// when this write commits under a different transaction
|
|
@@ -364,7 +433,7 @@ function recompute(e, t = false) {
|
|
|
364
433
|
let t = e.Vt;
|
|
365
434
|
if (t !== activeTransition) {
|
|
366
435
|
e.Vt = activeTransition;
|
|
367
|
-
if (t !== null && (t = currentTransition(t)) !== activeTransition && !t.
|
|
436
|
+
if (t !== null && (t = currentTransition(t)) !== activeTransition && !t.yt) {
|
|
368
437
|
(t.xt ??= []).push(e);
|
|
369
438
|
if (activeTransition !== null) (activeTransition.xt ??= []).push(e);
|
|
370
439
|
}
|
|
@@ -372,8 +441,8 @@ function recompute(e, t = false) {
|
|
|
372
441
|
}
|
|
373
442
|
}
|
|
374
443
|
if (e.o?._) ; else if (f) {
|
|
375
|
-
const i = u ? e.o?.
|
|
376
|
-
if (t ||
|
|
444
|
+
const i = u ? e.o?.Ne : undefined;
|
|
445
|
+
if (t && R === null ||
|
|
377
446
|
// Plain sync flush (no transition on either side) commits effect
|
|
378
447
|
// values directly — the pending round-trip (queuePendingNode +
|
|
379
448
|
// commitPendingNodes) exists to sequence transition reveals, and
|
|
@@ -382,31 +451,53 @@ function recompute(e, t = false) {
|
|
|
382
451
|
// their own held transition: their applies deliver on a microtask,
|
|
383
452
|
// not the stashed queues, so a staged value would hand the immediate
|
|
384
453
|
// apply stale state — see CONFIG_DIRECT_COMMIT.
|
|
385
|
-
n && (activeTransition !== e.
|
|
386
|
-
|
|
387
|
-
//
|
|
388
|
-
//
|
|
389
|
-
//
|
|
390
|
-
//
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
454
|
+
n && R === null && (activeTransition !== e.Te || activeTransition === null || e.C & CONFIG_DIRECT_COMMIT) || l) {
|
|
455
|
+
// Lanes stage (#3479): a lane pass on a memo publishes its speculative
|
|
456
|
+
// result as an OVERRIDE — `_value` stays the committed truth, so a
|
|
457
|
+
// reader off the lane (A17's committed view, #3460) sees a whole
|
|
458
|
+
// committed frame: the source's shadow and its derivations together,
|
|
459
|
+
// never a committed shadow beside a speculative memo. The lane's own
|
|
460
|
+
// readers and untracked reads see the override (A17); the revert
|
|
461
|
+
// drops it and re-derives (resolveOptimisticNodes). Effects keep the
|
|
462
|
+
// direct commit — their `_value` is a run result the lane's queues
|
|
463
|
+
// already sequence. Either way the lane pass drops any superseded
|
|
464
|
+
// older hold so its queued commit can't clobber the fresh frame: a
|
|
465
|
+
// hold staged on an earlier, lane-free pass of the SAME transaction
|
|
466
|
+
// is superseded — left in place, the commit published the older
|
|
467
|
+
// frame over the fresh one (#3377). A reversion pass (OPT-dirty with
|
|
468
|
+
// no lane — the override dropped) commits directly: it IS the truth.
|
|
469
|
+
if (l && !n && currentOptimisticLane !== null) GlobalQueue.ge(e, E, currentOptimisticLane); else e.Ae = E;
|
|
470
|
+
if (l) e._e = NOT_PENDING;
|
|
395
471
|
} else {
|
|
396
|
-
e.
|
|
472
|
+
e._e = E;
|
|
473
|
+
if (R !== null) {
|
|
474
|
+
// Born held: the pass ran from mainline and derived from this
|
|
475
|
+
// transaction's staged world (enterStagedRead). Its value is the
|
|
476
|
+
// transaction's — staged into it directly, stamped, and committed
|
|
477
|
+
// with it; mainline's batch never sees it. A born-held effect
|
|
478
|
+
// skips its synchronous first run (effect()) and is replayed by
|
|
479
|
+
// the commit like a stale reader that showed the committed frame.
|
|
480
|
+
e.Te = R;
|
|
481
|
+
R.sn.push(e);
|
|
482
|
+
if (n) R.Ht.add(e);
|
|
483
|
+
}
|
|
397
484
|
// A window landing that gets held re-opens the window until the hold
|
|
398
485
|
// commits — the verdict's held-value branch is window-gated (#2990).
|
|
399
|
-
if (
|
|
400
|
-
//
|
|
486
|
+
if (_) e.Se = true;
|
|
487
|
+
// A staged sync recompute is a write path like setSignal/asyncWrite,
|
|
401
488
|
// so sync derivations of held sources stay visible to isPending()/latest()
|
|
402
489
|
// (#2831). Both companion writes are transition-scoped (optimistic) and
|
|
403
|
-
// auto-revert/re-derive at commit.
|
|
404
|
-
//
|
|
405
|
-
|
|
490
|
+
// auto-revert/re-derive at commit. Not gated on an active transition:
|
|
491
|
+
// a plain flush can still become a hold after this recompute — an
|
|
492
|
+
// async memo downstream pends and the batch is adopted into a
|
|
493
|
+
// transaction (scheduler.enterTransition) — and nothing re-derives
|
|
494
|
+
// the companion at adoption, so a memo held that way read
|
|
495
|
+
// isPending() false while its held source read true (#3413).
|
|
496
|
+
if (e.C & CONFIG_HAS_COMPANIONS && GlobalQueue.De !== null) GlobalQueue.De(e, E);
|
|
406
497
|
}
|
|
407
498
|
// insertSubs only walks _subs (no scheduling of its own), so a
|
|
408
499
|
// subscriber-less node has nothing to notify.
|
|
409
|
-
if (e.u !== null && (!u || l || e.o?.
|
|
500
|
+
if (e.u !== null && (!u || l || e.o?.Ne !== i)) insertSubs(e, l || u);
|
|
410
501
|
// A18 supersession, sync twin of asyncWrite's override branch (#3331):
|
|
411
502
|
// this pass published truth that differs from the override (the gate
|
|
412
503
|
// compared against it) into the transaction-held slot. Whether the
|
|
@@ -418,14 +509,14 @@ function recompute(e, t = false) {
|
|
|
418
509
|
// in this same tick (optimisticWrite stamps `_overrideTime`) is the
|
|
419
510
|
// newer intent over whatever this pass derives from the batch's staged
|
|
420
511
|
// inputs, and is not superseded by it.
|
|
421
|
-
else if (u && !l && e.o.
|
|
512
|
+
else if (u && !l && e.o.Qt !== clock) GlobalQueue.Oe(e, E);
|
|
422
513
|
} else if (u) {
|
|
423
514
|
// Unchanged value (equals the override) recomputed while the override
|
|
424
515
|
// is active: _value may still be stale, so hold the authoritative value
|
|
425
516
|
// for commit on its own transition's schedule — invisibly (A17/A18).
|
|
426
|
-
if (e.
|
|
427
|
-
e.
|
|
428
|
-
if (
|
|
517
|
+
if (e._e === NOT_PENDING) queuePendingNode(e);
|
|
518
|
+
e._e = E;
|
|
519
|
+
if (_) e.Se = true;
|
|
429
520
|
// see the held branch above (#2990)
|
|
430
521
|
// A confirmation after a supersession restores the override as the
|
|
431
522
|
// graph's value and notifies; a plain confirmation wakes only an
|
|
@@ -435,9 +526,9 @@ function recompute(e, t = false) {
|
|
|
435
526
|
// A17 silence holds for every ordinary subscriber. Both live in the
|
|
436
527
|
// engine's supersedeOverride.
|
|
437
528
|
GlobalQueue.Oe(e, E);
|
|
438
|
-
} else if (e.
|
|
439
|
-
for (let t = e.u; t !== null; t = t.
|
|
440
|
-
insertIntoHeapHeight(t.
|
|
529
|
+
} else if (e.Rt != I) {
|
|
530
|
+
for (let t = e.u; t !== null; t = t.ie) {
|
|
531
|
+
insertIntoHeapHeight(t.se, queueFor(t.se));
|
|
441
532
|
}
|
|
442
533
|
}
|
|
443
534
|
// Silent recovery: errored → unchanged value fires no notification, but
|
|
@@ -446,7 +537,7 @@ function recompute(e, t = false) {
|
|
|
446
537
|
// recoveries ride insertSubs above; a comparator throw re-errored the node
|
|
447
538
|
// (el._x?._error re-set), so this only runs on a genuinely clean recovery.
|
|
448
539
|
if (!f && !e.o?._) {
|
|
449
|
-
if (
|
|
540
|
+
if (r !== undefined) settleErroredDependents(e, r);
|
|
450
541
|
// Self-registration (this node's own superseded flight) is the #3181
|
|
451
542
|
// sweep's business below — retiring it here too would walk twice.
|
|
452
543
|
if (a) for (const t of a) if (t !== e) settlePendingSource(e, t);
|
|
@@ -454,29 +545,95 @@ function recompute(e, t = false) {
|
|
|
454
545
|
// #3181: a synchronous settle supersedes the old landing callback, so
|
|
455
546
|
// recompute owns its pending-source sweep. An uninitialized node without
|
|
456
547
|
// a replacement source still has no truth to reveal and must stay parked.
|
|
457
|
-
if (
|
|
548
|
+
if (c && !(e.S & (STATUS_PENDING | STATUS_UNINITIALIZED))) settlePendingSource(e);
|
|
549
|
+
}
|
|
550
|
+
// A REPORTER whose pass stopped reading a source it reported on (a gate
|
|
551
|
+
// closed) stops counting for the transaction waiting on it (A15 / #3426:
|
|
552
|
+
// the hold lasts while a live reporter observes the flight). Nothing else
|
|
553
|
+
// re-judges a parked transaction (see wokenTransitions; the disposal
|
|
554
|
+
// (#3372) and boundary (#3375) twins of this site), so the writes it held
|
|
555
|
+
// stayed staged for as long as the flight stayed up — forever, for one
|
|
556
|
+
// that never lands (fuzzer #3446 P1, spec O3). The event is "this pass
|
|
557
|
+
// dropped a dep" — deps past `_depsTail` (trimmed below, or kept by A30
|
|
558
|
+
// for a staged pass), or a pass that read nothing — not "recovered from
|
|
559
|
+
// pending": a reporter registered by the stale-reader carve-out
|
|
560
|
+
// (heldFromStale, an INITIALIZED source refetching) displays the committed
|
|
561
|
+
// value and is never pending (fuzzer case 79). Every parked transaction,
|
|
562
|
+
// not the reporter's stamp: the transaction waiting on it registered it
|
|
563
|
+
// without stamping it. One idle pass per parked transaction; done ones
|
|
564
|
+
// return at re-entry. Effects only — reporters register from render-effect
|
|
565
|
+
// notification (INV-3).
|
|
566
|
+
// (`_depsTail` was reset at the top of the pass; TS keeps that narrowing.)
|
|
567
|
+
const G = e.Fe;
|
|
568
|
+
if (n && (s && !(e.S & STATUS_PENDING) || (G === null ? e.ee !== null : G.te !== null))) wakeParked();
|
|
569
|
+
// Dependencies are the committed frame's until it is replaced (A30, #3410; the
|
|
570
|
+
// deps twin of the held children above): a pass that staged its value
|
|
571
|
+
// leaves the previous pass's tail linked for `commitPendingNode` to trim,
|
|
572
|
+
// so a write to a dependency the committed value still derives from
|
|
573
|
+
// reaches this node — and joins its hold if the stage is transaction-held
|
|
574
|
+
// by then (a plain flush decides nothing here: the transaction that holds
|
|
575
|
+
// the pass may open later in the same flush). A pass that published
|
|
576
|
+
// directly (a first pass, a lane's own reveal) trims now. An errored pass
|
|
577
|
+
// (a throw, NotReady included, or a comparator throw above) keeps its full
|
|
578
|
+
// list as before — `_depsTail` marks where it stopped — and the commit
|
|
579
|
+
// skips it by the same `_error`. An effect's frame is the run its value is
|
|
580
|
+
// applied by, not the value slot (#3438): a direct-committed pass that
|
|
581
|
+
// still owes a run (`_modified`) has not replaced what the last run
|
|
582
|
+
// published — the same flush may stash that run into a transaction it
|
|
583
|
+
// opens later — so its tail waits for `runEffect` to trim once the run
|
|
584
|
+
// applies. A pass that changed nothing replaced nothing either (#3469): the
|
|
585
|
+
// same flush may park with its inputs held, and the committed frame still
|
|
586
|
+
// derives from the tail — its trim waits on the flush's verdict (heldTrims).
|
|
587
|
+
// A tracked effect's pass IS its run (it bypasses the heap and runs after
|
|
588
|
+
// the commit): the frame is replaced, trim now.
|
|
589
|
+
if (!e.o?._ && e._e === NOT_PENDING && !(n && e.Nt)) {
|
|
590
|
+
if (t || l || n === EFFECT_TRACKED) trimStaleDeps(e); else if (e.Fe?.te ?? e.ee) heldTrims.push(e);
|
|
458
591
|
}
|
|
459
592
|
// Attribution hook: fired before the lane restore so `currentOptimisticLane`
|
|
460
593
|
// still reflects THIS run's posture. The facts distinguish an overlay
|
|
461
594
|
// recompute (optimistic lane, transition replay, transition-held commit)
|
|
462
595
|
// from a plain committed one — the engine must not blame overlay runs as
|
|
463
596
|
// waste or double-count them against plain aggregates.
|
|
464
|
-
if (attrHooks !== null) attrHooks.recomputeEnd(e, t, i, l || currentOptimisticLane !== null, activeTransition !== null || e.
|
|
465
|
-
currentOptimisticLane =
|
|
466
|
-
const
|
|
597
|
+
if (attrHooks !== null) attrHooks.recomputeEnd(e, t, i, l || currentOptimisticLane !== null, activeTransition !== null || e.Te !== null, e._e !== NOT_PENDING);
|
|
598
|
+
currentOptimisticLane = O;
|
|
599
|
+
const D = e._e !== NOT_PENDING || e.o !== null && (e.o.Pt !== null || e.o.Dt !== null) || (e.S & (STATUS_PENDING | STATUS_UNINITIALIZED)) !== 0;
|
|
467
600
|
// Override-covered holds (hasOverride) always queue: their commit belongs
|
|
468
601
|
// to their own transition's schedule (A18 re-rule) and is unobservable
|
|
469
602
|
// under the override (A17). Revert no longer commits anything, so an
|
|
470
603
|
// unqueued covered hold would leak (INV-7) once the revert clears
|
|
471
604
|
// _transition.
|
|
472
|
-
|
|
473
|
-
|
|
605
|
+
|
|
606
|
+
// While a pass's result waits on a commit, its children (and `_disposal`)
|
|
607
|
+
// wait with it, and a re-run may tear them down immediately
|
|
608
|
+
// (CONFIG_HELD_CHILDREN, #3404). One exception: a transaction-owned effect
|
|
609
|
+
// recomputed mainline (contested, #3322) published its value directly — a
|
|
610
|
+
// `_pendingValue` left from an earlier held pass is that transaction's,
|
|
611
|
+
// not this one's — so this pass's children are the frame's, and any
|
|
612
|
+
// zombies deferred at the top are superseded on that same frame. Its
|
|
613
|
+
// commit rides the transaction, not this flush: release them here rather
|
|
614
|
+
// than let two generations render at once.
|
|
615
|
+
let h = D && (!t || R !== null || (e.S & STATUS_PENDING) !== 0);
|
|
616
|
+
if (h && (!e.Te || u)) queuePendingNode(e); else if (h && activeTransition === null && !(e.S & (STATUS_PENDING | STATUS_UNINITIALIZED))) {
|
|
617
|
+
h = false;
|
|
618
|
+
disposeChildren(e, false, true);
|
|
619
|
+
}
|
|
620
|
+
if (h) e.C |= CONFIG_HELD_CHILDREN; else e.C &= ~CONFIG_HELD_CHILDREN;
|
|
621
|
+
// (A born-held pass IS the transaction's staged view — nothing to refresh.)
|
|
622
|
+
if (e.Te && n && activeTransition !== e.Te && R === null) {
|
|
623
|
+
// The re-run refreshes the transaction's STAGED view (_pendingValue); the
|
|
624
|
+
// value this pass published in _value belongs to the run that just
|
|
625
|
+
// finished. Keep that ownership, or the effect phase parks a
|
|
626
|
+
// mainline-computed value with the transaction (#3412).
|
|
627
|
+
const t = e.Vt;
|
|
628
|
+
runInTransition(e.Te, () => recompute(e));
|
|
629
|
+
e.Vt = t;
|
|
630
|
+
}
|
|
474
631
|
// Missed-wake reschedule (see the finally above): values this pass read
|
|
475
632
|
// before the nested commit are stale, so run again now that the heap will
|
|
476
633
|
// accept the node. Equality gates stop same-value landings from cascading,
|
|
477
634
|
// and a re-run only latches again if another nested commit changes a dep
|
|
478
635
|
// beneath it — convergent unless deps genuinely keep changing.
|
|
479
|
-
if (
|
|
636
|
+
if (T) {
|
|
480
637
|
enqueueSub(e);
|
|
481
638
|
schedule();
|
|
482
639
|
}
|
|
@@ -489,23 +646,23 @@ function updateIfNecessary(e) {
|
|
|
489
646
|
// (#3037); readers meanwhile serve the values the pass has so far.
|
|
490
647
|
// Never recompute a DISPOSED node either: recompute rewrites _flags and
|
|
491
648
|
// would resurrect it (#2983) — readers serve its last value.
|
|
492
|
-
if (e.
|
|
493
|
-
if (e.
|
|
494
|
-
for (let t = e.
|
|
495
|
-
const n = t.
|
|
496
|
-
const i = n.
|
|
497
|
-
if (i.
|
|
649
|
+
if (e.ft & (REACTIVE_RECOMPUTING_DEPS | REACTIVE_DISPOSED)) return;
|
|
650
|
+
if (e.ft & REACTIVE_CHECK) {
|
|
651
|
+
for (let t = e.ee; t; t = t.te) {
|
|
652
|
+
const n = t.re;
|
|
653
|
+
const i = n.ne || n;
|
|
654
|
+
if (i.ht) {
|
|
498
655
|
updateIfNecessary(i);
|
|
499
656
|
}
|
|
500
|
-
if (e.
|
|
657
|
+
if (e.ft & REACTIVE_DIRTY) {
|
|
501
658
|
break;
|
|
502
659
|
}
|
|
503
660
|
}
|
|
504
661
|
}
|
|
505
|
-
if (e.
|
|
662
|
+
if (e.ft & (REACTIVE_DIRTY | REACTIVE_OPTIMISTIC_DIRTY) || e.o?._ && e.fe < clock && !e.o?.ce) {
|
|
506
663
|
recompute(e);
|
|
507
664
|
}
|
|
508
|
-
e.
|
|
665
|
+
e.ft = e.ft & (REACTIVE_SNAPSHOT_STALE | REACTIVE_IN_HEAP | REACTIVE_IN_HEAP_HEIGHT);
|
|
509
666
|
}
|
|
510
667
|
|
|
511
668
|
function computed(e, t) {
|
|
@@ -524,37 +681,37 @@ function computed(e, t) {
|
|
|
524
681
|
// plus `_name`.
|
|
525
682
|
const l = {
|
|
526
683
|
id: inheritId(t, n, context),
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
684
|
+
C: (n ? CONFIG_TRANSPARENT : 0) | (t?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (!context || t?.lazy ? CONFIG_AUTO_DISPOSE : 0) | (t?.sync ? CONFIG_SYNC : 0) | (t?.q ? CONFIG_NO_SNAPSHOT : 0) | (snapshotCaptureActive && ownerInSnapshotScope(context) ? CONFIG_IN_SNAPSHOT_SCOPE : 0),
|
|
685
|
+
Re: t?.equals ?? isEqual,
|
|
686
|
+
Ue: null,
|
|
687
|
+
T: context?.T ?? globalQueue,
|
|
688
|
+
wt: context?.wt ?? defaultContext,
|
|
689
|
+
Ft: 0,
|
|
690
|
+
ht: e,
|
|
691
|
+
Ae: i ? t.loadingValue : undefined,
|
|
692
|
+
Rt: 0,
|
|
693
|
+
Wt: undefined,
|
|
694
|
+
Mt: null,
|
|
695
|
+
ee: null,
|
|
696
|
+
Fe: null,
|
|
697
|
+
ye: 0,
|
|
541
698
|
u: null,
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
699
|
+
qt: null,
|
|
700
|
+
_parent: context,
|
|
701
|
+
At: null,
|
|
702
|
+
Yt: null,
|
|
703
|
+
Ot: null,
|
|
704
|
+
ft: t?.lazy ? REACTIVE_LAZY : REACTIVE_NONE,
|
|
548
705
|
S: i ? 0 : STATUS_UNINITIALIZED,
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
706
|
+
fe: clock,
|
|
707
|
+
_e: NOT_PENDING,
|
|
708
|
+
Te: null,
|
|
709
|
+
Zt: -1,
|
|
710
|
+
Se: i,
|
|
554
711
|
o: null,
|
|
555
712
|
_name: t?.name ?? "computed"
|
|
556
713
|
};
|
|
557
|
-
if (t?.unobserved) ext(l).
|
|
714
|
+
if (t?.unobserved) ext(l).Kt = t.unobserved;
|
|
558
715
|
setupComputedNode(l, t);
|
|
559
716
|
return l;
|
|
560
717
|
}
|
|
@@ -564,28 +721,29 @@ function computed(e, t) {
|
|
|
564
721
|
* this; hot paths read `el._x?._field` gated by the _config presence bits.
|
|
565
722
|
* Never call ext() just to store a field's default. */ function ext(e) {
|
|
566
723
|
return e.o ??= {
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
724
|
+
Ne: undefined,
|
|
725
|
+
jt: undefined,
|
|
726
|
+
Qt: 0,
|
|
727
|
+
Bt: NOT_PENDING,
|
|
728
|
+
zt: 0,
|
|
729
|
+
de: undefined,
|
|
730
|
+
he: undefined,
|
|
731
|
+
Ge: undefined,
|
|
732
|
+
nn: undefined,
|
|
575
733
|
t: 0,
|
|
576
|
-
Se: null,
|
|
577
734
|
ce: null,
|
|
735
|
+
ae: null,
|
|
578
736
|
_: undefined,
|
|
579
|
-
|
|
580
|
-
|
|
737
|
+
oe: undefined,
|
|
738
|
+
ut: undefined,
|
|
581
739
|
h: undefined,
|
|
582
|
-
|
|
740
|
+
Ee: false,
|
|
583
741
|
i: null,
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
742
|
+
Kt: undefined,
|
|
743
|
+
Gt: undefined,
|
|
744
|
+
Dt: null,
|
|
745
|
+
Pt: null,
|
|
746
|
+
Jt: undefined
|
|
589
747
|
};
|
|
590
748
|
}
|
|
591
749
|
|
|
@@ -603,39 +761,39 @@ function computed(e, t) {
|
|
|
603
761
|
// the options to get it.
|
|
604
762
|
const o = {
|
|
605
763
|
id: inheritId(l, u, context),
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
764
|
+
C: (u ? CONFIG_TRANSPARENT : 0) | (l?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (l?.sync ? CONFIG_SYNC : 0) | (l?.Xt ?? 0) | (snapshotCaptureActive && ownerInSnapshotScope(context) ? CONFIG_IN_SNAPSHOT_SCOPE : 0),
|
|
765
|
+
Re: false,
|
|
766
|
+
Ue: null,
|
|
767
|
+
T: context?.T ?? globalQueue,
|
|
768
|
+
wt: context?.wt ?? defaultContext,
|
|
769
|
+
Ft: 0,
|
|
770
|
+
ht: e,
|
|
771
|
+
Ae: undefined,
|
|
772
|
+
Rt: 0,
|
|
773
|
+
Wt: undefined,
|
|
774
|
+
Mt: null,
|
|
775
|
+
ee: null,
|
|
776
|
+
Fe: null,
|
|
777
|
+
ye: 0,
|
|
620
778
|
u: null,
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
779
|
+
qt: null,
|
|
780
|
+
_parent: context,
|
|
781
|
+
At: null,
|
|
782
|
+
Yt: null,
|
|
783
|
+
Ot: null,
|
|
784
|
+
ft: REACTIVE_LAZY,
|
|
627
785
|
S: STATUS_UNINITIALIZED,
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
786
|
+
fe: clock,
|
|
787
|
+
_e: NOT_PENDING,
|
|
788
|
+
Te: null,
|
|
789
|
+
Zt: -1,
|
|
790
|
+
Se: false,
|
|
791
|
+
Nt: false,
|
|
792
|
+
$t: undefined,
|
|
793
|
+
ln: t,
|
|
794
|
+
un: n,
|
|
795
|
+
an: undefined,
|
|
796
|
+
Pe: i,
|
|
639
797
|
Vt: null,
|
|
640
798
|
o: null,
|
|
641
799
|
_name: l?.name ?? "effect"
|
|
@@ -645,7 +803,7 @@ function computed(e, t) {
|
|
|
645
803
|
// allocation on EVERY effect at creation (an alloc + 19 field stores,
|
|
646
804
|
// +23% effect creation, caught by the creation benches). Only genuinely
|
|
647
805
|
// per-node channels (boundaries) live on _x.
|
|
648
|
-
if (l?.unobserved) ext(o).
|
|
806
|
+
if (l?.unobserved) ext(o).Kt = l.unobserved;
|
|
649
807
|
setupComputedNode(o, lazyOptions);
|
|
650
808
|
return o;
|
|
651
809
|
}
|
|
@@ -668,7 +826,7 @@ function setEffectStatusNotify(e) {
|
|
|
668
826
|
* per-node field did when every effect carried one. */ function statusNotifierOf(e) {
|
|
669
827
|
const t = e.o?.h;
|
|
670
828
|
if (t !== undefined) return t;
|
|
671
|
-
return e.
|
|
829
|
+
return e.Pe ? effectStatusNotify ?? undefined : undefined;
|
|
672
830
|
}
|
|
673
831
|
|
|
674
832
|
const lazyOptions = {
|
|
@@ -676,25 +834,25 @@ const lazyOptions = {
|
|
|
676
834
|
};
|
|
677
835
|
|
|
678
836
|
function setupComputedNode(e, t) {
|
|
679
|
-
e.
|
|
680
|
-
const n = context
|
|
837
|
+
e.Mt = e;
|
|
838
|
+
const n = context?.cn ? context.fn : context;
|
|
681
839
|
if (context) {
|
|
682
|
-
const t = context.
|
|
840
|
+
const t = context.Ot;
|
|
683
841
|
if (t === null) {
|
|
684
|
-
context.
|
|
842
|
+
context.Ot = e;
|
|
685
843
|
} else {
|
|
686
|
-
e.
|
|
687
|
-
t.
|
|
688
|
-
context.
|
|
844
|
+
e.At = t;
|
|
845
|
+
t.Yt = e;
|
|
846
|
+
context.Ot = e;
|
|
689
847
|
}
|
|
690
848
|
}
|
|
691
|
-
if (n) e.
|
|
692
|
-
if (GlobalQueue.
|
|
849
|
+
if (n) e.Rt = n.Rt + 1;
|
|
850
|
+
if (GlobalQueue._n !== null) GlobalQueue._n(e);
|
|
693
851
|
!t?.lazy && recompute(e, true);
|
|
694
852
|
if (snapshotCaptureActive && !t?.lazy) {
|
|
695
|
-
if (!(e.S & STATUS_PENDING) && !(e.
|
|
696
|
-
ext(e).
|
|
697
|
-
e.
|
|
853
|
+
if (!(e.S & STATUS_PENDING) && !(e.C & CONFIG_NO_SNAPSHOT)) {
|
|
854
|
+
ext(e).Gt = e.Ae === undefined ? NO_SNAPSHOT : e.Ae;
|
|
855
|
+
e.C |= CONFIG_HAS_SNAPSHOT;
|
|
698
856
|
snapshotSources.add(e);
|
|
699
857
|
}
|
|
700
858
|
}
|
|
@@ -706,27 +864,27 @@ function signal(e, t, n = null) {
|
|
|
706
864
|
// createSignal nodes so ownerPath can locate signal subjects; null here,
|
|
707
865
|
// and staying null on internal signals — one shape either way).
|
|
708
866
|
const i = {
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
867
|
+
Re: t?.equals ?? isEqual,
|
|
868
|
+
C: (t?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (t?.q ? CONFIG_NO_SNAPSHOT : 0),
|
|
869
|
+
Ae: e,
|
|
712
870
|
u: null,
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
871
|
+
qt: null,
|
|
872
|
+
fe: clock,
|
|
873
|
+
ne: n,
|
|
716
874
|
ue: n?.o?.i || null,
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
875
|
+
Nn: null,
|
|
876
|
+
_e: NOT_PENDING,
|
|
877
|
+
Te: null,
|
|
878
|
+
Zt: -1,
|
|
721
879
|
o: null,
|
|
722
880
|
_name: t?.name ?? "signal",
|
|
723
|
-
|
|
881
|
+
nt: null
|
|
724
882
|
};
|
|
725
|
-
if (t?.unobserved) ext(i).
|
|
883
|
+
if (t?.unobserved) ext(i).Kt = t.unobserved;
|
|
726
884
|
if (n) linkFirewallChild(n, i);
|
|
727
|
-
if (snapshotCaptureActive && !(i.
|
|
728
|
-
ext(i).
|
|
729
|
-
i.
|
|
885
|
+
if (snapshotCaptureActive && !(i.C & CONFIG_NO_SNAPSHOT) && !((n?.S ?? 0) & STATUS_PENDING)) {
|
|
886
|
+
ext(i).Gt = e === undefined ? NO_SNAPSHOT : e;
|
|
887
|
+
i.C |= CONFIG_HAS_SNAPSHOT;
|
|
730
888
|
snapshotSources.add(i);
|
|
731
889
|
}
|
|
732
890
|
return i;
|
|
@@ -755,46 +913,52 @@ function signal(e, t, n = null) {
|
|
|
755
913
|
* unlinks in O(1) — the chain is walked per mark of the projection and
|
|
756
914
|
* would otherwise grow by one node per leaf ever read (#3351). */ function linkFirewallChild(e, t) {
|
|
757
915
|
const n = t.ue;
|
|
758
|
-
if (n !== null) n.
|
|
916
|
+
if (n !== null) n.Nn = t;
|
|
759
917
|
ext(e).i = t;
|
|
760
|
-
e.
|
|
918
|
+
e.C |= CONFIG_FW_CHILDREN;
|
|
761
919
|
}
|
|
762
920
|
|
|
763
921
|
/** Release a firewall child the store no longer addresses (unobserved sweep
|
|
764
922
|
* dropped it from its target's cache): unlink it from the chain so the
|
|
765
923
|
* projection stops retaining it and its last value. The node keeps its own
|
|
766
|
-
* `_nextChild` so a walk that is mid-chain on it still terminates.
|
|
767
|
-
* `_companionChildren`
|
|
768
|
-
*
|
|
924
|
+
* `_nextChild` so a walk that is mid-chain on it still terminates. It also
|
|
925
|
+
* leaves `_companionChildren` (#3503): the companions themselves are
|
|
926
|
+
* permanent on the node, but the node is unreachable through the store, so
|
|
927
|
+
* the set would only retain it and its last value. */ function unlinkFirewallChild(e) {
|
|
769
928
|
const t = e;
|
|
770
|
-
const n = t.
|
|
929
|
+
const n = t.ne;
|
|
771
930
|
if (!n) return;
|
|
772
|
-
const i = t.
|
|
931
|
+
const i = t.Nn;
|
|
773
932
|
const l = t.ue;
|
|
774
933
|
if (i !== null) i.ue = l; else if (n.o.i === t) n.o.i = l;
|
|
775
|
-
if (l !== null) l.
|
|
776
|
-
t.
|
|
934
|
+
if (l !== null) l.Nn = i;
|
|
935
|
+
t.Nn = null;
|
|
936
|
+
// #3503: the companion set is the last projection-side reference to a
|
|
937
|
+
// released leaf; leaving it there kept the leaf and its last value alive
|
|
938
|
+
// for the projection's lifetime after every latest()/isPending() reader
|
|
939
|
+
// was disposed.
|
|
940
|
+
n.o.Jt?.delete(t);
|
|
777
941
|
}
|
|
778
942
|
|
|
779
943
|
function slotSignal(e, t, n, i, l, u = null) {
|
|
780
944
|
// Prod and observe boilerplates — see computed(). The store relabels the
|
|
781
945
|
// observe slot (`store.<key>`) when the attribution engine is installed.
|
|
782
946
|
const o = {
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
947
|
+
Re: t,
|
|
948
|
+
C: CONFIG_OWNED_WRITE | CONFIG_SLOT_NODE,
|
|
949
|
+
Ae: e,
|
|
786
950
|
u: null,
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
951
|
+
qt: null,
|
|
952
|
+
fe: clock,
|
|
953
|
+
ne: u,
|
|
790
954
|
ue: u?.o?.i || null,
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
955
|
+
Nn: null,
|
|
956
|
+
_e: NOT_PENDING,
|
|
957
|
+
Te: null,
|
|
958
|
+
Zt: -1,
|
|
795
959
|
o: null,
|
|
796
|
-
|
|
797
|
-
|
|
960
|
+
dn: n,
|
|
961
|
+
En: i,
|
|
798
962
|
acc: l,
|
|
799
963
|
px: undefined,
|
|
800
964
|
pxv: undefined,
|
|
@@ -802,8 +966,8 @@ function slotSignal(e, t, n, i, l, u = null) {
|
|
|
802
966
|
};
|
|
803
967
|
if (u) linkFirewallChild(u, o);
|
|
804
968
|
if (snapshotCaptureActive && !((u?.S ?? 0) & STATUS_PENDING)) {
|
|
805
|
-
ext(o).
|
|
806
|
-
o.
|
|
969
|
+
ext(o).Gt = e === undefined ? NO_SNAPSHOT : e;
|
|
970
|
+
o.C |= CONFIG_HAS_SNAPSHOT;
|
|
807
971
|
snapshotSources.add(o);
|
|
808
972
|
}
|
|
809
973
|
return o;
|
|
@@ -811,15 +975,15 @@ function slotSignal(e, t, n, i, l, u = null) {
|
|
|
811
975
|
|
|
812
976
|
function optimisticSignal(e, t) {
|
|
813
977
|
const n = signal(e, t);
|
|
814
|
-
ext(n).
|
|
815
|
-
n.
|
|
978
|
+
ext(n).Ne = NOT_PENDING;
|
|
979
|
+
n.C |= CONFIG_OPTIMISTIC;
|
|
816
980
|
return n;
|
|
817
981
|
}
|
|
818
982
|
|
|
819
983
|
function optimisticComputed(e, t) {
|
|
820
984
|
const n = computed(e, t);
|
|
821
|
-
ext(n).
|
|
822
|
-
n.
|
|
985
|
+
ext(n).Ne = NOT_PENDING;
|
|
986
|
+
n.C |= CONFIG_OPTIMISTIC;
|
|
823
987
|
return n;
|
|
824
988
|
}
|
|
825
989
|
|
|
@@ -849,33 +1013,58 @@ function isEqual(e, t) {
|
|
|
849
1013
|
* );
|
|
850
1014
|
* ```
|
|
851
1015
|
*/ function untrack(e, t) {
|
|
852
|
-
if (GlobalQueue.
|
|
1016
|
+
if (GlobalQueue.In === null && !tracking && true) return e();
|
|
853
1017
|
const n = tracking;
|
|
854
1018
|
tracking = false;
|
|
855
1019
|
try {
|
|
856
|
-
if (GlobalQueue.
|
|
1020
|
+
if (GlobalQueue.In !== null) return GlobalQueue.In(e);
|
|
857
1021
|
return e();
|
|
858
1022
|
} finally {
|
|
859
1023
|
tracking = n;
|
|
860
1024
|
}
|
|
861
1025
|
}
|
|
862
1026
|
|
|
1027
|
+
/**
|
|
1028
|
+
* Set while runtime bookkeeping evaluates a user accessor inside another
|
|
1029
|
+
* node's pass (a loading boundary's `on` key, read from `notify` while the
|
|
1030
|
+
* node that went pending is still recomputing). `context` is that node, but
|
|
1031
|
+
* the read is nobody's: the value is compared, never derived from, so the
|
|
1032
|
+
* untracked-pending re-run link (`read`, `!tracking`) must not be recorded on
|
|
1033
|
+
* it — it made the key's source a dependency of an unrelated async memo
|
|
1034
|
+
* (#3528: `isPending(m2)` through a memo in `on()` linked the memo into `m2`,
|
|
1035
|
+
* a cycle that re-derived `m2` on every pending mark and never converged).
|
|
1036
|
+
*/ let spectating = false;
|
|
1037
|
+
|
|
1038
|
+
/**
|
|
1039
|
+
* Evaluates `fn` untracked and without recording a dependency for the
|
|
1040
|
+
* untracked-pending re-run rule on the current `context`. For bookkeeping
|
|
1041
|
+
* reads made mid-propagation on behalf of no node — see `spectating`.
|
|
1042
|
+
*/ function spectate(e) {
|
|
1043
|
+
const t = spectating;
|
|
1044
|
+
spectating = true;
|
|
1045
|
+
try {
|
|
1046
|
+
return untrack(e);
|
|
1047
|
+
} finally {
|
|
1048
|
+
spectating = t;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
863
1052
|
/**
|
|
864
1053
|
* Bring a computed to a readable state: lazy/disposed nodes are (re)computed;
|
|
865
1054
|
* an isPending() probe (`refresh`) additionally pulls the node fully up to
|
|
866
1055
|
* date so its status flags reflect the current graph.
|
|
867
1056
|
*/ function prepareComputed(e, t) {
|
|
868
|
-
if (e.
|
|
869
|
-
e.
|
|
1057
|
+
if (e.ft & REACTIVE_LAZY) {
|
|
1058
|
+
e.ft &= ~REACTIVE_LAZY;
|
|
870
1059
|
recompute(e, true);
|
|
871
|
-
} else if (e.
|
|
1060
|
+
} else if (e.ft & REACTIVE_DISPOSED) {
|
|
872
1061
|
// Two disposal lifecycles share the flag (#3024). Observation-lifecycle
|
|
873
1062
|
// nodes (CONFIG_AUTO_DISPOSE) are dormant — torn down by unobserved()
|
|
874
1063
|
// when the last subscriber left — and reads reawaken them; that is the
|
|
875
1064
|
// pay-for-use contract. Owner-lifecycle nodes are dead: recomputing would
|
|
876
1065
|
// re-run user code in a torn-down tree (and discard manual writes on
|
|
877
1066
|
// derived-writable signals), so reads return the last committed value.
|
|
878
|
-
if (e.
|
|
1067
|
+
if (e.C & CONFIG_AUTO_DISPOSE) recompute(e, true);
|
|
879
1068
|
} else if (t) {
|
|
880
1069
|
updateIfNecessary(e);
|
|
881
1070
|
}
|
|
@@ -903,14 +1092,14 @@ function isEqual(e, t) {
|
|
|
903
1092
|
* Pay-for-use: reached through GlobalQueue._notifyAuthoritativeObservers,
|
|
904
1093
|
* installed at first until() call — apps that never use until() shake it.
|
|
905
1094
|
*/ function notifyAuthoritativeObservers(e) {
|
|
906
|
-
for (let t = e.u; t !== null; t = t.
|
|
907
|
-
const e = t.
|
|
908
|
-
if (!(e.
|
|
1095
|
+
for (let t = e.u; t !== null; t = t.ie) {
|
|
1096
|
+
const e = t.se;
|
|
1097
|
+
if (!(e.C & CONFIG_AUTHORITATIVE_READ)) continue;
|
|
909
1098
|
// Missed-wake latch (#3037), same contract as insertSubs: the reader may
|
|
910
1099
|
// itself have pulled this recompute (updateIfNecessary from its own
|
|
911
1100
|
// read), and the heap refuses RECOMPUTING nodes — latch so recompute's
|
|
912
1101
|
// tail reschedules it with the staged value visible.
|
|
913
|
-
if (e.
|
|
1102
|
+
if (e.ft & REACTIVE_RECOMPUTING_DEPS && t.pe === e.ye && t !== e.Fe) e.ft |= REACTIVE_MISSED_WAKE;
|
|
914
1103
|
enqueueSub(e);
|
|
915
1104
|
}
|
|
916
1105
|
schedule();
|
|
@@ -921,7 +1110,7 @@ function isEqual(e, t) {
|
|
|
921
1110
|
* before its first read (same late-binding contract as the optimistic engine;
|
|
922
1111
|
* the gating bit is only ever set by such a read, so the `!` call sites are
|
|
923
1112
|
* safe once every setter installs, #3303). */ function installAuthoritativeRead() {
|
|
924
|
-
if (GlobalQueue.
|
|
1113
|
+
if (GlobalQueue.Tn === null) GlobalQueue.Tn = notifyAuthoritativeObservers;
|
|
925
1114
|
}
|
|
926
1115
|
|
|
927
1116
|
/**
|
|
@@ -934,19 +1123,246 @@ function isEqual(e, t) {
|
|
|
934
1123
|
* already use). An effect the transaction itself computed re-derives at its
|
|
935
1124
|
* commit on its own (parked run, or the contested re-derive, #3322) and is
|
|
936
1125
|
* not recorded — replaying it too would publish the frame twice.
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1126
|
+
*
|
|
1127
|
+
* Flight twin (the pending-branch carve-out): the reader is served the
|
|
1128
|
+
* node's committed, pre-flight value and now observes that flight — A15:
|
|
1129
|
+
* async work observed by a reader settles as one unit with the writes that
|
|
1130
|
+
* asked it — so it joins the transaction's reporters for the node. The
|
|
1131
|
+
* reporter the transaction recorded when the flight started may be gone (a
|
|
1132
|
+
* keyed remount disposed it, #3374); a completion check that found no live
|
|
1133
|
+
* reporter committed the writes ahead of the answer, tearing the new
|
|
1134
|
+
* reader's frame (`Count: 1` beside `Details: 0`). Joins an entry the
|
|
1135
|
+
* transaction already holds; a flight nobody had observed yet has none (the
|
|
1136
|
+
* reader is its first observer — a conditional that just revealed it, #3458)
|
|
1137
|
+
* and is notified up the reader's own queue chain under that transaction,
|
|
1138
|
+
* the one sanctioned registration site (INV-3): a collecting boundary above
|
|
1139
|
+
* the reader consumes it as it would any pending, an unboundaried reader
|
|
1140
|
+
* opens the entry — and the transaction, judged complete on its other
|
|
1141
|
+
* flights, revealed the inputs beside the reader's pre-flight value
|
|
1142
|
+
* otherwise (`Count: 1 | A: 1` beside `B: 0`). A staged signal or a settled
|
|
1143
|
+
* node registers nothing. Every reporter dies with its reader
|
|
1144
|
+
* (reporterBlocksSource: the read linked it as a dep). The node's own entry
|
|
1145
|
+
* is the only one that can matter: a chain's intermediate memo is re-pulled
|
|
1146
|
+
* by the read (updateIfNecessary's retry) and enters the transaction, so the
|
|
1147
|
+
* reader holds through the normal path; a node with its own flight that is
|
|
1148
|
+
* also pending on an upstream re-ask blocks through that flight until it
|
|
1149
|
+
* lands, and its landing re-runs the reader into the normal path.
|
|
1150
|
+
*/
|
|
1151
|
+
/** The replay half of the stale-of-foreign clause (A15 / A26): a stale reader
|
|
1152
|
+
* served the committed value because `txn` holds what it read re-runs at
|
|
1153
|
+
* txn's commit, when the value it was denied becomes the frame — unless its
|
|
1154
|
+
* own last value already came from that transaction. One registration for
|
|
1155
|
+
* the node path (heldFromStale) and the store's backing paths, which have
|
|
1156
|
+
* no node to carry the hold (heldFromReader, the adoption hold view). */ function recordStaleReplay(e, t) {
|
|
1157
|
+
const n = t.Vt;
|
|
1158
|
+
if (n == null || currentTransition(n) !== e) e.Ht.add(t);
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
/**
|
|
1162
|
+
* The ownership relation (DESIGN-CONSOLIDATION §6, ruled 2026-09-17): is
|
|
1163
|
+
* `hold` part of the running pass's world? A plain reader's world is the
|
|
1164
|
+
* transaction it runs under, through merges. A lane reader's world is its
|
|
1165
|
+
* lane AND the transition that owns the lane — the one asymmetry between a
|
|
1166
|
+
* lane and a separate transaction (a lane sees what lands from its parent as
|
|
1167
|
+
* its own; a separate transaction would wait for the parent to settle) —
|
|
1168
|
+
* see `ownsLane` in lanes.ts, built on this. One relation for the
|
|
1169
|
+
* stale-of-foreign clause (heldFromStale), the lane arm (readsHeldCommitted)
|
|
1170
|
+
* and the store's backing holds (foreignHold); `serve` has no lane arm of
|
|
1171
|
+
* its own, the lane's extra visibility lives here.
|
|
1172
|
+
*/ function ownsHold(e) {
|
|
1173
|
+
return activeTransition !== null && currentTransition(e) === currentTransition(activeTransition);
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
function heldFromStale(e, t) {
|
|
1177
|
+
const n = e.Te;
|
|
1178
|
+
if (n === null || ownsHold(n)) return false;
|
|
940
1179
|
const i = currentTransition(n);
|
|
941
|
-
|
|
942
|
-
|
|
1180
|
+
recordStaleReplay(i, t);
|
|
1181
|
+
const l = i.ot.get(e);
|
|
1182
|
+
if (l) l.add(t); else if (e.S & STATUS_PENDING) runInTransition(i, () => t.T.notify(t, STATUS_PENDING, STATUS_PENDING, e.o._));
|
|
943
1183
|
return true;
|
|
944
1184
|
}
|
|
945
1185
|
|
|
1186
|
+
/**
|
|
1187
|
+
* A tracked computation about to be served a live transaction's staged value
|
|
1188
|
+
* derives from that transaction's world, so it enters the transaction and
|
|
1189
|
+
* this pass's result is held with it (A29, #3408). The write side (`setSignal`)
|
|
1190
|
+
* and a stamped node's recompute already enter; a reader that only now
|
|
1191
|
+
* starts reading the held node — a conditional memo whose branch flipped —
|
|
1192
|
+
* was the gap: it published a value derived from the staged world into the
|
|
1193
|
+
* mainline frame. Not for a probe (`isPending(() => x())` observes, it does
|
|
1194
|
+
* not derive) and a no-op for the ambient batch (`_transition` null) or the
|
|
1195
|
+
* transaction already active.
|
|
1196
|
+
*/
|
|
1197
|
+
/** The transaction a pass running OUTSIDE a flush was served a staged value
|
|
1198
|
+
* from (A29, creation-time form). Inside a flush the pass enters through
|
|
1199
|
+
* initTransition; outside one — a memo or effect created from mainline code
|
|
1200
|
+
* while a hold is live — entering would leave `activeTransition` and the
|
|
1201
|
+
* batch pointed at the transaction for the rest of the synchronous block,
|
|
1202
|
+
* so an unrelated write made after the mount was held with someone else's
|
|
1203
|
+
* action. The entry is the pass's alone: recompute stages the node into the
|
|
1204
|
+
* transaction (born held) and mainline is never touched. */ let stagedEntry = null;
|
|
1205
|
+
|
|
1206
|
+
function enterStagedRead(e, t = e.Te) {
|
|
1207
|
+
if (!t || t === activeTransition || pendingCheckActive) return;
|
|
1208
|
+
// A companion (the latest() shadow, the isPending() verdict signal) is the
|
|
1209
|
+
// engine's mirror of the flushed world — reading it, or being it, is an
|
|
1210
|
+
// observation, not a derivation from the hold: latest(x) never enters x's
|
|
1211
|
+
// transaction, and the shadow's own pass never enters either (it would
|
|
1212
|
+
// flip activeTransition under the reader that pulled it). (`el` is null for
|
|
1213
|
+
// a store backing served under a hold — no node, the transaction is the
|
|
1214
|
+
// fold's.)
|
|
1215
|
+
if (e?.o?.nn || context?.o?.nn) return;
|
|
1216
|
+
// Verdict machinery (GlobalQueue._verdictPull: companion creation and the
|
|
1217
|
+
// latest()/isPending() pulls — the latest() shadow is created before it is
|
|
1218
|
+
// marked optimistic, so the bit alone cannot tell) and optimistic nodes
|
|
1219
|
+
// (own lane posture, A31) read staged truth by design; they keep the
|
|
1220
|
+
// entering path.
|
|
1221
|
+
// (`context` is non-null here: every caller selected a value for a reader.)
|
|
1222
|
+
const n = context;
|
|
1223
|
+
if (activeTransition === null && !globalQueue.Sn) {
|
|
1224
|
+
// Verdict pulls are observations, not derivations: a latest() /
|
|
1225
|
+
// isPending() call from mainline must never enter a transaction (it
|
|
1226
|
+
// would capture the rest of the caller's synchronous block).
|
|
1227
|
+
if (GlobalQueue.On) return;
|
|
1228
|
+
if (n.ft & REACTIVE_RECOMPUTING_DEPS && !(n.C & CONFIG_OPTIMISTIC) && (stagedEntry === null || stagedEntry === t)) {
|
|
1229
|
+
stagedEntry = t;
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
globalQueue.initTransition(t);
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
/**
|
|
1237
|
+
* Rule 1 (value selection), the full arm: does this reader see a STAGED
|
|
1238
|
+
* node's COMMITTED value? One implementation of the rule the fast paths
|
|
1239
|
+
* (readNodeFast, read's fast block) carry as their trivial ternary and that
|
|
1240
|
+
* every slow site — read's tail, the store's backing selection, the lane and
|
|
1241
|
+
* verdict arms — used to restate by hand (docs/DESIGN-CONSOLIDATION.md, move 3b). In order:
|
|
1242
|
+
* - no reader at all (an untracked read) — the committed frame;
|
|
1243
|
+
* - a reader under an optimistic lane the engine says reads committed
|
|
1244
|
+
* (laneReadsCommitted: another lane's hold, #3460);
|
|
1245
|
+
* - nothing staged;
|
|
1246
|
+
* - a children-forbidden reader (createTrackedEffect / onSettled: the frame,
|
|
1247
|
+
* never the graph — A32);
|
|
1248
|
+
* - a stale reader (render effect) of a FOREIGN transaction's staged write —
|
|
1249
|
+
* committed, no entanglement (heldFromStale registers the replay; a node
|
|
1250
|
+
* born held has no committed frame to fall back to, `noCommitted`);
|
|
1251
|
+
* - A17 for HELD truth (#3164, CONFIG_HELD_TRUTH): staged confirming truth —
|
|
1252
|
+
* fold-staged onto an armed family, or entangle-stolen by an awaited
|
|
1253
|
+
* until() — is masked from ordinary readers until its transaction's
|
|
1254
|
+
* reveal, the retaining transaction's own speculative recomputes included
|
|
1255
|
+
* (partial override coverage would otherwise compose override + staged
|
|
1256
|
+
* truth into a state no timeline contains). Authoritative readers
|
|
1257
|
+
* (until()'s predicate) and latest() see the staged truth — the tunnel that
|
|
1258
|
+
* keeps the hold deadlock-free.
|
|
1259
|
+
* False means the reader derives from the staged value and enters its
|
|
1260
|
+
* transaction (enterStagedRead, A29).
|
|
1261
|
+
*/ function readerSeesCommitted(e, t, n, i) {
|
|
1262
|
+
return !!(!t || currentOptimisticLane !== null && GlobalQueue.An(e, n, t) || e._e === NOT_PENDING || t.C & CONFIG_CHILDREN_FORBIDDEN || stale && !i && heldFromStale(e, t) || e.C & CONFIG_HELD_TRUTH && !latestReadActive && !(t.C & CONFIG_AUTHORITATIVE_READ));
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
/** A28 — set when a node is staged (queuePendingNode) or a held node rewritten
|
|
1266
|
+
* (stashHeldRewrite) OUTSIDE a flush; cleared when the next flush begins. The
|
|
1267
|
+
* read sites test this one module boolean instead of `globalQueue._running`:
|
|
1268
|
+
* inside a flush it is false and the A28 arm costs nothing; outside, only a
|
|
1269
|
+
* tick with unflushed writes pays the staged-node check. */ let unflushedStaged = false;
|
|
1270
|
+
|
|
1271
|
+
function markUnflushedStaged() {
|
|
1272
|
+
unflushedStaged = true;
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
/** A28 — a write becomes visible at flush. Outside a flush, a node holding an
|
|
1276
|
+
* AMBIENT staged value (no transaction stamp) was written since the last
|
|
1277
|
+
* flush: ambient staging commits at flush end, so nothing else leaves a node
|
|
1278
|
+
* in this state; a stamped value is the flushed held world, which A28 says
|
|
1279
|
+
* latest() serves. Inside a flush the rule does not apply (A28 (4): promoted
|
|
1280
|
+
* within the round). Structural — no marker on the write path. */ function unflushed(e) {
|
|
1281
|
+
return unflushedValue(e) !== NOT_PENDING;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
/** The value an unflushed node serves — the committed value for an ambient
|
|
1285
|
+
* write, the flushed staged value for a rewrite of a held node — or
|
|
1286
|
+
* NOT_PENDING when nothing is unflushed. Exempt: owned-write nodes (A28 (4):
|
|
1287
|
+
* a write issued inside a recompute is promoted at that recompute's end —
|
|
1288
|
+
* boundary and loading machinery, until()'s internals, signals declared for
|
|
1289
|
+
* in-computation writes) and engine companions (the isPending() verdict
|
|
1290
|
+
* signal, the latest() shadow: the system's own writes, made at the source's
|
|
1291
|
+
* write to mirror it, installing eagerly — A28, A8). */ function unflushedValue(e, t = e.Ae) {
|
|
1292
|
+
if (globalQueue.Sn || e._e === NOT_PENDING || e.C & CONFIG_PROMOTED || e.o?.nn) return NOT_PENDING;
|
|
1293
|
+
// Ambient, or adopted by a transaction before any flush carried the staging
|
|
1294
|
+
// (CONFIG_ADOPTED_UNFLUSHED): nothing flushed is staged — the committed
|
|
1295
|
+
// value answers (the caller's notion of committed: a store node's backing).
|
|
1296
|
+
// A held node: unflushed only if rewritten since the last flush (stash).
|
|
1297
|
+
if (e.Te === null || e.C & CONFIG_ADOPTED_UNFLUSHED) return t;
|
|
1298
|
+
return e.o === null ? NOT_PENDING : e.o.Bt;
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
/** Held nodes rewritten since the last flush (setSignal); the flush clears
|
|
1302
|
+
* their stash — from then on latest() answers with the rewrite. */ const unflushedRewrites = [];
|
|
1303
|
+
|
|
1304
|
+
/** Nodes written inside a creation-time recompute (CONFIG_PROMOTED). */ const promotedWrites = [];
|
|
1305
|
+
|
|
1306
|
+
/** A28 (5): an optimistic write becomes the ACTIVE override at the flush that
|
|
1307
|
+
* carries it. `_overrideTime` is stamped with `clock` at the write and `clock`
|
|
1308
|
+
* advances after every flush, so "this tick, outside a flush" is unflushed. */ function unflushedOverride(e) {
|
|
1309
|
+
// Companions are optimistic signals written by the engine (see unflushed).
|
|
1310
|
+
return !globalQueue.Sn && e.o?.Qt === clock && !e.o?.nn;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
/** Active optimistic override on an armed node (an armed slot idles at
|
|
1314
|
+
* NOT_PENDING; undefined = unarmed plain node). The writer's own channels —
|
|
1315
|
+
* the draft, `in`/keys inside the setter — compose on this regardless of
|
|
1316
|
+
* flush state. */ function hasActiveOverride(e) {
|
|
1317
|
+
const t = e.o;
|
|
1318
|
+
return t !== null && t.Ne !== undefined && t.Ne !== NOT_PENDING;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
/** The override a READER sees: installed, and carried by a flush (A28 (5) —
|
|
1322
|
+
* an optimistic write is a write; until its flush no reader sees it). One
|
|
1323
|
+
* implementation for read()'s override arm, the verdict channels
|
|
1324
|
+
* (latestRead, computePendingState) and the store's selection
|
|
1325
|
+
* (docs/DESIGN-CONSOLIDATION.md, move 3b). */ function visibleOverride(e) {
|
|
1326
|
+
return hasActiveOverride(e) && !unflushedOverride(e);
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
/** A derivation served the committed value because of an unflushed write
|
|
1330
|
+
* (A28) must run again in the flush that carries it — the late-linker case
|
|
1331
|
+
* (#3337's reason to defer the walk): it linked after the write walked. */ function markLateLinker(e) {
|
|
1332
|
+
// The pass's own tail re-enqueues on this latch (recompute's finally) —
|
|
1333
|
+
// a direct enqueue here would be wiped by the pass's flag reset.
|
|
1334
|
+
e.ft |= REACTIVE_MISSED_WAKE;
|
|
1335
|
+
return true;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
/** Companion-bearing nodes written outside a flush (setSignal); the flush
|
|
1339
|
+
* that carries their writes re-syncs their companions (A28). */ const unflushedCompanions = [];
|
|
1340
|
+
|
|
1341
|
+
function resyncUnflushedCompanions() {
|
|
1342
|
+
unflushedStaged = false;
|
|
1343
|
+
// Length-guarded: the common flush has nothing here, and must allocate nothing.
|
|
1344
|
+
// Length-guarded: the common flush has nothing here and allocates nothing.
|
|
1345
|
+
if (unflushedRewrites.length !== 0) {
|
|
1346
|
+
for (const e of unflushedRewrites) e.o.Bt = NOT_PENDING;
|
|
1347
|
+
unflushedRewrites.length = 0;
|
|
1348
|
+
}
|
|
1349
|
+
if (promotedWrites.length !== 0) {
|
|
1350
|
+
for (const e of promotedWrites) e.C &= ~CONFIG_PROMOTED;
|
|
1351
|
+
promotedWrites.length = 0;
|
|
1352
|
+
}
|
|
1353
|
+
if (unflushedCompanions.length !== 0) {
|
|
1354
|
+
for (const e of unflushedCompanions) GlobalQueue.De(e, e._e !== NOT_PENDING ? e._e : e.Ae);
|
|
1355
|
+
unflushedCompanions.length = 0;
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
|
|
946
1359
|
function readNodeFast(e) {
|
|
947
|
-
if (latestReadActive || pendingCheckActive || e.
|
|
1360
|
+
if (latestReadActive || pendingCheckActive || e.ht || e.ne || e.o?.Ne !== undefined || e.o?.Gt !== undefined || activeTransition !== null || currentOptimisticLane !== null || snapshotCaptureActive ||
|
|
1361
|
+
// A28: a staged node read outside a flush may serve its committed value;
|
|
1362
|
+
// that arm lives on the slow path so this body stays inlinable.
|
|
1363
|
+
unflushedStaged && e._e !== NOT_PENDING || false) return READ_SLOW;
|
|
948
1364
|
let t = context;
|
|
949
|
-
if (t
|
|
1365
|
+
if (t?.cn) t = t.fn;
|
|
950
1366
|
if (t && tracking) link(e, t);
|
|
951
1367
|
// Children-forbidden readers (createTrackedEffect / onSettled callbacks) get
|
|
952
1368
|
// committed visibility: like the effect half of createEffect and event
|
|
@@ -958,7 +1374,8 @@ function readNodeFast(e) {
|
|
|
958
1374
|
// the same rule the slow path applies (#3322). Without it a zombie
|
|
959
1375
|
// recompute, or a commit-time re-derive of a contested effect, published
|
|
960
1376
|
// another transaction's uncommitted value.
|
|
961
|
-
return !t || e.
|
|
1377
|
+
return !t || e._e === NOT_PENDING || t.C & CONFIG_CHILDREN_FORBIDDEN || stale && heldFromStale(e, t) ? e.Ae : (enterStagedRead(e),
|
|
1378
|
+
e._e);
|
|
962
1379
|
}
|
|
963
1380
|
|
|
964
1381
|
function read(e) {
|
|
@@ -966,31 +1383,33 @@ function read(e) {
|
|
|
966
1383
|
// Checked before isPending so that isPending(() => latest(x)) checks
|
|
967
1384
|
// the _pendingSignal of _latestValueComputed (async in flight) rather
|
|
968
1385
|
// than the original node (which stays "pending" while held in a transition).
|
|
969
|
-
if (latestReadActive) return GlobalQueue.
|
|
1386
|
+
if (latestReadActive) return GlobalQueue.Cn(e);
|
|
970
1387
|
let t = context;
|
|
971
|
-
if (t
|
|
1388
|
+
if (t?.cn) t = t.fn;
|
|
972
1389
|
const n = e;
|
|
973
|
-
const i = e.
|
|
1390
|
+
const i = e.ne;
|
|
974
1391
|
const l = i || e;
|
|
975
1392
|
// Handle isPending() mode: collect pending state while preserving normal read semantics.
|
|
976
1393
|
// Probe mode is suspended while preparing the node so nested reads during a
|
|
977
1394
|
// recompute don't collect into the probe.
|
|
978
1395
|
if (pendingCheckActive) {
|
|
979
|
-
GlobalQueue.
|
|
980
|
-
} else if (typeof n.
|
|
1396
|
+
GlobalQueue.pn(e, t, l, i);
|
|
1397
|
+
} else if (typeof n.ht === "function") {
|
|
981
1398
|
prepareComputed(e, false);
|
|
982
1399
|
}
|
|
983
|
-
if (!n.
|
|
1400
|
+
if (!n.ht && l === e && e.o?.Ne === undefined && e.o?.Gt === undefined && activeTransition === null && currentOptimisticLane === null && !snapshotCaptureActive && (!unflushedStaged || e._e === NOT_PENDING) && // A28, see readNodeFast
|
|
1401
|
+
true) {
|
|
984
1402
|
if (t && tracking) link(e, t);
|
|
985
1403
|
// Committed visibility for children-forbidden readers and for stale
|
|
986
1404
|
// readers of a foreign transaction's staged write — see readNodeFast.
|
|
987
|
-
return !t || e.
|
|
1405
|
+
return !t || e._e === NOT_PENDING || t.C & CONFIG_CHILDREN_FORBIDDEN || stale && heldFromStale(e, t) ? e.Ae : (enterStagedRead(e),
|
|
1406
|
+
e._e);
|
|
988
1407
|
}
|
|
989
1408
|
if (t && tracking) {
|
|
990
1409
|
link(e, t, pendingCheckActive);
|
|
991
|
-
if (l.
|
|
1410
|
+
if (l.ht) {
|
|
992
1411
|
const n = queueFor(e);
|
|
993
|
-
if (l.
|
|
1412
|
+
if (l.Rt >= n.Ct) {
|
|
994
1413
|
markNode(t);
|
|
995
1414
|
markHeap(n);
|
|
996
1415
|
updateIfNecessary(l);
|
|
@@ -1001,11 +1420,11 @@ function read(e) {
|
|
|
1001
1420
|
// never read the PRE-re-ask value as settled. Self-guarded: a clean
|
|
1002
1421
|
// node no-ops and updateIfNecessary refuses disposed nodes (#2983) —
|
|
1003
1422
|
// a dead target serves its last value, which is already quiescent.
|
|
1004
|
-
else if (t.
|
|
1005
|
-
const i = l.
|
|
1423
|
+
else if (t.C & CONFIG_FRESH_READ) updateIfNecessary(l);
|
|
1424
|
+
const i = l.Rt;
|
|
1006
1425
|
// parent check is shallow, might need to be recursive
|
|
1007
|
-
if (i >= t.
|
|
1008
|
-
t.
|
|
1426
|
+
if (i >= t.Rt && e._parent !== t) {
|
|
1427
|
+
t.Rt = i + 1;
|
|
1009
1428
|
}
|
|
1010
1429
|
}
|
|
1011
1430
|
}
|
|
@@ -1026,14 +1445,17 @@ function read(e) {
|
|
|
1026
1445
|
// (firewall-backed store reads always did; plain memos since the #3043
|
|
1027
1446
|
// port): falling through served `undefined` as if settled and stranded
|
|
1028
1447
|
// the reader outside both transactions, so it never re-ran.
|
|
1029
|
-
if (t && !(stale && !(l.S & STATUS_UNINITIALIZED) && !(l.
|
|
1448
|
+
if (t && !(stale && !(l.S & STATUS_UNINITIALIZED) && !(l.C & CONFIG_INPUTS_PUBLISHED) && !(l.C & CONFIG_HAS_LANE && GlobalQueue.Rn(l)) && heldFromStale(l, t))) {
|
|
1030
1449
|
// Per-lane suspension lives with the engine (a non-null lane implies it
|
|
1031
1450
|
// is installed): under a lane, only same-lane pending async without an
|
|
1032
1451
|
// active override throws — plus uninitialized sources regardless of
|
|
1033
1452
|
// lane (#3276); that check rides laneSuspends so floor bundles don't
|
|
1034
1453
|
// pay for it.
|
|
1035
|
-
if (currentOptimisticLane === null || GlobalQueue.
|
|
1036
|
-
|
|
1454
|
+
if (currentOptimisticLane === null || GlobalQueue.Gn(l)) {
|
|
1455
|
+
// An untracked read of a pending node still re-runs its reader when
|
|
1456
|
+
// the node settles — unless nobody is reading (`spectating`, #3528)
|
|
1457
|
+
// or the reader is the node itself.
|
|
1458
|
+
if (!tracking && !spectating && e !== t) link(e, t);
|
|
1037
1459
|
throw l.o?._;
|
|
1038
1460
|
}
|
|
1039
1461
|
} else if (!t && l.S & STATUS_UNINITIALIZED) {
|
|
@@ -1044,26 +1466,60 @@ function read(e) {
|
|
|
1044
1466
|
// firewall-backed reads follow the same rules (memo parity, #2897 ruling):
|
|
1045
1467
|
// an errored derive throws for every late reader instead of silently
|
|
1046
1468
|
// serving node values (the seed, or last-good data after a failed refetch).
|
|
1047
|
-
if (l.
|
|
1469
|
+
if (l.ht && l.S & STATUS_ERROR) {
|
|
1048
1470
|
// Only a genuine reactive re-read may retry an errored async source:
|
|
1049
1471
|
// - tracking: owned/tracked scope only (never events / `untrack` / effect side-effect phase)
|
|
1050
1472
|
// - !pendingCheckActive: an `isPending` probe observes the error, never refetches
|
|
1051
1473
|
// - owner._time < clock: only on a later cycle than the one the error was found
|
|
1052
|
-
if (tracking && !pendingCheckActive && l.
|
|
1474
|
+
if (tracking && !pendingCheckActive && l.fe < clock) {
|
|
1053
1475
|
recompute(l);
|
|
1054
1476
|
return read(e);
|
|
1055
1477
|
} else throw l.o?._;
|
|
1056
1478
|
}
|
|
1057
|
-
if (snapshotCaptureActive && t && t.
|
|
1058
|
-
const n = e.o?.
|
|
1479
|
+
if (snapshotCaptureActive && t && t.C & CONFIG_IN_SNAPSHOT_SCOPE) {
|
|
1480
|
+
const n = e.o?.Gt;
|
|
1059
1481
|
if (n !== undefined) {
|
|
1060
1482
|
const i = n === NO_SNAPSHOT ? undefined : n;
|
|
1061
|
-
const l = e.
|
|
1062
|
-
if (l !== i) t.
|
|
1483
|
+
const l = e._e !== NOT_PENDING ? e._e : e.Ae;
|
|
1484
|
+
if (l !== i) t.ft |= REACTIVE_SNAPSHOT_STALE;
|
|
1063
1485
|
return i;
|
|
1064
1486
|
}
|
|
1065
1487
|
}
|
|
1066
|
-
|
|
1488
|
+
const u = serve(e, t, l, e.Ae);
|
|
1489
|
+
if (!t && l === e && typeof n.ht === "function" && e.C & CONFIG_AUTO_DISPOSE && !(l.S & STATUS_PENDING) && !e.u &&
|
|
1490
|
+
// An untracked read served a visible override returned before this
|
|
1491
|
+
// sweep registration when the arm was inline; keep that.
|
|
1492
|
+
!visibleOverride(e)) {
|
|
1493
|
+
// Deferred, not inline (#3078): an inline unobserved() here made untracked
|
|
1494
|
+
// reads destructive — dispose on this read, full revival recompute on the
|
|
1495
|
+
// next — so consecutive reads could answer differently with no write in
|
|
1496
|
+
// between (the revival samples the ambient transition/lane context).
|
|
1497
|
+
// The sweep at flush finalization re-validates and reclaims; schedule()
|
|
1498
|
+
// guarantees that flush happens even if nothing else is queued.
|
|
1499
|
+
dormantNodes.add(e);
|
|
1500
|
+
schedule();
|
|
1501
|
+
}
|
|
1502
|
+
return u;
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
/**
|
|
1506
|
+
* Rule 1, the one slow implementation (DESIGN-CONSOLIDATION move 3b, step
|
|
1507
|
+
* 6c): the value a reader `c` (null = untracked, no pass) is served from
|
|
1508
|
+
* `el`, whose committed value is `committed` — the node's own `_value` for
|
|
1509
|
+
* a signal or memo, the BACKING for a store property node (single-home rule,
|
|
1510
|
+
* O6: committed truth lives in the backing and a node's `_value` is never
|
|
1511
|
+
* served for one). Called by read()'s slow tail and by the store's untracked
|
|
1512
|
+
* node path (nodeValue); the fast paths (readNodeFast, read's fast block)
|
|
1513
|
+
* keep their trivial ternary by design (perf, see the doc). Arms, in order:
|
|
1514
|
+
* - the override (A17), routed through the engine for a tracked reader under
|
|
1515
|
+
* a lane or a supersession (A18), an authoritative reader marked instead;
|
|
1516
|
+
* - the lane entanglement gate (committed, recorded for replay);
|
|
1517
|
+
* - a node born held has nothing for an untracked reader (A19 exception 1);
|
|
1518
|
+
* - an unflushed write serves committed and re-runs the reader in the
|
|
1519
|
+
* carrying flush (A28);
|
|
1520
|
+
* - readerSeesCommitted, else the staged value and the transaction (A29).
|
|
1521
|
+
*/ function serve(e, t, n, i) {
|
|
1522
|
+
if (hasActiveOverride(e)) {
|
|
1067
1523
|
// A17: the override IS the value for every reader — except an authoritative
|
|
1068
1524
|
// reader (until()'s predicate carries CONFIG_AUTHORITATIVE_READ): it must
|
|
1069
1525
|
// observe independently-arriving truth, and serving it the caller's own
|
|
@@ -1074,16 +1530,20 @@ function read(e) {
|
|
|
1074
1530
|
// authoritative — optimism never lives there); the sticky mark makes the
|
|
1075
1531
|
// A17-silent "landing equals override" paths notify this node's subs so
|
|
1076
1532
|
// the reader re-runs when truth arrives.
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
//
|
|
1082
|
-
//
|
|
1083
|
-
|
|
1084
|
-
|
|
1533
|
+
// A28 (5): an optimistic write written this tick, outside a flush, is not
|
|
1534
|
+
// yet the active override — fall through to the normal selection (the
|
|
1535
|
+
// authoritative mark below still applies: until() must wake on landing).
|
|
1536
|
+
if (!(t && t.C & CONFIG_AUTHORITATIVE_READ) && !unflushedOverride(e)) {
|
|
1537
|
+
// A tracked read of an override is the engine's selection (a lane or a
|
|
1538
|
+
// supersession implies the engine): a render effect OFF the override's
|
|
1539
|
+
// held lane sees the committed value (#3460, lanes mirror transitions);
|
|
1540
|
+
// a node whose own source answered with a DIFFERENT value hands a
|
|
1541
|
+
// tracked reader the staged truth (A18 supersession, #3331). Untracked
|
|
1542
|
+
// reads display the override.
|
|
1543
|
+
if (t && e.C & (CONFIG_HAS_LANE | CONFIG_OVERRIDE_SUPERSEDED)) return GlobalQueue.Dn(e, t);
|
|
1544
|
+
return unwrapOverride(e.o?.Ne);
|
|
1085
1545
|
}
|
|
1086
|
-
e.
|
|
1546
|
+
e.C |= CONFIG_AUTHORITATIVE_OBSERVED;
|
|
1087
1547
|
}
|
|
1088
1548
|
// Entanglement gate: a reader recomputing under an optimistic lane that reads
|
|
1089
1549
|
// a pending mid-transition write sees the committed value. Projection-store
|
|
@@ -1092,8 +1552,8 @@ function read(e) {
|
|
|
1092
1552
|
// _pendingValue for correct fetching. The sub is recorded for replay at commit
|
|
1093
1553
|
// so it re-runs with the new committed view. (Gate details live with the
|
|
1094
1554
|
// engine — a non-null lane implies it is installed.)
|
|
1095
|
-
if (currentOptimisticLane !== null && activeTransition !== null && t !== null && GlobalQueue.
|
|
1096
|
-
return
|
|
1555
|
+
if (currentOptimisticLane !== null && activeTransition !== null && t !== null && GlobalQueue.hn(e, n, t)) {
|
|
1556
|
+
return i;
|
|
1097
1557
|
}
|
|
1098
1558
|
// In optimistic lane context, return _value for optimistic/lane-assigned signals
|
|
1099
1559
|
// and for regular signals in stale mode (render effects). Non-stale readers (user
|
|
@@ -1101,72 +1561,112 @@ function read(e) {
|
|
|
1101
1561
|
// (The lane-context clause lives with the engine.) Children-forbidden readers
|
|
1102
1562
|
// (createTrackedEffect / onSettled callbacks) get committed visibility — see
|
|
1103
1563
|
// readNodeFast (#3006).
|
|
1104
|
-
|
|
1105
|
-
//
|
|
1106
|
-
//
|
|
1107
|
-
//
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1564
|
+
// A node born held (recompute) has a staged value and no committed one: a
|
|
1565
|
+
// stale reader cannot fall back to the committed frame, so it takes the
|
|
1566
|
+
// staged value and enters like a tracked reader (A29); an untracked reader
|
|
1567
|
+
// has nothing to serve and holds (A19 exception 1).
|
|
1568
|
+
const l = e._e !== NOT_PENDING && (e.S & STATUS_UNINITIALIZED) !== 0;
|
|
1569
|
+
if (l && !t) throw new NotReadyError(null);
|
|
1570
|
+
const u = t && unflushedStaged ? unflushedValue(e, i) : NOT_PENDING;
|
|
1571
|
+
if (u !== NOT_PENDING) {
|
|
1572
|
+
markLateLinker(t);
|
|
1573
|
+
if (pendingCheckActive) GlobalQueue.Pn(e, u);
|
|
1574
|
+
return u;
|
|
1575
|
+
}
|
|
1576
|
+
const o = readerSeesCommitted(e, t, n, l) ? i : (enterStagedRead(e), e._e);
|
|
1114
1577
|
// Record that this isPending() probe observed the fresh pending value, so
|
|
1115
1578
|
// the probe doesn't pair "pending" with the new value (#2831).
|
|
1116
|
-
if (pendingCheckActive) GlobalQueue.
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1579
|
+
if (pendingCheckActive) GlobalQueue.Pn(e, o);
|
|
1580
|
+
return o;
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
/** A28 — a rewrite of a HELD node outside a flush keeps the staged value the
|
|
1584
|
+
* last flush left, for latest()/verdicts, until the next flush carries it
|
|
1585
|
+
* (stash, cleared at that flush). Cold: only stamped nodes reach here. */ function stashHeldRewrite(e) {
|
|
1586
|
+
if (globalQueue.Sn) return;
|
|
1587
|
+
const t = ext(e);
|
|
1588
|
+
if (t.Bt === NOT_PENDING) {
|
|
1589
|
+
t.Bt = e._e;
|
|
1590
|
+
unflushedRewrites.push(e);
|
|
1591
|
+
unflushedStaged = true;
|
|
1126
1592
|
}
|
|
1127
|
-
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
/** A28 (4) — written inside a recompute that runs OUTSIDE a flush (a
|
|
1596
|
+
* creation-time compute): promoted at that pass's end, visible to the rest of
|
|
1597
|
+
* the block. Cold: only contextual writes reach here. */ function notePromotedWrite(e) {
|
|
1598
|
+
if (globalQueue.Sn || e.C & CONFIG_PROMOTED) return;
|
|
1599
|
+
e.C |= CONFIG_PROMOTED;
|
|
1600
|
+
promotedWrites.push(e);
|
|
1128
1601
|
}
|
|
1129
1602
|
|
|
1130
1603
|
function setSignal(e, t) {
|
|
1131
|
-
|
|
1604
|
+
// A write to a held node is a second proposal on a contested node (A34, #3494):
|
|
1605
|
+
// the writer's tick reveals with the hold, the same value or another. Inside
|
|
1606
|
+
// a flush the round enters now; from mainline the entry waits for the next
|
|
1607
|
+
// flush's start (batchJoins) — entering here left activeTransition set for
|
|
1608
|
+
// the rest of the caller's block, so a memo created after the write became
|
|
1609
|
+
// the transaction's instead of mainline's (A28, A29). Before the equality
|
|
1610
|
+
// gate below: repeating the held value proposes it too — and that repeat
|
|
1611
|
+
// leaves through the gate, so the join schedules its own flush here; left
|
|
1612
|
+
// for the next flush to find, it adopted an unrelated tick (#3519 review).
|
|
1613
|
+
if (e.Te && activeTransition !== e.Te) {
|
|
1614
|
+
if (globalQueue.Sn) globalQueue.initTransition(e.Te); else {
|
|
1615
|
+
batchJoins.push(e.Te);
|
|
1616
|
+
// dupes: a bare return in initTransition
|
|
1617
|
+
schedule();
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1132
1620
|
// The optimistic write path lives with the engine: only optimisticSignal /
|
|
1133
1621
|
// optimisticComputed callers and optimistic store nodes carry an
|
|
1134
1622
|
// _overrideValue slot (flagged by CONFIG_OPTIMISTIC — a masked read of the
|
|
1135
1623
|
// always-present config instead of a missing-property probe), and every
|
|
1136
1624
|
// module that installs one installs the engine first.
|
|
1137
|
-
if (e.
|
|
1138
|
-
if (!projectionWriteActive) return GlobalQueue.
|
|
1625
|
+
if (e.C & CONFIG_OPTIMISTIC) {
|
|
1626
|
+
if (!projectionWriteActive) return GlobalQueue.mn(e, t);
|
|
1139
1627
|
// An authoritative store landing on an override-covered node: the store
|
|
1140
1628
|
// twin of asyncWrite's override branch, decided by the engine (#3331).
|
|
1141
|
-
const n = e.o?.
|
|
1142
|
-
if (n !== undefined && n !== NOT_PENDING) return GlobalQueue.
|
|
1629
|
+
const n = e.o?.Ne;
|
|
1630
|
+
if (n !== undefined && n !== NOT_PENDING) return GlobalQueue.Fn(e, t);
|
|
1143
1631
|
}
|
|
1144
|
-
const n = e.
|
|
1632
|
+
const n = e._e === NOT_PENDING ? e.Ae : e._e;
|
|
1145
1633
|
if (typeof t === "function") t = t(n);
|
|
1146
1634
|
// Uninitialized check first: the first commit has no previous value, so the
|
|
1147
1635
|
// user comparator must not run against `undefined` (matches recompute).
|
|
1148
|
-
const i = !!(e.S & STATUS_UNINITIALIZED) || !e.
|
|
1636
|
+
const i = !!(e.S & STATUS_UNINITIALIZED) || !e.Re || !e.Re(n, t);
|
|
1149
1637
|
if (!i) return t;
|
|
1150
1638
|
// Attribution hook: this committed write is where a re-run chain begins.
|
|
1151
1639
|
if (attrHooks !== null) attrHooks.write(e, n, t);
|
|
1152
|
-
const l = e.
|
|
1640
|
+
const l = e._e !== NOT_PENDING;
|
|
1153
1641
|
if (!l) queuePendingNode(e);
|
|
1154
|
-
|
|
1642
|
+
// A28 arms, gated on the loads the write already pays for (a plain ambient
|
|
1643
|
+
// rewrite outside a flush — the write-loop shape — costs `_transition` and
|
|
1644
|
+
// `context` here and nothing else; the arms themselves are cold helpers so
|
|
1645
|
+
// setSignal stays within every setter's inlining budget, ~300 B bytecode).
|
|
1646
|
+
else if (e.Te !== null) stashHeldRewrite(e);
|
|
1647
|
+
e._e = t;
|
|
1648
|
+
if (context !== null) notePromotedWrite(e);
|
|
1155
1649
|
// syncCompanions only pokes _pendingSignal/_latestValueComputed — with
|
|
1156
1650
|
// neither companion present the call is a guaranteed no-op (companions are
|
|
1157
1651
|
// only ever created, never removed, and creating one installs the hook and
|
|
1158
1652
|
// sets CONFIG_HAS_COMPANIONS — one masked read replaces two optional-field
|
|
1159
1653
|
// probes on every write).
|
|
1160
|
-
e.
|
|
1654
|
+
if (e.C & CONFIG_HAS_COMPANIONS && GlobalQueue.De !== null) {
|
|
1655
|
+
GlobalQueue.De(e, t);
|
|
1656
|
+
// A28 (2): the verdict computed here is the PRE-flush one (an unflushed
|
|
1657
|
+
// write is not pending); the flush that carries the write re-syncs so
|
|
1658
|
+
// the companions mirror the flushed world. Only companion nodes pay.
|
|
1659
|
+
if (!globalQueue.Sn) unflushedCompanions.push(e);
|
|
1660
|
+
}
|
|
1161
1661
|
// _time is a computed-only slot (§12e): writing it on a signal would fork
|
|
1162
1662
|
// the lean shape. Every read site is computed-typed.
|
|
1163
|
-
if (e.
|
|
1663
|
+
if (e.ht !== undefined) e.fe = clock;
|
|
1164
1664
|
// Staged-rewrite fast path (§12d): a re-write to a node whose subscribers
|
|
1165
1665
|
// were already walked — and where nothing has recomputed or linked since
|
|
1166
1666
|
// (epoch) — re-stages the value and stops. The walk is idempotent (subs
|
|
1167
1667
|
// marked, heap entries flag-guarded, effects queued once); lane and reask
|
|
1168
1668
|
// contexts change what a walk MEANS, so they always walk.
|
|
1169
|
-
if (l && e.
|
|
1669
|
+
if (l && e.Zt === notifyEpoch && currentOptimisticLane === null && !reaskArmed) return t;
|
|
1170
1670
|
insertSubs(e);
|
|
1171
1671
|
schedule();
|
|
1172
1672
|
return t;
|
|
@@ -1180,12 +1680,12 @@ function setSignal(e, t) {
|
|
|
1180
1680
|
* cleanup point.
|
|
1181
1681
|
*/ function suppressComputedRecompute(e) {
|
|
1182
1682
|
deleteFromHeap(e, queueFor(e));
|
|
1183
|
-
if (!(e.
|
|
1683
|
+
if (!(e.ft & REACTIVE_MANUAL_WRITE) && e._e === NOT_PENDING) {
|
|
1184
1684
|
queuePendingNode(e);
|
|
1185
1685
|
schedule();
|
|
1186
1686
|
}
|
|
1187
|
-
e.
|
|
1188
|
-
e.
|
|
1687
|
+
e.ft = e.ft & -4 | REACTIVE_MANUAL_WRITE;
|
|
1688
|
+
e.gn = clock;
|
|
1189
1689
|
}
|
|
1190
1690
|
|
|
1191
1691
|
/**
|
|
@@ -1247,16 +1747,16 @@ function staleValues(e, t = true) {
|
|
|
1247
1747
|
* recompute as a quiet re-ask and schedules it; no-ops for non-derived or
|
|
1248
1748
|
* disposed targets and for same-tick manual writes.
|
|
1249
1749
|
*/ function markRefresh(e) {
|
|
1250
|
-
if (typeof e.
|
|
1251
|
-
if (e.
|
|
1750
|
+
if (typeof e.ht === "function" && !(e.ft & REACTIVE_DISPOSED)) {
|
|
1751
|
+
if (e.ft & REACTIVE_MANUAL_WRITE) {
|
|
1252
1752
|
// A manual write in the CURRENT tick wins over the refresh (#2692).
|
|
1253
1753
|
// A mask stamped in an earlier tick only survives because a
|
|
1254
1754
|
// transaction (action) is holding the pending drain open; there the
|
|
1255
1755
|
// refresh is a later, explicit re-ask and lifts the mask — otherwise
|
|
1256
1756
|
// any setStore early in an action silently swallows every refresh()
|
|
1257
1757
|
// for the rest of the transaction (#3026).
|
|
1258
|
-
if (e.
|
|
1259
|
-
e.
|
|
1758
|
+
if (e.gn === clock) return;
|
|
1759
|
+
e.ft &= ~REACTIVE_MANUAL_WRITE;
|
|
1260
1760
|
// The lift falls through to the re-ask classification below. The held
|
|
1261
1761
|
// write's value change already rides the transaction; the refetch it
|
|
1262
1762
|
// asks for is the same question with unchanged inputs. Skipping the
|
|
@@ -1273,11 +1773,11 @@ function staleValues(e, t = true) {
|
|
|
1273
1773
|
// REACTIVE_IN_HEAP counts as dirt: insertSubs schedules subscribers by
|
|
1274
1774
|
// heap insertion alone (no DIRTY/CHECK flag), so a same-batch value
|
|
1275
1775
|
// change followed by refresh() must not be laundered into a quiet re-ask.
|
|
1276
|
-
if (!(e.
|
|
1277
|
-
e.
|
|
1776
|
+
if (!(e.ft & (REACTIVE_DIRTY | REACTIVE_CHECK | REACTIVE_IN_HEAP))) {
|
|
1777
|
+
e.ft |= REACTIVE_REASK;
|
|
1278
1778
|
armReaskClear();
|
|
1279
1779
|
}
|
|
1280
|
-
e.
|
|
1780
|
+
e.ft = e.ft & ~REACTIVE_CHECK | REACTIVE_DIRTY;
|
|
1281
1781
|
// A refresh() self-invalidation is a root cause too — the target's next
|
|
1282
1782
|
// run has no changed dep to point at, so it points here instead.
|
|
1283
1783
|
if (attrHooks !== null) attrHooks.refreshed(e);
|
|
@@ -1286,4 +1786,4 @@ function staleValues(e, t = true) {
|
|
|
1286
1786
|
}
|
|
1287
1787
|
}
|
|
1288
1788
|
|
|
1289
|
-
export { READ_SLOW, clearSnapshots, computed, context, createEffectNode, currentOptimisticLane, effectStatusNotify, ext, installAuthoritativeRead, isEqual, latestReadActive, markRefresh, markSnapshotScope, notifyAuthoritativeObservers, optimisticComputed, optimisticSignal, pendingCheckActive, prepareComputed, read, readNodeFast, recompute, releaseSnapshotScope, runWithOwner, setContextInternal, setEffectStatusNotify, setLatestReadActive, setMemo, setPendingCheckActive, setSignal, setSlotUnobserved, setSnapshotCapture, signal, slotSignal, slotUnobservedHook, snapshotCaptureActive, snapshotSources, stale, staleValues, statusNotifierOf, suppressComputedRecompute, tracking, unlinkFirewallChild, untrack };
|
|
1789
|
+
export { READ_SLOW, clearSnapshots, computed, context, createEffectNode, currentOptimisticLane, effectStatusNotify, enterStagedRead, ext, hasActiveOverride, installAuthoritativeRead, isEqual, latestReadActive, markLateLinker, markRefresh, markSnapshotScope, markUnflushedStaged, notifyAuthoritativeObservers, optimisticComputed, optimisticSignal, ownsHold, pendingCheckActive, prepareComputed, read, readNodeFast, readerSeesCommitted, recompute, recordStaleReplay, releaseSnapshotScope, resyncUnflushedCompanions, runWithOwner, serve, setContextInternal, setEffectStatusNotify, setLatestReadActive, setMemo, setPendingCheckActive, setSignal, setSlotUnobserved, setSnapshotCapture, signal, slotSignal, slotUnobservedHook, snapshotCaptureActive, snapshotSources, spectate, spectating, stale, staleValues, statusNotifierOf, suppressComputedRecompute, tracking, unflushed, unflushedCompanions, unflushedOverride, unflushedStaged, unflushedValue, unlinkFirewallChild, untrack, visibleOverride };
|